-
Notifications
You must be signed in to change notification settings - Fork 151
/
nodes.ts
139 lines (124 loc) · 5.48 KB
/
nodes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { StringContext } from './strings';
import { NodeType, TransferrableNodeIndex } from '../transfer/TransferrableNodes';
/**
* IE11 doesn't support NodeList.prototype.forEach
* https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
* @param list NodeList to iterate over
* @param callback method to call with each node
*/
const nodeListEach = (list: NodeList, callback: (value: Node, key: number) => any): void => Array.prototype.forEach.call(list, callback);
export const BASE_ELEMENT_INDEX = 1;
export class NodeContext {
public baseElement: HTMLElement;
private stringContext: StringContext;
private count: number;
private nodes: Map<number, Node>;
/**
* Called when initializing a Worker, ensures the nodes in baseElement are
* known for transmission into the Worker and future mutation events from the
* Worker.
* @param baseElement Element that will be controlled by a Worker
*/
constructor(stringContext: StringContext, baseElement: Element) {
this.count = 2;
this.stringContext = stringContext;
// The nodes map is populated with two default values pointing to baseElement.
// These are [document, document.body] from the worker.
this.nodes = new Map([
[BASE_ELEMENT_INDEX, baseElement],
[2, baseElement],
]);
this.baseElement = baseElement as HTMLElement;
// To ensure a lookup works correctly from baseElement
// add an index equal to the background thread document.body.
baseElement._index_ = 2;
// Lastly, it's important while initializing the document that we store
// the default nodes present in the server rendered document.
nodeListEach(baseElement.childNodes, (n: ChildNode) => this.storeNodes(n));
}
public createNodes = (buffer: ArrayBuffer, sanitizer?: Sanitizer): void => {
const nodeBuffer = new Uint16Array(buffer);
const nodeBufferLength = nodeBuffer.length;
for (let iterator = 0; iterator < nodeBufferLength; iterator += TransferrableNodeIndex.End) {
let node: Node;
if (nodeBuffer[iterator + TransferrableNodeIndex.NodeType] === NodeType.TEXT_NODE) {
node = document.createTextNode(this.stringContext.get(nodeBuffer[iterator + TransferrableNodeIndex.TextContent]));
} else if (nodeBuffer[iterator + TransferrableNodeIndex.NodeType] === NodeType.COMMENT_NODE) {
node = document.createComment(this.stringContext.get(nodeBuffer[iterator + TransferrableNodeIndex.TextContent]));
} else if (nodeBuffer[iterator + TransferrableNodeIndex.NodeType] === NodeType.DOCUMENT_FRAGMENT_NODE) {
node = document.createDocumentFragment();
} else {
const nodeName = this.stringContext.get(nodeBuffer[iterator + TransferrableNodeIndex.NodeName]);
node =
nodeBuffer[iterator + TransferrableNodeIndex.Namespace] !== 0
? document.createElementNS(this.stringContext.get(nodeBuffer[iterator + TransferrableNodeIndex.Namespace]), nodeName)
: document.createElement(nodeName);
// TODO(KB): Restore Properties
// skeleton.properties.forEach(property => {
// node[`${property.name}`] = property.value;
// });
// ((skeleton as TransferrableElement)[TransferrableKeys.childNodes] || []).forEach(childNode => {
// if (childNode[TransferrableKeys.transferred] === NumericBoolean.FALSE) {
// node.appendChild(this.createNode(childNode as TransferrableNode));
// }
// });
// If `node` is removed by the sanitizer, don't store it and return null.
if (sanitizer && !sanitizer.sanitize(node)) {
continue;
}
}
this.storeNode(node, nodeBuffer[iterator]);
}
};
/**
* Returns the real DOM Element corresponding to a serialized Element object.
* @param id
* @return RenderableElement | null
*/
public getNode = (id: number): RenderableElement | null => {
const node = this.nodes.get(id);
if (node && node.nodeName === 'BODY') {
// If the node requested is the "BODY"
// Then we return the base node this specific <amp-script> comes from.
// This encapsulates each <amp-script> node.
return this.baseElement as RenderableElement;
}
return node as RenderableElement;
};
/**
* Store the requested node and all of its children.
* @param node node to store.
*/
private storeNodes = (node: Node): void => {
this.storeNode(node, ++this.count);
nodeListEach(node.childNodes, (n: ChildNode) => this.storeNodes(n));
};
/**
* Establish link between DOM `node` and worker-generated identifier `id`.
*
* These _shouldn't_ collide between instances of <amp-script> since
* each element creates it's own pool on both sides of the worker
* communication bridge.
* @param node
* @param id
*/
private storeNode(node: Node, id: number): void {
(node as Node)._index_ = id;
this.nodes.set(id, node as Node);
}
}