@braidlabs/core 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -0
- package/package.json +37 -0
- package/src/adapters/adapter.d.ts +75 -0
- package/src/adapters/adapter.js +35 -0
- package/src/adapters/adapter.js.map +1 -0
- package/src/adapters/compat-adapter.d.ts +27 -0
- package/src/adapters/compat-adapter.js +113 -0
- package/src/adapters/compat-adapter.js.map +1 -0
- package/src/adapters/custom-element-adapter.d.ts +21 -0
- package/src/adapters/custom-element-adapter.js +91 -0
- package/src/adapters/custom-element-adapter.js.map +1 -0
- package/src/compat/born-inert-scripts.d.ts +30 -0
- package/src/compat/born-inert-scripts.js +85 -0
- package/src/compat/born-inert-scripts.js.map +1 -0
- package/src/compat/document-facade.d.ts +44 -0
- package/src/compat/document-facade.js +104 -0
- package/src/compat/document-facade.js.map +1 -0
- package/src/compat/document-member-classification.d.ts +30 -0
- package/src/compat/document-member-classification.js +64 -0
- package/src/compat/document-member-classification.js.map +1 -0
- package/src/compat/document-overrides.d.ts +44 -0
- package/src/compat/document-overrides.js +225 -0
- package/src/compat/document-overrides.js.map +1 -0
- package/src/compat/document-surface.generated.d.ts +21 -0
- package/src/compat/document-surface.generated.js +292 -0
- package/src/compat/document-surface.generated.js.map +1 -0
- package/src/compat/fragment-boundary.d.ts +14 -0
- package/src/compat/fragment-boundary.js +335 -0
- package/src/compat/fragment-boundary.js.map +1 -0
- package/src/compat/metadata.d.ts +15 -0
- package/src/compat/metadata.js +21 -0
- package/src/compat/metadata.js.map +1 -0
- package/src/compat/navigation-bus.d.ts +29 -0
- package/src/compat/navigation-bus.js +92 -0
- package/src/compat/navigation-bus.js.map +1 -0
- package/src/compat/realm-patches.d.ts +21 -0
- package/src/compat/realm-patches.js +573 -0
- package/src/compat/realm-patches.js.map +1 -0
- package/src/compat/register-worker.d.ts +45 -0
- package/src/compat/register-worker.js +57 -0
- package/src/compat/register-worker.js.map +1 -0
- package/src/compat/script-execution.d.ts +65 -0
- package/src/compat/script-execution.js +361 -0
- package/src/compat/script-execution.js.map +1 -0
- package/src/compat/service-worker.d.ts +22 -0
- package/src/compat/service-worker.js +104 -0
- package/src/compat/service-worker.js.map +1 -0
- package/src/config.d.ts +37 -0
- package/src/config.js +26 -0
- package/src/config.js.map +1 -0
- package/src/context/context-bus.d.ts +67 -0
- package/src/context/context-bus.js +117 -0
- package/src/context/context-bus.js.map +1 -0
- package/src/elements/fragment-slot.d.ts +39 -0
- package/src/elements/fragment-slot.js +281 -0
- package/src/elements/fragment-slot.js.map +1 -0
- package/src/env/create-env.d.ts +31 -0
- package/src/env/create-env.js +91 -0
- package/src/env/create-env.js.map +1 -0
- package/src/env/fragment-env.d.ts +56 -0
- package/src/env/fragment-env.js +14 -0
- package/src/env/fragment-env.js.map +1 -0
- package/src/errors.d.ts +21 -0
- package/src/errors.js +21 -0
- package/src/errors.js.map +1 -0
- package/src/index.d.ts +36 -0
- package/src/index.js +40 -0
- package/src/index.js.map +1 -0
- package/src/protocol.d.ts +38 -0
- package/src/protocol.js +45 -0
- package/src/protocol.js.map +1 -0
- package/src/realm/realm-manager.d.ts +60 -0
- package/src/realm/realm-manager.js +296 -0
- package/src/realm/realm-manager.js.map +1 -0
- package/src/utils/assert.d.ts +7 -0
- package/src/utils/assert.js +10 -0
- package/src/utils/assert.js.map +1 -0
- package/src/utils/selector-helpers.d.ts +10 -0
- package/src/utils/selector-helpers.js +13 -0
- package/src/utils/selector-helpers.js.map +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { BraidError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the worker and introduces the page to it.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { registerBraidServiceWorker } from '@braidlabs/core';
|
|
7
|
+
* await registerBraidServiceWorker({ buildId: BUILD_ID });
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Resolves to the registration, or `null` where service workers are unavailable — a non-secure
|
|
11
|
+
* origin, a browser without them, or SSR. That is a `null` rather than a throw because a worker is
|
|
12
|
+
* an enhancement: a page that works without one should not fail to start because it could not have
|
|
13
|
+
* one.
|
|
14
|
+
*/
|
|
15
|
+
export async function registerBraidServiceWorker(options = {}) {
|
|
16
|
+
const container = globalThis.navigator
|
|
17
|
+
?.serviceWorker;
|
|
18
|
+
if (!container)
|
|
19
|
+
return null;
|
|
20
|
+
const url = options.url ?? '/__braid/sw.js';
|
|
21
|
+
const scope = options.scope ?? '/';
|
|
22
|
+
let registration;
|
|
23
|
+
try {
|
|
24
|
+
registration = await container.register(url, { scope, type: 'module' });
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
// The overwhelmingly common cause is the scope: a script at /__braid/sw.js may only claim '/'
|
|
28
|
+
// if its response carried Service-Worker-Allowed, and a shell serving the script itself
|
|
29
|
+
// (rather than through the gateway) usually has not configured that.
|
|
30
|
+
throw new BraidError(`registering the Braid service worker at ${url} failed`, {
|
|
31
|
+
fragmentId: 'braid-sw',
|
|
32
|
+
stage: 'realm-boot',
|
|
33
|
+
cause,
|
|
34
|
+
fixHint: `the script response must send "Service-Worker-Allowed: ${scope}" — set serviceWorker: true ` +
|
|
35
|
+
'on the gateway, which is the component that can send it',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (options.onVersion) {
|
|
39
|
+
const listener = (event) => {
|
|
40
|
+
const data = event.data;
|
|
41
|
+
if (data?.type === 'braid-sw:version')
|
|
42
|
+
options.onVersion?.(data);
|
|
43
|
+
};
|
|
44
|
+
container.addEventListener('message', listener);
|
|
45
|
+
}
|
|
46
|
+
// The controller is what receives messages; a first-load registration has none until it activates
|
|
47
|
+
// and takes over, so there is nobody to introduce the page to yet. The next navigation does it.
|
|
48
|
+
container.controller?.postMessage({
|
|
49
|
+
type: 'braid-sw:hello',
|
|
50
|
+
page: {
|
|
51
|
+
...(options.buildId === undefined ? {} : { buildId: options.buildId }),
|
|
52
|
+
...(options.snapshotId === undefined ? {} : { snapshotId: options.snapshotId }),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return registration;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=register-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-worker.js","sourceRoot":"","sources":["../../../../../libs/core/src/compat/register-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAmC1C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,UAA6C,EAAE;IAE/C,MAAM,SAAS,GAAI,UAAyE,CAAC,SAAS;QACpG,EAAE,aAAa,CAAC;IAClB,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,gBAAgB,CAAC;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC;IAEnC,IAAI,YAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8FAA8F;QAC9F,wFAAwF;QACxF,qEAAqE;QACrE,MAAM,IAAI,UAAU,CAAC,2CAA2C,GAAG,SAAS,EAAE;YAC5E,UAAU,EAAE,UAAU;YACtB,KAAK,EAAE,YAAY;YACnB,KAAK;YACL,OAAO,EACL,0DAA0D,KAAK,8BAA8B;gBAC7F,yDAAyD;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,CAAC,KAAmB,EAAE,EAAE;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAqC,CAAC;YACzD,IAAI,IAAI,EAAE,IAAI,KAAK,kBAAkB;gBAAE,OAAO,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC;QACF,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAyB,CAAC,CAAC;IACnE,CAAC;IAED,kGAAkG;IAClG,gGAAgG;IAChG,SAAS,CAAC,UAAU,EAAE,WAAW,CAAC;QAChC,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE;YACJ,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YACtE,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;SAChF;KACF,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script execution machinery for compat fragments: any script that enters a fragment's DOM is
|
|
3
|
+
* neutralized in the main document (an inert placeholder keeps the DOM shape the app expects)
|
|
4
|
+
* and executed in the fragment's realm instead.
|
|
5
|
+
*/
|
|
6
|
+
export declare function compatDomInsertion<T extends Node>(nodeToInsert: T, doInsertTheNode: Function, realmDocument?: Document): T;
|
|
7
|
+
/**
|
|
8
|
+
* Handles a multi-node insertion (append, prepend, replaceChildren, replaceWith) into a fragment
|
|
9
|
+
* DOM.
|
|
10
|
+
*
|
|
11
|
+
* All scripts and preload links across all node arguments are neutralized first, then the actual
|
|
12
|
+
* insertion runs exactly once, and only then are the scripts restored and executed in the
|
|
13
|
+
* fragment's realm, in argument order. (Neutralizing/restoring per-node around a shared deferred
|
|
14
|
+
* insertion would restore earlier scripts to their live form before the insertion actually
|
|
15
|
+
* happens, causing them to execute in the main JS context.)
|
|
16
|
+
*/
|
|
17
|
+
export declare function compatMultiNodeDomInsertion(nodes: (Node | string)[], doInsertTheNodes: () => void, realmDocument?: Document): void;
|
|
18
|
+
/**
|
|
19
|
+
* Activates the initial content of a fragment: patches braid-html/head/body stand-ins and
|
|
20
|
+
* executes every neutralized script/preload-link in document order in the fragment's realm.
|
|
21
|
+
*
|
|
22
|
+
* Used for both server-pierced content and content fetched client-side by the slot (which the
|
|
23
|
+
* compat adapter neutralizes before insertion).
|
|
24
|
+
*/
|
|
25
|
+
export declare function executeScriptsInFragmentContent(contentRoot: ParentNode, realmDocument: Document): void;
|
|
26
|
+
/**
|
|
27
|
+
* Weak map of scripts running in the realm to their inert clones in the fragment DOM.
|
|
28
|
+
*/
|
|
29
|
+
export declare const execToInertScriptMap: WeakMap<HTMLScriptElement, HTMLScriptElement>;
|
|
30
|
+
export declare const alreadyExecutedScriptsAndLinks: WeakSet<HTMLScriptElement | HTMLLinkElement>;
|
|
31
|
+
/**
|
|
32
|
+
* Executes a script in the fragment's realm.
|
|
33
|
+
* @param inertScript inert script already appended to a document within the fragment DOM
|
|
34
|
+
* @param realmDocument realm document in which the script should execute
|
|
35
|
+
* @returns true if the script executed, false if it was ignored
|
|
36
|
+
*/
|
|
37
|
+
export declare function executeInertScript(inertScript: HTMLScriptElement, realmDocument: Document): boolean;
|
|
38
|
+
export declare function executeInertPreloadLink(inertLink: HTMLLinkElement, realmDocument: Document): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Attaches exec scripts to the realm and keeps a record of already evaluated scripts.
|
|
41
|
+
* @param inertScript inert script already appended to a document within the fragment DOM
|
|
42
|
+
* @param execScript exec script to be attached to the realm document
|
|
43
|
+
* @param realmDocument realm document in which the script should execute
|
|
44
|
+
*/
|
|
45
|
+
export declare function attachScriptToRealm({ inertScript, execScript, realmDocument, }: {
|
|
46
|
+
inertScript: HTMLScriptElement;
|
|
47
|
+
execScript?: HTMLScriptElement;
|
|
48
|
+
realmDocument: Document;
|
|
49
|
+
}): void;
|
|
50
|
+
/**
|
|
51
|
+
* Set the type attribute of a script element to "inert".
|
|
52
|
+
*
|
|
53
|
+
* This prevents the script from executing in the main JS context when the script is attached to
|
|
54
|
+
* the main document.
|
|
55
|
+
*/
|
|
56
|
+
export declare function setInertScriptType(script: HTMLScriptElement): void;
|
|
57
|
+
/**
|
|
58
|
+
* Restore the original script type and erase any signs of us making the script inert.
|
|
59
|
+
*/
|
|
60
|
+
export declare function restoreScriptType(script: HTMLScriptElement): void;
|
|
61
|
+
/**
|
|
62
|
+
* Neutralizes the original link, so that it doesn't result in a duplicate network call.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setInertLinkRel(link: HTMLLinkElement): void;
|
|
65
|
+
export declare function patchSpecialHtmlElement(node: Element, realmDocument: Document): void;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-function-type --
|
|
2
|
+
* ported from the matrix-tested web-fragments fork: the deferred-insertion callbacks are
|
|
3
|
+
* arbitrary pre-bound native method wrappers. */
|
|
4
|
+
import { assert } from '../utils/assert.js';
|
|
5
|
+
import { getInternalReference } from './metadata.js';
|
|
6
|
+
/**
|
|
7
|
+
* Script execution machinery for compat fragments: any script that enters a fragment's DOM is
|
|
8
|
+
* neutralized in the main document (an inert placeholder keeps the DOM shape the app expects)
|
|
9
|
+
* and executed in the fragment's realm instead.
|
|
10
|
+
*/
|
|
11
|
+
export function compatDomInsertion(nodeToInsert, doInsertTheNode, realmDocument) {
|
|
12
|
+
// if we are operating outside of a fragment DOM or the appended child is neither an element nor
|
|
13
|
+
// a document fragment (which can carry scripts, e.g. cloned template contents), then just append
|
|
14
|
+
if (!realmDocument || !(nodeToInsert instanceof HTMLElement || nodeToInsert instanceof DocumentFragment)) {
|
|
15
|
+
return doInsertTheNode();
|
|
16
|
+
}
|
|
17
|
+
// if the child's prototype is directly HTMLElement, then check if this is a HTML/BODY/HEAD element and rewrite it
|
|
18
|
+
if (nodeToInsert instanceof HTMLElement && Object.getPrototypeOf(nodeToInsert) === HTMLElement.prototype) {
|
|
19
|
+
patchSpecialHtmlElement(nodeToInsert, realmDocument);
|
|
20
|
+
}
|
|
21
|
+
// if the child is a script, then append and execute it in the fragment's realm
|
|
22
|
+
if (nodeToInsert instanceof HTMLScriptElement) {
|
|
23
|
+
// if the child is an unattached inline script that doesn't yet have any text content, then we need to treat it in a special way
|
|
24
|
+
if (!nodeToInsert.src && !nodeToInsert.firstChild && !nodeToInsert.parentNode) {
|
|
25
|
+
prepareUnattachedInlineScript(nodeToInsert, realmDocument);
|
|
26
|
+
return doInsertTheNode();
|
|
27
|
+
}
|
|
28
|
+
setInertScriptType(nodeToInsert);
|
|
29
|
+
const returnVal = doInsertTheNode();
|
|
30
|
+
restoreScriptType(nodeToInsert);
|
|
31
|
+
executeInertScript(nodeToInsert, realmDocument);
|
|
32
|
+
return returnVal;
|
|
33
|
+
}
|
|
34
|
+
// if it's a preload or prefetch link for a script then append it to the realm and leave an inert node in the fragment DOM
|
|
35
|
+
if (nodeToInsert instanceof HTMLLinkElement &&
|
|
36
|
+
(nodeToInsert.rel === 'preload' || nodeToInsert.rel === 'prefetch' || nodeToInsert.rel === 'modulepreload')) {
|
|
37
|
+
setInertLinkRel(nodeToInsert);
|
|
38
|
+
const returnVal = doInsertTheNode();
|
|
39
|
+
executeInertPreloadLink(nodeToInsert, realmDocument);
|
|
40
|
+
return returnVal;
|
|
41
|
+
}
|
|
42
|
+
const nestedScriptsAndLinks = nodeToInsert.querySelectorAll?.('script,link[rel=preload],link[rel=prefetch],link[rel=modulepreload]');
|
|
43
|
+
// if the child doesn't contain nested scripts or links, then just append it
|
|
44
|
+
if (nestedScriptsAndLinks.length === 0) {
|
|
45
|
+
return doInsertTheNode();
|
|
46
|
+
}
|
|
47
|
+
// otherwise, append the child and execute all nested scripts in the fragment's realm
|
|
48
|
+
nestedScriptsAndLinks.forEach((element) => makeElementInert(element));
|
|
49
|
+
const returnVal = doInsertTheNode();
|
|
50
|
+
nestedScriptsAndLinks.forEach((element) => restoreElement(element));
|
|
51
|
+
nestedScriptsAndLinks.forEach((element) => executeElement(element, realmDocument));
|
|
52
|
+
return returnVal;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Handles a multi-node insertion (append, prepend, replaceChildren, replaceWith) into a fragment
|
|
56
|
+
* DOM.
|
|
57
|
+
*
|
|
58
|
+
* All scripts and preload links across all node arguments are neutralized first, then the actual
|
|
59
|
+
* insertion runs exactly once, and only then are the scripts restored and executed in the
|
|
60
|
+
* fragment's realm, in argument order. (Neutralizing/restoring per-node around a shared deferred
|
|
61
|
+
* insertion would restore earlier scripts to their live form before the insertion actually
|
|
62
|
+
* happens, causing them to execute in the main JS context.)
|
|
63
|
+
*/
|
|
64
|
+
export function compatMultiNodeDomInsertion(nodes, doInsertTheNodes, realmDocument) {
|
|
65
|
+
if (!realmDocument) {
|
|
66
|
+
return doInsertTheNodes();
|
|
67
|
+
}
|
|
68
|
+
const scriptsAndLinks = [];
|
|
69
|
+
for (const node of nodes) {
|
|
70
|
+
if (typeof node === 'string') {
|
|
71
|
+
console.warn('Braid compat: string arguments to append/prepend/replaceChildren/replaceWith are not supported and could result in incorrect script execution. Inserted string: ', node);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (node instanceof HTMLElement && Object.getPrototypeOf(node) === HTMLElement.prototype) {
|
|
75
|
+
patchSpecialHtmlElement(node, realmDocument);
|
|
76
|
+
}
|
|
77
|
+
if (node instanceof HTMLScriptElement) {
|
|
78
|
+
if (!node.src && !node.firstChild && !node.parentNode) {
|
|
79
|
+
prepareUnattachedInlineScript(node, realmDocument);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
scriptsAndLinks.push(node);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (node instanceof HTMLLinkElement &&
|
|
86
|
+
(node.rel === 'preload' || node.rel === 'prefetch' || node.rel === 'modulepreload')) {
|
|
87
|
+
scriptsAndLinks.push(node);
|
|
88
|
+
}
|
|
89
|
+
else if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
90
|
+
const nested = node.querySelectorAll?.('script,link[rel=preload],link[rel=prefetch],link[rel=modulepreload]');
|
|
91
|
+
nested?.forEach((element) => scriptsAndLinks.push(element));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
scriptsAndLinks.forEach((element) => makeElementInert(element));
|
|
95
|
+
doInsertTheNodes();
|
|
96
|
+
scriptsAndLinks.forEach((element) => restoreElement(element));
|
|
97
|
+
scriptsAndLinks.forEach((element) => executeElement(element, realmDocument));
|
|
98
|
+
}
|
|
99
|
+
function makeElementInert(element) {
|
|
100
|
+
if (element instanceof HTMLScriptElement) {
|
|
101
|
+
setInertScriptType(element);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
setInertLinkRel(element);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function restoreElement(element) {
|
|
108
|
+
if (element instanceof HTMLScriptElement) {
|
|
109
|
+
restoreScriptType(element);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// don't restore links as we currently don't have another way of keeping them inert
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function executeElement(element, realmDocument) {
|
|
116
|
+
if (element instanceof HTMLScriptElement) {
|
|
117
|
+
executeInertScript(element, realmDocument);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
executeInertPreloadLink(element, realmDocument);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Activates the initial content of a fragment: patches braid-html/head/body stand-ins and
|
|
125
|
+
* executes every neutralized script/preload-link in document order in the fragment's realm.
|
|
126
|
+
*
|
|
127
|
+
* Used for both server-pierced content and content fetched client-side by the slot (which the
|
|
128
|
+
* compat adapter neutralizes before insertion).
|
|
129
|
+
*/
|
|
130
|
+
export function executeScriptsInFragmentContent(contentRoot, realmDocument) {
|
|
131
|
+
// In addition to executing scripts, we also need to patch braid- tags if they are present so
|
|
132
|
+
// that the scripts see them without the prefix.
|
|
133
|
+
[...contentRoot.querySelectorAll('braid-html, braid-body, braid-head')].forEach((element) => patchSpecialHtmlElement(element, realmDocument));
|
|
134
|
+
const scriptsAndLinks = [
|
|
135
|
+
...contentRoot.querySelectorAll('link[rel=inert-preload],link[rel=inert-prefetch],link[rel=inert-modulepreload],script'),
|
|
136
|
+
];
|
|
137
|
+
scriptsAndLinks.forEach((inertElement) => {
|
|
138
|
+
if (inertElement instanceof HTMLLinkElement) {
|
|
139
|
+
executeInertPreloadLink(inertElement, realmDocument);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
restoreScriptType(inertElement);
|
|
143
|
+
executeInertScript(inertElement, realmDocument);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Weak map of scripts running in the realm to their inert clones in the fragment DOM.
|
|
149
|
+
*/
|
|
150
|
+
export const execToInertScriptMap = new WeakMap();
|
|
151
|
+
export const alreadyExecutedScriptsAndLinks = new WeakSet();
|
|
152
|
+
/**
|
|
153
|
+
* Executes a script in the fragment's realm.
|
|
154
|
+
* @param inertScript inert script already appended to a document within the fragment DOM
|
|
155
|
+
* @param realmDocument realm document in which the script should execute
|
|
156
|
+
* @returns true if the script executed, false if it was ignored
|
|
157
|
+
*/
|
|
158
|
+
export function executeInertScript(inertScript, realmDocument) {
|
|
159
|
+
// If the inert script has already been evaluated but later re-added to the DOM via any DOM
|
|
160
|
+
// insertion method (i.e. insertBefore() and appendChild()), do not evaluate the script again
|
|
161
|
+
if (alreadyExecutedScriptsAndLinks.has(inertScript)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
// If the script does not have a valid type attribute, treat the script node as a data block.
|
|
165
|
+
// Data blocks live in the main document as-is.
|
|
166
|
+
const validScriptTypes = ['module', 'text/javascript', 'importmap', 'speculationrules', '', null];
|
|
167
|
+
if (!validScriptTypes.includes(inertScript.getAttribute('type'))) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
assert(!!(inertScript.src || inertScript.textContent), `Can't execute script without src or textContent!`);
|
|
171
|
+
attachScriptToRealm({ inertScript, realmDocument });
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
export function executeInertPreloadLink(inertLink, realmDocument) {
|
|
175
|
+
// If the inert link has already been evaluated but later re-added to the DOM via any DOM
|
|
176
|
+
// insertion method, do not evaluate the link again
|
|
177
|
+
if (alreadyExecutedScriptsAndLinks.has(inertLink)) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
// If the link does not have a valid href attribute then skip
|
|
181
|
+
if (!inertLink.href)
|
|
182
|
+
return false;
|
|
183
|
+
const execLink = realmDocument.importNode(inertLink, true);
|
|
184
|
+
restoreLinkRel(execLink);
|
|
185
|
+
// Grab the Event constructor from the realm
|
|
186
|
+
const Event = realmDocument.defaultView.Event;
|
|
187
|
+
// Redispatch the load event onto the inert link in the fragment DOM
|
|
188
|
+
execLink.addEventListener('load', () => {
|
|
189
|
+
inertLink.dispatchEvent(new Event('load', { cancelable: false }));
|
|
190
|
+
});
|
|
191
|
+
// Redispatch the error event onto the inert link in the fragment DOM
|
|
192
|
+
execLink.addEventListener('error', () => {
|
|
193
|
+
inertLink.dispatchEvent(new Event('error', { cancelable: false }));
|
|
194
|
+
});
|
|
195
|
+
getInternalReference(realmDocument, 'body').appendChild(execLink);
|
|
196
|
+
alreadyExecutedScriptsAndLinks.add(inertLink);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Attaches exec scripts to the realm and keeps a record of already evaluated scripts.
|
|
201
|
+
* @param inertScript inert script already appended to a document within the fragment DOM
|
|
202
|
+
* @param execScript exec script to be attached to the realm document
|
|
203
|
+
* @param realmDocument realm document in which the script should execute
|
|
204
|
+
*/
|
|
205
|
+
export function attachScriptToRealm({ inertScript, execScript, realmDocument, }) {
|
|
206
|
+
if (!execScript) {
|
|
207
|
+
// Build a FRESH script element rather than cloning: a script that descends from a parser
|
|
208
|
+
// document (DOMParser, fragment parsing) carries the spec's "already started" flag, and
|
|
209
|
+
// cloning preserves the flag — a clone would silently never execute.
|
|
210
|
+
//
|
|
211
|
+
// The fresh element must be created realm-side with the realm document's NATIVE
|
|
212
|
+
// createElement: the facade virtualizes `realmDocument.createElement` into the main
|
|
213
|
+
// document, and a script adopted across documents does not execute.
|
|
214
|
+
execScript = Document.prototype.createElement.call(realmDocument, 'script');
|
|
215
|
+
for (const attribute of inertScript.attributes) {
|
|
216
|
+
execScript.setAttribute(attribute.name, attribute.value);
|
|
217
|
+
}
|
|
218
|
+
execScript.textContent = inertScript.textContent;
|
|
219
|
+
}
|
|
220
|
+
// the following line will append the executable script to the realm
|
|
221
|
+
// - inline scripts (script with textContent) will be executed synchronously when attached
|
|
222
|
+
// - external scripts (with src attribute) will execute once the current turn of the event loop unwinds
|
|
223
|
+
execToInertScriptMap.set(execScript, inertScript);
|
|
224
|
+
// redispatch the load event onto the inert script in the fragment DOM
|
|
225
|
+
execScript.addEventListener('load', () => {
|
|
226
|
+
inertScript.dispatchEvent(new Event('load', { cancelable: false }));
|
|
227
|
+
});
|
|
228
|
+
// redispatch the error event onto the inert script in the fragment DOM
|
|
229
|
+
execScript.addEventListener('error', () => {
|
|
230
|
+
inertScript.dispatchEvent(new Event('error', { cancelable: false }));
|
|
231
|
+
});
|
|
232
|
+
getInternalReference(realmDocument, 'body').appendChild(execScript);
|
|
233
|
+
alreadyExecutedScriptsAndLinks.add(inertScript);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Set the type attribute of a script element to "inert".
|
|
237
|
+
*
|
|
238
|
+
* This prevents the script from executing in the main JS context when the script is attached to
|
|
239
|
+
* the main document.
|
|
240
|
+
*/
|
|
241
|
+
export function setInertScriptType(script) {
|
|
242
|
+
const scriptType = script.type;
|
|
243
|
+
if (scriptType) {
|
|
244
|
+
script.setAttribute('data-script-type', scriptType);
|
|
245
|
+
}
|
|
246
|
+
script.setAttribute('type', 'inert');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Restore the original script type and erase any signs of us making the script inert.
|
|
250
|
+
*/
|
|
251
|
+
export function restoreScriptType(script) {
|
|
252
|
+
const scriptType = script.getAttribute('data-script-type');
|
|
253
|
+
script.removeAttribute('data-script-type');
|
|
254
|
+
script.removeAttribute('type');
|
|
255
|
+
if (scriptType) {
|
|
256
|
+
script.setAttribute('type', scriptType);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Neutralizes the original link, so that it doesn't result in a duplicate network call.
|
|
261
|
+
*/
|
|
262
|
+
export function setInertLinkRel(link) {
|
|
263
|
+
const linkRel = link.rel;
|
|
264
|
+
if (linkRel) {
|
|
265
|
+
link.rel = 'inert-' + linkRel;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Reverses the `setInertLinkRel` operation.
|
|
270
|
+
*/
|
|
271
|
+
function restoreLinkRel(link) {
|
|
272
|
+
const linkRel = link.rel;
|
|
273
|
+
if (linkRel && linkRel.startsWith('inert-')) {
|
|
274
|
+
// strip the 'inert-' prefix
|
|
275
|
+
link.rel = linkRel.slice(6);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Inline scripts that do not have textContent set are not evaluated until the first time
|
|
280
|
+
* textContent is set.
|
|
281
|
+
*
|
|
282
|
+
* We prepare these scripts for future execution by:
|
|
283
|
+
* - appending a non-neutralized clone of the script to the realm document
|
|
284
|
+
* - neutralizing the original script so that it doesn't execute in the main JS context in the future
|
|
285
|
+
* - patching the script's appendChild method to also append to the clone which will execute the
|
|
286
|
+
* script in the fragment's realm
|
|
287
|
+
*
|
|
288
|
+
* If text content is added later, the script then executes in the fragment's realm.
|
|
289
|
+
*/
|
|
290
|
+
function prepareUnattachedInlineScript(script, realmDocument) {
|
|
291
|
+
// We must clone the script before neutralizing it, otherwise the clone will also be neutralized
|
|
292
|
+
const execScript = realmDocument.importNode(script, true);
|
|
293
|
+
const inertScript = script;
|
|
294
|
+
// neutralize the script so that it doesn't execute in the main JS context
|
|
295
|
+
inertScript.textContent = '//inert';
|
|
296
|
+
document.body.appendChild(inertScript);
|
|
297
|
+
// now restore the inert script so the code using it doesn't see what we did
|
|
298
|
+
inertScript.remove();
|
|
299
|
+
inertScript.firstChild.remove();
|
|
300
|
+
// We have already cloned the inertScript so we don't need to clone it again.
|
|
301
|
+
// Cloning again would cause the execScript to be neutralized.
|
|
302
|
+
attachScriptToRealm({ inertScript, execScript, realmDocument });
|
|
303
|
+
const origScriptAppendChild = inertScript.appendChild;
|
|
304
|
+
inertScript.appendChild = function (node) {
|
|
305
|
+
origScriptAppendChild.call(inertScript, node);
|
|
306
|
+
execScript.appendChild.call(execScript, realmDocument.importNode(node, true));
|
|
307
|
+
// restore appendChild since only the first invocation should execute a script
|
|
308
|
+
inertScript.appendChild = origScriptAppendChild;
|
|
309
|
+
return node;
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Rewrite the tagName for the special braid-* stand-in elements so fragment code sees plain
|
|
314
|
+
* HTML/HEAD/BODY, and map their sizing reads onto the host page's real elements.
|
|
315
|
+
*/
|
|
316
|
+
let lazyBraidCustomElements;
|
|
317
|
+
function getBraidCustomElements() {
|
|
318
|
+
lazyBraidCustomElements ??= new Map([
|
|
319
|
+
['BRAID-HTML', document.documentElement],
|
|
320
|
+
['BRAID-HEAD', document.head],
|
|
321
|
+
['BRAID-BODY', document.body],
|
|
322
|
+
]);
|
|
323
|
+
return lazyBraidCustomElements;
|
|
324
|
+
}
|
|
325
|
+
export function patchSpecialHtmlElement(node, realmDocument) {
|
|
326
|
+
const originalTagName = node.tagName;
|
|
327
|
+
const mappedElement = getBraidCustomElements().get(originalTagName);
|
|
328
|
+
if (mappedElement) {
|
|
329
|
+
Object.defineProperties(node, {
|
|
330
|
+
clientWidth: {
|
|
331
|
+
get() {
|
|
332
|
+
return mappedElement.clientWidth;
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
clientHeight: {
|
|
336
|
+
get() {
|
|
337
|
+
return mappedElement.clientHeight;
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
nodeName: {
|
|
341
|
+
get() {
|
|
342
|
+
return originalTagName.replace(/^BRAID-/i, '');
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
tagName: {
|
|
346
|
+
get() {
|
|
347
|
+
return originalTagName.replace(/^BRAID-/i, '');
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
// if the node is BRAID-HEAD, we are done
|
|
352
|
+
if (node.tagName === 'HEAD') {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// otherwise we need to patch addEventListener and removeEventListener to support retargeting
|
|
356
|
+
// to the main <html> and <body> elements
|
|
357
|
+
node.addEventListener = realmDocument.addEventListener;
|
|
358
|
+
node.removeEventListener = realmDocument.removeEventListener;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=script-execution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-execution.js","sourceRoot":"","sources":["../../../../../libs/core/src/compat/script-execution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service worker containment for compat realms.
|
|
3
|
+
*
|
|
4
|
+
* A realm's `navigator` is aliased to the host window's, so without this a fragment reaches the
|
|
5
|
+
* host page's real `ServiceWorkerContainer` — and `register()` installs a **persistent worker on
|
|
6
|
+
* the host's origin** that outlives the fragment's unmount, because `env.signal` teardown has
|
|
7
|
+
* nothing to unregister it with.
|
|
8
|
+
*
|
|
9
|
+
* The invariants had a hole here worth naming. Host purity says *Braid* never patches the host;
|
|
10
|
+
* nothing said anything about a *fragment* installing origin-wide persistent state. Realm isolation
|
|
11
|
+
* covers JavaScript, and a service worker is neither JavaScript state nor scoped to a realm — it is
|
|
12
|
+
* infrastructure attached to the origin.
|
|
13
|
+
*
|
|
14
|
+
* So registration is refused, loudly. A fragment that wants offline behavior should get it from the
|
|
15
|
+
* host's worker rather than installing a second one, and two workers racing for one scope is a
|
|
16
|
+
* debugging problem nobody should inherit by accident.
|
|
17
|
+
*
|
|
18
|
+
* The container stays *present* rather than being deleted: `'serviceWorker' in navigator` is how
|
|
19
|
+
* every app feature-detects, and answering false would make an app that genuinely needs a worker
|
|
20
|
+
* degrade in silence instead of saying so.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createRealmNavigator(hostNavigator: Navigator, fragmentId: string): Navigator;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { BraidError } from '../errors.js';
|
|
2
|
+
import { isDevMode } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service worker containment for compat realms.
|
|
5
|
+
*
|
|
6
|
+
* A realm's `navigator` is aliased to the host window's, so without this a fragment reaches the
|
|
7
|
+
* host page's real `ServiceWorkerContainer` — and `register()` installs a **persistent worker on
|
|
8
|
+
* the host's origin** that outlives the fragment's unmount, because `env.signal` teardown has
|
|
9
|
+
* nothing to unregister it with.
|
|
10
|
+
*
|
|
11
|
+
* The invariants had a hole here worth naming. Host purity says *Braid* never patches the host;
|
|
12
|
+
* nothing said anything about a *fragment* installing origin-wide persistent state. Realm isolation
|
|
13
|
+
* covers JavaScript, and a service worker is neither JavaScript state nor scoped to a realm — it is
|
|
14
|
+
* infrastructure attached to the origin.
|
|
15
|
+
*
|
|
16
|
+
* So registration is refused, loudly. A fragment that wants offline behavior should get it from the
|
|
17
|
+
* host's worker rather than installing a second one, and two workers racing for one scope is a
|
|
18
|
+
* debugging problem nobody should inherit by accident.
|
|
19
|
+
*
|
|
20
|
+
* The container stays *present* rather than being deleted: `'serviceWorker' in navigator` is how
|
|
21
|
+
* every app feature-detects, and answering false would make an app that genuinely needs a worker
|
|
22
|
+
* degrade in silence instead of saying so.
|
|
23
|
+
*/
|
|
24
|
+
export function createRealmNavigator(hostNavigator, fragmentId) {
|
|
25
|
+
const container = createContainmentContainer(fragmentId);
|
|
26
|
+
return new Proxy(hostNavigator, {
|
|
27
|
+
get(target, property, receiver) {
|
|
28
|
+
if (property === 'serviceWorker')
|
|
29
|
+
return container;
|
|
30
|
+
const value = Reflect.get(target, property, target);
|
|
31
|
+
// Navigator members are natively bound to the real navigator; handing back an unbound
|
|
32
|
+
// function would make `navigator.sendBeacon(...)` throw an illegal-invocation error.
|
|
33
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
34
|
+
},
|
|
35
|
+
// `navigator.serviceWorker = x` is silently ignored by the platform (the property has no
|
|
36
|
+
// setter); mirroring that is more predictable than throwing.
|
|
37
|
+
set(target, property, value, receiver) {
|
|
38
|
+
if (property === 'serviceWorker')
|
|
39
|
+
return true;
|
|
40
|
+
return Reflect.set(target, property, value, receiver);
|
|
41
|
+
},
|
|
42
|
+
has(target, property) {
|
|
43
|
+
return property === 'serviceWorker' || Reflect.has(target, property);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createContainmentContainer(fragmentId) {
|
|
48
|
+
const refuse = (operation) => new BraidError(`a fragment may not ${operation} a service worker`, {
|
|
49
|
+
fragmentId,
|
|
50
|
+
stage: 'realm-boot',
|
|
51
|
+
fixHint: 'a service worker attaches to the whole origin, outlives this fragment, and is not scoped ' +
|
|
52
|
+
'by the realm — so the shell must own it. Register it from the host and let this fragment ' +
|
|
53
|
+
"share it, or drop the registration if the fragment's offline behavior is not needed here.",
|
|
54
|
+
});
|
|
55
|
+
const report = (operation) => {
|
|
56
|
+
if (isDevMode()) {
|
|
57
|
+
console.warn(`[braid:${fragmentId}] refused to ${operation} a service worker. Registration would install ` +
|
|
58
|
+
`origin-wide persistent state that survives this fragment's unmount; the shell owns the worker.`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const target = new EventTarget();
|
|
62
|
+
const container = {
|
|
63
|
+
async register() {
|
|
64
|
+
report('register');
|
|
65
|
+
throw refuse('register');
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Empty rather than the host's registrations.
|
|
69
|
+
*
|
|
70
|
+
* Reporting them would tell a fragment what else is installed on the origin — the host's
|
|
71
|
+
* infrastructure is not the fragment's business, and a fragment acting on a registration it
|
|
72
|
+
* did not create is a worse outcome than one that sees none.
|
|
73
|
+
*/
|
|
74
|
+
async getRegistration() {
|
|
75
|
+
return undefined;
|
|
76
|
+
},
|
|
77
|
+
async getRegistrations() {
|
|
78
|
+
return [];
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Rejected, which is a deliberate deviation: the spec leaves `ready` pending forever when there
|
|
82
|
+
* is no registration. A promise that never settles is indistinguishable from a hang, and in an
|
|
83
|
+
* emulation layer an honest error beats a spec-accurate silence.
|
|
84
|
+
*/
|
|
85
|
+
get ready() {
|
|
86
|
+
report('await readiness of');
|
|
87
|
+
return Promise.reject(refuse('use'));
|
|
88
|
+
},
|
|
89
|
+
get controller() {
|
|
90
|
+
return null;
|
|
91
|
+
},
|
|
92
|
+
startMessages() {
|
|
93
|
+
// no worker, so no messages to start delivering
|
|
94
|
+
},
|
|
95
|
+
addEventListener: target.addEventListener.bind(target),
|
|
96
|
+
removeEventListener: target.removeEventListener.bind(target),
|
|
97
|
+
dispatchEvent: target.dispatchEvent.bind(target),
|
|
98
|
+
oncontrollerchange: null,
|
|
99
|
+
onmessage: null,
|
|
100
|
+
onmessageerror: null,
|
|
101
|
+
};
|
|
102
|
+
return container;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=service-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-worker.js","sourceRoot":"","sources":["../../../../../libs/core/src/compat/service-worker.ts"],"names":[],"mappings":"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"}
|