clarity-js 0.7.68 → 0.7.70

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.
@@ -26,7 +26,7 @@ let mutations: MutationQueue[] = [];
26
26
  let throttledMutations: { [key: number]: MutationRecordWithTime } = {};
27
27
  let insertRule: (rule: string, index?: number) => number = null;
28
28
  let deleteRule: (index?: number) => void = null;
29
- let attachShadow: (init: ShadowRootInit) => ShadowRoot = null;
29
+ let attachShadow: (init: ShadowRootInit) => ShadowRoot = null;
30
30
  let mediaInsertRule: (rule: string, index?: number) => number = null;
31
31
  let mediaDeleteRule: (index?: number) => void = null;
32
32
  let queue: Node[] = [];
@@ -35,64 +35,82 @@ let throttleDelay: number = null;
35
35
  let activePeriod = null;
36
36
  let history: MutationHistory = {};
37
37
  let criticalPeriod = null;
38
+ let observedNodes: WeakMap<Node, MutationObserver> = new WeakMap<Node, MutationObserver>();
39
+
40
+ // We ignore mutations if these attributes are updated
41
+ const IGNORED_ATTRIBUTES = ["data-google-query-id", "data-load-complete", "data-google-container-id"];
38
42
 
39
43
  export function start(): void {
40
- start.dn = FunctionNames.MutationStart;
41
- observers = [];
42
- queue = [];
43
- timeout = null;
44
- activePeriod = 0;
45
- history = {};
46
- criticalPeriod = 0;
47
-
48
- // Some popular open source libraries, like styled-components, optimize performance
49
- // by injecting CSS using insertRule API vs. appending text node. A side effect of
50
- // using javascript API is that it doesn't trigger DOM mutation and therefore we
51
- // need to override the insertRule API and listen for changes manually.
52
- if (insertRule === null) {
53
- insertRule = CSSStyleSheet.prototype.insertRule;
54
- CSSStyleSheet.prototype.insertRule = function(): number {
55
- if (core.active()) { schedule(this.ownerNode); }
56
- return insertRule.apply(this, arguments);
57
- };
58
- }
44
+ start.dn = FunctionNames.MutationStart;
45
+ observers = [];
46
+ queue = [];
47
+ timeout = null;
48
+ activePeriod = 0;
49
+ history = {};
50
+ criticalPeriod = 0;
51
+ observedNodes = new WeakMap<Node, MutationObserver>();
59
52
 
60
- if ("CSSMediaRule" in window && mediaInsertRule === null) {
61
- mediaInsertRule = CSSMediaRule.prototype.insertRule;
62
- CSSMediaRule.prototype.insertRule = function(): number {
63
- if (core.active()) { schedule(this.parentStyleSheet.ownerNode); }
64
- return mediaInsertRule.apply(this, arguments);
65
- };
66
- }
53
+ // Some popular open source libraries, like styled-components, optimize performance
54
+ // by injecting CSS using insertRule API vs. appending text node. A side effect of
55
+ // using javascript API is that it doesn't trigger DOM mutation and therefore we
56
+ // need to override the insertRule API and listen for changes manually.
57
+ if (insertRule === null) {
58
+ insertRule = CSSStyleSheet.prototype.insertRule;
59
+ CSSStyleSheet.prototype.insertRule = function (): number {
60
+ if (core.active()) {
61
+ schedule(this.ownerNode);
62
+ }
63
+ return insertRule.apply(this, arguments);
64
+ };
65
+ }
67
66
 
68
- if (deleteRule === null) {
69
- deleteRule = CSSStyleSheet.prototype.deleteRule;
70
- CSSStyleSheet.prototype.deleteRule = function(): void {
71
- if (core.active()) { schedule(this.ownerNode); }
72
- return deleteRule.apply(this, arguments);
73
- };
74
- }
67
+ if ("CSSMediaRule" in window && mediaInsertRule === null) {
68
+ mediaInsertRule = CSSMediaRule.prototype.insertRule;
69
+ CSSMediaRule.prototype.insertRule = function (): number {
70
+ if (core.active()) {
71
+ schedule(this.parentStyleSheet.ownerNode);
72
+ }
73
+ return mediaInsertRule.apply(this, arguments);
74
+ };
75
+ }
76
+
77
+ if (deleteRule === null) {
78
+ deleteRule = CSSStyleSheet.prototype.deleteRule;
79
+ CSSStyleSheet.prototype.deleteRule = function (): void {
80
+ if (core.active()) {
81
+ schedule(this.ownerNode);
82
+ }
83
+ return deleteRule.apply(this, arguments);
84
+ };
85
+ }
75
86
 
76
- if ("CSSMediaRule" in window && mediaDeleteRule === null) {
77
- mediaDeleteRule = CSSMediaRule.prototype.deleteRule;
78
- CSSMediaRule.prototype.deleteRule = function(): void {
79
- if (core.active()) { schedule(this.parentStyleSheet.ownerNode); }
80
- return mediaDeleteRule.apply(this, arguments);
87
+ if ("CSSMediaRule" in window && mediaDeleteRule === null) {
88
+ mediaDeleteRule = CSSMediaRule.prototype.deleteRule;
89
+ CSSMediaRule.prototype.deleteRule = function (): void {
90
+ if (core.active()) {
91
+ schedule(this.parentStyleSheet.ownerNode);
92
+ }
93
+ return mediaDeleteRule.apply(this, arguments);
94
+ };
95
+ }
96
+
97
+ // Add a hook to attachShadow API calls
98
+ // In case we are unable to add a hook and browser throws an exception,
99
+ // reset attachShadow variable and resume processing like before
100
+ if (attachShadow === null) {
101
+ attachShadow = Element.prototype.attachShadow;
102
+ try {
103
+ Element.prototype.attachShadow = function (): ShadowRoot {
104
+ if (core.active()) {
105
+ return schedule(attachShadow.apply(this, arguments)) as ShadowRoot;
106
+ } else {
107
+ return attachShadow.apply(this, arguments);
108
+ }
81
109
  };
110
+ } catch {
111
+ attachShadow = null;
82
112
  }
83
-
84
- // Add a hook to attachShadow API calls
85
- // In case we are unable to add a hook and browser throws an exception,
86
- // reset attachShadow variable and resume processing like before
87
- if (attachShadow === null) {
88
- attachShadow = Element.prototype.attachShadow;
89
- try {
90
- Element.prototype.attachShadow = function (): ShadowRoot {
91
- if (core.active()) { return schedule(attachShadow.apply(this, arguments)) as ShadowRoot; }
92
- else { return attachShadow.apply(this, arguments)}
93
- }
94
- } catch { attachShadow = null; }
95
- }
113
+ }
96
114
  }
97
115
 
98
116
  export function observe(node: Node): void {
@@ -101,13 +119,21 @@ export function observe(node: Node): void {
101
119
  // For this reason, we need to wire up mutations every time we see a new shadow dom.
102
120
  // Also, wrap it inside a try / catch. In certain browsers (e.g. legacy Edge), observer on shadow dom can throw errors
103
121
  try {
122
+ // Cleanup old observer if present.
123
+ if (observedNodes.has(node)) {
124
+ observedNodes.get(node)?.disconnect();
125
+ }
126
+
104
127
  let m = api(Constant.MutationObserver);
105
- let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
128
+ let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
106
129
  if (observer) {
107
130
  observer.observe(node, { attributes: true, childList: true, characterData: true, subtree: true });
131
+ observedNodes.set(node, observer);
108
132
  observers.push(observer);
109
133
  }
110
- } catch (e) { internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null); }
134
+ } catch (e) {
135
+ internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null);
136
+ }
111
137
  }
