feat(ivy): support for i18n & ICU expressions (#27101)

PR Close #27101
This commit is contained in:
Olivier Combe 2018-11-13 09:36:30 +01:00 committed by Miško Hevery
parent 92b05652b2
commit e22a302cad
25 changed files with 2980 additions and 2537 deletions

View File

@ -83,23 +83,23 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nEnd();
$r3$.ɵelementEnd();
$r3$.ɵelementStart(2, "div");
$r3$.ɵi18nAttribute(3, $_c2$);
$r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵtext(4, "Content B");
$r3$.ɵelementEnd();
$r3$.ɵelementStart(5, "div");
$r3$.ɵi18nAttribute(6, $_c4$);
$r3$.ɵi18nAttributes(6, $_c4$);
$r3$.ɵtext(7, "Content C");
$r3$.ɵelementEnd();
$r3$.ɵelementStart(8, "div");
$r3$.ɵi18nAttribute(9, $_c6$);
$r3$.ɵi18nAttributes(9, $_c6$);
$r3$.ɵtext(10, "Content D");
$r3$.ɵelementEnd();
$r3$.ɵelementStart(11, "div");
$r3$.ɵi18nAttribute(12, $_c8$);
$r3$.ɵi18nAttributes(12, $_c8$);
$r3$.ɵtext(13, "Content E");
$r3$.ɵelementEnd();
$r3$.ɵelementStart(14, "div");
$r3$.ɵi18nAttribute(15, $_c10$);
$r3$.ɵi18nAttributes(15, $_c10$);
$r3$.ɵtext(16, "Content F");
$r3$.ɵelementEnd();
}
@ -142,7 +142,7 @@ describe('i18n support in the view compiler', () => {
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵi18nAttribute(1, $_c2$);
$r3$.ɵi18nAttributes(1, $_c2$);
$r3$.ɵelementEnd();
}
}
@ -207,10 +207,10 @@ describe('i18n support in the view compiler', () => {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵpipe(1, "uppercase");
$r3$.ɵi18nAttribute(2, $_c4$);
$r3$.ɵi18nAttributes(2, $_c4$);
$r3$.ɵelementEnd();
$r3$.ɵelementStart(3, "div", $_c5$);
$r3$.ɵi18nAttribute(4, $_c8$);
$r3$.ɵi18nAttributes(4, $_c8$);
$r3$.ɵelementEnd();
}
if (rf & 2) {
@ -265,7 +265,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div");
$r3$.ɵelementStart(1, "div");
$r3$.ɵpipe(2, "uppercase");
$r3$.ɵi18nAttribute(3, $_c2$);
$r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd();
$r3$.ɵelementEnd();
}
@ -345,10 +345,10 @@ describe('i18n support in the view compiler', () => {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵpipe(1, "uppercase");
$r3$.ɵi18nAttribute(2, $_c4$);
$r3$.ɵi18nAttributes(2, $_c4$);
$r3$.ɵelementEnd();
$r3$.ɵelementStart(3, "div", $_c5$);
$r3$.ɵi18nAttribute(4, $_c8$);
$r3$.ɵi18nAttributes(4, $_c8$);
$r3$.ɵelementEnd();
}
if (rf & 2) {
@ -403,7 +403,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div");
$r3$.ɵelementStart(1, "div");
$r3$.ɵpipe(2, "uppercase");
$r3$.ɵi18nAttribute(3, $_c2$);
$r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd();
$r3$.ɵelementEnd();
}
@ -693,7 +693,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div");
$r3$.ɵi18nStart(1, $MSG_APP_SPEC_TS_0$);
$r3$.ɵelementStart(2, "span");
$r3$.ɵi18nAttribute(3, $_c2$);
$r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd();
$r3$.ɵi18nEnd();
$r3$.ɵelementEnd();
@ -701,7 +701,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nStart(5, $MSG_APP_SPEC_TS_3$);
$r3$.ɵpipe(6, "uppercase");
$r3$.ɵelementStart(7, "span");
$r3$.ɵi18nAttribute(8, $_c5$);
$r3$.ɵi18nAttributes(8, $_c5$);
$r3$.ɵelementEnd();
$r3$.ɵi18nEnd();
$r3$.ɵelementEnd();

View File

@ -95,7 +95,7 @@ export class Identifiers {
static pipeBind4: o.ExternalReference = {name: 'ɵpipeBind4', moduleName: CORE};
static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE};
static i18nAttribute: o.ExternalReference = {name: 'ɵi18nAttribute', moduleName: CORE};
static i18nAttributes: o.ExternalReference = {name: 'ɵi18nAttributes', moduleName: CORE};
static i18nExp: o.ExternalReference = {name: 'ɵi18nExp', moduleName: CORE};
static i18nStart: o.ExternalReference = {name: 'ɵi18nStart', moduleName: CORE};
static i18nEnd: o.ExternalReference = {name: 'ɵi18nEnd', moduleName: CORE};

View File

@ -490,7 +490,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (i18nAttrArgs.length) {
const index: o.Expression = o.literal(this.allocateDataSlot());
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
this.creationInstruction(element.sourceSpan, R3.i18nAttribute, [index, args]);
this.creationInstruction(element.sourceSpan, R3.i18nAttributes, [index, args]);
if (hasBindings) {
this.updateInstruction(element.sourceSpan, R3.i18nApply, [index]);
}

View File

@ -108,24 +108,12 @@ export {
PipeDef as ɵPipeDef,
PipeDefWithMeta as ɵPipeDefWithMeta,
whenRendered as ɵwhenRendered,
i18nAttribute as ɵi18nAttribute,
i18nAttributes as ɵi18nAttributes,
i18nExp as ɵi18nExp,
i18nStart as ɵi18nStart,
i18nEnd as ɵi18nEnd,
i18nApply as ɵi18nApply,
i18nExpMapping as ɵi18nExpMapping,
i18nInterpolation1 as ɵi18nInterpolation1,
i18nInterpolation2 as ɵi18nInterpolation2,
i18nInterpolation3 as ɵi18nInterpolation3,
i18nInterpolation4 as ɵi18nInterpolation4,
i18nInterpolation5 as ɵi18nInterpolation5,
i18nInterpolation6 as ɵi18nInterpolation6,
i18nInterpolation7 as ɵi18nInterpolation7,
i18nInterpolation8 as ɵi18nInterpolation8,
i18nInterpolationV as ɵi18nInterpolationV,
i18nMapping as ɵi18nMapping,
I18nInstruction as ɵI18nInstruction,
I18nExpInstruction as ɵI18nExpInstruction,
i18nIcuReplaceVars as ɵi18nIcuReplaceVars,
WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2,
setClassMetadata as ɵsetClassMetadata,
} from './render3/index';

View File

@ -205,12 +205,12 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
### I18N
| Feature | Runtime | Spec | Compiler |
| ----------------------------------- | ------- | -------- | -------- |
| i18nStart | | ✅ | ✅ |
| i18nEnd | | ✅ | ✅ |
| i18nAttributes | | ✅ | ✅ |
| i18nExp | | ✅ | ✅ |
| i18nApply | | ✅ | ✅ |
| ICU expressions | | ✅ | ❌ |
| i18nStart | | ✅ | ✅ |
| i18nEnd | | ✅ | ✅ |
| i18nAttributes | | ✅ | ✅ |
| i18nExp | | ✅ | ✅ |
| i18nApply | | ✅ | ✅ |
| ICU expressions | | ✅ | ❌ |
| closure support for g3 | ✅ | ✅ | ❌ |
| runtime service for external world | ❌ | ❌ | ❌ |
| migration tool | ❌ | ❌ | ❌ |

View File

@ -11,12 +11,12 @@ For example index `123` may point to a component instance in the `LViewData` but
The layout is as such:
| Section | `LViewData` | `TView.data`
| ---------- | --------------------------------------------- | --------------------------------------------------
| `HEADER` | contextual data | mostly `null`
| `CONSTS` | DOM, pipe, and local ref instances |
| `VARS` | binding values | property names
| `EXPANDO` | host bindings; directive instances; providers | host prop names; directive tokens; provider tokens
| Section | `LViewData` | `TView.data`
| ---------- | ------------------------------------------------------------ | --------------------------------------------------
| `HEADER` | contextual data | mostly `null`
| `CONSTS` | DOM, pipe, and local ref instances |
| `VARS` | binding values | property names
| `EXPANDO` | host bindings; directive instances; providers; dynamic nodes | host prop names; directive tokens; provider tokens; `null`
## `HEADER`

View File

@ -23,7 +23,7 @@ class MyComponent {
```
NOTE:
- There really is only two kinds of i18n text.
1. In attribute as in `i18n-title`.
1. In attribute as in `title` (with `i18n-title` present).
2. In element body marked as as `<div i18n>`.
- The element body i18n can have internal DOM structure which may consist of sub-templates.
@ -160,8 +160,8 @@ i18nAttributes(1, MSG_div_attr);
```
The above instruction checks the `TView.data` cache at position `1` and if empty will create `I18nUpdateOpCodes` like so:
```typescript
<I18nUpdateOpCodes>[
// The following OpCodes represent: `<div i18n-title="Hello <20>0<EFBFBD>!">`
const i18nUpdateOpCodes = <I18nUpdateOpCodes>[
// The following OpCodes represent: `<div i18n-title title="Hello <20>0<EFBFBD>!">`
// If `changeMask & 0b11`
// has changed then execute update OpCodes.
// has NOT changed then skip `7` values and start processing next OpCodes.
@ -170,12 +170,13 @@ The above instruction checks the `TView.data` cache at position `1` and if empty
'Hello ', // accumulate('Hello ');
-1, // accumulate(-1);
'!', // accumulate('!');
// Update attribute: `elementAttribute(1, 'title', accumulatorFlush(null));`
// Update attribute: `elementAttribute(0, 'title', accumulatorFlush(null));`
// NOTE: `null` means don't sanitize
1 << SHIFT_REF | Attr, 'title', null,
0 << SHIFT_REF | Attr, 'title', null,
]
```
NOTE:
- `i18nAttributes` updates the attributes of the previous element.
- If there is more than one attribute which needs to be internationalized it is added to the array as `[attributeName, translation]` tuple.
- Even attributes which don't have bindings must go through `i18nAttributes` so that they correctly work with i18n in server environment.
@ -216,7 +217,7 @@ will generate
```typescript
// Text broken down to allow addition of comments (Generated code will not have comments)
const MSG_div =
'List: '
'List: ' +
'<27>*2:1<>' + // template(2, MyComponent_NgIf_Template_0, ...);
'<27>#1:1<>' + // elementStart(1, 'ul');
'<27>*2:2<>' + // template(2, MyComponent_NgIf_NgFor_Template_1, ...);
@ -298,7 +299,7 @@ i18nEnd(); // The instruction which is responsible for inserting text node
The `i18nStart` generates these instructions which are cached in the `TView` and then processed by `i18nEnd`.
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -362,7 +363,7 @@ This case is more complex because it contains an ICU.
ICUs are pre-parsed and then stored in the `TVIEW.data` as follows.
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 3 + Math.max(4, 3, 3), // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed)
expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200
create: <I18nMutateOpCodes>[
@ -379,16 +380,16 @@ ICUs are pre-parsed and then stored in the `TVIEW.data` as follows.
// has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2,
-1, // accumulate(-1);
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch,
// Switch ICU: `icuSwitchCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
200 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have
// Update ICU: `icuUpdateCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate,
200 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
],
icus: [
<TIcu>{
@ -544,7 +545,7 @@ The OpCodes require that offsets for the EXPANDO index for the reference.
The question is how do we compute this:
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 1,
expandoStartIndex: 100, // Retrieved from `tView.blueprint.length` at i18nStart invocation.
create: <I18nMutateOpCodes>[
@ -623,7 +624,7 @@ The rules for attribute ICUs should be the same as for normal ICUs.
For this reason we would like to reuse as much code as possible for parsing and processing of the ICU for simplicity and consistency.
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 0, // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed)
expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200
create: <I18nMutateOpCodes>[
@ -635,18 +636,18 @@ For this reason we would like to reuse as much code as possible for parsing and
// has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2,
-1, // accumulate(-1)
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch,
// Switch ICU: `icuSwitchCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
200 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 4,
'You have ', // accumulate('You have ');
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have
// Update ICU: `icuUpdateCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate,
200 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
'.', // accumulate('.');
@ -699,7 +700,7 @@ For this reason we would like to reuse as much code as possible for parsing and
// has changed then execute update OpCodes.
// has NOT changed then skip `1` values and start processing next OpCodes.
-1, 2,
-1, // accumulate(lviewData[bindIndex-1]);
-1, // accumulate(lViewData[bindIndex-1]);
'emails', // accumulate('no emails');
]
]
@ -724,7 +725,7 @@ Given
```
The above needs to be parsed into:
```TypeScript
{
const icu = {
type: 'plural', // or 'select'
expressionBindingIndex: 0, // from <20>0<EFBFBD>,
cases: [
@ -757,7 +758,7 @@ NOTE: The updates to attributes with placeholders require that we go through san
## Translation without top level element
Placing `i18n` attribute on an existing elements is easy because the element defines parent and the translated element can be insert synchronously.
Placing `i18n` attribute on an existing elements is easy because the element defines parent and the translated element can be inserted synchronously.
For virtual elements such as `<ng-container>` or `<ng-template>` this is more complicated because there is no common root element to insert into.
In such a case the `i18nStart` acts as the element to insert into.
This is similar to `<ng-container>` behavior.
@ -783,7 +784,7 @@ function MyComponent_Template_0(rf: RenderFlags, ctx: any) {
Which would get parsed into:
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -869,7 +870,7 @@ NOTE:
The internal data structure will be:
```typescript
<TI18n>{
const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -881,16 +882,16 @@ The internal data structure will be:
// has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2,
-1, // accumulate(-1);
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch,
// Switch ICU: `icuSwitchCase(lViewData[100 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
100 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have
// Update ICU: `icuUpdateCase(lViewData[100 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate,
100 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
],
icus: [
<TIcu>{ // {<7B>0<EFBFBD>, plural, =0 {zero} other {<7B>0<EFBFBD> <!--subICU-->}}
@ -906,7 +907,7 @@ The internal data structure will be:
'', 1 << SHIFT_PARENT | AppendChild, // Expando location: 100
COMMENT_MARKER, '', 0 << SHIFT_PARENT | AppendChild, // Expando location: 101
],
]
],
remove: [
<I18nMutateOpCodes>[ // Case: `0`: `{zero}`
1 << SHIFT_PARENT | 100 << SHIFT_REF | Remove,
@ -921,16 +922,16 @@ The internal data structure will be:
],
<I18nMutateOpCodes>[ // Case: `other`: `{<7B>0<EFBFBD> <!--subICU-->}`
0b1, 3,
-2, ' ', 100 << SHIFT_REF | Text // Case: `<60>0<EFBFBD> `
-2, ' ', 100 << SHIFT_REF | Text, // Case: `<60>0<EFBFBD> `
0b10, 5,
-1
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 1 /*SHIFT_REF*/, accumulatorFlush());`
0 << SHIFT_ICU | 1 << SHIFT_REF | IcuSwitch,
-1,
// Switch ICU: `icuSwitchCase(lViewData[101 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
101 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks int the ICU.
0b10, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 1 /*SHIFT_REF*/);`
0 << SHIFT_ICU | 1 << SHIFT_REF | IcuUpdate,
// Update ICU: `icuUpdateCase(lViewData[101 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
101 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
],
]
},
@ -1057,7 +1058,7 @@ Here is a more complete example.
Given this Angular's template:
```HTML
<div i18n-title="Hello {{name}}!" i18n="Some description.">
<div i18n-title title="Hello {{name}}!" i18n="Some description.">
{{count}} is rendered as:
<b *ngIf="true">
{ count, plural,
@ -1111,7 +1112,7 @@ To generate code where the extracted i18n messages have the same ids, the `ngtsc
Given this Angular's template:
```HTML
<div i18n-title="Hello {{name}}!" i18n="Some description.">
<div i18n-title title="Hello {{name}}!" i18n="Some description.">
{{count}} is rendered as:
<b *ngIf="true">
{ count, plural,

File diff suppressed because it is too large Load Diff

View File

@ -87,24 +87,12 @@ export {
} from './state';
export {
i18nAttribute,
i18nAttributes,
i18nExp,
i18nStart,
i18nEnd,
i18nApply,
i18nMapping,
i18nInterpolation1,
i18nInterpolation2,
i18nInterpolation3,
i18nInterpolation4,
i18nInterpolation5,
i18nInterpolation6,
i18nInterpolation7,
i18nInterpolation8,
i18nInterpolationV,
i18nExpMapping,
I18nInstruction,
I18nExpInstruction
i18nIcuReplaceVars,
} from './i18n';
export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';

View File

@ -7,7 +7,6 @@
*/
import './ng_dev_mode';
import {resolveForwardRef} from '../di/forward_ref';
import {InjectionToken} from '../di/injection_token';
import {InjectFlags} from '../di/injector_compatibility';
@ -16,7 +15,6 @@ import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {Type} from '../type';
import {noop} from '../util/noop';
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
@ -25,11 +23,12 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingIndex} from './interfaces/styling';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
@ -42,7 +41,6 @@ import {getStylingContext} from './styling/util';
import {NO_CHANGE} from './tokens';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
/**
* A permanent marker promise which signifies that the current CD tree is
* clean.
@ -54,11 +52,6 @@ const enum BindingDirection {
Output,
}
/**
* Function used to sanitize the value before writing it into the renderer.
*/
type SanitizerFn = (value: any) => string;
/**
* Refreshes the view, executing the following steps in that order:
* triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host
@ -197,9 +190,13 @@ export function createNodeAtIndex(
export function createNodeAtIndex(
index: number, type: TNodeType.ElementContainer, native: RComment, name: null,
attrs: TAttributes | null): TElementContainerNode;
export function createNodeAtIndex(
index: number, type: TNodeType.IcuContainer, native: RComment, name: null,
attrs: TAttributes | null): TElementContainerNode;
export function createNodeAtIndex(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode {
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&
TIcuContainerNode {
const viewData = getViewData();
const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
@ -233,7 +230,7 @@ export function createNodeAtIndex(
setPreviousOrParentTNode(tNode);
setIsParent(true);
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
TProjectionNode;
TProjectionNode & TIcuContainerNode;
}
export function createViewNode(index: number, view: LViewData) {
@ -255,11 +252,12 @@ export function createViewNode(index: number, view: LViewData) {
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
* template passes.
*/
export function adjustBlueprintForNewNode(view: LViewData) {
export function allocExpando(view: LViewData) {
const tView = view[TVIEW];
if (tView.firstTemplatePass) {
tView.expandoStartIndex++;
tView.blueprint.push(null);
tView.data.push(null);
view.push(null);
}
}
@ -914,7 +912,7 @@ export function elementEnd(): void {
* @param sanitizer An optional function used to sanitize the value.
*/
export function elementAttribute(
index: number, name: string, value: any, sanitizer?: SanitizerFn): void {
index: number, name: string, value: any, sanitizer?: SanitizerFn | null): void {
if (value !== NO_CHANGE) {
const viewData = getViewData();
const renderer = getRenderer();
@ -947,7 +945,7 @@ export function elementAttribute(
*/
export function elementProperty<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): void {
if (value === NO_CHANGE) return;
const viewData = getViewData();
const element = getNativeByIndex(index, viewData) as RElement | RComment;

View File

@ -16,24 +16,32 @@
*
* See: `I18nCreateOpCodes` for example of usage.
*/
import {SanitizerFn} from './sanitization';
export const enum I18nMutateOpCode {
/// Stores shift amount for bits 17-2 that contain reference index.
SHIFT_REF = 2,
/// Stores shift amount for bits 17-3 that contain reference index.
SHIFT_REF = 3,
/// Stores shift amount for bits 31-17 that contain parent index.
SHIFT_PARENT = 17,
/// Mask for OpCode
MASK_OPCODE = 0b11,
MASK_OPCODE = 0b111,
/// Mask for reference index.
MASK_REF = ((2 ^ 16) - 1) << SHIFT_REF,
/// OpCode to select a node. (next OpCode will contain the operation.)
Select = 0b00,
Select = 0b000,
/// OpCode to append the current node to `PARENT`.
AppendChild = 0b01,
AppendChild = 0b001,
/// OpCode to insert the current node to `PARENT` before `REF`.
InsertBefore = 0b10,
InsertBefore = 0b010,
/// OpCode to remove the `REF` node from `PARENT`.
Remove = 0b11,
Remove = 0b011,
/// OpCode to set the attribute of a node.
Attr = 0b100,
/// OpCode to simulate elementEnd()
ElementEnd = 0b101,
/// OpCode to read the remove OpCodes for the nested ICU
RemoveNestedIcu = 0b110,
}
/**
@ -114,8 +122,8 @@ export interface COMMENT_MARKER { marker: 'comment'; }
* // For removing existing nodes
* // --------------------------------------------------
* // const node = lViewData[1];
* // lViewData[2].remove(node);
* 2 << SHIFT_PARENT | 1 << SHIFT_REF | Remove,
* // removeChild(tView.data(1), node, lViewData);
* 1 << SHIFT_REF | Remove,
*
* // For writing attributes
* // --------------------------------------------------
@ -178,7 +186,7 @@ export const enum I18nUpdateOpCode {
* }
* ```
* We can assume that each call to `i18nExp` sets an internal `changeMask` bit depending on the
* index of `i18nExp` index.
* index of `i18nExp`.
*
* OpCodes
* ```
@ -222,7 +230,7 @@ export const enum I18nUpdateOpCode {
* ```
*
*/
export interface I18nUpdateOpCodes extends Array<string|number|((text: string) => string | null)> {}
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null> {}
/**
* Store information for the i18n translation block.
@ -355,10 +363,6 @@ export interface TIcu {
update: I18nUpdateOpCodes[];
}
/**
* Stores currently selected case in each ICU.
*
* For each ICU in translation, the `Li18n` stores the currently selected case for the current
* `LView`. For perf reasons this array is only created if a translation block has an ICU.
*/
export interface LI18n extends Array<number> {}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;

View File

@ -21,6 +21,7 @@ export const enum TNodeType {
Element = 0b011,
ViewOrElement = 0b010,
ElementContainer = 0b100,
IcuContainer = 0b101,
}
/**
@ -255,7 +256,7 @@ export interface TNode {
parent: TElementNode|TContainerNode|null;
/**
* If this node is part of an i18n block, it indicates whether this container is part of the DOM
* If this node is part of an i18n block, it indicates whether this node is part of the DOM.
* If this node is not part of an i18n block, this field is null.
*/
detached: boolean|null;
@ -358,7 +359,6 @@ export interface TContainerNode extends TNode {
projection: null;
}
/** Static data for an <ng-container> */
export interface TElementContainerNode extends TNode {
/** Index in the LViewData[] array. */
@ -369,6 +369,21 @@ export interface TElementContainerNode extends TNode {
projection: null;
}
/** Static data for an ICU expression */
export interface TIcuContainerNode extends TNode {
/** Index in the LViewData[] array. */
index: number;
child: TElementNode|TTextNode|null;
parent: TElementNode|TElementContainerNode|null;
tViews: null;
projection: null;
/**
* Indicates the current active case for an ICU expression.
* It is null when there is no active case.
*/
activeCaseIndex: number|null;
}
/** Static data for a view */
export interface TViewNode extends TNode {
/** If -1, it's a dynamically created view. Otherwise, it is the view block ID. */

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
/**
* Function used to sanitize the value before writing it into the renderer.
*/
export type SanitizerFn = (value: any) => string;

View File

@ -11,9 +11,9 @@ import {Injector} from '../../di/injector';
import {QueryList} from '../../linker';
import {Sanitizer} from '../../sanitization/security';
import {Type} from '../../type';
import {LContainer} from './container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition';
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries} from './query';
@ -297,7 +297,7 @@ export interface TView {
/** Whether or not this template has been processed. */
firstTemplatePass: boolean;
/** Static data equivalent of LView.data[]. Contains TNodes. */
/** Static data equivalent of LView.data[]. Contains TNodes, PipeDefInternal or TI18n. */
data: TData;
/**
@ -535,7 +535,7 @@ export type HookData = (number | (() => void))[];
*/
export type TData =
(TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | Type<any>|
InjectionToken<any>| null)[];
InjectionToken<any>| TI18n | I18nUpdateOpCodes | null)[];
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.

View File

@ -97,7 +97,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵtextBinding': r3.textBinding,
'ɵembeddedViewStart': r3.embeddedViewStart,
'ɵembeddedViewEnd': r3.embeddedViewEnd,
'ɵi18nAttribute': r3.i18nAttribute,
'ɵi18nAttributes': r3.i18nAttributes,
'ɵi18nExp': r3.i18nExp,
'ɵi18nStart': r3.i18nStart,
'ɵi18nEnd': r3.i18nEnd,

View File

@ -21,8 +21,23 @@ const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
/** Retrieves the parent element of a given node. */
export function getParentNative(tNode: TNode, currentView: LViewData): RElement|RComment|null {
return tNode.parent == null ? getHostNative(currentView) :
getNativeByTNode(tNode.parent, currentView);
if (tNode.parent == null) {
return getHostNative(currentView);
} else {
const parentTNode = getFirstParentNative(tNode);
return getNativeByTNode(parentTNode, currentView);
}
}
/**
* Get the first parent of a node that isn't an IcuContainer TNode
*/
function getFirstParentNative(tNode: TNode): TNode {
let parent = tNode.parent;
while (parent && parent.type === TNodeType.IcuContainer) {
parent = parent.parent;
}
return parent !;
}
/**
@ -578,17 +593,22 @@ function canInsertNativeChildOfView(viewTNode: TViewNode, view: LViewData): bool
*
*
* @param parent The parent where the child will be inserted into.
* @param tNode The tNode of the node that we want to insert.
* @param currentView Current LView being processed.
* @return boolean Whether the child should be inserted now (or delayed until later).
* @return boolean Whether the node should be inserted now (or delayed until later).
*/
export function canInsertNativeNode(tNode: TNode, currentView: LViewData): boolean {
let currentNode = tNode;
let parent: TNode|null = tNode.parent;
if (tNode.parent && tNode.parent.type === TNodeType.ElementContainer) {
currentNode = getHighestElementContainer(tNode);
parent = currentNode.parent;
if (tNode.parent) {
if (tNode.parent.type === TNodeType.ElementContainer) {
currentNode = getHighestElementContainer(tNode);
parent = currentNode.parent;
} else if (tNode.parent.type === TNodeType.IcuContainer) {
currentNode = getFirstParentNative(currentNode);
parent = currentNode.parent;
}
}
if (parent === null) parent = currentView[HOST_NODE];
@ -639,7 +659,7 @@ export function nativeNextSibling(renderer: Renderer3, node: RNode): RNode|null
* @returns Whether or not the child was appended
*/
export function appendChild(
childEl: RNode | null, childTNode: TNode, currentView: LViewData): boolean {
childEl: RNode | null = null, childTNode: TNode, currentView: LViewData): boolean {
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const renderer = currentView[RENDERER];
const parentEl = getParentNative(childTNode, currentView);
@ -655,6 +675,9 @@ export function appendChild(
} else if (parentTNode.type === TNodeType.ElementContainer) {
const renderParent = getRenderParent(childTNode, currentView) !;
nativeInsertBefore(renderer, renderParent, childEl, parentEl);
} else if (parentTNode.type === TNodeType.IcuContainer) {
const icuAnchorNode = getNativeByTNode(childTNode.parent !, currentView) !as RElement;
nativeInsertBefore(renderer, parentEl as RElement, childEl, icuAnchorNode);
} else {
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) :
parentEl !.appendChild(childEl);

View File

@ -157,6 +157,10 @@ export function getCurrentView(): OpaqueViewState {
return viewData as any as OpaqueViewState;
}
export function _getViewData(): LViewData {
return viewData;
}
/**
* Restores `contextViewData` to the given OpaqueViewState instance.
*

View File

@ -252,4 +252,16 @@ export function getParentInjectorTNode(
export const defaultScheduler =
(typeof requestAnimationFrame !== 'undefined' && requestAnimationFrame || // browser only
setTimeout // everything else
).bind(global);
).bind(global);
/**
* Equivalent to ES6 spread, add each item to an array.
*
* @param items The items to add
* @param arr The array to which you want to add the items
*/
export function addAllToArray(items: any[], arr: any[]) {
for (let i = 0; i < items.length; i++) {
arr.push(items[i]);
}
}

View File

@ -57,14 +57,14 @@ const INLINE_ELEMENTS = merge(
'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' +
'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video'));
const VALID_ELEMENTS =
export const VALID_ELEMENTS =
merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS);
// Attributes that have href and hence need to be sanitized
const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href');
export const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href');
// Attributes that have special href set hence need to be sanitized
const SRCSET_ATTRS = tagSet('srcset');
export const SRCSET_ATTRS = tagSet('srcset');
const HTML_ATTRS = tagSet(
'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' +
@ -81,7 +81,7 @@ const HTML_ATTRS = tagSet(
// can be sanitized, but they increase security surface area without a legitimate use case, so they
// are left out here.
const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS);
export const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS);
/**
* SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe
@ -265,7 +265,7 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string
}
}
function getTemplateContent(el: Node): Node|null {
export function getTemplateContent(el: Node): Node|null {
return 'content' in (el as any /** Microsoft/TypeScript#21517 */) && isTemplateElement(el) ?
el.content :
null;

View File

@ -626,6 +626,9 @@
{
"name": "getElementDepthCount"
},
{
"name": "getFirstParentNative"
},
{
"name": "getFirstTemplatePass"
},

View File

@ -260,6 +260,9 @@
{
"name": "getDirectiveDef"
},
{
"name": "getFirstParentNative"
},
{
"name": "getFirstTemplatePass"
},

View File

@ -854,6 +854,9 @@
{
"name": "getErrorLogger"
},
{
"name": "getFirstParentNative"
},
{
"name": "getFirstTemplatePass"
},
@ -1113,7 +1116,7 @@
"name": "markViewDirty"
},
{
"name": "merge"
"name": "merge$1"
},
{
"name": "mergeAll"

View File

@ -668,6 +668,9 @@
{
"name": "getElementDepthCount"
},
{
"name": "getFirstParentNative"
},
{
"name": "getFirstTemplatePass"
},

View File

@ -681,7 +681,7 @@
"name": "PlatformRef"
},
{
"name": "Plural"
"name": "Plural$1"
},
{
"name": "QUERIES"
@ -1697,6 +1697,9 @@
{
"name": "getErrorLogger"
},
{
"name": "getFirstParentNative"
},
{
"name": "getFirstTemplatePass"
},
@ -1860,7 +1863,7 @@
"name": "getPlayerContext"
},
{
"name": "getPluralCategory"
"name": "getPluralCategory$1"
},
{
"name": "getPointers"

File diff suppressed because it is too large Load Diff