@antelopejs/dms-marketing 0.6.1 → 0.7.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 +2 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/data-api/websites.d.ts +10 -4
- package/dist/data-api/websites.js +79 -3
- package/dist/data-api/websites.js.map +1 -1
- package/dist/db/models/funnels.model.d.ts +0 -1
- package/dist/db/models/funnels.model.js +0 -18
- package/dist/db/models/funnels.model.js.map +1 -1
- package/dist/db/models/page_snapshots.model.d.ts +2 -1
- package/dist/db/models/page_snapshots.model.js +6 -14
- package/dist/db/models/page_snapshots.model.js.map +1 -1
- package/dist/db/models/websites.model.d.ts +1 -1
- package/dist/db/models/websites.model.js +9 -29
- package/dist/db/models/websites.model.js.map +1 -1
- package/dist/db/tables/funnels.table.d.ts +0 -1
- package/dist/db/tables/funnels.table.js +0 -4
- package/dist/db/tables/funnels.table.js.map +1 -1
- package/dist/db/tables/page_snapshots.table.d.ts +1 -1
- package/dist/db/tables/page_snapshots.table.js +4 -4
- package/dist/db/tables/page_snapshots.table.js.map +1 -1
- package/dist/db/tables/websites.table.d.ts +2 -1
- package/dist/db/tables/websites.table.js +4 -4
- package/dist/db/tables/websites.table.js.map +1 -1
- package/dist/frontend-module.d.ts +6 -0
- package/dist/frontend-module.js +46 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms-marketing/index.js +0 -5
- package/dist/implementations/dms-marketing/index.js.map +1 -1
- package/dist/index.js +3 -13
- package/dist/index.js.map +1 -1
- package/dist/pages/acquisition/page.d.ts +1 -0
- package/dist/pages/acquisition/page.js +9 -4
- package/dist/pages/acquisition/page.js.map +1 -1
- package/dist/pages/blocks.d.ts +11 -2
- package/dist/pages/blocks.js +43 -5
- package/dist/pages/blocks.js.map +1 -1
- package/dist/pages/funnel/page.d.ts +1 -0
- package/dist/pages/funnel/page.js +1 -0
- package/dist/pages/funnel/page.js.map +1 -1
- package/dist/pages/funnels/page.d.ts +1 -0
- package/dist/pages/funnels/page.js +4 -2
- package/dist/pages/funnels/page.js.map +1 -1
- package/dist/pages/overview/page.d.ts +1 -0
- package/dist/pages/overview/page.js +5 -6
- package/dist/pages/overview/page.js.map +1 -1
- package/dist/pages/pages/page.d.ts +1 -0
- package/dist/pages/pages/page.js +1 -0
- package/dist/pages/pages/page.js.map +1 -1
- package/dist/pages/settings/form.js +1 -0
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/websites/page.d.ts +2 -3
- package/dist/pages/websites/page.js +63 -5
- package/dist/pages/websites/page.js.map +1 -1
- package/dist/routes/dashboard-tracking.js +5 -1
- package/dist/routes/dashboard-tracking.js.map +1 -1
- package/dist/routes/funnels.d.ts +2 -0
- package/dist/routes/funnels.js +6 -5
- package/dist/routes/funnels.js.map +1 -1
- package/dist/routes/public-endpoints.js +1 -1
- package/dist/routes/public-endpoints.js.map +1 -1
- package/dist/routes/snapshot.d.ts +1 -1
- package/dist/routes/snapshot.js +30 -13
- package/dist/routes/snapshot.js.map +1 -1
- package/dist/routes/stats.d.ts +10 -4
- package/dist/routes/stats.js +77 -23
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/websites.d.ts +5 -1
- package/dist/routes/websites.js +31 -13
- package/dist/routes/websites.js.map +1 -1
- package/dist/services/acquisition-report.d.ts +1 -0
- package/dist/services/acquisition-report.js +1 -0
- package/dist/services/acquisition-report.js.map +1 -1
- package/dist/services/collect.js +7 -2
- package/dist/services/collect.js.map +1 -1
- package/dist/services/dashboard-tracking.d.ts +7 -14
- package/dist/services/dashboard-tracking.js +42 -26
- package/dist/services/dashboard-tracking.js.map +1 -1
- package/dist/services/declarations.d.ts +0 -2
- package/dist/services/declarations.js +0 -11
- package/dist/services/declarations.js.map +1 -1
- package/dist/services/declared-funnel.js +11 -99
- package/dist/services/declared-funnel.js.map +1 -1
- package/dist/services/declared-snapshot.js +8 -37
- package/dist/services/declared-snapshot.js.map +1 -1
- package/dist/services/declared-website.js +5 -46
- package/dist/services/declared-website.js.map +1 -1
- package/dist/services/funnels-table.d.ts +3 -2
- package/dist/services/funnels-table.js +9 -9
- package/dist/services/funnels-table.js.map +1 -1
- package/dist/services/heatmap.d.ts +6 -0
- package/dist/services/heatmap.js +82 -17
- package/dist/services/heatmap.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/prune.js +6 -1
- package/dist/services/prune.js.map +1 -1
- package/dist/services/realtime.d.ts +3 -0
- package/dist/services/realtime.js +49 -0
- package/dist/services/realtime.js.map +1 -0
- package/dist/services/request-body.d.ts +1 -0
- package/dist/services/request-body.js +18 -0
- package/dist/services/request-body.js.map +1 -1
- package/dist/services/sessionize.d.ts +1 -0
- package/dist/services/sessionize.js +9 -0
- package/dist/services/sessionize.js.map +1 -1
- package/dist/services/snapshots.d.ts +29 -1
- package/dist/services/snapshots.js +151 -26
- package/dist/services/snapshots.js.map +1 -1
- package/dist/services/tenant-website.d.ts +11 -0
- package/dist/services/tenant-website.js +27 -5
- package/dist/services/tenant-website.js.map +1 -1
- package/dist/services/website-report.d.ts +5 -0
- package/dist/services/website-report.js +13 -1
- package/dist/services/website-report.js.map +1 -1
- package/dist/services/workspace-scope.d.ts +31 -0
- package/dist/services/workspace-scope.js +72 -0
- package/dist/services/workspace-scope.js.map +1 -0
- package/dist/types/constants.d.ts +7 -0
- package/dist/types/constants.js +8 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/types/events.d.ts +25 -25
- package/dist/types/snapshots.d.ts +29 -29
- package/dist/types/snapshots.js +1 -1
- package/dist/types/snapshots.js.map +1 -1
- package/dist/utils/funnel-steps-type.d.ts +13 -13
- package/frontend-vue/app/components/ChannelsCard.vue +19 -2
- package/frontend-vue/app/components/Context.vue +50 -376
- package/frontend-vue/app/components/FunnelBuilder.vue +28 -22
- package/frontend-vue/app/components/FunnelFigure.vue +15 -2
- package/frontend-vue/app/components/FunnelReport.vue +3 -0
- package/frontend-vue/app/components/FunnelTemplates.vue +15 -13
- package/frontend-vue/app/components/InstallGuide.vue +1 -1
- package/frontend-vue/app/components/PagePreview.vue +330 -74
- package/frontend-vue/app/components/PagesExplorer.vue +230 -85
- package/frontend-vue/app/components/TopListTabs.vue +51 -18
- package/frontend-vue/app/components/WebsiteCard.vue +394 -0
- package/frontend-vue/app/components/WebsiteCreate.vue +159 -0
- package/frontend-vue/app/components/WebsitePicker.vue +210 -0
- package/frontend-vue/app/components/WebsiteSettings.vue +539 -0
- package/frontend-vue/app/components/WorkspaceScope.vue +119 -0
- package/frontend-vue/app/composables/useContextWebsite.ts +105 -0
- package/frontend-vue/app/composables/useCopyDraft.ts +47 -0
- package/frontend-vue/app/composables/useMarketingApi.ts +92 -4
- package/frontend-vue/app/composables/useMarketingReads.ts +32 -23
- package/frontend-vue/app/composables/useMarketingRoutes.ts +25 -2
- package/frontend-vue/app/composables/useRecordCrumb.ts +17 -0
- package/frontend-vue/app/composables/useSnapshotFrame.ts +35 -1
- package/frontend-vue/app/utils/anchor-drift.ts +58 -0
- package/frontend-vue/app/utils/drift-dismissals.ts +20 -0
- package/frontend-vue/app/utils/fill-height.ts +141 -0
- package/frontend-vue/app/utils/format.ts +21 -2
- package/frontend-vue/app/utils/funnel-draft.ts +22 -0
- package/frontend-vue/app/utils/heatmap.ts +17 -0
- package/frontend-vue/app/utils/page-snapshot.ts +75 -0
- package/frontend-vue/app/utils/snapshot-options.ts +148 -0
- package/frontend-vue/app/utils/snapshotScrollRoot.ts +93 -0
- package/frontend-vue/app/utils/website-card.ts +93 -0
- package/frontend-vue/app/utils/website-purge.ts +95 -0
- package/frontend-vue/app/utils/website-settings.ts +121 -0
- package/frontend-vue/app/utils/website-state.ts +93 -0
- package/frontend-vue/app/utils/workspace-scope.ts +51 -0
- package/frontend-vue/i18n/locales/marketing-en-GB.json +112 -36
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +112 -36
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/anchor-drift.test.ts +59 -0
- package/frontend-vue/tests/build-flags.test.ts +38 -0
- package/frontend-vue/tests/context-bar.test.ts +226 -0
- package/frontend-vue/tests/copy-draft.test.ts +64 -0
- package/frontend-vue/tests/drift-dismissals.test.ts +12 -0
- package/frontend-vue/tests/fill-height.test.ts +110 -0
- package/frontend-vue/tests/format.test.ts +14 -0
- package/frontend-vue/tests/funnel-draft.test.ts +37 -0
- package/frontend-vue/tests/heatmap.test.ts +25 -0
- package/frontend-vue/tests/install-guide.test.ts +25 -0
- package/frontend-vue/tests/marketing-reads.test.ts +20 -6
- package/frontend-vue/tests/page-snapshot.test.ts +62 -0
- package/frontend-vue/tests/record-crumb.test.ts +43 -0
- package/frontend-vue/tests/registration.test.ts +4 -1
- package/frontend-vue/tests/routes.test.ts +21 -0
- package/frontend-vue/tests/save-funnel.test.ts +47 -0
- package/frontend-vue/tests/server-parity.test.ts +3 -0
- package/frontend-vue/tests/snapshot-options.test.ts +148 -0
- package/frontend-vue/tests/snapshot-scroll-root.test.ts +84 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +5 -1
- package/frontend-vue/tests/top-list-tabs.test.ts +158 -0
- package/frontend-vue/tests/website-card.test.ts +119 -0
- package/frontend-vue/tests/website-purge.test.ts +70 -0
- package/frontend-vue/tests/website-settings.test.ts +133 -0
- package/frontend-vue/tests/website-state.test.ts +96 -0
- package/frontend-vue/tests/workspace-scope-control.test.ts +113 -0
- package/frontend-vue/tests/workspace-scope.test.ts +55 -0
- package/package.json +8 -7
- package/skills/dms-marketing/SKILL.md +54 -12
- package/static/snapshot.js +539 -48
- package/static/tracker.js +154 -5
- package/dist/services/readiness.d.ts +0 -2
- package/dist/services/readiness.js +0 -15
- package/dist/services/readiness.js.map +0 -1
- package/frontend-vue/app/components/WebsitesGrid.vue +0 -812
package/static/snapshot.js
CHANGED
|
@@ -17,12 +17,23 @@
|
|
|
17
17
|
* an element with an empty box of its size. `maskText` masks every text
|
|
18
18
|
* node and text attribute of the page; data-* and link paths stay, since
|
|
19
19
|
* styles select on them.
|
|
20
|
+
*
|
|
21
|
+
* Transient overlays open at capture time — a prompt, a toast, an open
|
|
22
|
+
* menu or popover floating over the page — are left out (see
|
|
23
|
+
* isTransientOverlay): they belong to that one visit, not to the page.
|
|
24
|
+
*
|
|
25
|
+
* A page whose document does not scroll, an inner container doing it
|
|
26
|
+
* instead (see the scroll root in static/tracker.js), is captured with that
|
|
27
|
+
* container marked `data-marketing-scroll-root` and the height of its whole
|
|
28
|
+
* content recorded: the preview unrolls it, as tall as the page really is.
|
|
20
29
|
*/
|
|
21
30
|
(() => {
|
|
22
31
|
const CONFIG_GLOBAL = "__dmsMarketingSnapshotConfig";
|
|
23
32
|
const CONTEXT_GLOBAL = "__dmsMarketingSnapshotContext";
|
|
24
33
|
const MASK_MARKER = "dms-marketing-mask";
|
|
25
34
|
const BLOCK_MARKER = "dms-marketing-block";
|
|
35
|
+
/** Marks the page's inner scroll container in the capture (see above). */
|
|
36
|
+
const SCROLL_ROOT_ATTRIBUTE = "data-marketing-scroll-root";
|
|
26
37
|
const MASK_CHAR = "x";
|
|
27
38
|
/** Mirrors the backend MAX_URL_LENGTH. */
|
|
28
39
|
const MAX_URL_LENGTH = 500;
|
|
@@ -84,6 +95,21 @@
|
|
|
84
95
|
"background",
|
|
85
96
|
]);
|
|
86
97
|
const LINK_TAGS = new Set(["a", "area"]);
|
|
98
|
+
/** Roles of the overlays a visit opens over the page, dropped when they
|
|
99
|
+
* float over it (see isTransientOverlay). */
|
|
100
|
+
const OVERLAY_ROLES = new Set([
|
|
101
|
+
"dialog",
|
|
102
|
+
"alertdialog",
|
|
103
|
+
"tooltip",
|
|
104
|
+
"menu",
|
|
105
|
+
"listbox",
|
|
106
|
+
]);
|
|
107
|
+
const LIVE_ROLES = new Set(["status", "alert", "log"]);
|
|
108
|
+
const LIVE_POLITENESS = new Set(["polite", "assertive"]);
|
|
109
|
+
/** Lists a toast viewport stacks its toasts in. */
|
|
110
|
+
const LIST_TAGS = new Set(["ol", "ul"]);
|
|
111
|
+
/** In the top layer: a modal `<dialog>`, an open popover. */
|
|
112
|
+
const TOP_LAYER_SELECTOR = ":modal, :popover-open";
|
|
87
113
|
/** Attributes that carry text a visitor could read, masked with it. */
|
|
88
114
|
const TEXT_ATTRIBUTES = new Set([
|
|
89
115
|
"title",
|
|
@@ -106,18 +132,21 @@
|
|
|
106
132
|
return value && typeof value === "object" ? value : null;
|
|
107
133
|
}
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
/** Loaded by the module's tests (CommonJS) rather than by a page: the
|
|
136
|
+
* pure parts are exported instead of capturing anything. */
|
|
137
|
+
const isModule =
|
|
138
|
+
typeof window === "undefined" &&
|
|
139
|
+
typeof module === "object" &&
|
|
140
|
+
module !== null;
|
|
141
|
+
const config = isModule ? null : readGlobal(CONFIG_GLOBAL);
|
|
110
142
|
const maskAllText = config?.maskText === true;
|
|
111
|
-
const context = readGlobal(CONTEXT_GLOBAL);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
typeof config.token
|
|
116
|
-
typeof context.endpoint
|
|
117
|
-
window.top
|
|
118
|
-
) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
143
|
+
const context = isModule ? null : readGlobal(CONTEXT_GLOBAL);
|
|
144
|
+
const asked =
|
|
145
|
+
!!config &&
|
|
146
|
+
!!context &&
|
|
147
|
+
typeof config.token === "string" &&
|
|
148
|
+
typeof context.endpoint === "string" &&
|
|
149
|
+
window.top === window;
|
|
121
150
|
|
|
122
151
|
// --- Text -----------------------------------------------------------------
|
|
123
152
|
|
|
@@ -203,34 +232,306 @@
|
|
|
203
232
|
}
|
|
204
233
|
|
|
205
234
|
// --- Stylesheets ----------------------------------------------------------
|
|
235
|
+
//
|
|
236
|
+
// A page carries far more CSS than it uses — a framework's utilities, a
|
|
237
|
+
// component library, every layer a host app loads — and a capture inlining
|
|
238
|
+
// all of it routinely weighs megabytes. A style rule is kept only when its
|
|
239
|
+
// selector matches an element of the captured document, tested with the
|
|
240
|
+
// states it depends on (hover, focus, open…) and its pseudo-element taken
|
|
241
|
+
// off, so whatever could show in the snapshot keeps its style; a selector
|
|
242
|
+
// the browser cannot test is kept. Custom properties that no kept rule,
|
|
243
|
+
// inline style or kept property value refers to are dropped. Conditional
|
|
244
|
+
// wrappers (@media, @supports, @container…) stay around the rules they
|
|
245
|
+
// still hold — every layout's, not only the visitor's — and @layer blocks
|
|
246
|
+
// stay even empty (their order is the cascade's), as do @font-face,
|
|
247
|
+
// @keyframes, @property and the other at-rules. A stylesheet identical to
|
|
248
|
+
// one already serialized is not repeated.
|
|
249
|
+
|
|
250
|
+
const STYLE_RULE = 1;
|
|
251
|
+
const IMPORT_RULE = 3;
|
|
252
|
+
const KEYFRAMES_RULE = 7;
|
|
253
|
+
/** Selectors `querySelector` cannot answer for from the sheet's scope:
|
|
254
|
+
* the rule is kept. */
|
|
255
|
+
const UNTESTABLE_SELECTOR_PATTERN =
|
|
256
|
+
/:host|::slotted|::part|::cue|::highlight|::view-transition|:state\(|&|:scope/;
|
|
257
|
+
/** A pseudo-element renders on its element: the rule is tested on it. */
|
|
258
|
+
const PSEUDO_ELEMENT_PATTERN =
|
|
259
|
+
/::[\w-]+|:(?:before|after|first-line|first-letter)(?![\w-])/g;
|
|
260
|
+
/** States a snapshot may be looked at in, or a visitor's input sets: the
|
|
261
|
+
* rule is tested as if the state held. */
|
|
262
|
+
const STATE_PSEUDO_CLASS_PATTERN =
|
|
263
|
+
/:(?:hover|focus|focus-visible|focus-within|active|visited|link|any-link|target|target-within|checked|indeterminate|placeholder-shown|autofill|valid|invalid|user-valid|user-invalid|in-range|out-of-range|open|closed|popover-open|modal|fullscreen|playing|paused)(?![\w-])/g;
|
|
264
|
+
const COMBINATOR_SPACING_PATTERN = /\s*([>+~])\s*/g;
|
|
265
|
+
/** A compound the stripping above left empty stands for any element. */
|
|
266
|
+
const EMPTY_COMPOUND_PATTERN = /(^|[\s>+~(,])(?=$|[\s>+~),])/g;
|
|
267
|
+
const NEGATED_ANY_PATTERN = /:not\(\*\)/g;
|
|
268
|
+
const ESCAPED_COLON_PATTERN = /\\:/g;
|
|
269
|
+
/** Stands in for an escaped colon while pseudo-classes are stripped. */
|
|
270
|
+
const ESCAPED_COLON_TOKEN = "\u0001";
|
|
271
|
+
const CSS_VARIABLE_PATTERN = /var\(\s*(--[\w-]+)/g;
|
|
272
|
+
const LAYER_PRELUDE_PATTERN = /^@layer\b/;
|
|
273
|
+
|
|
274
|
+
/** Selector list split on its top-level commas. */
|
|
275
|
+
function splitSelectorList(list) {
|
|
276
|
+
const parts = [];
|
|
277
|
+
let depth = 0;
|
|
278
|
+
let quote = "";
|
|
279
|
+
let start = 0;
|
|
280
|
+
for (let index = 0; index < list.length; index++) {
|
|
281
|
+
const char = list[index];
|
|
282
|
+
if (quote) {
|
|
283
|
+
if (char === "\\") {
|
|
284
|
+
index++;
|
|
285
|
+
} else if (char === quote) {
|
|
286
|
+
quote = "";
|
|
287
|
+
}
|
|
288
|
+
} else if (char === '"' || char === "'") {
|
|
289
|
+
quote = char;
|
|
290
|
+
} else if (char === "(" || char === "[") {
|
|
291
|
+
depth++;
|
|
292
|
+
} else if (char === ")" || char === "]") {
|
|
293
|
+
depth--;
|
|
294
|
+
} else if (char === "," && depth === 0) {
|
|
295
|
+
parts.push(list.slice(start, index).trim());
|
|
296
|
+
start = index + 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
parts.push(list.slice(start).trim());
|
|
300
|
+
return parts;
|
|
301
|
+
}
|
|
206
302
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
303
|
+
/** One selector as tested, null when it cannot be (the rule stays). */
|
|
304
|
+
function testedSelector(selector) {
|
|
305
|
+
if (UNTESTABLE_SELECTOR_PATTERN.test(selector)) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
// An escaped colon belongs to a class name (`.md\:hover\:flex`).
|
|
309
|
+
const escaped = selector.replace(ESCAPED_COLON_PATTERN, ESCAPED_COLON_TOKEN);
|
|
310
|
+
const stripped = escaped
|
|
311
|
+
.replace(PSEUDO_ELEMENT_PATTERN, "")
|
|
312
|
+
.replace(STATE_PSEUDO_CLASS_PATTERN, "");
|
|
313
|
+
if (stripped === escaped) {
|
|
314
|
+
return selector;
|
|
315
|
+
}
|
|
316
|
+
// `.a > ::before` → `.a>*`; `.a:not(:hover)` → `.a`, not `.a:not(*)`,
|
|
317
|
+
// which would match nothing.
|
|
318
|
+
return stripped
|
|
319
|
+
.replace(COMBINATOR_SPACING_PATTERN, "$1")
|
|
320
|
+
.trim()
|
|
321
|
+
.replace(EMPTY_COMPOUND_PATTERN, "$1*")
|
|
322
|
+
.replace(NEGATED_ANY_PATTERN, "")
|
|
323
|
+
.replaceAll(ESCAPED_COLON_TOKEN, "\\:");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function selectorMatches(scope, selector) {
|
|
215
327
|
try {
|
|
216
|
-
|
|
328
|
+
return scope.querySelector(selector) !== null;
|
|
217
329
|
} catch {
|
|
218
|
-
|
|
330
|
+
// Unknown to this browser's querySelector (a vendor pseudo-class, a
|
|
331
|
+
// newer syntax): it may well match, so it stays.
|
|
332
|
+
return true;
|
|
219
333
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function cssVariables(text, into) {
|
|
337
|
+
for (const match of text.matchAll(CSS_VARIABLE_PATTERN)) {
|
|
338
|
+
into.add(match[1]);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* What the stylesheets of one capture keep, across every sheet and shadow
|
|
344
|
+
* root: custom properties inherit, so a variable one sheet defines another
|
|
345
|
+
* uses. Sheets are read into segments first (strings, or style rules with
|
|
346
|
+
* their custom property declarations set apart), and rendered once every
|
|
347
|
+
* sheet and inline style has said which variables it needs.
|
|
348
|
+
*/
|
|
349
|
+
function createStyleCollector() {
|
|
350
|
+
/** Variables referred to outside a custom property's own value. */
|
|
351
|
+
const used = new Set();
|
|
352
|
+
/** Every kept value of each custom property, for the transitive pass. */
|
|
353
|
+
const definitions = new Map();
|
|
354
|
+
/** Per scope (document, shadow root): selector list → applies. */
|
|
355
|
+
const verdicts = new Map();
|
|
356
|
+
|
|
357
|
+
function ruleApplies(scope, selectorText) {
|
|
358
|
+
let cache = verdicts.get(scope);
|
|
359
|
+
if (!cache) {
|
|
360
|
+
cache = new Map();
|
|
361
|
+
verdicts.set(scope, cache);
|
|
362
|
+
}
|
|
363
|
+
let applies = cache.get(selectorText);
|
|
364
|
+
if (applies === undefined) {
|
|
365
|
+
applies = splitSelectorList(selectorText).some((part) => {
|
|
366
|
+
const tested = testedSelector(part);
|
|
367
|
+
return tested === null || selectorMatches(scope, tested);
|
|
368
|
+
});
|
|
369
|
+
cache.set(selectorText, applies);
|
|
370
|
+
}
|
|
371
|
+
return applies;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function keptText(text) {
|
|
375
|
+
cssVariables(text, used);
|
|
376
|
+
return text;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** A kept style rule; a declaration whose text cannot be located in
|
|
380
|
+
* the rule's stays in it. */
|
|
381
|
+
function styleRuleSegment(rule, base) {
|
|
382
|
+
const text = absolutizeCss(rule.cssText, base);
|
|
383
|
+
const style = rule.style;
|
|
384
|
+
const declarations = [];
|
|
385
|
+
let rest = text;
|
|
386
|
+
for (let index = 0; index < (style ? style.length : 0); index++) {
|
|
387
|
+
const name = style[index];
|
|
388
|
+
if (!name.startsWith("--")) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const value = style.getPropertyValue(name);
|
|
392
|
+
const important =
|
|
393
|
+
style.getPropertyPriority(name) === "important" ? " !important" : "";
|
|
394
|
+
const source = `${name}: ${value}${important};`;
|
|
395
|
+
if (!rest.includes(source)) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
rest = rest.replace(source, "");
|
|
399
|
+
declarations.push({ name, source });
|
|
400
|
+
const values = definitions.get(name) || [];
|
|
401
|
+
values.push(value);
|
|
402
|
+
definitions.set(name, values);
|
|
403
|
+
}
|
|
404
|
+
cssVariables(rest, used);
|
|
405
|
+
return declarations.length > 0 ? { text, declarations } : text;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function ruleListSegments(rules, base, scope) {
|
|
409
|
+
const segments = [];
|
|
410
|
+
for (const rule of rules) {
|
|
411
|
+
segments.push(...ruleSegments(rule, base, scope));
|
|
412
|
+
}
|
|
413
|
+
return segments;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function ruleSegments(rule, base, scope) {
|
|
417
|
+
if (rule.type === IMPORT_RULE) {
|
|
418
|
+
const imported = rule.styleSheet
|
|
419
|
+
? sheetSegments(rule.styleSheet, rule.styleSheet.href || base, scope)
|
|
225
420
|
: null;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
421
|
+
if (imported === null) {
|
|
422
|
+
return [keptText(absolutizeCss(rule.cssText, base))];
|
|
423
|
+
}
|
|
424
|
+
const media = rule.media?.mediaText;
|
|
425
|
+
return media ? [`@media ${media}{`, ...imported, "}"] : imported;
|
|
426
|
+
}
|
|
427
|
+
if (rule.type === STYLE_RULE) {
|
|
428
|
+
if (!ruleApplies(scope, rule.selectorText)) {
|
|
429
|
+
// Nested rules included: each one only matches within (or on) an
|
|
430
|
+
// element its parent matches.
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
// A nested block is not tested rule by rule: it stays whole.
|
|
434
|
+
return rule.cssRules?.length
|
|
435
|
+
? [keptText(absolutizeCss(rule.cssText, base))]
|
|
436
|
+
: [styleRuleSegment(rule, base)];
|
|
437
|
+
}
|
|
438
|
+
if (rule.cssRules && rule.type !== KEYFRAMES_RULE) {
|
|
439
|
+
// A grouping rule: its prelude is whatever precedes its block.
|
|
440
|
+
const prelude = rule.cssText.slice(0, rule.cssText.indexOf("{"));
|
|
441
|
+
const inner = ruleListSegments(rule.cssRules, base, scope);
|
|
442
|
+
if (inner.length === 0 && !LAYER_PRELUDE_PATTERN.test(prelude)) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
return [`${prelude}{`, ...inner, "}"];
|
|
229
446
|
}
|
|
230
|
-
|
|
231
|
-
out += media ? `@media ${media}{${imported}}\n` : `${imported}\n`;
|
|
447
|
+
return [keptText(absolutizeCss(rule.cssText, base))];
|
|
232
448
|
}
|
|
233
|
-
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The rules of a sheet worth keeping, as segments, tested against
|
|
452
|
+
* `scope` (the document, or the shadow root holding the sheet). Null
|
|
453
|
+
* when the browser refuses to read them (cross-origin sheet): the caller
|
|
454
|
+
* keeps a link then. Rules are read from the CSSOM, not from the
|
|
455
|
+
* element's text: CSS-in-JS libraries insert theirs with `insertRule`,
|
|
456
|
+
* leaving the `<style>` empty. Readable imports are inlined in place,
|
|
457
|
+
* under their media.
|
|
458
|
+
*/
|
|
459
|
+
function sheetSegments(sheet, base, scope) {
|
|
460
|
+
let rules;
|
|
461
|
+
try {
|
|
462
|
+
rules = sheet.cssRules;
|
|
463
|
+
} catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
return ruleListSegments(rules, base, scope);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Every variable reachable from what the capture uses. */
|
|
470
|
+
function reachableVariables() {
|
|
471
|
+
const reached = new Set(used);
|
|
472
|
+
const pending = [...used];
|
|
473
|
+
while (pending.length > 0) {
|
|
474
|
+
for (const value of definitions.get(pending.pop()) || []) {
|
|
475
|
+
const found = new Set();
|
|
476
|
+
cssVariables(value, found);
|
|
477
|
+
for (const name of found) {
|
|
478
|
+
if (!reached.has(name)) {
|
|
479
|
+
reached.add(name);
|
|
480
|
+
pending.push(name);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return reached;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function render(segments, reached) {
|
|
489
|
+
let out = "";
|
|
490
|
+
for (const segment of segments) {
|
|
491
|
+
if (typeof segment === "string") {
|
|
492
|
+
out += `${segment}\n`;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
let text = segment.text;
|
|
496
|
+
for (const declaration of segment.declarations) {
|
|
497
|
+
if (!reached.has(declaration.name)) {
|
|
498
|
+
text = text.replace(declaration.source, "");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
out += `${text}\n`;
|
|
502
|
+
}
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
sheetSegments,
|
|
508
|
+
/** Variables an inline style or an attribute refers to. */
|
|
509
|
+
noteVariables: (text) => cssVariables(text, used),
|
|
510
|
+
/** Each sheet's final text; empty for one already rendered. */
|
|
511
|
+
finish(sheets) {
|
|
512
|
+
const reached = reachableVariables();
|
|
513
|
+
const seen = new Set();
|
|
514
|
+
return sheets.map((segments) => {
|
|
515
|
+
const css = render(segments, reached);
|
|
516
|
+
if (seen.has(css)) {
|
|
517
|
+
return "";
|
|
518
|
+
}
|
|
519
|
+
seen.add(css);
|
|
520
|
+
return css;
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** CSS as the text of a `<style>`: the character after a `<` that could
|
|
527
|
+
* open markup (a letter, `!`, `/`, `?`) is written as a CSS escape, the
|
|
528
|
+
* same token to CSS (`</style` in a string becomes `<\2f style`), so
|
|
529
|
+
* nothing in it closes the element and the server keeps the sheet. */
|
|
530
|
+
function styleText(css) {
|
|
531
|
+
return css.replace(
|
|
532
|
+
/<([a-z!/?])/gi,
|
|
533
|
+
(_match, next) => `<\\${next.charCodeAt(0).toString(16)} `,
|
|
534
|
+
);
|
|
234
535
|
}
|
|
235
536
|
|
|
236
537
|
function mediaAttribute(element) {
|
|
@@ -238,14 +539,36 @@
|
|
|
238
539
|
return media ? ` media="${escapeAttribute(media)}"` : "";
|
|
239
540
|
}
|
|
240
541
|
|
|
542
|
+
/** Sheets of the capture in progress, rendered at its end (see
|
|
543
|
+
* serializeDocument): their text depends on the whole document. */
|
|
544
|
+
let styles = null;
|
|
545
|
+
let pendingSheets = [];
|
|
546
|
+
let placeholderNonce = "";
|
|
547
|
+
/** The page's inner scroll container during a capture, null when the
|
|
548
|
+
* document scrolls. */
|
|
549
|
+
let scrollRootElement = null;
|
|
550
|
+
|
|
551
|
+
/** Comments are never serialized, and markup in text or attributes is
|
|
552
|
+
* escaped: the marker cannot occur otherwise. */
|
|
553
|
+
function sheetPlaceholder(segments, media) {
|
|
554
|
+
const index = pendingSheets.push({ segments, media }) - 1;
|
|
555
|
+
return `<!--dms-marketing-sheet-${placeholderNonce}-${index}-->`;
|
|
556
|
+
}
|
|
557
|
+
|
|
241
558
|
function serializeStyle(element) {
|
|
242
559
|
const sheet = element.sheet;
|
|
243
560
|
if (sheet?.disabled) {
|
|
244
561
|
return "";
|
|
245
562
|
}
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
563
|
+
const segments = sheet
|
|
564
|
+
? styles.sheetSegments(sheet, location.href, element.getRootNode())
|
|
565
|
+
: null;
|
|
566
|
+
if (segments === null) {
|
|
567
|
+
const text = element.textContent;
|
|
568
|
+
styles.noteVariables(text);
|
|
569
|
+
return `<style${mediaAttribute(element)}>${styleText(text)}</style>`;
|
|
570
|
+
}
|
|
571
|
+
return sheetPlaceholder(segments, mediaAttribute(element));
|
|
249
572
|
}
|
|
250
573
|
|
|
251
574
|
/** Only stylesheets render; preloads, icons and manifests are requests
|
|
@@ -257,9 +580,11 @@
|
|
|
257
580
|
return "";
|
|
258
581
|
}
|
|
259
582
|
const href = element.href;
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
583
|
+
const segments = sheet
|
|
584
|
+
? styles.sheetSegments(sheet, href, element.getRootNode())
|
|
585
|
+
: null;
|
|
586
|
+
if (segments !== null) {
|
|
587
|
+
return sheetPlaceholder(segments, mediaAttribute(element));
|
|
263
588
|
}
|
|
264
589
|
return `<link rel="stylesheet" href="${escapeAttribute(href)}"${mediaAttribute(element)}>`;
|
|
265
590
|
}
|
|
@@ -267,7 +592,10 @@
|
|
|
267
592
|
function adoptedStyles(root) {
|
|
268
593
|
let out = "";
|
|
269
594
|
for (const sheet of root.adoptedStyleSheets || []) {
|
|
270
|
-
out +=
|
|
595
|
+
out += sheetPlaceholder(
|
|
596
|
+
styles.sheetSegments(sheet, location.href, root) || [],
|
|
597
|
+
"",
|
|
598
|
+
);
|
|
271
599
|
}
|
|
272
600
|
return out;
|
|
273
601
|
}
|
|
@@ -281,6 +609,85 @@
|
|
|
281
609
|
);
|
|
282
610
|
}
|
|
283
611
|
|
|
612
|
+
function isOutOfFlow(position) {
|
|
613
|
+
return position === "fixed" || position === "absolute";
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function inTopLayer(element) {
|
|
617
|
+
try {
|
|
618
|
+
return element.matches(TOP_LAYER_SELECTOR);
|
|
619
|
+
} catch {
|
|
620
|
+
// A browser without one of the pseudo-classes has no such layer.
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** A portal: the nearest positioned ancestor is fixed and hangs off
|
|
626
|
+
* `body` — where floating menus and popovers are mounted, unlike a fixed
|
|
627
|
+
* header, which lives in the app's own tree. */
|
|
628
|
+
function inFloatingPortal(element, positionOf, body) {
|
|
629
|
+
for (
|
|
630
|
+
let node = element.parentElement;
|
|
631
|
+
node && node !== body;
|
|
632
|
+
node = node.parentElement
|
|
633
|
+
) {
|
|
634
|
+
const position = positionOf(node);
|
|
635
|
+
if (position !== "static") {
|
|
636
|
+
return position === "fixed" && node.parentElement === body;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function isLive(element) {
|
|
643
|
+
return (
|
|
644
|
+
LIVE_ROLES.has(element.getAttribute("role")) ||
|
|
645
|
+
LIVE_POLITENESS.has(element.getAttribute("aria-live"))
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* An overlay the visit opened over the page, not part of it:
|
|
651
|
+
* - anything in the top layer (a modal `<dialog>`, an open popover);
|
|
652
|
+
* - a dialog, alert dialog, tooltip, menu or listbox positioned out of
|
|
653
|
+
* the flow, or mounted in a fixed portal under `body`;
|
|
654
|
+
* - a live region (role status, alert or log, `aria-live`) fixed to the
|
|
655
|
+
* viewport: a toast, a notification;
|
|
656
|
+
* - a toast viewport: an element whose list child is fixed and holds the
|
|
657
|
+
* toasts (Reka / Nuxt UI, Radix, Sonner).
|
|
658
|
+
* In-flow content stays whatever its role: an inline `role="status"`
|
|
659
|
+
* badge, a listbox in a form, a menu in the page's own navigation.
|
|
660
|
+
* `positionOf` is the computed `position` of an element.
|
|
661
|
+
*/
|
|
662
|
+
function isTransientOverlay(element, positionOf, body) {
|
|
663
|
+
if (inTopLayer(element)) {
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
if (OVERLAY_ROLES.has(element.getAttribute("role"))) {
|
|
667
|
+
return (
|
|
668
|
+
isOutOfFlow(positionOf(element)) ||
|
|
669
|
+
inFloatingPortal(element, positionOf, body)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const live = isLive(element);
|
|
673
|
+
if (live && positionOf(element) === "fixed") {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
if (!live && element.getAttribute("role") !== "region") {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
for (const child of element.children) {
|
|
680
|
+
if (LIST_TAGS.has(child.localName) && positionOf(child) === "fixed") {
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function computedPosition(element) {
|
|
688
|
+
return getComputedStyle(element).position;
|
|
689
|
+
}
|
|
690
|
+
|
|
284
691
|
function imageSource(element, value) {
|
|
285
692
|
const current = element.currentSrc;
|
|
286
693
|
if (current && !current.startsWith("data:")) {
|
|
@@ -339,8 +746,17 @@
|
|
|
339
746
|
}
|
|
340
747
|
value = LINK_TAGS.has(tag) ? withoutQuery(absolute, masked) : absolute;
|
|
341
748
|
}
|
|
749
|
+
if (value.includes("var(")) {
|
|
750
|
+
styles.noteVariables(value);
|
|
751
|
+
}
|
|
342
752
|
out += ` ${name}="${escapeAttribute(value)}"`;
|
|
343
753
|
}
|
|
754
|
+
if (
|
|
755
|
+
element === scrollRootElement &&
|
|
756
|
+
!element.hasAttribute(SCROLL_ROOT_ATTRIBUTE)
|
|
757
|
+
) {
|
|
758
|
+
out += ` ${SCROLL_ROOT_ATTRIBUTE}=""`;
|
|
759
|
+
}
|
|
344
760
|
return out;
|
|
345
761
|
}
|
|
346
762
|
|
|
@@ -406,6 +822,9 @@
|
|
|
406
822
|
if (isMarked(element, BLOCK_MARKER)) {
|
|
407
823
|
return serializeBlocked(element);
|
|
408
824
|
}
|
|
825
|
+
if (isTransientOverlay(element, computedPosition, document.body)) {
|
|
826
|
+
return "";
|
|
827
|
+
}
|
|
409
828
|
const mask =
|
|
410
829
|
masked || isMarked(element, MASK_MARKER) || element.isContentEditable;
|
|
411
830
|
if (tag === "canvas") {
|
|
@@ -432,7 +851,23 @@
|
|
|
432
851
|
}
|
|
433
852
|
|
|
434
853
|
function serializeDocument() {
|
|
435
|
-
|
|
854
|
+
styles = createStyleCollector();
|
|
855
|
+
pendingSheets = [];
|
|
856
|
+
placeholderNonce = Math.random().toString(36).slice(2);
|
|
857
|
+
const html = `<!DOCTYPE html>${serializeElement(document.documentElement, false)}`;
|
|
858
|
+
const css = styles.finish(pendingSheets.map((sheet) => sheet.segments));
|
|
859
|
+
const marker = new RegExp(
|
|
860
|
+
`<!--dms-marketing-sheet-${placeholderNonce}-([0-9]+)-->`,
|
|
861
|
+
"g",
|
|
862
|
+
);
|
|
863
|
+
const out = html.replace(marker, (_match, index) =>
|
|
864
|
+
css[index]
|
|
865
|
+
? `<style${pendingSheets[index].media}>${styleText(css[index])}</style>`
|
|
866
|
+
: "",
|
|
867
|
+
);
|
|
868
|
+
styles = null;
|
|
869
|
+
pendingSheets = [];
|
|
870
|
+
return out;
|
|
436
871
|
}
|
|
437
872
|
|
|
438
873
|
// --- Timing ---------------------------------------------------------------
|
|
@@ -476,8 +911,25 @@
|
|
|
476
911
|
|
|
477
912
|
// --- Upload ---------------------------------------------------------------
|
|
478
913
|
|
|
479
|
-
|
|
480
|
-
|
|
914
|
+
/** Gzipped where the browser can (the backend recognizes the gzip
|
|
915
|
+
* header), null otherwise: the upload then goes as plain JSON. */
|
|
916
|
+
async function gzip(text) {
|
|
917
|
+
if (typeof CompressionStream !== "function") {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
const stream = new Blob([text])
|
|
922
|
+
.stream()
|
|
923
|
+
.pipeThrough(new CompressionStream("gzip"));
|
|
924
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
925
|
+
} catch {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// text/plain is CORS-safelisted (no preflight), compressed or not; no-cors
|
|
931
|
+
// keeps the unreadable answer from logging an error on the visitor's
|
|
932
|
+
// console.
|
|
481
933
|
function post(body) {
|
|
482
934
|
fetch(context.endpoint, {
|
|
483
935
|
method: "POST",
|
|
@@ -487,12 +939,33 @@
|
|
|
487
939
|
}).catch(() => {});
|
|
488
940
|
}
|
|
489
941
|
|
|
490
|
-
|
|
942
|
+
/** The tracker's detection; a tracker older than this script has none,
|
|
943
|
+
* and the document is taken as scrolling. */
|
|
944
|
+
function findScrollRoot() {
|
|
945
|
+
try {
|
|
946
|
+
return typeof context.scrollRoot === "function"
|
|
947
|
+
? context.scrollRoot() || null
|
|
948
|
+
: null;
|
|
949
|
+
} catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/** The whole content: the document, plus what the scroll root hides. */
|
|
955
|
+
function contentSize(root, scrollRoot) {
|
|
956
|
+
const hidden = scrollRoot
|
|
957
|
+
? Math.max(scrollRoot.scrollHeight - scrollRoot.clientHeight, 0)
|
|
958
|
+
: 0;
|
|
959
|
+
return { width: root.scrollWidth, height: root.scrollHeight + hidden };
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function capture() {
|
|
491
963
|
// The visitor navigated on since the negotiation: this is another page.
|
|
492
964
|
if (location.pathname !== context.path) {
|
|
493
965
|
return;
|
|
494
966
|
}
|
|
495
967
|
const root = document.documentElement;
|
|
968
|
+
scrollRootElement = findScrollRoot();
|
|
496
969
|
const page = {
|
|
497
970
|
website: context.website,
|
|
498
971
|
token: config.token,
|
|
@@ -508,20 +981,38 @@
|
|
|
508
981
|
const html = serializeDocument();
|
|
509
982
|
const body = JSON.stringify({
|
|
510
983
|
...page,
|
|
511
|
-
document:
|
|
984
|
+
document: contentSize(root, scrollRootElement),
|
|
512
985
|
html,
|
|
513
986
|
});
|
|
514
|
-
|
|
515
|
-
|
|
987
|
+
scrollRootElement = null;
|
|
988
|
+
// The cap applies to what travels: the compressed upload when there is
|
|
989
|
+
// one.
|
|
990
|
+
const compressed = await gzip(body);
|
|
991
|
+
const size = compressed ? compressed.byteLength : new Blob([body]).size;
|
|
992
|
+
if (size <= config.maxBytes) {
|
|
993
|
+
post(
|
|
994
|
+
compressed ? new Blob([compressed], { type: "text/plain" }) : body,
|
|
995
|
+
);
|
|
516
996
|
return;
|
|
517
997
|
}
|
|
518
998
|
post(
|
|
519
999
|
JSON.stringify({
|
|
520
1000
|
...page,
|
|
521
|
-
oversizeBytes: Math.min(
|
|
1001
|
+
oversizeBytes: Math.min(size, MAX_REPORTED_BYTES),
|
|
522
1002
|
}),
|
|
523
1003
|
);
|
|
524
1004
|
}
|
|
525
1005
|
|
|
526
|
-
|
|
1006
|
+
if (isModule) {
|
|
1007
|
+
module.exports = {
|
|
1008
|
+
contentSize,
|
|
1009
|
+
createStyleCollector,
|
|
1010
|
+
isTransientOverlay,
|
|
1011
|
+
splitSelectorList,
|
|
1012
|
+
styleText,
|
|
1013
|
+
testedSelector,
|
|
1014
|
+
};
|
|
1015
|
+
} else if (asked) {
|
|
1016
|
+
whenSettled(() => whenIdle(() => void capture().catch(() => {})));
|
|
1017
|
+
}
|
|
527
1018
|
})();
|