UNPKG

24.1 kBJavaScriptView Raw
1/* Partytown 0.2.1 - MIT builder.io */
2(window => {
3 const isPromise = v => "object" == typeof v && v && v.then;
4 const noop = () => true;
5 const len = obj => obj.length;
6 const getConstructorName = obj => {
7 try {
8 return obj.constructor.name;
9 } catch (e) {}
10 return "";
11 };
12 const startsWith = (str, val) => str.startsWith(val);
13 const isValidMemberName = memberName => !(startsWith(memberName, "webkit") || startsWith(memberName, "toJSON") || startsWith(memberName, "constructor") || startsWith(memberName, "toString") || startsWith(memberName, "_"));
14 const randomId = () => Math.round(999999999 * Math.random() + 4);
15 const InstanceIdKey = Symbol();
16 const CreatedKey = Symbol();
17 const instances = new Map;
18 const mainRefs = new Map;
19 const winCtxs = {};
20 const windowIds = new WeakMap;
21 const getAndSetInstanceId = (instance, instanceId, nodeName) => {
22 if (instance) {
23 if (instance === instance.window) {
24 return 0;
25 }
26 if ("#document" === (nodeName = instance.nodeName)) {
27 return 1;
28 }
29 if ("HTML" === nodeName) {
30 return 2;
31 }
32 if ("HEAD" === nodeName) {
33 return 3;
34 }
35 if ("BODY" === nodeName) {
36 return 4;
37 }
38 "number" != typeof (instanceId = instance[InstanceIdKey]) && setInstanceId(instance, instanceId = randomId());
39 return instanceId;
40 }
41 return -1;
42 };
43 const getInstance = (winId, instanceId, winCtx, win, doc) => {
44 winCtx = winCtxs[winId];
45 if (winCtx) {
46 win = winCtx.$window$;
47 if (win) {
48 doc = win.document;
49 return 0 === instanceId ? win : 1 === instanceId ? doc : 2 === instanceId ? doc.documentElement : 3 === instanceId ? doc.head : 4 === instanceId ? doc.body : instances.get(instanceId);
50 }
51 }
52 };
53 const setInstanceId = (instance, instanceId, now) => {
54 if (instance) {
55 instances.set(instanceId, instance);
56 instance[InstanceIdKey] = instanceId;
57 instance[CreatedKey] = now = Date.now();
58 if (now > lastCleanup + 5e3) {
59 instances.forEach(((storedInstance, instanceId) => {
60 storedInstance[CreatedKey] < lastCleanup && storedInstance.nodeType && !storedInstance.isConnected && instances.delete(instanceId);
61 }));
62 lastCleanup = now;
63 }
64 }
65 };
66 let lastCleanup = 0;
67 const mainWindow = window.parent;
68 const doc = document;
69 const config = mainWindow.partytown || {};
70 const libPath = (config.lib || "/~partytown/") + "debug/";
71 const logMain = msg => {
72 console.debug.apply(console, [ "%cMain 🌎", "background: #717171; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;", msg ]);
73 };
74 const winIds = [];
75 const normalizedWinId = winId => {
76 winIds.includes(winId) || winIds.push(winId);
77 return winIds.indexOf(winId) + 1;
78 };
79 const serializeForWorker = ($winId$, value, added, type, cstrName) => void 0 !== value && (type = typeof value) ? "string" === type || "number" === type || "boolean" === type || null == value ? [ 0, value ] : "function" === type ? [ 6 ] : (added = added || new Set) && Array.isArray(value) ? added.has(value) ? [ 1, [] ] : added.add(value) && [ 1, value.map((v => serializeForWorker($winId$, v, added))) ] : "object" === type ? "" === (cstrName = getConstructorName(value)) ? [ 2, {} ] : "Window" === cstrName ? [ 3, {
80 $winId$: $winId$,
81 $instanceId$: 0
82 } ] : "HTMLCollection" === cstrName || "NodeList" === cstrName ? [ 7, Array.from(value).map((v => serializeForWorker($winId$, v, added)[1])) ] : "Event" === cstrName ? [ 5, serializeObjectForWorker($winId$, value, added) ] : "CSSRuleList" === cstrName ? [ 12, Array.from(value).map(serializeCssRuleForWorker) ] : startsWith(cstrName, "CSS") && cstrName.endsWith("Rule") ? [ 11, serializeCssRuleForWorker(value) ] : "CSSStyleDeclaration" === cstrName ? [ 13, serializeObjectForWorker($winId$, value, added) ] : "Attr" === cstrName ? [ 10, [ value.name, value.value ] ] : value.nodeType ? [ 3, {
83 $winId$: $winId$,
84 $instanceId$: getAndSetInstanceId(value),
85 $nodeName$: value.nodeName
86 } ] : [ 2, serializeObjectForWorker($winId$, value, added, true, true) ] : void 0 : value;
87 const serializeObjectForWorker = (winId, obj, added, includeFunctions, includeEmptyStrings, serializedObj, propName, propValue) => {
88 serializedObj = {};
89 if (!added.has(obj)) {
90 added.add(obj);
91 for (propName in obj) {
92 if (isValidMemberName(propName)) {
93 propValue = obj[propName];
94 (includeFunctions || "function" != typeof propValue) && (includeEmptyStrings || "" !== propValue) && (serializedObj[propName] = serializeForWorker(winId, propValue, added));
95 }
96 }
97 }
98 return serializedObj;
99 };
100 const serializeCssRuleForWorker = cssRule => {
101 let obj = {};
102 let key;
103 for (key in cssRule) {
104 validCssRuleProps.includes(key) && (obj[key] = cssRule[key]);
105 }
106 return obj;
107 };
108 const deserializeFromWorker = (worker, serializedTransfer, serializedType, serializedValue) => {
109 if (serializedTransfer) {
110 serializedType = serializedTransfer[0];
111 serializedValue = serializedTransfer[1];
112 return 0 === serializedType ? serializedValue : 4 === serializedType ? deserializeRefFromWorker(worker, serializedValue) : 1 === serializedType ? serializedValue.map((v => deserializeFromWorker(worker, v))) : 3 === serializedType ? getInstance(serializedValue.$winId$, serializedValue.$instanceId$) : 5 === serializedType ? constructEvent(deserializeObjectFromWorker(worker, serializedValue)) : 2 === serializedType ? deserializeObjectFromWorker(worker, serializedValue) : 8 === serializedType ? serializedValue : 9 === serializedType ? new window[serializedTransfer[2]](serializedValue) : void 0;
113 }
114 };
115 const deserializeRefFromWorker = (worker, {$winId$: $winId$, $instanceId$: $instanceId$, $refId$: $refId$}, ref) => {
116 ref = mainRefs.get($refId$);
117 if (!ref) {
118 ref = function(...args) {
119 const refHandlerData = {
120 $instanceId$: $instanceId$,
121 $refId$: $refId$,
122 $thisArg$: serializeForWorker($winId$, this),
123 $args$: serializeForWorker($winId$, args)
124 };
125 worker.postMessage([ 7, refHandlerData ]);
126 };
127 mainRefs.set($refId$, ref);
128 }
129 return ref;
130 };
131 const constructEvent = eventProps => new ("detail" in eventProps ? CustomEvent : Event)(eventProps.type, eventProps);
132 const deserializeObjectFromWorker = (worker, serializedValue, obj, key) => {
133 obj = {};
134 for (key in serializedValue) {
135 obj[key] = deserializeFromWorker(worker, serializedValue[key]);
136 }
137 return obj;
138 };
139 const validCssRuleProps = "cssText,selectorText,href,media,namespaceURI,prefix,name,conditionText".split(",");
140 const mainAccessHandler = async (worker, accessReq) => {
141 let accessRsp = {
142 $msgId$: accessReq.$msgId$
143 };
144 let totalTasks = len(accessReq.$tasks$);
145 let i = 0;
146 let task;
147 let winId;
148 let applyPath;
149 let instance;
150 let rtnValue;
151 for (;i < totalTasks; i++) {
152 try {
153 task = accessReq.$tasks$[i];
154 winId = task.$winId$;
155 applyPath = task.$applyPath$;
156 winCtxs[winId] || await new Promise((resolve => {
157 let check = 0;
158 let callback = () => {
159 winCtxs[winId] || check++ > 999 ? resolve() : setTimeout(callback, 9);
160 };
161 callback();
162 }));
163 if (1 === applyPath[0] && applyPath[1] in winCtxs[winId].$window$) {
164 setInstanceId(new winCtxs[winId].$window$[applyPath[1]](...deserializeFromWorker(worker, applyPath[2])), task.$instanceId$);
165 } else {
166 instance = getInstance(winId, task.$instanceId$);
167 if (instance) {
168 rtnValue = applyToInstance(worker, instance, applyPath, task.$groupedGetters$);
169 task.$assignInstanceId$ && setInstanceId(rtnValue, task.$assignInstanceId$);
170 if (isPromise(rtnValue)) {
171 rtnValue = await rtnValue;
172 accessRsp.$isPromise$ = true;
173 }
174 accessRsp.$rtnValue$ = serializeForWorker(winId, rtnValue);
175 } else {
176 accessRsp.$error$ = `Error finding instance "${task.$instanceId$}" on window ${normalizedWinId(winId)} (${winId})`;
177 console.error(accessRsp.$error$, task);
178 }
179 }
180 } catch (e) {
181 i === totalTasks - 1 ? accessRsp.$error$ = String(e.stack || e) : console.error(e);
182 }
183 }
184 return accessRsp;
185 };
186 const applyToInstance = (worker, instance, applyPath, groupedGetters) => {
187 let i = 0;
188 let l = len(applyPath);
189 let next;
190 let current;
191 let previous;
192 let args;
193 let groupedRtnValues;
194 for (;i < l; i++) {
195 current = applyPath[i];
196 next = applyPath[i + 1];
197 previous = applyPath[i - 1];
198 try {
199 if (!Array.isArray(next)) {
200 if ("string" == typeof current || "number" == typeof current) {
201 if (i + 1 === l && groupedGetters) {
202 groupedRtnValues = {};
203 groupedGetters.map((propName => groupedRtnValues[propName] = instance[propName]));
204 return groupedRtnValues;
205 }
206 instance = instance[current];
207 } else {
208 if (0 === next) {
209 instance[previous] = deserializeFromWorker(worker, current);
210 return;
211 }
212 if ("function" == typeof instance[previous]) {
213 args = deserializeFromWorker(worker, current);
214 "insertRule" === previous && args[1] > len(instance.cssRules) && (args[1] = len(instance.cssRules));
215 instance = instance[previous].apply(instance, args);
216 if ("play" === previous) {
217 return Promise.resolve();
218 }
219 }
220 }
221 }
222 } catch (err) {
223 console.debug("Non-blocking setter error:", err);
224 }
225 }
226 return instance;
227 };
228 const registerWindow = (worker, $winId$, $window$) => {
229 if (!windowIds.has($window$)) {
230 windowIds.set($window$, $winId$);
231 const doc = $window$.document;
232 const history = $window$.history;
233 const envData = {
234 $winId$: $winId$,
235 $parentWinId$: windowIds.get($window$.parent),
236 $url$: doc.baseURI
237 };
238 const sendInitEnvData = () => worker.postMessage([ 3, envData ]);
239 const pushState = history.pushState.bind(history);
240 const replaceState = history.replaceState.bind(history);
241 const onLocationChange = () => setTimeout((() => worker.postMessage([ 11, $winId$, doc.baseURI ])));
242 history.pushState = (data, _, url) => {
243 pushState(data, _, url);
244 onLocationChange();
245 };
246 history.replaceState = (data, _, url) => {
247 replaceState(data, _, url);
248 onLocationChange();
249 };
250 $window$.addEventListener("popstate", onLocationChange);
251 $window$.addEventListener("hashchange", onLocationChange);
252 winCtxs[$winId$] = {
253 $winId$: $winId$,
254 $window$: $window$
255 };
256 winCtxs[$winId$].$startTime$ = performance.now();
257 {
258 const winType = envData.$winId$ === envData.$parentWinId$ ? "top" : "iframe";
259 logMain(`Registered ${winType} window ${normalizedWinId($winId$)} (${$winId$})`);
260 }
261 "complete" === doc.readyState ? sendInitEnvData() : $window$.addEventListener("load", sendInitEnvData);
262 }
263 };
264 const readNextScript = (worker, winCtx) => {
265 let $winId$ = winCtx.$winId$;
266 let win = winCtx.$window$;
267 let doc = win.document;
268 let scriptSelector = 'script[type="text/partytown"]:not([data-ptid]):not([data-pterror])';
269 let scriptElm = doc.querySelector('script[type="text/partytown"]:not([data-ptid]):not([data-pterror]):not([async]):not([defer])');
270 let $instanceId$;
271 let scriptData;
272 scriptElm || (scriptElm = doc.querySelector(scriptSelector));
273 if (scriptElm) {
274 scriptElm.dataset.ptid = $instanceId$ = getAndSetInstanceId(scriptElm, $winId$);
275 scriptData = {
276 $winId$: $winId$,
277 $instanceId$: $instanceId$
278 };
279 if (scriptElm.src) {
280 scriptData.$url$ = scriptElm.src;
281 scriptData.$orgUrl$ = scriptElm.dataset.ptsrc || scriptElm.src;
282 } else {
283 scriptData.$content$ = scriptElm.innerHTML;
284 }
285 worker.postMessage([ 6, scriptData ]);
286 } else if (!winCtx.$isInitialized$) {
287 winCtx.$isInitialized$ = 1;
288 ((worker, $winId$, win) => {
289 let queuedForwardCalls = win._ptf;
290 let forwards = (win.partytown || {}).forward || [];
291 let i;
292 let mainForwardFn;
293 let forwardCall = ($forward$, args) => worker.postMessage([ 8, {
294 $winId$: $winId$,
295 $forward$: $forward$,
296 $args$: serializeForWorker($winId$, Array.from(args))
297 } ]);
298 win._ptf = void 0;
299 forwards.map((forwardProps => {
300 mainForwardFn = win;
301 forwardProps.split(".").map(((_, i, arr) => {
302 mainForwardFn = mainForwardFn[arr[i]] = i + 1 < len(arr) ? mainForwardFn[arr[i]] || ("push" === arr[i + 1] ? [] : {}) : (...args) => forwardCall(arr, args);
303 }));
304 }));
305 if (queuedForwardCalls) {
306 for (i = 0; i < len(queuedForwardCalls); i += 2) {
307 forwardCall(queuedForwardCalls[i], queuedForwardCalls[i + 1]);
308 }
309 }
310 })(worker, $winId$, win);
311 doc.dispatchEvent(new CustomEvent("pt0"));
312 {
313 const winType = win === win.top ? "top" : "iframe";
314 logMain(`Executed ${winType} window ${normalizedWinId($winId$)} environment scripts in ${(performance.now() - winCtx.$startTime$).toFixed(1)}ms`);
315 }
316 worker.postMessage([ 4, $winId$ ]);
317 }
318 };
319 const onMessageFromWebWorker = (worker, msg, winCtx) => {
320 if (2 === msg[0]) {
321 registerWindow(worker, randomId(), mainWindow);
322 } else {
323 winCtx = winCtxs[msg[1]];
324 winCtx && (6 === msg[0] ? readNextScript(worker, winCtx) : 5 === msg[0] && ((worker, winCtx, instanceId, errorMsg, script) => {
325 script = winCtx.$window$.document.querySelector(`[data-ptid="${instanceId}"]`);
326 script && (errorMsg ? script.dataset.pterror = errorMsg : script.type += "-x");
327 readNextScript(worker, winCtx);
328 })(worker, winCtx, msg[2], msg[3]));
329 }
330 };
331 const readImplementation = (cstrName, impl, memberName) => {
332 let interfaceMembers = [];
333 let interfaceInfo = [ cstrName, "Object", interfaceMembers ];
334 for (memberName in impl) {
335 readImplementationMember(interfaceMembers, impl, memberName);
336 }
337 return interfaceInfo;
338 };
339 const readOwnImplementation = (interfaces, cstrName, CstrPrototype, impl, interfaceType) => {
340 if ("Object" !== cstrName && !interfaces.some((i => i[0] === cstrName))) {
341 const SuperCstr = Object.getPrototypeOf(CstrPrototype);
342 const superCstrName = getConstructorName(SuperCstr);
343 const interfaceMembers = [];
344 readOwnImplementation(interfaces, superCstrName, SuperCstr, impl, interfaceType);
345 Object.keys(Object.getOwnPropertyDescriptors(CstrPrototype)).map((memberName => readImplementationMember(interfaceMembers, impl, memberName)));
346 interfaces.push([ cstrName, superCstrName, interfaceMembers, interfaceType, impl.nodeName ]);
347 }
348 };
349 const readImplementationMember = (interfaceMembers, implementation, memberName, value, memberType, cstrName) => {
350 try {
351 if (isValidMemberName(memberName) && isNaN(memberName[0]) && "all" !== memberName) {
352 value = implementation[memberName];
353 memberType = typeof value;
354 if ("function" === memberType) {
355 (String(value).includes("[native") || Object.getPrototypeOf(implementation)[memberName]) && interfaceMembers.push([ memberName, 5 ]);
356 } else if ("object" === memberType && null != value) {
357 cstrName = getConstructorName(value);
358 "Object" !== cstrName && self[cstrName] && interfaceMembers.push([ memberName, value.nodeType || cstrName ]);
359 } else {
360 "symbol" !== memberType && (memberName.toUpperCase() === memberName ? interfaceMembers.push([ memberName, 6, value ]) : interfaceMembers.push([ memberName, 6 ]));
361 }
362 }
363 } catch (e) {
364 console.warn(e);
365 }
366 };
367 const htmlConstructorToTagMap = {
368 Anchor: "A",
369 DList: "DL",
370 Image: "IMG",
371 OList: "OL",
372 Paragraph: "P",
373 TableCaption: "CAPTION",
374 TableCell: "TD",
375 TableCol: "COLGROUP",
376 TableRow: "TR",
377 TableSection: "TBODY",
378 UList: "UL"
379 };
380 const getHtmlTagNameFromConstructor = t => {
381 t = t.slice(4).replace("Element", "");
382 return htmlConstructorToTagMap[t] || t;
383 };
384 const readStorage = storageName => {
385 let items = [];
386 let i = 0;
387 let l = len(mainWindow[storageName]);
388 let key;
389 for (;i < l; i++) {
390 key = mainWindow[storageName].key(i);
391 items.push([ key, mainWindow[storageName].getItem(key) ]);
392 }
393 return items;
394 };
395 let worker;
396 (async receiveMessage => {
397 const sharedDataBuffer = new SharedArrayBuffer(1073741824);
398 const sharedData = new Int32Array(sharedDataBuffer);
399 return (worker, msg) => {
400 const msgType = msg[0];
401 const accessReq = msg[1];
402 if (0 === msgType) {
403 const initData = (() => {
404 const startTime = performance.now();
405 const docImpl = doc.implementation.createHTMLDocument();
406 const textNode = docImpl.createTextNode("");
407 const comment = docImpl.createComment("");
408 const frag = docImpl.createDocumentFragment();
409 const svg = docImpl.createElementNS("http://www.w3.org/2000/svg", "svg");
410 const mutationObserver = new MutationObserver(noop);
411 const resizeObserver = new ResizeObserver(noop);
412 const perf = mainWindow.performance;
413 const screen = mainWindow.screen;
414 const elms = Object.getOwnPropertyNames(mainWindow).filter((c => /^HTML.+Element$/.test(c))).map((htmlCstrName => [ docImpl.createElement(getHtmlTagNameFromConstructor(htmlCstrName)) ]));
415 const elm = elms[0][0];
416 const impls = [ [ mainWindow.history ], [ perf ], [ perf.navigation ], [ perf.timing ], [ screen ], [ screen.orientation ], [ mutationObserver, 12 ], [ resizeObserver, 12 ], [ textNode ], [ comment ], [ frag ], [ elm ], [ elm.attributes ], [ elm.classList ], [ elm.dataset ], [ elm.style ], [ svg ], [ docImpl ], [ docImpl.doctype ], ...elms ].filter((implData => implData[0])).map((implData => {
417 const impl = implData[0];
418 const interfaceType = implData[1];
419 const cstrName = getConstructorName(impl);
420 const CstrPrototype = mainWindow[cstrName].prototype;
421 return [ cstrName, CstrPrototype, impl, interfaceType ];
422 }));
423 const $interfaces$ = [ readImplementation("Window", mainWindow), readImplementation("Node", textNode) ];
424 const $config$ = JSON.stringify(config, ((k, v) => {
425 if ("function" == typeof v) {
426 v = String(v);
427 v.startsWith(k + "(") && (v = "function " + v);
428 }
429 return v;
430 }));
431 const initWebWorkerData = {
432 $config$: $config$,
433 $libPath$: new URL(libPath, mainWindow.location) + "",
434 $interfaces$: $interfaces$,
435 $localStorage$: readStorage("localStorage"),
436 $sessionStorage$: readStorage("sessionStorage")
437 };
438 impls.map((([cstrName, CstrPrototype, impl, intefaceType]) => readOwnImplementation($interfaces$, cstrName, CstrPrototype, impl, intefaceType)));
439 logMain(`Read ${$interfaces$.length} interfaces in ${(performance.now() - startTime).toFixed(1)}ms`);
440 return initWebWorkerData;
441 })();
442 initData.$sharedDataBuffer$ = sharedDataBuffer;
443 worker.postMessage([ 1, initData ]);
444 } else {
445 9 === msgType ? receiveMessage(accessReq, (accessRsp => {
446 const stringifiedData = JSON.stringify(accessRsp);
447 const stringifiedDataLength = stringifiedData.length;
448 for (let i = 0; i < stringifiedDataLength; i++) {
449 sharedData[i + 1] = stringifiedData.charCodeAt(i);
450 }
451 sharedData[0] = stringifiedDataLength;
452 Atomics.notify(sharedData, 0);
453 })) : onMessageFromWebWorker(worker, msg);
454 }
455 };
456 })(((accessReq, responseCallback) => mainAccessHandler(worker, accessReq).then(responseCallback))).then((onMessageHandler => {
457 if (onMessageHandler) {
458 worker = new Worker(libPath + "partytown-ww-atomics.js", {
459 name: "Partytown 🎉"
460 });
461 worker.onmessage = ev => {
462 const msg = ev.data;
463 10 === msg[0] ? mainAccessHandler(worker, msg[1]) : onMessageHandler(worker, msg);
464 };
465 logMain("Created Partytown web worker (0.2.1)");
466 worker.onerror = ev => console.error("Web Worker Error", ev);
467 mainWindow.addEventListener("pt1", (ev => registerWindow(worker, getAndSetInstanceId(ev.detail.frameElement), ev.detail)));
468 }
469 }));
470})(window);