112
138
 
113
139
  export function monitor(frame: HTMLIFrameElement): void {
@@ -120,7 +146,11 @@ export function monitor(frame: HTMLIFrameElement): void {
120
146
  }
121
147
 
122
148
  export function stop(): void {
123
- for (let observer of observers) { if (observer) { observer.disconnect(); } }
149
+ for (let observer of observers) {
150
+ if (observer) {
151
+ observer.disconnect();
152
+ }
153
+ }
124
154
  observers = [];
125
155
  history = {};
126
156
  mutations = [];
@@ -141,28 +171,43 @@ function handle(m: MutationRecord[]): void {
141
171
  // Queue up mutation records for asynchronous processing
142
172
  let now = time();
143
173
  summary.track(Event.Mutation, now);
144
- mutations.push({ time: now, mutations: m});
174
+ mutations.push({ time: now, mutations: m });
145
175
  task.schedule(process, Priority.High).then((): void => {
146
- setTimeout(doc.compute)
147
- measure(region.compute)();
176
+ setTimeout(doc.compute);
177
+ measure(region.compute)();
148
178
  });
149
179
  }
150
180
 
151
181
  async function processMutation(timer: Timer, mutation: MutationRecord, instance: number, timestamp: number): Promise<void> {
152
182
  let state = task.state(timer);
153
- if (state === Task.Wait) { state = await task.suspend(timer); }
154
- if (state === Task.Stop) { return; }
183
+
184
+ if (state === Task.Wait) {
185
+ state = await task.suspend(timer);
186
+ }
187
+ if (state === Task.Stop) {
188
+ return;
189
+ }
190
+
155
191
  let target = mutation.target;
156
192
  let type = config.throttleDom ? track(mutation, timer, instance, timestamp) : mutation.type;
157
- if (type && target && target.ownerDocument) { dom.parse(target.ownerDocument); }
158
- if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) { dom.parse(target as ShadowRoot); }
193
+
194
+ if (type && target && target.ownerDocument) {
195
+ dom.parse(target.ownerDocument);
196
+ }
197
+
198
+ if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) {
199
+ dom.parse(target as ShadowRoot);
200
+ }
201
+
159
202
  switch (type) {
160
203
  case Constant.Attributes:
204
+ if (IGNORED_ATTRIBUTES.indexOf(mutation.attributeName) < 0) {
161
205
  processNode(target, Source.Attributes, timestamp);
162
- break;
206
+ }
207
+ break;
163
208
  case Constant.CharacterData:
164
- processNode(target, Source.CharacterData, timestamp);
165
- break;
209
+ processNode(target, Source.CharacterData, timestamp);
210
+ break;
166
211
  case Constant.ChildList:
167
212
  processNodeList(mutation.addedNodes, Source.ChildListAdd, timer, timestamp);
168
213
  processNodeList(mutation.removedNodes, Source.ChildListRemove, timer, timestamp);
@@ -179,7 +224,7 @@ async function process(): Promise<void> {
179
224
  let record = mutations.shift();
180
225
  let instance = time();
181
226
  for (let mutation of record.mutations) {
182
- await processMutation(timer, mutation, instance, record.time)
227
+ await processMutation(timer, mutation, instance, record.time);
183
228
  }
184
229
  await encode(Event.Mutation, timer, record.time);
185
230
  }
@@ -202,13 +247,13 @@ async function process(): Promise<void> {
202
247
  }
203
248
 
204
249
  cleanHistory();
205
-
250
+
206
251
  task.stop(timer);
207
252
  }
208
253
 
209
254
  function cleanHistory(): void {
210
255
  let now = time();
211
- if (Object.keys(history).length > Setting.MaxMutationHistoryCount) {
256
+ if (Object.keys(history).length > Setting.MaxMutationHistoryCount) {
212
257
  history = {};
213
258
  metric.count(Metric.HistoryClear);
214
259
  }
@@ -223,33 +268,40 @@ function cleanHistory(): void {
223
268
 
224
269
  function track(m: MutationRecord, timer: Timer, instance: number, timestamp: number): string {
225
270
  let value = m.target ? dom.get(m.target.parentNode) : null;
271
+
226
272
  // Check if the parent is already discovered and that the parent is not the document root
227
273
  if (value && value.data.tag !== Constant.HTML) {
228
274
  // calculate inactive period based on the timestamp of the mutation not when the mutation is processed
229
275
  let inactive = timestamp > activePeriod;
276
+
230
277
  // Calculate critical period based on when mutation is processed
231
278
  const critical = instance < criticalPeriod;
232
279
  let target = dom.get(m.target);
233
280
  let element = target && target.selector ? target.selector.join() : m.target.nodeName;
234
281
  let parent = value.selector ? value.selector.join() : Constant.Empty;
282
+
235
283
  // Check if its a low priority (e.g., ads related) element mutation happening during critical period
236
284
  // If the discard list is empty, we discard all mutations during critical period
237
- const lowPriMutation =
238
- config.throttleMutations && critical &&
239
- (config.discard.length === 0 || config.discard.some((key) => element.includes(key)));
285
+ const lowPriMutation = config.throttleMutations && critical && (config.discard.length === 0 || config.discard.some((key) => element.includes(key)));
286
+
240
287
  // We use selector, instead of id, to determine the key (signature for the mutation) because in some cases
241
288
  // repeated mutations can cause elements to be destroyed and then recreated as new DOM nodes
242
289
  // In those cases, IDs will change however the selector (which is relative to DOM xPath) remains the same
243
290
  let key = [parent, element, m.attributeName, names(m.addedNodes), names(m.removedNodes)].join();
291
+
244
292
  // Initialize an entry if it doesn't already exist
245
293
  history[key] = key in history ? history[key] : [0, instance];
246
294
  let h = history[key];
295
+
247
296
  // Lookup any pending nodes queued up for removal, and process them now if we suspended a mutation before
248
- if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) { processNodeList(h[2], Source.ChildListRemove, timer, timestamp); }
249
-
297
+ if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) {
298
+ processNodeList(h[2], Source.ChildListRemove, timer, timestamp);
299
+ }
300
+
250
301
  // Update the counter, do not reset counter if its critical period
251
302
  h[0] = inactive || lowPriMutation ? (h[1] === instance ? h[0] : h[0] + 1) : 1;
252
303
  h[1] = instance;
304
+
253
305
  // Return updated mutation type based on,
254
306
  // 1. if we have already hit the threshold or not
255
307
  // 2. if its a low priority mutation happening during critical time period
@@ -260,11 +312,13 @@ function track(m: MutationRecord, timer: Timer, instance: number, timestamp: num
260
312
  if (instance > timestamp + Setting.MutationActivePeriod) {
261
313
  return m.type;
262
314
  }
315
+
263
316
  if (!config.dropMutations) {
264
317
  // we only store the most recent mutation for a given key if it is being throttled
265
- throttledMutations[key] = {mutation: m, timestamp};
318
+ throttledMutations[key] = { mutation: m, timestamp };
266
319
  }
267
- return Constant.Throttle;
320
+
321
+ return Constant.Throttle;
268
322
  }
269
323
  }
270
324
  return m.type;
@@ -272,7 +326,9 @@ function track(m: MutationRecord, timer: Timer, instance: number, timestamp: num
272
326
 
273
327
  function names(nodes: NodeList): string {
274
328
  let output: string[] = [];
275
- for (let i = 0; nodes && i < nodes.length; i++) { output.push(nodes[i].nodeName); }
329
+ for (let i = 0; nodes && i < nodes.length; i++) {
330
+ output.push(nodes[i].nodeName);
331
+ }
276
332
  return output.join();
277
333
  }
278
334
 
@@ -284,8 +340,12 @@ async function processNodeList(list: NodeList, source: Source, timer: Timer, tim
284
340
  traverse(node, timer, source, timestamp);
285
341
  } else {
286
342
  let state = task.state(timer);
287
- if (state === Task.Wait) { state = await task.suspend(timer); }
288
- if (state === Task.Stop) { break; }
343
+ if (state === Task.Wait) {
344
+ state = await task.suspend(timer);
345
+ }
346
+ if (state === Task.Stop) {
347
+ break;
348
+ }
289
349
  processNode(node, source, timestamp);
290
350
  }
291
351
  }
@@ -295,18 +355,26 @@ function processThrottledMutations(): void {
295
355
  if (throttleDelay) {
296
356
  clearTimeout(throttleDelay);
297
357
  }
298
- throttleDelay = setTimeout(() => { task.schedule(process, Priority.High) }, Setting.LookAhead);
358
+ throttleDelay = setTimeout(() => {
359
+ task.schedule(process, Priority.High);
360
+ }, Setting.LookAhead);
299
361
  }
300
362
 
301
363
  export function schedule(node: Node): Node {
302
364
  // Only schedule manual trigger for this node if it's not already in the queue
303
- if (queue.indexOf(node) < 0) { queue.push(node); }
365
+ if (queue.indexOf(node) < 0) {
366
+ queue.push(node);
367
+ }
304
368
 
305
369
  // Cancel any previous trigger before scheduling a new one.
306
370
  // It's common for a webpage to call multiple synchronous "insertRule" / "deleteRule" calls.
307
371
  // And in those cases we do not wish to monitor changes multiple times for the same node.
308
- if (timeout) { clearTimeout(timeout); }
309
- timeout = setTimeout(() => { trigger() }, Setting.LookAhead);
372
+ if (timeout) {
373
+ clearTimeout(timeout);
374
+ }
375
+ timeout = setTimeout(() => {
376
+ trigger();
377
+ }, Setting.LookAhead);
310
378
 
311
379
  return node;
312
380
  }
@@ -317,7 +385,9 @@ function trigger(): void {
317
385
  if (node) {
318
386
  let shadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
319
387
  // Skip re-processing shadowRoot if it was already discovered
320
- if (shadowRoot && dom.has(node)) { continue; }
388
+ if (shadowRoot && dom.has(node)) {
389
+ continue;
390
+ }
321
391
  generate(node, shadowRoot ? Constant.ChildList : Constant.CharacterData);
322
392
  }
323
393
  }
@@ -326,15 +396,17 @@ function trigger(): void {
326
396
 
327
397
  function generate(target: Node, type: MutationRecordType): void {
328
398
  generate.dn = FunctionNames.MutationGenerate;
329
- measure(handle)([{
330
- addedNodes: [target],
331
- attributeName: null,
332
- attributeNamespace: null,
333
- nextSibling: null,
334
- oldValue: null,
335
- previousSibling: null,
336
- removedNodes: [],
337
- target,
338
- type
339
- }]);
399
+ measure(handle)([
400
+ {
401
+ addedNodes: [target],
402
+ attributeName: null,
403
+ attributeNamespace: null,
404
+ nextSibling: null,
405
+ oldValue: null,
406
+ previousSibling: null,
407
+ removedNodes: [],
408
+ target,
409
+ type,
410
+ },
411
+ ]);
340
412
  }
package/types/data.d.ts CHANGED
@@ -433,6 +433,12 @@ export interface BaselineData {
433
433
  downX: number;
434
434
  downY: number;
435
435
  downTime: number;
436
+ upX: number;
437
+ upY: number;
438
+ upTime: number;
439
+ pointerPrevX: number;
440
+ pointerPrevY: number;
441
+ pointerPrevTime: number;
436
442
  }
437
443
 
438
444
  export interface IdentityData {