@gemx-dev/heatmap-react 3.5.92-dev.19 → 3.5.92-dev.20
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/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -1
- package/dist/esm/index.js +226 -417
- package/dist/esm/index.mjs +226 -417
- package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts +11 -9
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/cache/config.d.ts +1 -1
- package/dist/esm/libs/visualizer/cache/config.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/types/visualize.d.ts +19 -0
- package/dist/esm/libs/visualizer/types/visualize.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/utils/delay.d.ts +21 -0
- package/dist/esm/libs/visualizer/utils/delay.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/utils/render.d.ts +7 -0
- package/dist/esm/libs/visualizer/utils/render.d.ts.map +1 -0
- package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts +11 -9
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/cache/config.d.ts +1 -1
- package/dist/umd/libs/visualizer/cache/config.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/types/visualize.d.ts +19 -0
- package/dist/umd/libs/visualizer/types/visualize.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/utils/delay.d.ts +21 -0
- package/dist/umd/libs/visualizer/utils/delay.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/utils/render.d.ts +7 -0
- package/dist/umd/libs/visualizer/utils/render.d.ts.map +1 -0
- package/package.json +4 -4
package/dist/esm/index.mjs
CHANGED
|
@@ -1948,7 +1948,7 @@ class Logger {
|
|
|
1948
1948
|
}
|
|
1949
1949
|
}
|
|
1950
1950
|
// Export singleton instance
|
|
1951
|
-
const logger$
|
|
1951
|
+
const logger$3 = new Logger();
|
|
1952
1952
|
// Export factory function để tạo logger với config riêng
|
|
1953
1953
|
function createLogger(config = {}) {
|
|
1954
1954
|
const instance = new Logger();
|
|
@@ -2337,7 +2337,7 @@ function findElementByHash(props) {
|
|
|
2337
2337
|
}
|
|
2338
2338
|
}
|
|
2339
2339
|
catch (error) {
|
|
2340
|
-
logger$
|
|
2340
|
+
logger$3.warn(`Invalid selector "${selector}":`, error);
|
|
2341
2341
|
}
|
|
2342
2342
|
const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
|
|
2343
2343
|
return elementByHash;
|
|
@@ -2359,7 +2359,7 @@ function hydrateAreaNode(props) {
|
|
|
2359
2359
|
const { id, hash, selector } = persistedData;
|
|
2360
2360
|
const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
|
|
2361
2361
|
if (!element) {
|
|
2362
|
-
logger$
|
|
2362
|
+
logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
|
|
2363
2363
|
return null;
|
|
2364
2364
|
}
|
|
2365
2365
|
const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
|
|
@@ -2376,7 +2376,7 @@ function hydrateAreas(props) {
|
|
|
2376
2376
|
hydratedAreas.push(area);
|
|
2377
2377
|
}
|
|
2378
2378
|
}
|
|
2379
|
-
logger$
|
|
2379
|
+
logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
|
|
2380
2380
|
return hydratedAreas;
|
|
2381
2381
|
}
|
|
2382
2382
|
/**
|
|
@@ -3080,16 +3080,16 @@ const calcCalloutPositionAbsolute = (props) => {
|
|
|
3080
3080
|
|
|
3081
3081
|
function validateAreaCreation(dataInfo, hash, areas) {
|
|
3082
3082
|
if (!dataInfo?.clickMapMetrics || !dataInfo?.totalClicks) {
|
|
3083
|
-
logger$
|
|
3083
|
+
logger$3.warn('Cannot create area: missing heatmap data');
|
|
3084
3084
|
return false;
|
|
3085
3085
|
}
|
|
3086
3086
|
if (!hash) {
|
|
3087
|
-
logger$
|
|
3087
|
+
logger$3.warn('Cannot create area: missing hash');
|
|
3088
3088
|
return false;
|
|
3089
3089
|
}
|
|
3090
3090
|
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
3091
3091
|
if (alreadyExists) {
|
|
3092
|
-
logger$
|
|
3092
|
+
logger$3.warn(`Area already exists for element: ${hash}`);
|
|
3093
3093
|
return false;
|
|
3094
3094
|
}
|
|
3095
3095
|
return true;
|
|
@@ -3102,14 +3102,14 @@ function identifyConflictingAreas(area) {
|
|
|
3102
3102
|
// Case 1: New area is a child of an existing area
|
|
3103
3103
|
if (area.parentNode) {
|
|
3104
3104
|
conflicts.parentId = area.parentNode.id;
|
|
3105
|
-
logger$
|
|
3105
|
+
logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
|
|
3106
3106
|
}
|
|
3107
3107
|
// Case 2: New area is a parent of existing area(s)
|
|
3108
3108
|
if (area.childNodes.size > 0) {
|
|
3109
3109
|
area.childNodes.forEach((childArea) => {
|
|
3110
3110
|
conflicts.childrenIds.push(childArea.id);
|
|
3111
3111
|
});
|
|
3112
|
-
logger$
|
|
3112
|
+
logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
|
|
3113
3113
|
}
|
|
3114
3114
|
return conflicts;
|
|
3115
3115
|
}
|
|
@@ -3160,7 +3160,7 @@ function useAreaCreation(options = {}) {
|
|
|
3160
3160
|
}
|
|
3161
3161
|
}
|
|
3162
3162
|
catch (error) {
|
|
3163
|
-
logger$
|
|
3163
|
+
logger$3.error('Failed to create area:', error);
|
|
3164
3164
|
}
|
|
3165
3165
|
}, [dataInfo, areas, addArea, removeArea, removeClickArea, customShadowRoot, onAreaCreated]);
|
|
3166
3166
|
return {
|
|
@@ -3275,16 +3275,16 @@ function useAreaHydration(options) {
|
|
|
3275
3275
|
return;
|
|
3276
3276
|
if (!dataInfo)
|
|
3277
3277
|
return;
|
|
3278
|
-
logger$
|
|
3278
|
+
logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
|
|
3279
3279
|
const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
|
|
3280
3280
|
if (!hydratedAreas?.length) {
|
|
3281
|
-
logger$
|
|
3281
|
+
logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
|
|
3282
3282
|
return;
|
|
3283
3283
|
}
|
|
3284
3284
|
setIsInitializing(true);
|
|
3285
3285
|
buildAreaGraph(hydratedAreas);
|
|
3286
3286
|
setAreas(hydratedAreas);
|
|
3287
|
-
logger$
|
|
3287
|
+
logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
|
|
3288
3288
|
}, [dataInfo, vizRef, isInitializing, clickAreas]);
|
|
3289
3289
|
useEffect(() => {
|
|
3290
3290
|
if (!enabled)
|
|
@@ -3418,7 +3418,7 @@ function useAreaRectSync(options) {
|
|
|
3418
3418
|
area.rect.update(newRect);
|
|
3419
3419
|
}
|
|
3420
3420
|
catch (error) {
|
|
3421
|
-
logger$
|
|
3421
|
+
logger$3.error(`Failed to update rect for area ${area.id}:`, error);
|
|
3422
3422
|
}
|
|
3423
3423
|
});
|
|
3424
3424
|
buildAreaGraph(areas);
|
|
@@ -3567,7 +3567,7 @@ const useScrollmap = () => {
|
|
|
3567
3567
|
}, { timeout: 300 });
|
|
3568
3568
|
}
|
|
3569
3569
|
catch (error) {
|
|
3570
|
-
logger$
|
|
3570
|
+
logger$3.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3571
3571
|
}
|
|
3572
3572
|
}, [vizRef, scrollmap, isDomLoaded]);
|
|
3573
3573
|
return { start };
|
|
@@ -4172,7 +4172,7 @@ function flush() {
|
|
|
4172
4172
|
window.__gemxPerf = getReport();
|
|
4173
4173
|
}
|
|
4174
4174
|
// ── Global singleton export ───────────────────────────────────────────────────
|
|
4175
|
-
const perf$
|
|
4175
|
+
const perf$3 = {
|
|
4176
4176
|
startSession,
|
|
4177
4177
|
endSession,
|
|
4178
4178
|
mark: globalMark,
|
|
@@ -4220,7 +4220,7 @@ function createPerfTimer(config) {
|
|
|
4220
4220
|
* DOM observation setup — ResizeObserver + MutationObserver.
|
|
4221
4221
|
* Returns a cleanup function that disconnects both observers.
|
|
4222
4222
|
*/
|
|
4223
|
-
const logger$
|
|
4223
|
+
const logger$2 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
|
|
4224
4224
|
function setup(doc, onChange) {
|
|
4225
4225
|
const resizeObserver = new ResizeObserver(onChange);
|
|
4226
4226
|
resizeObserver.observe(doc.documentElement);
|
|
@@ -4232,11 +4232,11 @@ function setup(doc, onChange) {
|
|
|
4232
4232
|
attributes: true,
|
|
4233
4233
|
attributeFilter: ['style', 'class', 'hidden', 'data-v'],
|
|
4234
4234
|
});
|
|
4235
|
-
logger$
|
|
4235
|
+
logger$2.log('DOM observers started (ResizeObserver + MutationObserver)');
|
|
4236
4236
|
return () => {
|
|
4237
4237
|
resizeObserver.disconnect();
|
|
4238
4238
|
mutationObserver.disconnect();
|
|
4239
|
-
logger$
|
|
4239
|
+
logger$2.log('DOM observers disconnected');
|
|
4240
4240
|
};
|
|
4241
4241
|
}
|
|
4242
4242
|
|
|
@@ -4452,9 +4452,9 @@ function createNavigationListeners() {
|
|
|
4452
4452
|
};
|
|
4453
4453
|
}
|
|
4454
4454
|
|
|
4455
|
-
const logger$
|
|
4455
|
+
const logger$1 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
|
|
4456
4456
|
function configure$1(debug) {
|
|
4457
|
-
logger$
|
|
4457
|
+
logger$1.configure({ enabled: debug });
|
|
4458
4458
|
}
|
|
4459
4459
|
// ─── DOM Utilities ────────────────────────────────────────────────────────────
|
|
4460
4460
|
function disableAllLinks(doc) {
|
|
@@ -4478,10 +4478,10 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
|
|
|
4478
4478
|
return;
|
|
4479
4479
|
const href = link.getAttribute('href');
|
|
4480
4480
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
4481
|
-
logger$
|
|
4481
|
+
logger$1.log('Allowed hash navigation:', href);
|
|
4482
4482
|
return;
|
|
4483
4483
|
}
|
|
4484
|
-
logger$
|
|
4484
|
+
logger$1.log('Blocked link navigation to:', href);
|
|
4485
4485
|
e.preventDefault();
|
|
4486
4486
|
e.stopPropagation();
|
|
4487
4487
|
e.stopImmediatePropagation();
|
|
@@ -4495,7 +4495,7 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
|
|
|
4495
4495
|
return;
|
|
4496
4496
|
const href = link.getAttribute('href');
|
|
4497
4497
|
if (href && !href.startsWith('#')) {
|
|
4498
|
-
logger$
|
|
4498
|
+
logger$1.log('Blocked auxclick navigation');
|
|
4499
4499
|
e.preventDefault();
|
|
4500
4500
|
e.stopPropagation();
|
|
4501
4501
|
e.stopImmediatePropagation();
|
|
@@ -4516,7 +4516,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
|
|
|
4516
4516
|
const form = e.target;
|
|
4517
4517
|
const action = form.getAttribute('action');
|
|
4518
4518
|
if (!action || action === '' || action === '#') {
|
|
4519
|
-
logger$
|
|
4519
|
+
logger$1.log('Allowed same-page form');
|
|
4520
4520
|
e.preventDefault();
|
|
4521
4521
|
const data = {};
|
|
4522
4522
|
new FormData(form).forEach((value, key) => {
|
|
@@ -4525,7 +4525,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
|
|
|
4525
4525
|
onFormSubmit(form, data);
|
|
4526
4526
|
return;
|
|
4527
4527
|
}
|
|
4528
|
-
logger$
|
|
4528
|
+
logger$1.log('Blocked form submission to:', action);
|
|
4529
4529
|
e.preventDefault();
|
|
4530
4530
|
e.stopPropagation();
|
|
4531
4531
|
e.stopImmediatePropagation();
|
|
@@ -4539,7 +4539,7 @@ function setupWindowOpenBlocker(win, originalOpen, isEnabled, onBlocked) {
|
|
|
4539
4539
|
if (!isEnabled())
|
|
4540
4540
|
return originalOpen(...args);
|
|
4541
4541
|
const url = args[0]?.toString() || 'popup';
|
|
4542
|
-
logger$
|
|
4542
|
+
logger$1.log('Blocked window.open:', url);
|
|
4543
4543
|
onBlocked(url);
|
|
4544
4544
|
return null;
|
|
4545
4545
|
});
|
|
@@ -4551,14 +4551,14 @@ function setupUnloadBlocker(win, isEnabled) {
|
|
|
4551
4551
|
const beforeUnloadListener = (e) => {
|
|
4552
4552
|
if (!isEnabled())
|
|
4553
4553
|
return;
|
|
4554
|
-
logger$
|
|
4554
|
+
logger$1.log('Blocked beforeunload');
|
|
4555
4555
|
e.preventDefault();
|
|
4556
4556
|
e.returnValue = '';
|
|
4557
4557
|
};
|
|
4558
4558
|
const unloadListener = (e) => {
|
|
4559
4559
|
if (!isEnabled())
|
|
4560
4560
|
return;
|
|
4561
|
-
logger$
|
|
4561
|
+
logger$1.log('Blocked unload');
|
|
4562
4562
|
e.preventDefault();
|
|
4563
4563
|
e.stopPropagation();
|
|
4564
4564
|
};
|
|
@@ -4602,7 +4602,7 @@ function start$4(s, iframe, cfg) {
|
|
|
4602
4602
|
];
|
|
4603
4603
|
s.listeners.attach(cfg?.debug);
|
|
4604
4604
|
s.running = true;
|
|
4605
|
-
s.logger.log('
|
|
4605
|
+
s.logger.log('Started');
|
|
4606
4606
|
}
|
|
4607
4607
|
function stop$4(s) {
|
|
4608
4608
|
if (!s.running)
|
|
@@ -4613,7 +4613,7 @@ function stop$4(s) {
|
|
|
4613
4613
|
s.isEnabled = false;
|
|
4614
4614
|
s.showMessage = false;
|
|
4615
4615
|
s.running = false;
|
|
4616
|
-
s.logger.log('
|
|
4616
|
+
s.logger.log('Stopped');
|
|
4617
4617
|
}
|
|
4618
4618
|
function enable(s) {
|
|
4619
4619
|
if (!s.running) {
|
|
@@ -4674,8 +4674,8 @@ function createNavigationBlocker() {
|
|
|
4674
4674
|
function attach(s, debug) {
|
|
4675
4675
|
s.logger.configure({ enabled: !!debug });
|
|
4676
4676
|
s.dimensionsListener = (e) => {
|
|
4677
|
-
const ev = e
|
|
4678
|
-
s.logger.log('Dimensions applied:', ev.detail);
|
|
4677
|
+
// const ev = e as CustomEvent<IframeDimensionsDetail>;
|
|
4678
|
+
// s.logger.log('Dimensions applied:', ev.detail);
|
|
4679
4679
|
};
|
|
4680
4680
|
window.addEventListener('iframe-dimensions-applied', s.dimensionsListener);
|
|
4681
4681
|
}
|
|
@@ -4807,7 +4807,7 @@ function start$3(s, d, w, cfg, options = {}) {
|
|
|
4807
4807
|
s.config = cfg;
|
|
4808
4808
|
s.running = true;
|
|
4809
4809
|
s.logger.configure({ enabled: !!options.debug });
|
|
4810
|
-
s.logger.log('
|
|
4810
|
+
s.logger.log('Started');
|
|
4811
4811
|
}
|
|
4812
4812
|
function stop$3(s) {
|
|
4813
4813
|
if (!s.running)
|
|
@@ -4818,7 +4818,7 @@ function stop$3(s) {
|
|
|
4818
4818
|
s.elementsWithViewportUnits.clear();
|
|
4819
4819
|
s.originalValues = new WeakMap();
|
|
4820
4820
|
s.running = false;
|
|
4821
|
-
s.logger.log('
|
|
4821
|
+
s.logger.log('Stopped');
|
|
4822
4822
|
}
|
|
4823
4823
|
function reset(s) {
|
|
4824
4824
|
if (!s.running) {
|
|
@@ -4827,7 +4827,7 @@ function reset(s) {
|
|
|
4827
4827
|
}
|
|
4828
4828
|
s.elementsWithViewportUnits.clear();
|
|
4829
4829
|
s.originalValues = new WeakMap();
|
|
4830
|
-
s.logger.log('
|
|
4830
|
+
s.logger.log('Reset');
|
|
4831
4831
|
}
|
|
4832
4832
|
function trackElement(s, element, propertyOriginalValues) {
|
|
4833
4833
|
if (!s.running) {
|
|
@@ -4945,254 +4945,6 @@ function getActiveFixes(ctx) {
|
|
|
4945
4945
|
});
|
|
4946
4946
|
}
|
|
4947
4947
|
|
|
4948
|
-
/**
|
|
4949
|
-
* Core viewport unit replacement logic.
|
|
4950
|
-
* Converts vh/vw/svh/dvh/% to pixel values across all CSS in the iframe.
|
|
4951
|
-
*/
|
|
4952
|
-
const logger$1 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
|
|
4953
|
-
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
4954
|
-
const HEIGHT_RELATED_PROPERTIES = ['height', 'min-height', 'max-height', 'top', 'bottom'];
|
|
4955
|
-
/**
|
|
4956
|
-
* Number of top-level CSS rules to process before yielding to the browser.
|
|
4957
|
-
* Keeps the main thread responsive during large stylesheets (prevents tab kill on mobile).
|
|
4958
|
-
*/
|
|
4959
|
-
const YIELD_EVERY_RULES = 100;
|
|
4960
|
-
// ─── Scheduler ────────────────────────────────────────────────────────────────
|
|
4961
|
-
/**
|
|
4962
|
-
* Yield control back to the browser so it can handle input, paint frames, and
|
|
4963
|
-
* avoid "page unresponsive" / tab-kill on mobile during heavy CSS processing.
|
|
4964
|
-
*
|
|
4965
|
-
* Uses `scheduler.yield()` (Chrome 115+) when available; falls back to a
|
|
4966
|
-
* zero-timeout macrotask which is universally supported.
|
|
4967
|
-
*/
|
|
4968
|
-
function yieldToMain$1() {
|
|
4969
|
-
if (typeof globalThis !== 'undefined' && 'scheduler' in globalThis) {
|
|
4970
|
-
return globalThis.scheduler.yield();
|
|
4971
|
-
}
|
|
4972
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
4973
|
-
}
|
|
4974
|
-
// ─── Per-run tracking state (reset on each process() call) ───────────────────
|
|
4975
|
-
let elementsWithViewportUnits = new Set();
|
|
4976
|
-
let originalValues = new WeakMap();
|
|
4977
|
-
// ─── Regex ────────────────────────────────────────────────────────────────────
|
|
4978
|
-
/**
|
|
4979
|
-
* Stateless test-only regex (no `g` flag) — safe to share across calls.
|
|
4980
|
-
* Used exclusively for `.test()` checks before doing a full replacement.
|
|
4981
|
-
*/
|
|
4982
|
-
const VIEWPORT_RE_TEST = /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/i;
|
|
4983
|
-
/** Fresh `g`-flagged instance for String.replace() callbacks. */
|
|
4984
|
-
function createRegex() {
|
|
4985
|
-
return /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
|
|
4986
|
-
}
|
|
4987
|
-
// ─── Unit conversion ─────────────────────────────────────────────────────────
|
|
4988
|
-
function px(value) {
|
|
4989
|
-
return `${value.toFixed(2)}px`;
|
|
4990
|
-
}
|
|
4991
|
-
function buildUnitMap(ctx) {
|
|
4992
|
-
return {
|
|
4993
|
-
vh: ctx.targetHeight,
|
|
4994
|
-
svh: ctx.targetHeight,
|
|
4995
|
-
lvh: ctx.targetHeight,
|
|
4996
|
-
dvh: ctx.targetHeight,
|
|
4997
|
-
vw: ctx.targetWidth,
|
|
4998
|
-
svw: ctx.targetWidth,
|
|
4999
|
-
lvw: ctx.targetWidth,
|
|
5000
|
-
dvw: ctx.targetWidth,
|
|
5001
|
-
};
|
|
5002
|
-
}
|
|
5003
|
-
function toPx(value, unit, unitMap, targetHeight) {
|
|
5004
|
-
const u = unit.toLowerCase();
|
|
5005
|
-
if (u === '%')
|
|
5006
|
-
return (value / 100) * targetHeight;
|
|
5007
|
-
return (value / 100) * (unitMap[u] ?? 0);
|
|
5008
|
-
}
|
|
5009
|
-
function convert(value, unit, unitMap, targetHeight) {
|
|
5010
|
-
const num = parseFloat(value);
|
|
5011
|
-
return isNaN(num) ? value : px(toPx(num, unit, unitMap, targetHeight));
|
|
5012
|
-
}
|
|
5013
|
-
function isHeightRelated(prop) {
|
|
5014
|
-
return HEIGHT_RELATED_PROPERTIES.includes(prop);
|
|
5015
|
-
}
|
|
5016
|
-
/**
|
|
5017
|
-
* Use `matchOffset` (from replace() callback) instead of indexOf to get the
|
|
5018
|
-
* exact position of the current match — avoids false matches for duplicate values.
|
|
5019
|
-
*/
|
|
5020
|
-
function extractProperty(cssText, matchOffset) {
|
|
5021
|
-
const before = cssText.substring(0, matchOffset);
|
|
5022
|
-
const m = before.match(/([a-z-]+)\s*:\s*[^;{}]*$/i);
|
|
5023
|
-
return m ? m[1].toLowerCase() : '';
|
|
5024
|
-
}
|
|
5025
|
-
function replaceInText(cssText, ctx) {
|
|
5026
|
-
const unitMap = buildUnitMap(ctx);
|
|
5027
|
-
const { targetHeight } = ctx;
|
|
5028
|
-
return cssText.replace(createRegex(), (match, value, unit, offset) => {
|
|
5029
|
-
if (unit === '%') {
|
|
5030
|
-
return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, unitMap, targetHeight) : match;
|
|
5031
|
-
}
|
|
5032
|
-
return convert(value, unit, unitMap, targetHeight);
|
|
5033
|
-
});
|
|
5034
|
-
}
|
|
5035
|
-
// ─── Element tracking ─────────────────────────────────────────────────────────
|
|
5036
|
-
function trackSelector(selector, propOriginals, ctx) {
|
|
5037
|
-
try {
|
|
5038
|
-
ctx.doc.querySelectorAll(selector).forEach((el) => {
|
|
5039
|
-
elementsWithViewportUnits.add(el);
|
|
5040
|
-
let originals = originalValues.get(el);
|
|
5041
|
-
if (!originals) {
|
|
5042
|
-
originals = new Map();
|
|
5043
|
-
originalValues.set(el, originals);
|
|
5044
|
-
}
|
|
5045
|
-
propOriginals.forEach((v, k) => {
|
|
5046
|
-
if (!originals.has(k))
|
|
5047
|
-
originals.set(k, v);
|
|
5048
|
-
});
|
|
5049
|
-
ctx.enforcer?.trackElement(el, propOriginals);
|
|
5050
|
-
});
|
|
5051
|
-
}
|
|
5052
|
-
catch {
|
|
5053
|
-
logger$1.warn('Invalid selector, skipping:', selector);
|
|
5054
|
-
}
|
|
5055
|
-
}
|
|
5056
|
-
// ─── CSS processing ───────────────────────────────────────────────────────────
|
|
5057
|
-
function processInlineStyles(ctx) {
|
|
5058
|
-
let count = 0;
|
|
5059
|
-
ctx.doc.querySelectorAll('[style]').forEach((el) => {
|
|
5060
|
-
const style = el.getAttribute('style');
|
|
5061
|
-
if (style && VIEWPORT_RE_TEST.test(style)) {
|
|
5062
|
-
elementsWithViewportUnits.add(el);
|
|
5063
|
-
el.setAttribute('style', replaceInText(style, ctx));
|
|
5064
|
-
count++;
|
|
5065
|
-
}
|
|
5066
|
-
});
|
|
5067
|
-
logger$1.log(`Replaced ${count} inline style elements`);
|
|
5068
|
-
return count;
|
|
5069
|
-
}
|
|
5070
|
-
function processStyleTags(ctx) {
|
|
5071
|
-
let count = 0;
|
|
5072
|
-
ctx.doc.querySelectorAll('style').forEach((tag) => {
|
|
5073
|
-
const css = tag.textContent || '';
|
|
5074
|
-
if (VIEWPORT_RE_TEST.test(css)) {
|
|
5075
|
-
tag.textContent = replaceInText(css, ctx);
|
|
5076
|
-
count++;
|
|
5077
|
-
}
|
|
5078
|
-
});
|
|
5079
|
-
logger$1.log(`Replaced ${count} <style> tags`);
|
|
5080
|
-
return count;
|
|
5081
|
-
}
|
|
5082
|
-
function processRule(rule, ctx) {
|
|
5083
|
-
let count = 0;
|
|
5084
|
-
if ('style' in rule && rule.style) {
|
|
5085
|
-
const cssRule = rule;
|
|
5086
|
-
const style = cssRule.style;
|
|
5087
|
-
let hasVp = false;
|
|
5088
|
-
const propOriginals = new Map();
|
|
5089
|
-
for (let i = 0; i < style.length; i++) {
|
|
5090
|
-
const prop = style[i];
|
|
5091
|
-
const value = style.getPropertyValue(prop);
|
|
5092
|
-
if (value && VIEWPORT_RE_TEST.test(value)) {
|
|
5093
|
-
hasVp = true;
|
|
5094
|
-
propOriginals.set(prop, value);
|
|
5095
|
-
style.setProperty(prop, replaceInText(value, ctx), style.getPropertyPriority(prop));
|
|
5096
|
-
count++;
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
if (hasVp && cssRule.selectorText)
|
|
5100
|
-
trackSelector(cssRule.selectorText, propOriginals, ctx);
|
|
5101
|
-
}
|
|
5102
|
-
if ('cssRules' in rule) {
|
|
5103
|
-
const nested = rule.cssRules;
|
|
5104
|
-
if (nested) {
|
|
5105
|
-
for (let i = 0; i < nested.length; i++) {
|
|
5106
|
-
count += processRule(nested[i], ctx);
|
|
5107
|
-
}
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
return count;
|
|
5111
|
-
}
|
|
5112
|
-
/** Processes only inline <style> sheets. Linked sheets are handled by processLinkedStylesheets. */
|
|
5113
|
-
async function processStylesheets(ctx) {
|
|
5114
|
-
let total = 0;
|
|
5115
|
-
let rulesSinceYield = 0;
|
|
5116
|
-
const sheets = ctx.doc.styleSheets;
|
|
5117
|
-
for (let i = 0; i < sheets.length; i++) {
|
|
5118
|
-
const sheet = sheets[i];
|
|
5119
|
-
if (sheet.href)
|
|
5120
|
-
continue; // deferred to processLinkedStylesheets
|
|
5121
|
-
try {
|
|
5122
|
-
const rules = sheet.cssRules;
|
|
5123
|
-
for (let j = 0; j < rules.length; j++) {
|
|
5124
|
-
total += processRule(rules[j], ctx);
|
|
5125
|
-
rulesSinceYield++;
|
|
5126
|
-
if (rulesSinceYield >= YIELD_EVERY_RULES) {
|
|
5127
|
-
rulesSinceYield = 0;
|
|
5128
|
-
await yieldToMain$1();
|
|
5129
|
-
}
|
|
5130
|
-
}
|
|
5131
|
-
}
|
|
5132
|
-
catch (e) {
|
|
5133
|
-
logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
5134
|
-
}
|
|
5135
|
-
}
|
|
5136
|
-
logger$1.log(`Replaced ${total} rules in inline stylesheets`);
|
|
5137
|
-
return total;
|
|
5138
|
-
}
|
|
5139
|
-
async function processLinkedStylesheets(ctx) {
|
|
5140
|
-
const links = ctx.doc.querySelectorAll('link[rel="stylesheet"]');
|
|
5141
|
-
let count = 0;
|
|
5142
|
-
for (let i = 0; i < links.length; i++) {
|
|
5143
|
-
const link = links[i];
|
|
5144
|
-
// Skip cross-origin — already in browser CSSOM, handled via processStylesheets
|
|
5145
|
-
if (link.href && !link.href.startsWith(ctx.win.location.origin)) {
|
|
5146
|
-
logger$1.log('Skipping cross-origin CSS:', link.href);
|
|
5147
|
-
continue;
|
|
5148
|
-
}
|
|
5149
|
-
try {
|
|
5150
|
-
const res = await fetch(link.href);
|
|
5151
|
-
let css = await res.text();
|
|
5152
|
-
if (VIEWPORT_RE_TEST.test(css)) {
|
|
5153
|
-
css = replaceInText(css, ctx);
|
|
5154
|
-
const style = ctx.doc.createElement('style');
|
|
5155
|
-
style.textContent = css;
|
|
5156
|
-
style.dataset.originalHref = link.href;
|
|
5157
|
-
link.parentNode?.insertBefore(style, link);
|
|
5158
|
-
link.remove();
|
|
5159
|
-
count++;
|
|
5160
|
-
}
|
|
5161
|
-
}
|
|
5162
|
-
catch (e) {
|
|
5163
|
-
logger$1.warn('Cannot load CSS:', link.href, e);
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
|
-
logger$1.log(`Replaced ${count} linked CSS files`);
|
|
5167
|
-
return count;
|
|
5168
|
-
}
|
|
5169
|
-
// ─── Public entry point ───────────────────────────────────────────────────────
|
|
5170
|
-
async function process$1(ctx) {
|
|
5171
|
-
logger$1.configure({ enabled: !!ctx.debug });
|
|
5172
|
-
// Reset tracking state from any previous run
|
|
5173
|
-
elementsWithViewportUnits = new Set();
|
|
5174
|
-
originalValues = new WeakMap();
|
|
5175
|
-
processInlineStyles(ctx);
|
|
5176
|
-
processStyleTags(ctx);
|
|
5177
|
-
await processStylesheets(ctx);
|
|
5178
|
-
await processLinkedStylesheets(ctx);
|
|
5179
|
-
// Wait for browser to apply the replaced styles
|
|
5180
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
5181
|
-
// Enforce final computed styles to inline with !important
|
|
5182
|
-
const count = ctx.enforcer?.processAll({ debug: !!ctx.debug }) ?? 0;
|
|
5183
|
-
logger$1.log(`Enforced ${count} computed styles for ${elementsWithViewportUnits.size} tracked elements`);
|
|
5184
|
-
}
|
|
5185
|
-
|
|
5186
|
-
/**
|
|
5187
|
-
* Built-in global fix — always runs, no shouldApply condition.
|
|
5188
|
-
* Registered first so it runs before any other global process hooks.
|
|
5189
|
-
*/
|
|
5190
|
-
register$1({
|
|
5191
|
-
name: 'viewport-unit-replacer',
|
|
5192
|
-
description: 'Core: convert vh/vw/svh/dvh/% to px values across all iframe CSS',
|
|
5193
|
-
process: process$1,
|
|
5194
|
-
});
|
|
5195
|
-
|
|
5196
4948
|
/**
|
|
5197
4949
|
* GemPages v7 Slider Fix
|
|
5198
4950
|
*
|
|
@@ -5408,52 +5160,52 @@ function configure(debug) {
|
|
|
5408
5160
|
}
|
|
5409
5161
|
async function run$1(ctx, activeGlobal, shopFix) {
|
|
5410
5162
|
// ── Phase 1: beforeProcess ────────────────────────────────────────────────
|
|
5411
|
-
const t1 = perf$
|
|
5163
|
+
const t1 = perf$3.mark('phase1.beforeProcess');
|
|
5412
5164
|
for (const fix of activeGlobal) {
|
|
5413
5165
|
if (fix.beforeProcess) {
|
|
5414
5166
|
logger.log(`[beforeProcess] ${fix.name}`);
|
|
5415
|
-
const t = perf$
|
|
5167
|
+
const t = perf$3.mark(`phase1.${fix.name}.beforeProcess`);
|
|
5416
5168
|
await fix.beforeProcess(ctx);
|
|
5417
|
-
perf$
|
|
5169
|
+
perf$3.measure(`phase1.${fix.name}.beforeProcess`, t);
|
|
5418
5170
|
}
|
|
5419
5171
|
}
|
|
5420
5172
|
if (shopFix?.beforeProcess) {
|
|
5421
5173
|
logger.log('[beforeProcess] shop');
|
|
5422
|
-
const t = perf$
|
|
5174
|
+
const t = perf$3.mark('phase1.shop.beforeProcess');
|
|
5423
5175
|
await shopFix.beforeProcess(ctx);
|
|
5424
|
-
perf$
|
|
5176
|
+
perf$3.measure('phase1.shop.beforeProcess', t);
|
|
5425
5177
|
}
|
|
5426
|
-
perf$
|
|
5178
|
+
perf$3.measure('phase1.beforeProcess', t1);
|
|
5427
5179
|
// ── Phase 2: process ──────────────────────────────────────────────────────
|
|
5428
|
-
const t2 = perf$
|
|
5180
|
+
const t2 = perf$3.mark('phase2.process');
|
|
5429
5181
|
for (const fix of activeGlobal) {
|
|
5430
5182
|
if (fix.process) {
|
|
5431
5183
|
logger.log(`[process] ${fix.name}`);
|
|
5432
|
-
const t = perf$
|
|
5184
|
+
const t = perf$3.mark(`phase2.${fix.name}.process`);
|
|
5433
5185
|
await fix.process(ctx);
|
|
5434
|
-
perf$
|
|
5186
|
+
perf$3.measure(`phase2.${fix.name}.process`, t);
|
|
5435
5187
|
}
|
|
5436
5188
|
}
|
|
5437
|
-
perf$
|
|
5189
|
+
perf$3.measure('phase2.process', t2);
|
|
5438
5190
|
// ── Phase 3: afterProcess ─────────────────────────────────────────────────
|
|
5439
|
-
const t3 = perf$
|
|
5191
|
+
const t3 = perf$3.mark('phase3.afterProcess');
|
|
5440
5192
|
if (shopFix?.afterProcess) {
|
|
5441
5193
|
logger.log('[afterProcess] shop');
|
|
5442
|
-
const t = perf$
|
|
5194
|
+
const t = perf$3.mark('phase3.shop.afterProcess');
|
|
5443
5195
|
await shopFix.afterProcess(ctx);
|
|
5444
|
-
perf$
|
|
5196
|
+
perf$3.measure('phase3.shop.afterProcess', t);
|
|
5445
5197
|
}
|
|
5446
5198
|
for (const fix of activeGlobal) {
|
|
5447
5199
|
if (fix.afterProcess) {
|
|
5448
5200
|
logger.log(`[afterProcess] ${fix.name}`);
|
|
5449
|
-
const t = perf$
|
|
5201
|
+
const t = perf$3.mark(`phase3.${fix.name}.afterProcess`);
|
|
5450
5202
|
await fix.afterProcess(ctx);
|
|
5451
|
-
perf$
|
|
5203
|
+
perf$3.measure(`phase3.${fix.name}.afterProcess`, t);
|
|
5452
5204
|
}
|
|
5453
5205
|
}
|
|
5454
|
-
perf$
|
|
5206
|
+
perf$3.measure('phase3.afterProcess', t3);
|
|
5455
5207
|
// ── Phase 4: getDimensions ────────────────────────────────────────────────
|
|
5456
|
-
const t4 = perf$
|
|
5208
|
+
const t4 = perf$3.mark('phase4.getDimensions');
|
|
5457
5209
|
return new Promise((resolve) => {
|
|
5458
5210
|
requestAnimationFrame(() => {
|
|
5459
5211
|
let dimensions = null;
|
|
@@ -5478,7 +5230,7 @@ async function run$1(ctx, activeGlobal, shopFix) {
|
|
|
5478
5230
|
dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
|
|
5479
5231
|
}
|
|
5480
5232
|
logger.log('Final dimensions:', dimensions);
|
|
5481
|
-
perf$
|
|
5233
|
+
perf$3.measure('phase4.getDimensions', t4);
|
|
5482
5234
|
resolve(dimensions);
|
|
5483
5235
|
});
|
|
5484
5236
|
});
|
|
@@ -5606,14 +5358,14 @@ async function run(s) {
|
|
|
5606
5358
|
const activeGlobal = getActiveFixes(ctx);
|
|
5607
5359
|
if (activeGlobal.length > 0)
|
|
5608
5360
|
s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
|
|
5609
|
-
const tRun = perf$
|
|
5361
|
+
const tRun = perf$3.mark('viewport.run');
|
|
5610
5362
|
try {
|
|
5611
5363
|
const result = await run$1(ctx, activeGlobal, s.shopFix);
|
|
5612
|
-
perf$
|
|
5364
|
+
perf$3.measure('viewport.run', tRun);
|
|
5613
5365
|
return result;
|
|
5614
5366
|
}
|
|
5615
5367
|
catch (err) {
|
|
5616
|
-
perf$
|
|
5368
|
+
perf$3.measure('viewport.run', tRun);
|
|
5617
5369
|
s.logger.error('Critical error:', err);
|
|
5618
5370
|
return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
|
|
5619
5371
|
}
|
|
@@ -5663,22 +5415,23 @@ async function process(s) {
|
|
|
5663
5415
|
return;
|
|
5664
5416
|
}
|
|
5665
5417
|
const sessionId = `render-${Date.now()}`;
|
|
5666
|
-
perf$
|
|
5667
|
-
const t0 = perf$
|
|
5418
|
+
perf$3.startSession(sessionId);
|
|
5419
|
+
const t0 = perf$3.mark('orchestrator.process');
|
|
5668
5420
|
try {
|
|
5669
|
-
s.logger.
|
|
5421
|
+
s.logger.groupCollapsed('Processing...');
|
|
5670
5422
|
s.viewportReplacer.start(s.iframe, s.config);
|
|
5671
5423
|
s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
|
|
5672
5424
|
const result = await s.viewportReplacer.run();
|
|
5673
|
-
perf$
|
|
5674
|
-
perf$
|
|
5425
|
+
perf$3.measure('orchestrator.process', t0);
|
|
5426
|
+
perf$3.endSession();
|
|
5427
|
+
s.logger.groupEnd();
|
|
5675
5428
|
s.logger.log('Process completed:', result);
|
|
5676
5429
|
s.config.onSuccess?.(result);
|
|
5677
5430
|
dispatchDimensionsEvent(result);
|
|
5678
5431
|
}
|
|
5679
5432
|
catch (error) {
|
|
5680
|
-
perf$
|
|
5681
|
-
perf$
|
|
5433
|
+
perf$3.measure('orchestrator.process', t0);
|
|
5434
|
+
perf$3.endSession();
|
|
5682
5435
|
s.logger.error('Failed to process:', error);
|
|
5683
5436
|
s.config.onError?.(error);
|
|
5684
5437
|
}
|
|
@@ -5706,7 +5459,7 @@ function start$1(s, cfg) {
|
|
|
5706
5459
|
s.config = cfg;
|
|
5707
5460
|
s.running = true;
|
|
5708
5461
|
s.logger.configure({ enabled: !!cfg.debug });
|
|
5709
|
-
s.logger.log('
|
|
5462
|
+
s.logger.log('Started');
|
|
5710
5463
|
initialize(s);
|
|
5711
5464
|
}
|
|
5712
5465
|
function stop$1(s) {
|
|
@@ -5722,7 +5475,7 @@ function stop$1(s) {
|
|
|
5722
5475
|
s.iframe = null;
|
|
5723
5476
|
s.config = null;
|
|
5724
5477
|
s.running = false;
|
|
5725
|
-
s.logger.log('
|
|
5478
|
+
s.logger.log('Stopped');
|
|
5726
5479
|
}
|
|
5727
5480
|
async function recalculate$1(s) {
|
|
5728
5481
|
if (!s.running) {
|
|
@@ -5936,7 +5689,7 @@ function getHtmlCacheConfig() {
|
|
|
5936
5689
|
return _config;
|
|
5937
5690
|
}
|
|
5938
5691
|
/** Build a full cache key that includes the cache version to handle invalidation. */
|
|
5939
|
-
function buildCacheKey(baseKey, shortCircuitStrategy) {
|
|
5692
|
+
function buildCacheKey(baseKey, shortCircuitStrategy = 0) {
|
|
5940
5693
|
return `v${_config.cacheVersion}:${baseKey}:${shortCircuitStrategy}`;
|
|
5941
5694
|
}
|
|
5942
5695
|
|
|
@@ -6436,7 +6189,7 @@ function filterShadowNodes(data, subtreeIds, shadowHostTags) {
|
|
|
6436
6189
|
return (data?.filter((node) => {
|
|
6437
6190
|
if (isShadowDomNode(node.tag))
|
|
6438
6191
|
return true;
|
|
6439
|
-
if (shadowHostTags.has(node.tag ?? '')
|
|
6192
|
+
if (shadowHostTags.has(node.tag ?? ''))
|
|
6440
6193
|
return true;
|
|
6441
6194
|
return subtreeIds.has(node.id);
|
|
6442
6195
|
}) ?? []);
|
|
@@ -6471,6 +6224,35 @@ function extractSpecialEvents(events, dom, shadowHostTags) {
|
|
|
6471
6224
|
return result;
|
|
6472
6225
|
}
|
|
6473
6226
|
|
|
6227
|
+
// utils/retry.ts
|
|
6228
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6229
|
+
/**
|
|
6230
|
+
* Retry until the callback returns truthy, or throw if the limit is exceeded.
|
|
6231
|
+
*
|
|
6232
|
+
* @example
|
|
6233
|
+
* await retry(() => doc.readyState === 'complete', { timeout: 5000, label: 'DOM ready' });
|
|
6234
|
+
*
|
|
6235
|
+
* @example
|
|
6236
|
+
* const el = await retry(() => document.getElementById('app'), { maxRetries: 20 });
|
|
6237
|
+
*/
|
|
6238
|
+
async function retry(callback, options = {}) {
|
|
6239
|
+
const { interval = 100, label = 'Retry' } = options;
|
|
6240
|
+
const maxRetries = options.timeout != null ? Math.ceil(options.timeout / interval) : (options.maxRetries ?? 50);
|
|
6241
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
6242
|
+
console.log(`🔄 [${label}] attempt ${attempt}/${maxRetries}`);
|
|
6243
|
+
const result = await callback();
|
|
6244
|
+
if (result) {
|
|
6245
|
+
return result;
|
|
6246
|
+
}
|
|
6247
|
+
if (attempt < maxRetries) {
|
|
6248
|
+
await delay(interval);
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
const totalMs = maxRetries * interval;
|
|
6252
|
+
throw new Error(`[${label}] Timed out after ${maxRetries} retries (${totalMs}ms)`);
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
const perf$2 = createPerfTimer('Render');
|
|
6474
6256
|
const YIELD_INTERVAL_MS = 16;
|
|
6475
6257
|
async function yieldToMain() {
|
|
6476
6258
|
if ('scheduler' in globalThis && typeof globalThis.scheduler?.yield === 'function') {
|
|
@@ -6480,6 +6262,43 @@ async function yieldToMain() {
|
|
|
6480
6262
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
6481
6263
|
}
|
|
6482
6264
|
}
|
|
6265
|
+
const renderLoop = async (ctx, options) => {
|
|
6266
|
+
const { events, useproxy } = options;
|
|
6267
|
+
const shortCircuitStrategy = options.shortCircuitStrategy ?? ShortCircuitStrategy.None;
|
|
6268
|
+
const hash = options.hash ?? null;
|
|
6269
|
+
const t0 = perf$2.mark('RenderLoop start');
|
|
6270
|
+
let lastYield = performance.now();
|
|
6271
|
+
const totalEvents = events.length;
|
|
6272
|
+
for (let i = 0; i < totalEvents; i++) {
|
|
6273
|
+
const entry = events[i];
|
|
6274
|
+
const entryEvent = entry.event;
|
|
6275
|
+
const now = performance.now();
|
|
6276
|
+
if (now - lastYield > YIELD_INTERVAL_MS) {
|
|
6277
|
+
console.log(`[RenderLoop] ${i}/${totalEvents} time:`, now - lastYield);
|
|
6278
|
+
await yieldToMain();
|
|
6279
|
+
lastYield = performance.now();
|
|
6280
|
+
}
|
|
6281
|
+
switch (entryEvent) {
|
|
6282
|
+
case Event.StyleSheetAdoption:
|
|
6283
|
+
case Event.StyleSheetUpdate:
|
|
6284
|
+
ctx.layout.styleChange(entry);
|
|
6285
|
+
break;
|
|
6286
|
+
case Event.CustomElement:
|
|
6287
|
+
ctx.layout.customElement(entry);
|
|
6288
|
+
break;
|
|
6289
|
+
case Event.Mutation: {
|
|
6290
|
+
const domEvent = entry;
|
|
6291
|
+
ctx.renderTime = domEvent.time;
|
|
6292
|
+
if (ctx.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
|
|
6293
|
+
return;
|
|
6294
|
+
ctx.layout.markup(domEvent, useproxy);
|
|
6295
|
+
break;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
perf$2.measure('RenderLoop', t0);
|
|
6300
|
+
};
|
|
6301
|
+
|
|
6483
6302
|
class GXVisualizer extends Visualizer {
|
|
6484
6303
|
attentionMap;
|
|
6485
6304
|
originalClearmap;
|
|
@@ -6491,104 +6310,93 @@ class GXVisualizer extends Visualizer {
|
|
|
6491
6310
|
this.originalClearmap = this.clearmap;
|
|
6492
6311
|
this.clearmap = this.clearmapOverride;
|
|
6493
6312
|
this.setup = this.setupOverride;
|
|
6494
|
-
this.html = this.htmlOverride;
|
|
6495
6313
|
}
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
this.attentionMap?.clear();
|
|
6505
|
-
};
|
|
6506
|
-
renderLoop = async (v, events, hash, useproxy, shortCircuitStrategy) => {
|
|
6507
|
-
let lastYield = performance.now();
|
|
6508
|
-
for (let i = 0; i < events.length; i++) {
|
|
6509
|
-
const now = performance.now();
|
|
6510
|
-
if (now - lastYield > YIELD_INTERVAL_MS) {
|
|
6511
|
-
await yieldToMain();
|
|
6512
|
-
lastYield = performance.now();
|
|
6513
|
-
}
|
|
6514
|
-
const entry = events[i];
|
|
6515
|
-
const entryEvent = entry.event;
|
|
6516
|
-
switch (entryEvent) {
|
|
6517
|
-
case Event.StyleSheetAdoption:
|
|
6518
|
-
case Event.StyleSheetUpdate:
|
|
6519
|
-
v.layout.styleChange(entry);
|
|
6520
|
-
break;
|
|
6521
|
-
case Event.CustomElement:
|
|
6522
|
-
v.layout.customElement(entry);
|
|
6523
|
-
break;
|
|
6524
|
-
case Event.Mutation: {
|
|
6525
|
-
const domEvent = entry;
|
|
6526
|
-
this.renderTime = domEvent.time;
|
|
6527
|
-
if (v.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
|
|
6528
|
-
return;
|
|
6529
|
-
v.layout.markup(domEvent, useproxy);
|
|
6530
|
-
break;
|
|
6531
|
-
}
|
|
6532
|
-
}
|
|
6314
|
+
htmlRender = async (props) => {
|
|
6315
|
+
const { decoded, target, portalCanvasId, useproxy, logerror } = props;
|
|
6316
|
+
if (!decoded || decoded.length === 0 || !target)
|
|
6317
|
+
return this;
|
|
6318
|
+
try {
|
|
6319
|
+
const merged = this.mergeForHtml(decoded);
|
|
6320
|
+
await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
|
|
6321
|
+
await this.renderLoop(this, { events: merged.events, target, useproxy });
|
|
6533
6322
|
}
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
try {
|
|
6538
|
-
const v = this;
|
|
6539
|
-
const merged = v.mergeForHtml(decoded);
|
|
6540
|
-
await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
|
|
6541
|
-
await this.renderLoop(v, merged.events, hash, useproxy, shortCircuitStrategy);
|
|
6542
|
-
}
|
|
6543
|
-
catch (e) {
|
|
6544
|
-
if (logerror)
|
|
6545
|
-
logerror(e);
|
|
6546
|
-
}
|
|
6323
|
+
catch (e) {
|
|
6324
|
+
if (logerror)
|
|
6325
|
+
logerror(e);
|
|
6547
6326
|
}
|
|
6548
6327
|
return this;
|
|
6549
6328
|
};
|
|
6550
|
-
htmlCached = async (cacheKey,
|
|
6329
|
+
htmlCached = async (cacheKey, options) => {
|
|
6330
|
+
const { decoded, target } = options;
|
|
6551
6331
|
if (!decoded || decoded.length === 0 || !target)
|
|
6552
6332
|
return this;
|
|
6553
|
-
const fullKey = buildCacheKey(cacheKey,
|
|
6333
|
+
const fullKey = buildCacheKey(cacheKey, options.shortCircuitStrategy);
|
|
6554
6334
|
const cached = await htmlCache.get(fullKey);
|
|
6555
6335
|
if (cached) {
|
|
6556
6336
|
try {
|
|
6557
|
-
|
|
6558
|
-
await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
|
|
6559
|
-
target.document.open();
|
|
6560
|
-
target.document.write(cached.html);
|
|
6561
|
-
target.document.close();
|
|
6562
|
-
v.layout.hydrate(target.document);
|
|
6563
|
-
// Replay shadow DOM from initial Discover event (was processed by setup() in full render)
|
|
6564
|
-
if (cached.specialDom) {
|
|
6565
|
-
v.layout.markup(cached.specialDom, useproxy);
|
|
6566
|
-
}
|
|
6567
|
-
// Replay shadow DOM mutations + StyleSheet + CustomElement
|
|
6568
|
-
await this.renderLoop(v, cached.specialEvents, '', useproxy, 0);
|
|
6337
|
+
await this.buildHtmlByCached(cached, options);
|
|
6569
6338
|
return this;
|
|
6570
6339
|
}
|
|
6571
6340
|
catch (e) {
|
|
6572
|
-
|
|
6573
|
-
logerror(e);
|
|
6341
|
+
options?.logerror?.(e);
|
|
6574
6342
|
}
|
|
6575
6343
|
}
|
|
6344
|
+
await this.buildHtmlForCache(fullKey, options);
|
|
6345
|
+
return this;
|
|
6346
|
+
};
|
|
6347
|
+
/**
|
|
6348
|
+
* Render attention/engagement map.
|
|
6349
|
+
* @param attentionData - Array of attention data points with start/end element hashes and time spent
|
|
6350
|
+
* @param avgFold - Average fold pixel position (used to scale top portion)
|
|
6351
|
+
* @param isSecondary - Whether to use secondary comparison iframe IDs
|
|
6352
|
+
*/
|
|
6353
|
+
attention = (attentionData, avgFold, isSecondary = false) => {
|
|
6354
|
+
this.clearmapOverride();
|
|
6355
|
+
this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
|
|
6356
|
+
};
|
|
6357
|
+
buildHtmlByCached = async (cached, options) => {
|
|
6358
|
+
const { target, useproxy, portalCanvasId } = options;
|
|
6359
|
+
if (!cached || !target)
|
|
6360
|
+
throw new Error('Failed to render HTML cached');
|
|
6361
|
+
const doc = target.document;
|
|
6362
|
+
target.window;
|
|
6576
6363
|
try {
|
|
6577
|
-
|
|
6578
|
-
|
|
6364
|
+
await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
|
|
6365
|
+
doc.open();
|
|
6366
|
+
doc.write(cached.html);
|
|
6367
|
+
doc.close();
|
|
6368
|
+
const process = async () => {
|
|
6369
|
+
this.layout.hydrate(doc);
|
|
6370
|
+
// Replay shadow DOM from initial Discover event
|
|
6371
|
+
if (cached.specialDom) {
|
|
6372
|
+
this.layout.markup(cached.specialDom, useproxy);
|
|
6373
|
+
}
|
|
6374
|
+
// Replay shadow DOM mutations + StyleSheet + CustomElement
|
|
6375
|
+
await this.renderLoop(this, { ...options, events: cached.specialEvents });
|
|
6376
|
+
};
|
|
6377
|
+
await retry(() => doc.readyState === 'complete', { timeout: 30000, label: 'DOM ready' });
|
|
6378
|
+
await process();
|
|
6379
|
+
return this;
|
|
6380
|
+
}
|
|
6381
|
+
catch (e) {
|
|
6382
|
+
throw new Error('Failed to render HTML cached', { cause: e });
|
|
6383
|
+
}
|
|
6384
|
+
};
|
|
6385
|
+
buildHtmlForCache = async (cacheKey, options) => {
|
|
6386
|
+
const { decoded, target, useproxy, portalCanvasId, logerror } = options;
|
|
6387
|
+
if (!decoded || decoded.length === 0 || !target)
|
|
6388
|
+
return this;
|
|
6389
|
+
try {
|
|
6390
|
+
const merged = this.mergeForHtml(decoded);
|
|
6579
6391
|
await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
|
|
6580
|
-
await this.renderLoop(
|
|
6392
|
+
await this.renderLoop(this, { events: merged.events, target, useproxy });
|
|
6393
|
+
const timestamp = Date.now();
|
|
6394
|
+
const version = decoded[0].envelope.version;
|
|
6395
|
+
const html = target.document.documentElement.outerHTML;
|
|
6581
6396
|
const shadowHostTags = collectShadowHostTags(target.document);
|
|
6582
6397
|
const specialDom = extractSpecialDom(merged.dom, merged.events, shadowHostTags);
|
|
6583
6398
|
const specialEvents = extractSpecialEvents(merged.events, merged.dom, shadowHostTags);
|
|
6584
|
-
void htmlCache.set({
|
|
6585
|
-
key: fullKey,
|
|
6586
|
-
html: target.document.documentElement.outerHTML,
|
|
6587
|
-
specialDom: specialDom,
|
|
6588
|
-
specialEvents,
|
|
6589
|
-
version: decoded[0].envelope.version,
|
|
6590
|
-
timestamp: Date.now(),
|
|
6591
|
-
});
|
|
6399
|
+
void htmlCache.set({ key: cacheKey, html, specialDom, specialEvents, version, timestamp });
|
|
6592
6400
|
}
|
|
6593
6401
|
catch (e) {
|
|
6594
6402
|
if (logerror)
|
|
@@ -6596,15 +6404,18 @@ class GXVisualizer extends Visualizer {
|
|
|
6596
6404
|
}
|
|
6597
6405
|
return this;
|
|
6598
6406
|
};
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
this
|
|
6607
|
-
|
|
6407
|
+
renderLoop = async (ctx, options) => {
|
|
6408
|
+
await renderLoop(ctx, options);
|
|
6409
|
+
};
|
|
6410
|
+
setupOverride = async (target, options) => {
|
|
6411
|
+
this.attentionMap?.clear();
|
|
6412
|
+
await this.originalSetup(target, options);
|
|
6413
|
+
this.attentionMap = new AttentionMapRenderer(this.state);
|
|
6414
|
+
return this;
|
|
6415
|
+
};
|
|
6416
|
+
clearmapOverride = () => {
|
|
6417
|
+
this.originalClearmap();
|
|
6418
|
+
this.attentionMap?.clear();
|
|
6608
6419
|
};
|
|
6609
6420
|
}
|
|
6610
6421
|
|
|
@@ -6701,12 +6512,6 @@ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Deskt
|
|
|
6701
6512
|
});
|
|
6702
6513
|
}
|
|
6703
6514
|
|
|
6704
|
-
const EShortCircuitStrategy = {
|
|
6705
|
-
None: 0,
|
|
6706
|
-
HashFirstTimestamp: 1,
|
|
6707
|
-
HashFirstTimestampPlusBuffer: 2,
|
|
6708
|
-
HashBeforeDeleted: 3,
|
|
6709
|
-
};
|
|
6710
6515
|
const perf = createPerfTimer('Render');
|
|
6711
6516
|
const useHeatmapRenderDom = () => {
|
|
6712
6517
|
const viewId = useViewIdContext();
|
|
@@ -6739,19 +6544,23 @@ const useHeatmapRenderDom = () => {
|
|
|
6739
6544
|
const abort = new AbortController();
|
|
6740
6545
|
abortRef.current = abort;
|
|
6741
6546
|
resetIframeSetup();
|
|
6742
|
-
const t0 = perf.mark('
|
|
6547
|
+
const t0 = perf.mark('RenderHeatmap start');
|
|
6743
6548
|
const visualizer = vizRef ?? new GXVisualizer();
|
|
6744
6549
|
if (!vizRef)
|
|
6745
6550
|
setVizRef(visualizer);
|
|
6746
6551
|
visualizer.configure({ excludeClassNames });
|
|
6747
6552
|
setIsDomLoaded(false);
|
|
6748
6553
|
// Phase 1: render DOM — does not depend on contentWidth/wrapperHeight
|
|
6749
|
-
const hash = elementToShowRef.current ?? undefined;
|
|
6750
6554
|
const cacheKey = dataHashRef.current;
|
|
6751
|
-
const
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
:
|
|
6555
|
+
const options = {
|
|
6556
|
+
decoded: payloads,
|
|
6557
|
+
target: contentWindow,
|
|
6558
|
+
portalCanvasId: viewId,
|
|
6559
|
+
logerror: (error) => {
|
|
6560
|
+
console.error('Error rendering HTML', error);
|
|
6561
|
+
},
|
|
6562
|
+
};
|
|
6563
|
+
await perf.wrap('RenderHtml', () => cacheKey ? visualizer.htmlCached(cacheKey, options) : visualizer.htmlRender(options));
|
|
6755
6564
|
if (abort.signal.aborted)
|
|
6756
6565
|
return;
|
|
6757
6566
|
// Phase 2: iframe setup — deferred to useIframeSetup (handles dims dependency)
|
|
@@ -7351,10 +7160,10 @@ const useScrollmapZones = (options) => {
|
|
|
7351
7160
|
const newZones = createZones(scrollmap);
|
|
7352
7161
|
setZones(newZones);
|
|
7353
7162
|
setIsReady(true);
|
|
7354
|
-
logger$
|
|
7163
|
+
logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
|
|
7355
7164
|
}
|
|
7356
7165
|
catch (error) {
|
|
7357
|
-
logger$
|
|
7166
|
+
logger$3.error('[useScrollmap] Error:', error);
|
|
7358
7167
|
setIsReady(false);
|
|
7359
7168
|
}
|
|
7360
7169
|
}, [enabled, scrollmap, mode, createZones]);
|