clarity-js 0.8.11 → 0.8.13-beta
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/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +2964 -3181
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +2964 -3181
- package/build/clarity.performance.js +1 -1
- package/package.json +69 -76
- package/rollup.config.ts +88 -84
- package/src/clarity.ts +28 -34
- package/src/core/config.ts +2 -2
- package/src/core/event.ts +32 -36
- package/src/core/hash.ts +6 -5
- package/src/core/history.ts +11 -10
- package/src/core/index.ts +11 -21
- package/src/core/measure.ts +5 -9
- package/src/core/report.ts +3 -3
- package/src/core/scrub.ts +20 -29
- package/src/core/task.ts +45 -73
- package/src/core/time.ts +3 -3
- package/src/core/timeout.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/data/baseline.ts +55 -60
- package/src/data/consent.ts +2 -2
- package/src/data/custom.ts +13 -8
- package/src/data/dimension.ts +7 -11
- package/src/data/encode.ts +30 -36
- package/src/data/envelope.ts +38 -38
- package/src/data/extract.ts +77 -86
- package/src/data/index.ts +6 -10
- package/src/data/limit.ts +1 -1
- package/src/data/metadata.ts +266 -305
- package/src/data/metric.ts +8 -18
- package/src/data/ping.ts +4 -8
- package/src/data/signal.ts +18 -18
- package/src/data/summary.ts +4 -6
- package/src/data/token.ts +8 -10
- package/src/data/upgrade.ts +3 -7
- package/src/data/upload.ts +49 -100
- package/src/data/variable.ts +20 -27
- package/src/diagnostic/encode.ts +2 -2
- package/src/diagnostic/fraud.ts +4 -3
- package/src/diagnostic/internal.ts +5 -11
- package/src/diagnostic/script.ts +8 -12
- package/src/global.ts +1 -1
- package/src/insight/blank.ts +4 -4
- package/src/insight/encode.ts +17 -23
- package/src/insight/snapshot.ts +37 -57
- package/src/interaction/change.ts +6 -9
- package/src/interaction/click.ts +28 -34
- package/src/interaction/clipboard.ts +2 -2
- package/src/interaction/encode.ts +31 -35
- package/src/interaction/input.ts +9 -11
- package/src/interaction/pointer.ts +24 -35
- package/src/interaction/resize.ts +5 -5
- package/src/interaction/scroll.ts +11 -14
- package/src/interaction/selection.ts +8 -12
- package/src/interaction/submit.ts +2 -2
- package/src/interaction/timeline.ts +9 -13
- package/src/interaction/unload.ts +1 -1
- package/src/interaction/visibility.ts +2 -2
- package/src/layout/animation.ts +41 -47
- package/src/layout/discover.ts +5 -5
- package/src/layout/document.ts +19 -31
- package/src/layout/dom.ts +91 -141
- package/src/layout/encode.ts +37 -52
- package/src/layout/mutation.ts +318 -321
- package/src/layout/node.ts +81 -104
- package/src/layout/offset.ts +6 -7
- package/src/layout/region.ts +40 -60
- package/src/layout/schema.ts +8 -15
- package/src/layout/selector.ts +25 -47
- package/src/layout/style.ts +36 -44
- package/src/layout/target.ts +10 -14
- package/src/layout/traverse.ts +11 -17
- package/src/performance/blank.ts +1 -1
- package/src/performance/encode.ts +4 -4
- package/src/performance/interaction.ts +70 -58
- package/src/performance/navigation.ts +2 -2
- package/src/performance/observer.ts +26 -59
- package/src/queue.ts +9 -16
- package/tsconfig.json +1 -1
- package/tslint.json +32 -25
- package/types/core.d.ts +13 -13
- package/types/data.d.ts +29 -32
- package/types/diagnostic.d.ts +1 -1
- package/types/interaction.d.ts +4 -4
- package/types/layout.d.ts +21 -36
- package/types/performance.d.ts +5 -6
- package/.lintstagedrc.yml +0 -3
- package/biome.json +0 -43
package/src/layout/mutation.ts
CHANGED
|
@@ -1,33 +1,25 @@
|
|
|
1
|
-
import { Priority, Task,
|
|
1
|
+
import { Priority, Task, Timer } from "@clarity-types/core";
|
|
2
2
|
import { Code, Event, Metric, Severity } from "@clarity-types/data";
|
|
3
|
-
import {
|
|
4
|
-
Constant,
|
|
5
|
-
type IWindowWithOverrides,
|
|
6
|
-
type MutationHistory,
|
|
7
|
-
type MutationQueue,
|
|
8
|
-
type MutationRecordWithTime,
|
|
9
|
-
Setting,
|
|
10
|
-
Source,
|
|
11
|
-
} from "@clarity-types/layout";
|
|
3
|
+
import { Constant, MutationHistory, MutationRecordWithTime, MutationQueue, Setting, Source } from "@clarity-types/layout";
|
|
12
4
|
import { FunctionNames } from "@clarity-types/performance";
|
|
13
|
-
import * as core from "@src/core";
|
|
14
5
|
import api from "@src/core/api";
|
|
15
|
-
import
|
|
6
|
+
import * as core from "@src/core";
|
|
16
7
|
import * as event from "@src/core/event";
|
|
17
8
|
import measure from "@src/core/measure";
|
|
18
9
|
import * as task from "@src/core/task";
|
|
19
10
|
import { time } from "@src/core/time";
|
|
20
11
|
import { clearTimeout, setTimeout } from "@src/core/timeout";
|
|
21
12
|
import { id } from "@src/data/metadata";
|
|
22
|
-
import * as metric from "@src/data/metric";
|
|
23
13
|
import * as summary from "@src/data/summary";
|
|
24
14
|
import * as internal from "@src/diagnostic/internal";
|
|
25
15
|
import * as doc from "@src/layout/document";
|
|
26
16
|
import * as dom from "@src/layout/dom";
|
|
17
|
+
import * as metric from "@src/data/metric";
|
|
27
18
|
import encode from "@src/layout/encode";
|
|
28
19
|
import * as region from "@src/layout/region";
|
|
29
20
|
import traverse from "@src/layout/traverse";
|
|
30
21
|
import processNode from "./node";
|
|
22
|
+
import config from "@src/core/config";
|
|
31
23
|
|
|
32
24
|
let observers: Set<MutationObserver> = new Set();
|
|
33
25
|
let mutations: MutationQueue[] = [];
|
|
@@ -43,377 +35,382 @@ let observedNodes: WeakMap<Node, MutationObserver> = new WeakMap<Node, MutationO
|
|
|
43
35
|
const IGNORED_ATTRIBUTES = ["data-google-query-id", "data-load-complete", "data-google-container-id"];
|
|
44
36
|
|
|
45
37
|
export function start(): void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
start.dn = FunctionNames.MutationStart;
|
|
39
|
+
observers = new Set();
|
|
40
|
+
queue = [];
|
|
41
|
+
timeout = null;
|
|
42
|
+
activePeriod = 0;
|
|
43
|
+
history = {};
|
|
44
|
+
observedNodes = new WeakMap<Node, MutationObserver>();
|
|
45
|
+
proxyStyleRules(window);
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
export function observe(node: Document | ShadowRoot): void {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (node instanceof Document && node.defaultView) {
|
|
70
|
-
proxyStyleRules(node.defaultView);
|
|
71
|
-
}
|
|
72
|
-
} catch (e) {
|
|
73
|
-
internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null);
|
|
49
|
+
// Create a new observer for every time a new DOM tree (e.g. root document or shadowdom root) is discovered on the page
|
|
50
|
+
// In the case of shadow dom, any mutations that happen within the shadow dom are not bubbled up to the host document
|
|
51
|
+
// For this reason, we need to wire up mutations every time we see a new shadow dom.
|
|
52
|
+
// Also, wrap it inside a try / catch. In certain browsers (e.g. legacy Edge), observer on shadow dom can throw errors
|
|
53
|
+
try {
|
|
54
|
+
|
|
55
|
+
let m = api(Constant.MutationObserver);
|
|
56
|
+
let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
|
|
57
|
+
if (observer) {
|
|
58
|
+
observer.observe(node, { attributes: true, childList: true, characterData: true, subtree: true });
|
|
59
|
+
observedNodes.set(node, observer);
|
|
60
|
+
observers.add(observer);
|
|
74
61
|
}
|
|
62
|
+
if (node['defaultView']) {
|
|
63
|
+
proxyStyleRules(node['defaultView']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
} catch (e) {
|
|
67
|
+
internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null);
|
|
68
|
+
}
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
export function monitor(frame: HTMLIFrameElement): void {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
// Bind to iframe's onload event so we get notified anytime there's an update to iframe content.
|
|
73
|
+
// This includes cases where iframe location is updated without explicitly updating src attribute
|
|
74
|
+
// E.g. iframe.contentWindow.location.href = "new-location";
|
|
75
|
+
if (dom.has(frame) === false) {
|
|
76
|
+
event.bind(frame, Constant.LoadEvent, generate.bind(this, frame, Constant.ChildList), true);
|
|
77
|
+
}
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
export function stop(): void {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
81
|
+
for (let observer of Array.from(observers)) {
|
|
82
|
+
if (observer) {
|
|
83
|
+
observer.disconnect();
|
|
91
84
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
}
|
|
86
|
+
observers = new Set();
|
|
87
|
+
history = {};
|
|
88
|
+
mutations = [];
|
|
89
|
+
throttledMutations = {};
|
|
90
|
+
queue = [];
|
|
91
|
+
activePeriod = 0;
|
|
92
|
+
timeout = null;
|
|
93
|
+
observedNodes = new WeakMap();
|
|
100
94
|
}
|
|
101
95
|
|
|
102
96
|
export function active(): void {
|
|
103
|
-
|
|
97
|
+
activePeriod = time() + Setting.MutationActivePeriod;
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
export function disconnect(n: Node): void {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
const ob = observedNodes.get(n);
|
|
102
|
+
if (ob) {
|
|
103
|
+
ob.disconnect();
|
|
104
|
+
observers.delete(ob);
|
|
105
|
+
observedNodes.delete(n);
|
|
106
|
+
}
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
function handle(m: MutationRecord[]): void {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
110
|
+
handle.dn = FunctionNames.MutationHandle;
|
|
111
|
+
// Queue up mutation records for asynchronous processing
|
|
112
|
+
let now = time();
|
|
113
|
+
summary.track(Event.Mutation, now);
|
|
114
|
+
mutations.push({ time: now, mutations: m });
|
|
115
|
+
task.schedule(process, Priority.High).then((): void => {
|
|
116
|
+
setTimeout(doc.compute);
|
|
117
|
+
measure(region.compute)();
|
|
118
|
+
});
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
async function processMutation(timer: Timer, mutation: MutationRecord, instance: number, timestamp: number): Promise<void> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
122
|
+
let state = task.state(timer);
|
|
123
|
+
|
|
124
|
+
if (state === Task.Wait) {
|
|
125
|
+
state = await task.suspend(timer);
|
|
126
|
+
}
|
|
127
|
+
if (state === Task.Stop) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let target = mutation.target;
|
|
132
|
+
let type = config.throttleDom ? track(mutation, timer, instance, timestamp) : mutation.type;
|
|
133
|
+
|
|
134
|
+
if (type && target && target.ownerDocument) {
|
|
135
|
+
dom.parse(target.ownerDocument);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) {
|
|
139
|
+
dom.parse(target as ShadowRoot);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
switch (type) {
|
|
143
|
+
case Constant.Attributes:
|
|
144
|
+
if (IGNORED_ATTRIBUTES.indexOf(mutation.attributeName) < 0) {
|
|
145
|
+
processNode(target, Source.Attributes, timestamp);
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
case Constant.CharacterData:
|
|
149
|
+
processNode(target, Source.CharacterData, timestamp);
|
|
150
|
+
break;
|
|
151
|
+
case Constant.ChildList:
|
|
152
|
+
processNodeList(mutation.addedNodes, Source.ChildListAdd, timer, timestamp);
|
|
153
|
+
processNodeList(mutation.removedNodes, Source.ChildListRemove, timer, timestamp);
|
|
154
|
+
break;
|
|
155
|
+
case Constant.Throttle:
|
|
156
|
+
default:
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
164
159
|
}
|
|
165
160
|
async function process(): Promise<void> {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
await encode(Event.Mutation, timer, record.time);
|
|
161
|
+
let timer: Timer = { id: id(), cost: Metric.LayoutCost };
|
|
162
|
+
task.start(timer);
|
|
163
|
+
while (mutations.length > 0) {
|
|
164
|
+
let record = mutations.shift();
|
|
165
|
+
let instance = time();
|
|
166
|
+
for (let mutation of record.mutations) {
|
|
167
|
+
await processMutation(timer, mutation, instance, record.time);
|
|
175
168
|
}
|
|
169
|
+
await encode(Event.Mutation, timer, record.time);
|
|
170
|
+
}
|
|
176
171
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
let processedMutations = false;
|
|
173
|
+
for (var key of Object.keys(throttledMutations)) {
|
|
174
|
+
let throttledMutationToProcess: MutationRecordWithTime = throttledMutations[key];
|
|
175
|
+
delete throttledMutations[key];
|
|
176
|
+
await processMutation(timer, throttledMutationToProcess.mutation, time(), throttledMutationToProcess.timestamp);
|
|
177
|
+
processedMutations = true;
|
|
178
|
+
}
|
|
184
179
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
if (Object.keys(throttledMutations).length > 0) {
|
|
181
|
+
processThrottledMutations();
|
|
182
|
+
}
|
|
188
183
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
// ensure we encode the previously throttled mutations once we have finished them
|
|
185
|
+
if (Object.keys(throttledMutations).length === 0 && processedMutations) {
|
|
186
|
+
await encode(Event.Mutation, timer, time());
|
|
187
|
+
}
|
|
193
188
|
|
|
194
|
-
|
|
189
|
+
cleanHistory();
|
|
195
190
|
|
|
196
|
-
|
|
191
|
+
task.stop(timer);
|
|
197
192
|
}
|
|
198
193
|
|
|
199
194
|
function cleanHistory(): void {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
let now = time();
|
|
196
|
+
if (Object.keys(history).length > Setting.MaxMutationHistoryCount) {
|
|
197
|
+
history = {};
|
|
198
|
+
metric.count(Metric.HistoryClear);
|
|
199
|
+
}
|
|
205
200
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
201
|
+
for (let key of Object.keys(history)) {
|
|
202
|
+
let h = history[key];
|
|
203
|
+
if (now > h[1] + Setting.MaxMutationHistoryTime) {
|
|
204
|
+
delete history[key];
|
|
211
205
|
}
|
|
206
|
+
}
|
|
212
207
|
}
|
|
213
208
|
|
|
214
209
|
function track(m: MutationRecord, timer: Timer, instance: number, timestamp: number): string {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
210
|
+
let value = m.target ? dom.get(m.target.parentNode) : null;
|
|
211
|
+
|
|
212
|
+
// Check if the parent is already discovered and that the parent is not the document root
|
|
213
|
+
if (value && value.data.tag !== Constant.HTML) {
|
|
214
|
+
// calculate inactive period based on the timestamp of the mutation not when the mutation is processed
|
|
215
|
+
let inactive = timestamp > activePeriod;
|
|
216
|
+
|
|
217
|
+
// Calculate critical period based on when mutation is processed
|
|
218
|
+
let target = dom.get(m.target);
|
|
219
|
+
let element = target && target.selector ? target.selector.join() : m.target.nodeName;
|
|
220
|
+
let parent = value.selector ? value.selector.join() : Constant.Empty;
|
|
221
|
+
|
|
222
|
+
// We use selector, instead of id, to determine the key (signature for the mutation) because in some cases
|
|
223
|
+
// repeated mutations can cause elements to be destroyed and then recreated as new DOM nodes
|
|
224
|
+
// In those cases, IDs will change however the selector (which is relative to DOM xPath) remains the same
|
|
225
|
+
let key = [parent, element, m.attributeName, names(m.addedNodes), names(m.removedNodes)].join();
|
|
226
|
+
|
|
227
|
+
// Initialize an entry if it doesn't already exist
|
|
228
|
+
history[key] = key in history ? history[key] : [0, instance];
|
|
229
|
+
let h = history[key];
|
|
230
|
+
|
|
231
|
+
// Lookup any pending nodes queued up for removal, and process them now if we suspended a mutation before
|
|
232
|
+
if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) {
|
|
233
|
+
processNodeList(h[2], Source.ChildListRemove, timer, timestamp);
|
|
234
|
+
}
|
|
240
235
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
236
|
+
// Update the counter, do not reset counter if its critical period
|
|
237
|
+
h[0] = inactive ? (h[1] === instance ? h[0] : h[0] + 1) : 1;
|
|
238
|
+
h[1] = instance;
|
|
239
|
+
|
|
240
|
+
// Return updated mutation type based on,
|
|
241
|
+
// 1. if we have already hit the threshold or not
|
|
242
|
+
// 2. if its a low priority mutation happening during critical time period
|
|
243
|
+
if (h[0] >= Setting.MutationSuspendThreshold) {
|
|
244
|
+
// Store a reference to removedNodes so we can process them later
|
|
245
|
+
// when we resume mutations again on user interactions
|
|
246
|
+
h[2] = m.removedNodes;
|
|
247
|
+
if (instance > timestamp + Setting.MutationActivePeriod) {
|
|
248
|
+
return m.type;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// we only store the most recent mutation for a given key if it is being throttled
|
|
252
|
+
throttledMutations[key] = { mutation: m, timestamp };
|
|
253
|
+
|
|
254
|
+
return Constant.Throttle;
|
|
261
255
|
}
|
|
262
|
-
|
|
256
|
+
}
|
|
257
|
+
return m.type;
|
|
263
258
|
}
|
|
264
259
|
|
|
265
260
|
function names(nodes: NodeList): string {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
261
|
+
let output: string[] = [];
|
|
262
|
+
for (let i = 0; nodes && i < nodes.length; i++) {
|
|
263
|
+
output.push(nodes[i].nodeName);
|
|
264
|
+
}
|
|
265
|
+
return output.join();
|
|
271
266
|
}
|
|
272
267
|
|
|
273
268
|
async function processNodeList(list: NodeList, source: Source, timer: Timer, timestamp: number): Promise<void> {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
269
|
+
let length = list ? list.length : 0;
|
|
270
|
+
for (let i = 0; i < length; i++) {
|
|
271
|
+
const node = list[i];
|
|
272
|
+
if (source === Source.ChildListAdd) {
|
|
273
|
+
traverse(node, timer, source, timestamp);
|
|
274
|
+
} else {
|
|
275
|
+
let state = task.state(timer);
|
|
276
|
+
if (state === Task.Wait) {
|
|
277
|
+
state = await task.suspend(timer);
|
|
278
|
+
}
|
|
279
|
+
if (state === Task.Stop) {
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
processNode(node, source, timestamp);
|
|
289
283
|
}
|
|
284
|
+
}
|
|
290
285
|
}
|
|
291
286
|
|
|
292
287
|
function processThrottledMutations(): void {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
if (throttleDelay) {
|
|
289
|
+
clearTimeout(throttleDelay);
|
|
290
|
+
}
|
|
291
|
+
throttleDelay = setTimeout(() => {
|
|
292
|
+
task.schedule(process, Priority.High);
|
|
293
|
+
}, Setting.LookAhead);
|
|
299
294
|
}
|
|
300
295
|
|
|
301
296
|
export function schedule(node: Node): Node {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
297
|
+
// Only schedule manual trigger for this node if it's not already in the queue
|
|
298
|
+
if (queue.indexOf(node) < 0) {
|
|
299
|
+
queue.push(node);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Cancel any previous trigger before scheduling a new one.
|
|
303
|
+
// It's common for a webpage to call multiple synchronous "insertRule" / "deleteRule" calls.
|
|
304
|
+
// And in those cases we do not wish to monitor changes multiple times for the same node.
|
|
305
|
+
if (timeout) {
|
|
306
|
+
clearTimeout(timeout);
|
|
307
|
+
}
|
|
308
|
+
timeout = setTimeout(() => {
|
|
309
|
+
trigger();
|
|
310
|
+
}, Setting.LookAhead);
|
|
311
|
+
|
|
312
|
+
return node;
|
|
318
313
|
}
|
|
319
314
|
|
|
320
315
|
function trigger(): void {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
316
|
+
for (let node of queue) {
|
|
317
|
+
// Generate a mutation for this node only if it still exists
|
|
318
|
+
if (node) {
|
|
319
|
+
let shadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
|
320
|
+
// Skip re-processing shadowRoot if it was already discovered
|
|
321
|
+
if (shadowRoot && dom.has(node)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
generate(node, shadowRoot ? Constant.ChildList : Constant.CharacterData);
|
|
331
325
|
}
|
|
332
|
-
|
|
326
|
+
}
|
|
327
|
+
queue = [];
|
|
333
328
|
}
|
|
334
329
|
|
|
335
330
|
function generate(target: Node, type: MutationRecordType): void {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
331
|
+
generate.dn = FunctionNames.MutationGenerate;
|
|
332
|
+
measure(handle)([
|
|
333
|
+
{
|
|
334
|
+
addedNodes: [target],
|
|
335
|
+
attributeName: null,
|
|
336
|
+
attributeNamespace: null,
|
|
337
|
+
nextSibling: null,
|
|
338
|
+
oldValue: null,
|
|
339
|
+
previousSibling: null,
|
|
340
|
+
removedNodes: [],
|
|
341
|
+
target,
|
|
342
|
+
type,
|
|
343
|
+
},
|
|
344
|
+
]);
|
|
350
345
|
}
|
|
351
346
|
|
|
352
|
-
function proxyStyleRules(win: IWindowWithOverrides): void {
|
|
353
|
-
if (win === null || win === undefined) {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
win.clarityOverrides = win.clarityOverrides || {};
|
|
358
|
-
|
|
359
|
-
// Some popular open source libraries, like styled-components, optimize performance
|
|
360
|
-
// by injecting CSS using insertRule API vs. appending text node. A side effect of
|
|
361
|
-
// using javascript API is that it doesn't trigger DOM mutation and therefore we
|
|
362
|
-
// need to override the insertRule API and listen for changes manually.
|
|
363
|
-
if (win.clarityOverrides.InsertRule === undefined) {
|
|
364
|
-
win.clarityOverrides.InsertRule = win.CSSStyleSheet.prototype.insertRule;
|
|
365
|
-
win.CSSStyleSheet.prototype.insertRule = function (...args): number {
|
|
366
|
-
if (core.active()) {
|
|
367
|
-
schedule(this.ownerNode);
|
|
368
|
-
}
|
|
369
|
-
return win.clarityOverrides.InsertRule.apply(this, args);
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if ("CSSMediaRule" in win && win.clarityOverrides.MediaInsertRule === undefined) {
|
|
374
|
-
win.clarityOverrides.MediaInsertRule = win.CSSMediaRule.prototype.insertRule;
|
|
375
|
-
win.CSSMediaRule.prototype.insertRule = function (...args): number {
|
|
376
|
-
if (core.active()) {
|
|
377
|
-
schedule(this.parentStyleSheet.ownerNode);
|
|
378
|
-
}
|
|
379
|
-
return win.clarityOverrides.MediaInsertRule.apply(this, args);
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (win.clarityOverrides.DeleteRule === undefined) {
|
|
384
|
-
win.clarityOverrides.DeleteRule = win.CSSStyleSheet.prototype.deleteRule;
|
|
385
|
-
win.CSSStyleSheet.prototype.deleteRule = function (...args): void {
|
|
386
|
-
if (core.active()) {
|
|
387
|
-
schedule(this.ownerNode);
|
|
388
|
-
}
|
|
389
|
-
win.clarityOverrides.DeleteRule.apply(this, args);
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if ("CSSMediaRule" in win && win.clarityOverrides.MediaDeleteRule === undefined) {
|
|
394
|
-
win.clarityOverrides.MediaDeleteRule = win.CSSMediaRule.prototype.deleteRule;
|
|
395
|
-
win.CSSMediaRule.prototype.deleteRule = function (...args): void {
|
|
396
|
-
if (core.active()) {
|
|
397
|
-
schedule(this.parentStyleSheet.ownerNode);
|
|
398
|
-
}
|
|
399
|
-
win.clarityOverrides.MediaDeleteRule.apply(this, args);
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
347
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
348
|
+
function proxyStyleRules(win: any): void {
|
|
349
|
+
if (win === null || win === undefined) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
win.clarityOverrides = win.clarityOverrides || {};
|
|
354
|
+
|
|
355
|
+
// Some popular open source libraries, like styled-components, optimize performance
|
|
356
|
+
// by injecting CSS using insertRule API vs. appending text node. A side effect of
|
|
357
|
+
// using javascript API is that it doesn't trigger DOM mutation and therefore we
|
|
358
|
+
// need to override the insertRule API and listen for changes manually.
|
|
359
|
+
if (win.clarityOverrides.InsertRule === undefined) {
|
|
360
|
+
win.clarityOverrides.InsertRule = win.CSSStyleSheet.prototype.insertRule;
|
|
361
|
+
win.CSSStyleSheet.prototype.insertRule = function (): number {
|
|
362
|
+
if (core.active()) {
|
|
363
|
+
schedule(this.ownerNode);
|
|
364
|
+
}
|
|
365
|
+
return win.clarityOverrides.InsertRule.apply(this, arguments);
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if ("CSSMediaRule" in win && win.clarityOverrides.MediaInsertRule === undefined) {
|
|
370
|
+
win.clarityOverrides.MediaInsertRule = win.CSSMediaRule.prototype.insertRule;
|
|
371
|
+
win.CSSMediaRule.prototype.insertRule = function (): number {
|
|
372
|
+
if (core.active()) {
|
|
373
|
+
schedule(this.parentStyleSheet.ownerNode);
|
|
374
|
+
}
|
|
375
|
+
return win.clarityOverrides.MediaInsertRule.apply(this, arguments);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (win.clarityOverrides.DeleteRule === undefined) {
|
|
380
|
+
win.clarityOverrides.DeleteRule = win.CSSStyleSheet.prototype.deleteRule;
|
|
381
|
+
win.CSSStyleSheet.prototype.deleteRule = function (): void {
|
|
382
|
+
if (core.active()) {
|
|
383
|
+
schedule(this.ownerNode);
|
|
384
|
+
}
|
|
385
|
+
return win.clarityOverrides.DeleteRule.apply(this, arguments);
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if ("CSSMediaRule" in win && win.clarityOverrides.MediaDeleteRule === undefined) {
|
|
390
|
+
win.clarityOverrides.MediaDeleteRule = win.CSSMediaRule.prototype.deleteRule;
|
|
391
|
+
win.CSSMediaRule.prototype.deleteRule = function (): void {
|
|
392
|
+
if (core.active()) {
|
|
393
|
+
schedule(this.parentStyleSheet.ownerNode);
|
|
394
|
+
}
|
|
395
|
+
return win.clarityOverrides.MediaDeleteRule.apply(this, arguments);
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Add a hook to attachShadow API calls
|
|
400
|
+
// In case we are unable to add a hook and browser throws an exception,
|
|
401
|
+
// reset attachShadow variable and resume processing like before
|
|
402
|
+
if (win.clarityOverrides.AttachShadow === undefined) {
|
|
403
|
+
win.clarityOverrides.AttachShadow = win.Element.prototype.attachShadow;
|
|
404
|
+
try {
|
|
405
|
+
win.Element.prototype.attachShadow = function (): ShadowRoot {
|
|
406
|
+
if (core.active()) {
|
|
407
|
+
return schedule(win.clarityOverrides.AttachShadow.apply(this, arguments)) as ShadowRoot;
|
|
408
|
+
} else {
|
|
409
|
+
return win.clarityOverrides.AttachShadow.apply(this, arguments);
|
|
417
410
|
}
|
|
411
|
+
};
|
|
412
|
+
} catch {
|
|
413
|
+
win.clarityOverrides.AttachShadow = null;
|
|
418
414
|
}
|
|
419
|
-
}
|
|
415
|
+
}
|
|
416
|
+
}
|