refactor(core): introduce domProperty instruction (#60608)

Renames the `hostProperty` instruction to `domProperty` since it's not really host-specific and we can use it for other DOM-specific operations in the future.

PR Close #60608
This commit is contained in:
Kristiyan Kostadinov
2025-03-28 14:56:37 +01:00
committed by Jessica Janiuk
parent 8562667b0e
commit 9c106f4401
34 changed files with 60 additions and 62 deletions
@@ -1,7 +1,7 @@
hostBindings: function MyDirective_HostBindings(rf, ctx) {
…
if (rf & 2) {
$r3$.ɵɵhostProperty("tabindex", 1);
$r3$.ɵɵdomProperty("tabindex", 1);
$r3$.ɵɵattribute("title", "my title")("id", "my-id");
}
}
@@ -1,6 +1,6 @@
hostBindings: function MyDirective_HostBindings(rf, ctx) {
…
if (rf & 2) {
$r3$.ɵɵhostProperty("title", ctx.myTitle)("tabindex", 1)("id", ctx.myId);
$r3$.ɵɵdomProperty("title", ctx.myTitle)("tabindex", 1)("id", ctx.myId);
}
}
@@ -1,6 +1,6 @@
hostBindings: function MyDirective_HostBindings(rf, ctx) {
…
if (rf & 2) {
$r3$.ɵɵhostProperty("tabindex", 1)("title", ctx.myTitle)("id", ctx.myId);
$r3$.ɵɵdomProperty("tabindex", 1)("title", ctx.myTitle)("id", ctx.myId);
}
}
@@ -1,7 +1,7 @@
hostBindings: function MyDirective_HostBindings(rf, ctx) {
…
if (rf & 2) {
$r3$.ɵɵhostProperty("title", "my title")("id", "my-id");
$r3$.ɵɵdomProperty("title", "my title")("id", "my-id");
$r3$.ɵɵattribute("tabindex", 1);
}
}
@@ -5,7 +5,7 @@ HostBindingDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({
hostVars: 1,
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.dirId);
$r3$.ɵɵdomProperty("id", ctx.dirId);
}
},
standalone: false
@@ -7,7 +7,7 @@ HostBindingComp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
hostVars: 3,
hostBindings: function HostBindingComp_HostBindings(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", $r3$.ɵɵpureFunction1(1, $ff$, ctx.id));
$r3$.ɵɵdomProperty("id", $r3$.ɵɵpureFunction1(1, $ff$, ctx.id));
}
},
standalone: false,
@@ -6,7 +6,7 @@ HostBindingDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
if (rf & 2) {
let $tmp0$;
$r3$.ɵɵhostProperty("id", ($tmp0$ = ctx.getData()) == null ? null : $tmp0$.id);
$r3$.ɵɵdomProperty("id", ($tmp0$ = ctx.getData()) == null ? null : $tmp0$.id);
}
},
standalone: false
@@ -1,13 +1,13 @@
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
if (rf & 2) {
i0.ɵɵhostProperty("innerHtml", ctx.evil, i0.ɵɵsanitizeHtml)("href", ctx.evil, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.evil, i0.ɵɵsanitizeUrlOrResourceUrl)("sandbox", ctx.evil, i0.ɵɵvalidateIframeAttribute);
i0.ɵɵattribute("style", ctx.evil, i0.ɵɵsanitizeStyle);
$r3$.ɵɵdomProperty("innerHtml", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
$r3$.ɵɵattribute("style", ctx.evil, $r3$.ɵɵsanitizeStyle);
}
}
…
hostBindings: function HostBindingDir2_HostBindings(rf, ctx) {
if (rf & 2) {
i0.ɵɵhostProperty("innerHtml", ctx.evil, i0.ɵɵsanitizeHtml)("href", ctx.evil, i0.ɵɵsanitizeUrl)("src", ctx.evil)("sandbox", ctx.evil, i0.ɵɵvalidateIframeAttribute);
i0.ɵɵattribute("style", ctx.evil, i0.ɵɵsanitizeStyle);
$r3$.ɵɵdomProperty("innerHtml", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrl)("src", ctx.evil)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
$r3$.ɵɵattribute("style", ctx.evil, $r3$.ɵɵsanitizeStyle);
}
}
@@ -5,7 +5,7 @@ function MyCmp_HostBindings(rf, ctx) {
i0.ɵɵlistener("event1", function MyCmp_event1_HostBindingHandler() { return ctx.foo(); });
}
if (rf & 2) {
i0.ɵɵhostProperty("prop1", ctx.foo);
i0.ɵɵdomProperty("prop1", ctx.foo);
i0.ɵɵattribute("attr1", ctx.foo);
i0.ɵɵstyleMap(ctx.foo);
i0.ɵɵclassMap(ctx.foo);
@@ -2,7 +2,7 @@ hostAttrs: ["title", "foo title", __AttributeMarker.Classes__, "foo", "baz", __A
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵdomProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵstyleMap(ctx.myStyle);
$r3$.ɵɵclassMap(ctx.myClass);
}
@@ -1,7 +1,7 @@
hostVars: 6,
hostBindings: function WidthDirective_HostBindings(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵdomProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵstyleProp("width", ctx.myWidth);
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
}
@@ -1,7 +1,7 @@
hostVars: 10,
hostBindings: function MyDir_HostBindings(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵhostProperty("title", ctx.title);
$r3$.ɵɵdomProperty("title", ctx.title);
$r3$.ɵɵsyntheticHostProperty("@anim",
$r3$.ɵɵpureFunction2(7, _c1, ctx._animValue,
$r3$.ɵɵpureFunction2(4, _c0, ctx._animParam1, ctx._animParam2)));
@@ -5238,7 +5238,7 @@ runInEachFileSystem((os: string) => {
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, i0.ɵɵresolveBody)("change", function FooCmp_change_HostBindingHandler() { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
}
if (rf & 2) {
i0.ɵɵhostProperty("prop", ctx.bar);
i0.ɵɵdomProperty("prop", ctx.bar);
i0.ɵɵattribute("hello", ctx.foo);
i0.ɵɵclassProp("someclass", ctx.someClass);
}
@@ -8271,7 +8271,7 @@ runInEachFileSystem((os: string) => {
hostVars: 6,
hostBindings: function UnsafePropsDirective_HostBindings(rf, ctx) {
if (rf & 2) {
i0.ɵɵhostProperty("href", ctx.propHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.propSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.propAction, i0.ɵɵsanitizeUrl)("profile", ctx.propProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.propInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.propSafeTitle);
i0.ɵɵdomProperty("href", ctx.propHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.propSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.propAction, i0.ɵɵsanitizeUrl)("profile", ctx.propProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.propInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.propSafeTitle);
}
}
`;
@@ -8306,7 +8306,7 @@ runInEachFileSystem((os: string) => {
hostVars: 6,
hostBindings: function FooCmp_HostBindings(rf, ctx) {
if (rf & 2) {
i0.ɵɵhostProperty("src", ctx.srcProp)("href", ctx.hrefProp)("title", ctx.titleProp);
i0.ɵɵdomProperty("src", ctx.srcProp)("href", ctx.hrefProp)("title", ctx.titleProp);
i0.ɵɵattribute("src", ctx.srcAttr)("href", ctx.hrefAttr)("title", ctx.titleAttr);
}
}
@@ -9523,7 +9523,7 @@ runInEachFileSystem((os: string) => {
// The `sandbox` is potentially a security-sensitive attribute of an <iframe>.
// Generate an extra validation function to invoke at runtime, which would
// check if an underlying host element is an <iframe>.
expect(jsContents).toContain('ɵɵhostProperty("sandbox", "", i0.ɵɵvalidateIframeAttribute)');
expect(jsContents).toContain('ɵɵdomProperty("sandbox", "", i0.ɵɵvalidateIframeAttribute)');
// Similar to the above, but for an attribute binding (host attributes are
// represented via `ɵɵattribute`).
@@ -339,7 +339,7 @@ export class Identifiers {
static pipeBind4: o.ExternalReference = {name: 'ɵɵpipeBind4', moduleName: CORE};
static pipeBindV: o.ExternalReference = {name: 'ɵɵpipeBindV', moduleName: CORE};
static hostProperty: o.ExternalReference = {name: 'ɵɵhostProperty', moduleName: CORE};
static domProperty: o.ExternalReference = {name: 'ɵɵdomProperty', moduleName: CORE};
static property: o.ExternalReference = {name: 'ɵɵproperty', moduleName: CORE};
@@ -176,9 +176,9 @@ export enum OpKind {
I18nMessage,
/**
* A host binding property.
* A binding to a native DOM property.
*/
HostProperty,
DomProperty,
/**
* A namespace change, which causes the subsequent elements to be processed as either HTML or SVG.
@@ -1099,7 +1099,7 @@ export function transformExpressionsInOp(
}
break;
case OpKind.Property:
case OpKind.HostProperty:
case OpKind.DomProperty:
case OpKind.Attribute:
if (op.expression instanceof Interpolation) {
transformExpressionsInInterpolation(op.expression, transform, flags);
@@ -18,10 +18,10 @@ import {NEW_OP} from './shared';
import type {Interpolation, UpdateOp} from './update';
/**
* Logical operation representing a host binding to a property.
* Logical operation representing a binding to a native DOM property.
*/
export interface HostPropertyOp extends Op<UpdateOp>, ConsumesVarsTrait {
kind: OpKind.HostProperty;
export interface DomPropertyOp extends Op<UpdateOp>, ConsumesVarsTrait {
kind: OpKind.DomProperty;
name: string;
expression: o.Expression | Interpolation;
isAnimationTrigger: boolean;
@@ -35,16 +35,16 @@ export interface HostPropertyOp extends Op<UpdateOp>, ConsumesVarsTrait {
sourceSpan: ParseSourceSpan | null;
}
export function createHostPropertyOp(
export function createDomPropertyOp(
name: string,
expression: o.Expression | Interpolation,
isAnimationTrigger: boolean,
i18nContext: XrefId | null,
securityContext: SecurityContext | SecurityContext[],
sourceSpan: ParseSourceSpan | null,
): HostPropertyOp {
): DomPropertyOp {
return {
kind: OpKind.HostProperty,
kind: OpKind.DomProperty,
name,
expression,
isAnimationTrigger,
@@ -27,7 +27,7 @@ import {
TRAIT_CONSUMES_VARS,
TRAIT_DEPENDS_ON_SLOT_CONTEXT,
} from '../traits';
import type {HostPropertyOp} from './host';
import type {DomPropertyOp} from './host';
import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
/**
@@ -47,7 +47,7 @@ export type UpdateOp =
| AdvanceOp
| VariableOp<UpdateOp>
| BindingOp
| HostPropertyOp
| DomPropertyOp
| ConditionalOp
| I18nExpressionOp
| I18nApplyOp
@@ -111,7 +111,7 @@ export function ingestHostBinding(
bindingKind === ir.BindingKind.Attribute,
)
.filter((context) => context !== SecurityContext.NONE);
ingestHostProperty(job, property, bindingKind, securityContexts);
ingestDomProperty(job, property, bindingKind, securityContexts);
}
for (const [name, expr] of Object.entries(input.attributes) ?? []) {
const securityContexts = bindingParser
@@ -127,7 +127,7 @@ export function ingestHostBinding(
// TODO: We should refactor the parser to use the same types and structures for host bindings as
// with ordinary components. This would allow us to share a lot more ingestion code.
export function ingestHostProperty(
export function ingestDomProperty(
job: HostBindingCompilationJob,
property: e.ParsedProperty,
bindingKind: ir.BindingKind,
@@ -747,7 +747,7 @@ export function classMapInterpolate(
);
}
export function hostProperty(
export function domProperty(
name: string,
expression: o.Expression,
sanitizer: o.Expression | null,
@@ -757,7 +757,7 @@ export function hostProperty(
if (sanitizer !== null) {
args.push(sanitizer);
}
return call(Identifiers.hostProperty, args, sourceSpan);
return call(Identifiers.domProperty, args, sourceSpan);
}
export function syntheticHostProperty(
@@ -71,7 +71,7 @@ export function specializeBindings(job: CompilationJob): void {
if (job.kind === CompilationJobKind.Host) {
ir.OpList.replace<ir.UpdateOp>(
op,
ir.createHostPropertyOp(
ir.createDomPropertyOp(
op.name,
op.expression,
op.bindingKind === ir.BindingKind.Animation,
@@ -20,7 +20,7 @@ const CHAIN_COMPATIBILITY = new Map<o.ExternalReference, o.ExternalReference>([
[R3.elementContainerStart, R3.elementContainerStart],
[R3.elementEnd, R3.elementEnd],
[R3.elementStart, R3.elementStart],
[R3.hostProperty, R3.hostProperty],
[R3.domProperty, R3.domProperty],
[R3.i18nExp, R3.i18nExp],
[R3.listener, R3.listener],
[R3.listener, R3.listener],
@@ -51,7 +51,7 @@ function addNamesToView(
for (const op of unit.ops()) {
switch (op.kind) {
case ir.OpKind.Property:
case ir.OpKind.HostProperty:
case ir.OpKind.DomProperty:
if (op.isAnimationTrigger) {
op.name = '@' + op.name;
}
@@ -14,7 +14,7 @@ function kindTest(kind: ir.OpKind): (op: ir.UpdateOp) => boolean {
}
function kindWithInterpolationTest(
kind: ir.OpKind.Attribute | ir.OpKind.Property | ir.OpKind.HostProperty,
kind: ir.OpKind.Attribute | ir.OpKind.Property | ir.OpKind.DomProperty,
interpolation: boolean,
): (op: ir.UpdateOp) => boolean {
return (op: ir.UpdateOp) => {
@@ -70,8 +70,8 @@ const UPDATE_ORDERING: Array<Rule<ir.UpdateOp>> = [
* Host bindings have their own update ordering.
*/
const UPDATE_HOST_ORDERING: Array<Rule<ir.UpdateOp>> = [
{test: kindWithInterpolationTest(ir.OpKind.HostProperty, true)},
{test: kindWithInterpolationTest(ir.OpKind.HostProperty, false)},
{test: kindWithInterpolationTest(ir.OpKind.DomProperty, true)},
{test: kindWithInterpolationTest(ir.OpKind.DomProperty, false)},
{test: kindTest(ir.OpKind.Attribute)},
{test: kindTest(ir.OpKind.StyleMap), transform: keepLast},
{test: kindTest(ir.OpKind.ClassMap), transform: keepLast},
@@ -91,7 +91,7 @@ const handledOpKinds = new Set([
ir.OpKind.ClassProp,
ir.OpKind.Property,
ir.OpKind.TwoWayProperty,
ir.OpKind.HostProperty,
ir.OpKind.DomProperty,
ir.OpKind.Attribute,
]);
@@ -575,7 +575,7 @@ function reifyUpdateOperations(_unit: CompilationUnit, ops: ir.OpList<ir.UpdateO
ir.OpList.replace(op, ng.attribute(op.name, op.expression, op.sanitizer, op.namespace));
}
break;
case ir.OpKind.HostProperty:
case ir.OpKind.DomProperty:
if (op.expression instanceof ir.Interpolation) {
throw new Error('not yet handled');
} else {
@@ -584,7 +584,7 @@ function reifyUpdateOperations(_unit: CompilationUnit, ops: ir.OpList<ir.UpdateO
} else {
ir.OpList.replace(
op,
ng.hostProperty(op.name, op.expression, op.sanitizer, op.sourceSpan),
ng.domProperty(op.name, op.expression, op.sanitizer, op.sourceSpan),
);
}
}
@@ -58,7 +58,7 @@ export function resolveSanitizers(job: CompilationJob): void {
switch (op.kind) {
case ir.OpKind.Property:
case ir.OpKind.Attribute:
case ir.OpKind.HostProperty:
case ir.OpKind.DomProperty:
let sanitizerFn: o.ExternalReference | null = null;
if (
Array.isArray(op.securityContext) &&
@@ -82,7 +82,7 @@ export function resolveSanitizers(job: CompilationJob): void {
// <iframe>).
if (op.sanitizer === null) {
let isIframe = false;
if (job.kind === CompilationJobKind.Host || op.kind === ir.OpKind.HostProperty) {
if (job.kind === CompilationJobKind.Host || op.kind === ir.OpKind.DomProperty) {
// Note: for host bindings defined on a directive, we do not try to find all
// possible places where it can be matched, so we can not determine whether
// the host element is an <iframe>. In this case, we just assume it is and append a
@@ -110,7 +110,7 @@ function varsUsedByOp(op: (ir.CreateOp | ir.UpdateOp) & ir.ConsumesVarsTrait): n
let slots: number;
switch (op.kind) {
case ir.OpKind.Property:
case ir.OpKind.HostProperty:
case ir.OpKind.DomProperty:
case ir.OpKind.Attribute:
// All of these bindings use 1 variable slot, plus 1 slot for every interpolated expression,
// if any.
@@ -118,7 +118,7 @@ export {
ɵɵFactoryDeclaration,
ɵɵgetCurrentView,
ɵɵgetInheritedFactory,
ɵɵhostProperty,
ɵɵdomProperty,
ɵɵi18n,
ɵɵi18nApply,
ɵɵi18nAttributes,
+1 -1
View File
@@ -84,7 +84,7 @@ export {
ɵɵelementEnd,
ɵɵelementStart,
ɵɵgetCurrentView,
ɵɵhostProperty,
ɵɵdomProperty,
ɵɵinjectAttribute,
ɵɵinvalidFactory,
ɵɵlistener,
@@ -44,7 +44,7 @@ export {
ɵsetUnknownPropertyStrictMode,
} from './element_validation';
export * from './get_current_view';
export * from './host_property';
export * from './dom_property';
export * from './i18n';
export * from './listener';
export * from './namespace';
@@ -20,24 +20,21 @@ import {NO_CHANGE} from '../tokens';
import {loadComponentRenderer, setDomProperty, storePropertyBindingMetadata} from './shared';
/**
* Update a property on a host element. Only applies to native node properties, not inputs.
* Update a DOM property on an element.
*
* Operates on the element selected by index via the {@link select} instruction.
*
* @param propName Name of property. Because it is going to DOM, this is not subject to
* renaming as part of minification.
* @param propName Name of property..
* @param value New value to write.
* @param sanitizer An optional function used to sanitize the value.
* @returns This function returns itself so that it may be chained
* (e.g. `property('name', ctx.name)('title', ctx.title)`)
* (e.g. `domProperty('name', ctx.name)('title', ctx.title)`)
*
* @codeGenApi
*/
export function ɵɵhostProperty<T>(
export function ɵɵdomProperty<T>(
propName: string,
value: T,
sanitizer?: SanitizerFn | null,
): typeof ɵɵhostProperty {
): typeof ɵɵdomProperty {
const lView = getLView();
const bindingIndex = nextBindingIndex();
if (bindingUpdated(lView, bindingIndex, value)) {
@@ -46,9 +43,10 @@ export function ɵɵhostProperty<T>(
setDomProperty(tNode, lView, propName, value, lView[RENDERER], sanitizer);
ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
}
return ɵɵhostProperty;
return ɵɵdomProperty;
}
// TODO(crisbeto): try to fold this into `domProperty`. Main difference is the renderer.
/**
* Updates a synthetic host binding (e.g. `[@foo]`) on a component or directive.
*
+1 -1
View File
@@ -84,7 +84,7 @@ export const angularCoreEnv: {[name: string]: unknown} = (() => ({
'ɵɵpipeBind4': r3.ɵɵpipeBind4,
'ɵɵpipeBindV': r3.ɵɵpipeBindV,
'ɵɵprojectionDef': r3.ɵɵprojectionDef,
'ɵɵhostProperty': r3.ɵɵhostProperty,
'ɵɵdomProperty': r3.ɵɵdomProperty,
'ɵɵproperty': r3.ɵɵproperty,
'ɵɵpropertyInterpolate': r3.ɵɵpropertyInterpolate,
'ɵɵpropertyInterpolate1': r3.ɵɵpropertyInterpolate1,
@@ -259,7 +259,7 @@ describe('component declaration jit compilation', () => {
],
hostBindings: functionContaining([
'return ctx.handleEvent($event)',
/hostProperty[^(]*\('foo',ctx\.foo\.prop\)/,
/domProperty[^(]*\('foo',ctx\.foo\.prop\)/,
/attribute[^(]*\('bar',ctx\.bar\.prop\)/,
]),
hostVars: 2,
@@ -244,7 +244,7 @@ describe('directive declaration jit compilation', () => {
],
hostBindings: functionContaining([
'return ctx.handleEvent($event)',
/hostProperty[^(]*\('foo',ctx\.foo\.prop\)/,
/domProperty[^(]*\('foo',ctx\.foo\.prop\)/,
/attribute[^(]*\('bar',ctx\.bar\.prop\)/,
]),
hostVars: 2,