Skip to content

Commit

Permalink
fix(ivy): ExpressionChangedAfterItHasBeenCheckedError for SafeValue
Browse files Browse the repository at this point in the history
Fix #33448
  • Loading branch information
mhevery committed Nov 11, 2019
1 parent b3c3000 commit 46af0bc
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 5 deletions.
5 changes: 3 additions & 2 deletions packages/core/src/render3/instructions/styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {activateStylingMapFeature} from '../styling/map_based_bindings';
import {attachStylingDebugObject} from '../styling/styling_debug';
import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils';
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, getValue, hasClassInput, hasStyleInput, hasValueChanged, isHostStylingActive, isStylingContext, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, selectClassBasedInputName, setValue, stylingMapToString} from '../util/styling_utils';
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, getValue, hasClassInput, hasStyleInput, hasValueChanged, hasValueChangedUnwrapSafeValue, isHostStylingActive, isStylingContext, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, selectClassBasedInputName, setValue, stylingMapToString} from '../util/styling_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils';


Expand Down Expand Up @@ -187,7 +187,7 @@ function stylingProp(
// as well.
if (ngDevMode && getCheckNoChangesMode()) {
const oldValue = getValue(lView, bindingIndex);
if (hasValueChanged(oldValue, value)) {
if (hasValueChangedUnwrapSafeValue(oldValue, value)) {
throwErrorIfNoChangesMode(false, oldValue, value);
}
}
Expand Down Expand Up @@ -366,6 +366,7 @@ function stylingMap(
// For this reason, the checkNoChanges situation must also be handled here
// as well.
if (ngDevMode && valueHasChanged && getCheckNoChangesMode()) {
debugger;
throwErrorIfNoChangesMode(false, oldValue, value);
}

Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/render3/util/styling_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {unwrapSafeValue} from '../../sanitization/bypass';
import {PropertyAliases, TNodeFlags} from '../interfaces/node';
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling';
import {NO_CHANGE} from '../tokens';
Expand Down Expand Up @@ -170,6 +171,14 @@ export function getPropValuesStartPosition(
return startPosition;
}

export function hasValueChangedUnwrapSafeValue(
a: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | {},
b: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined |
{}): boolean {
return hasValueChanged(unwrapSafeValue(a), unwrapSafeValue(b));
}


export function hasValueChanged(
a: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | {},
b: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined |
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/sanitization/bypass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
getTypeName() { return BypassType.ResourceUrl; }
}

export function unwrapSafeValue(value: string | SafeValue): string {
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity :
value as string;
export function unwrapSafeValue(value: SafeValue): string;
export function unwrapSafeValue<T>(value: T): T;
export function unwrapSafeValue<T>(value: T | SafeValue): T {
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity as any as T :
value as any as T;
}


Expand Down
19 changes: 19 additions & 0 deletions packages/core/test/acceptance/styling_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,25 @@ describe('styling', () => {

function splitSortJoin(s: string) { return s.split(/\s+/).sort().join(' ').trim(); }
});

describe('ExpressionChangedAfterItHasBeenCheckedError', () => {
it('should not throw when bound to bypass security value', () => {
@Component({template: `<div [style.background-image]="iconSafe"></div>`})
class MyComp {
icon = 'https://i.imgur.com/4AiXzf8.jpg';
get iconSafe() { return this.sanitizer.bypassSecurityTrustStyle(`url(${this.icon})`); }

constructor(private sanitizer: DomSanitizer) {}
}

const fixture =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
fixture.detectChanges(true /* Verify that check no changes does not cause an exception */);
const div: HTMLElement = fixture.nativeElement.querySelector('div');
expect(div.style.getPropertyValue('background-image'))
.toEqual('url(https://i.imgur.com/4AiXzf8.jpg)');
});
});
});

function assertStyleCounters(countForSet: number, countForRemove: number) {
Expand Down

0 comments on commit 46af0bc

Please sign in to comment.