clarity-js 0.7.67 → 0.7.69

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.
@@ -1,5 +1,5 @@
1
1
  import { BooleanFlag, Constant, Event, Setting } from "@clarity-types/data";
2
- import { BrowsingContext, ClickState } from "@clarity-types/interaction";
2
+ import { BrowsingContext, ClickState, TextInfo } from "@clarity-types/interaction";
3
3
  import { Box } from "@clarity-types/layout";
4
4
  import { FunctionNames } from "@clarity-types/performance";
5
5
  import { bind } from "@src/core/event";
@@ -55,6 +55,7 @@ function handler(event: Event, root: Node, evt: MouseEvent): void {
55
55
 
56
56
  // Check for null values before processing this event
57
57
  if (x !== null && y !== null) {
58
+ const textInfo = text(t);
58
59
  state.push({
59
60
  time: time(evt), event, data: {
60
61
  target: t,
@@ -65,10 +66,11 @@ function handler(event: Event, root: Node, evt: MouseEvent): void {
65
66
  button: evt.button,
66
67
  reaction: reaction(t),
67
68
  context: context(a),
68
- text: text(t),
69
+ text: textInfo.text,
69
70
  link: a ? a.href : null,
70
71
  hash: null,
71
- trust: evt.isTrusted ? BooleanFlag.True : BooleanFlag.False
72
+ trust: evt.isTrusted ? BooleanFlag.True : BooleanFlag.False,
73
+ isFullText: textInfo.isFullText,
72
74
  }
73
75
  });
74
76
  schedule(encode.bind(this, event));
@@ -88,19 +90,23 @@ function link(node: Node): HTMLAnchorElement {
88
90
  return null;
89
91
  }
90
92
 
91
- function text(element: Node): string {
93
+ function text(element: Node): TextInfo {
92
94
  let output = null;
95
+ let isFullText = false;
93
96
  if (element) {
94
97
  // Grab text using "textContent" for most HTMLElements, however, use "value" for HTMLInputElements and "alt" for HTMLImageElement.
95
98
  let t = element.textContent || String((element as HTMLInputElement).value || '') || (element as HTMLImageElement).alt;
96
99
  if (t) {
97
100
  // Replace multiple occurrence of space characters with a single white space
98
101
  // Also, trim any spaces at the beginning or at the end of string
102
+ const trimmedText = t.replace(/\s+/g, Constant.Space).trim();
99
103
  // Finally, send only first few characters as specified by the Setting
100
- output = t.replace(/\s+/g, Constant.Space).trim().substr(0, Setting.ClickText);
104
+ output = trimmedText.substring(0, Setting.ClickText);
105
+ isFullText = output.length === trimmedText.length;
101
106
  }
102
107
  }
103
- return output;
108
+
109
+ return { text: output, isFullText: isFullText ? BooleanFlag.True : BooleanFlag.False };
104
110
  }
105
111
 
106
112
  function reaction(element: Node): BooleanFlag {
@@ -67,6 +67,7 @@ export default async function (type: Event, ts: number = null): Promise<void> {
67
67
  tokens.push(scrub.url(entry.data.link));
68
68
  tokens.push(cHash);
69
69
  tokens.push(entry.data.trust);
70
+ tokens.push(entry.data.isFullText);
70
71
  queue(tokens);
71
72
  timeline.track(entry.time, entry.event, cHash, entry.data.x, entry.data.y, entry.data.reaction, entry.data.context);
72
73
  }
@@ -95,6 +96,7 @@ export default async function (type: Event, ts: number = null): Promise<void> {
95
96
  case Event.Unload:
96
97
  let u = unload.data;
97
98
  tokens.push(u.name);
99
+ tokens.push(u.persisted);
98
100
  unload.reset();
99
101
  queue(tokens);
100
102
  break;
@@ -39,7 +39,7 @@ export function track(time: number,
39
39
  // Since timeline only keeps the data for configured time, we still want to continue tracking these values
40
40
  // as part of the baseline. For instance, in a scenario where last scroll happened 5s ago.
41
41
  // We would still need to capture the last scroll position as part of the baseline event, even when timeline will be empty.
42
- baseline.track(event, x, y);
42
+ baseline.track(event, x, y, time);
43
43
  }
44
44
 
45
45
  export function compute(): void {
@@ -1,4 +1,4 @@
1
- import { Event } from "@clarity-types/data";
1
+ import { BooleanFlag, Event } from "@clarity-types/data";
2
2
  import { UnloadData } from "@clarity-types/interaction";
3
3
  import { FunctionNames } from "@clarity-types/performance";
4
4
  import * as clarity from "@src/clarity";
@@ -12,9 +12,9 @@ export function start(): void {
12
12
  bind(window, "pagehide", recompute);
13
13
  }
14
14
 
15
- function recompute(evt: UIEvent): void {
15
+ function recompute(evt: PageTransitionEvent): void {
16
16
  recompute.dn = FunctionNames.UnloadRecompute;
17
- data = { name: evt.type };
17
+ data = { name: evt.type, persisted: evt.persisted ? BooleanFlag.True : BooleanFlag.False };
18
18
  encode(Event.Unload, time(evt));
19
19
  clarity.stop();
20
20
  }
@@ -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[] = [];
@@ -36,63 +36,79 @@ let activePeriod = null;
36
36
  let history: MutationHistory = {};
37
37
  let criticalPeriod = null;
38
38
 
39
+ // We ignore mutations if these attributes are updated
40
+ const IGNORED_ATTRIBUTES = ["data-google-query-id", "data-load-complete", "data-google-container-id"];
41
+
39
42
  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
- }
43
+ start.dn = FunctionNames.MutationStart;
44
+ observers = [];
45
+ queue = [];
46
+ timeout = null;
47
+ activePeriod = 0;
48
+ history = {};
49
+ criticalPeriod = 0;
59
50
 
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
- }
51
+ // Some popular open source libraries, like styled-components, optimize performance
52
+ // by injecting CSS using insertRule API vs. appending text node. A side effect of
53
+ // using javascript API is that it doesn't trigger DOM mutation and therefore we
54
+ // need to override the insertRule API and listen for changes manually.
55
+ if (insertRule === null) {
56
+ insertRule = CSSStyleSheet.prototype.insertRule;
57
+ CSSStyleSheet.prototype.insertRule = function (): number {
58
+ if (core.active()) {
59
+ schedule(this.ownerNode);
60
+ }
61
+ return insertRule.apply(this, arguments);
62
+ };
63
+ }
67
64
 
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
- }
65
+ if ("CSSMediaRule" in window && mediaInsertRule === null) {
66
+ mediaInsertRule = CSSMediaRule.prototype.insertRule;
67
+ CSSMediaRule.prototype.insertRule = function (): number {
68
+ if (core.active()) {
69
+ schedule(this.parentStyleSheet.ownerNode);
70
+ }
71
+ return mediaInsertRule.apply(this, arguments);
72
+ };
73
+ }
74
+
75
+ if (deleteRule === null) {
76
+ deleteRule = CSSStyleSheet.prototype.deleteRule;
77
+ CSSStyleSheet.prototype.deleteRule = function (): void {
78
+ if (core.active()) {
79
+ schedule(this.ownerNode);
80
+ }
81
+ return deleteRule.apply(this, arguments);
82
+ };
83
+ }
84
+
85
+ if ("CSSMediaRule" in window && mediaDeleteRule === null) {
86
+ mediaDeleteRule = CSSMediaRule.prototype.deleteRule;
87
+ CSSMediaRule.prototype.deleteRule = function (): void {
88
+ if (core.active()) {
89
+ schedule(this.parentStyleSheet.ownerNode);
90
+ }
91
+ return mediaDeleteRule.apply(this, arguments);
92
+ };
93
+ }
75
94
 
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);
95
+ // Add a hook to attachShadow API calls
96
+ // In case we are unable to add a hook and browser throws an exception,
97
+ // reset attachShadow variable and resume processing like before
98
+ if (attachShadow === null) {
99
+ attachShadow = Element.prototype.attachShadow;
100
+ try {
101
+ Element.prototype.attachShadow = function (): ShadowRoot {
102
+ if (core.active()) {
103
+ return schedule(attachShadow.apply(this, arguments)) as ShadowRoot;
104
+ } else {
105
+ return attachShadow.apply(this, arguments);
106
+ }
81
107
  };
108
+ } catch {
109
+ attachShadow = null;
82
110
  }
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
- }
111
+ }
96
112
  }
97
113
 
98
114
  export function observe(node: Node): void {
@@ -102,12 +118,14 @@ export function observe(node: Node): void {
102
118
  // Also, wrap it inside a try / catch. In certain browsers (e.g. legacy Edge), observer on shadow dom can throw errors
103
119
  try {
104
120
  let m = api(Constant.MutationObserver);
105
- let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
121
+ let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
106
122
  if (observer) {
107
123
  observer.observe(node, { attributes: true, childList: true, characterData: true, subtree: true });
108
124
  observers.push(observer);
109
125
  }
110
- } catch (e) { internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null); }
126
+ } catch (e) {
127
+ internal.log(Code.MutationObserver, Severity.Info, e ? e.name : null);
128
+ }
111
129
  }
112
130
 
113
131
  export function monitor(frame: HTMLIFrameElement): void {
@@ -120,7 +138,11 @@ export function monitor(frame: HTMLIFrameElement): void {
120
138
  }
121
139
 
122
140
  export function stop(): void {
123
- for (let observer of observers) { if (observer) { observer.disconnect(); } }
141
+ for (let observer of observers) {
142
+ if (observer) {
143
+ observer.disconnect();
144
+ }
145
+ }
124
146
  observers = [];
125
147
  history = {};
126
148
  mutations = [];
@@ -141,28 +163,43 @@ function handle(m: MutationRecord[]): void {
141
163
  // Queue up mutation records for asynchronous processing
142
164
  let now = time();
143
165
  summary.track(Event.Mutation, now);
144
- mutations.push({ time: now, mutations: m});
166
+ mutations.push({ time: now, mutations: m });
145
167
  task.schedule(process, Priority.High).then((): void => {
146
- setTimeout(doc.compute)
147
- measure(region.compute)();
168
+ setTimeout(doc.compute);
169
+ measure(region.compute)();
148
170
  });
149
171
  }
150
172
 
151
173
  async function processMutation(timer: Timer, mutation: MutationRecord, instance: number, timestamp: number): Promise<void> {
152
174
  let state = task.state(timer);
153
- if (state === Task.Wait) { state = await task.suspend(timer); }
154
- if (state === Task.Stop) { return; }
175
+
176
+ if (state === Task.Wait) {
177
+ state = await task.suspend(timer);
178
+ }
179
+ if (state === Task.Stop) {
180
+ return;
181
+ }
182
+
155
183
  let target = mutation.target;
156
184
  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); }
185
+
186
+ if (type && target && target.ownerDocument) {
187
+ dom.parse(target.ownerDocument);
188
+ }
189
+
190
+ if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) {
191
+ dom.parse(target as ShadowRoot);
192
+ }
193
+
159
194
  switch (type) {
160
195
  case Constant.Attributes:
196
+ if (IGNORED_ATTRIBUTES.indexOf(mutation.attributeName) < 0) {
161
197
  processNode(target, Source.Attributes, timestamp);
162
- break;
198
+ }
199
+ break;
163
200
  case Constant.CharacterData:
164
- processNode(target, Source.CharacterData, timestamp);
165
- break;
201
+ processNode(target, Source.CharacterData, timestamp);
202
+ break;
166
203
  case Constant.ChildList:
167
204
  processNodeList(mutation.addedNodes, Source.ChildListAdd, timer, timestamp);
168
205
  processNodeList(mutation.removedNodes, Source.ChildListRemove, timer, timestamp);
@@ -179,7 +216,7 @@ async function process(): Promise<void> {
179
216
  let record = mutations.shift();
180
217
  let instance = time();
181
218
  for (let mutation of record.mutations) {
182
- await processMutation(timer, mutation, instance, record.time)
219
+ await processMutation(timer, mutation, instance, record.time);
183
220
  }
184
221
  await encode(Event.Mutation, timer, record.time);
185
222
  }
@@ -202,13 +239,13 @@ async function process(): Promise<void> {
202
239
  }
203
240
 
204
241
  cleanHistory();
205
-
242
+
206
243
  task.stop(timer);
207
244
  }
208
245
 
209
246
  function cleanHistory(): void {
210
247
  let now = time();
211
- if (Object.keys(history).length > Setting.MaxMutationHistoryCount) {
248
+ if (Object.keys(history).length > Setting.MaxMutationHistoryCount) {
212
249
  history = {};
213
250
  metric.count(Metric.HistoryClear);
214
251
  }
@@ -223,33 +260,40 @@ function cleanHistory(): void {
223
260
 
224
261
  function track(m: MutationRecord, timer: Timer, instance: number, timestamp: number): string {
225
262
  let value = m.target ? dom.get(m.target.parentNode) : null;
263
+
226
264
  // Check if the parent is already discovered and that the parent is not the document root
227
265
  if (value && value.data.tag !== Constant.HTML) {
228
266
  // calculate inactive period based on the timestamp of the mutation not when the mutation is processed
229
267
  let inactive = timestamp > activePeriod;
268
+
230
269
  // Calculate critical period based on when mutation is processed
231
270
  const critical = instance < criticalPeriod;
232
271
  let target = dom.get(m.target);
233
272
  let element = target && target.selector ? target.selector.join() : m.target.nodeName;
234
273
  let parent = value.selector ? value.selector.join() : Constant.Empty;
274
+
235
275
  // Check if its a low priority (e.g., ads related) element mutation happening during critical period
236
276
  // 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)));
277
+ const lowPriMutation = config.throttleMutations && critical && (config.discard.length === 0 || config.discard.some((key) => element.includes(key)));
278
+
240
279
  // We use selector, instead of id, to determine the key (signature for the mutation) because in some cases
241
280
  // repeated mutations can cause elements to be destroyed and then recreated as new DOM nodes
242
281
  // In those cases, IDs will change however the selector (which is relative to DOM xPath) remains the same
243
282
  let key = [parent, element, m.attributeName, names(m.addedNodes), names(m.removedNodes)].join();
283
+
244
284
  // Initialize an entry if it doesn't already exist
245
285
  history[key] = key in history ? history[key] : [0, instance];
246
286
  let h = history[key];
287
+
247
288
  // 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
-
289
+ if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) {
290
+ processNodeList(h[2], Source.ChildListRemove, timer, timestamp);
291
+ }
292
+
250
293
  // Update the counter, do not reset counter if its critical period
251
294
  h[0] = inactive || lowPriMutation ? (h[1] === instance ? h[0] : h[0] + 1) : 1;
252
295
  h[1] = instance;
296
+
253
297
  // Return updated mutation type based on,
254
298
  // 1. if we have already hit the threshold or not
255
299
  // 2. if its a low priority mutation happening during critical time period
@@ -260,11 +304,13 @@ function track(m: MutationRecord, timer: Timer, instance: number, timestamp: num
260
304
  if (instance > timestamp + Setting.MutationActivePeriod) {
261
305
  return m.type;
262
306
  }
307
+
263
308
  if (!config.dropMutations) {
264
309
  // we only store the most recent mutation for a given key if it is being throttled
265
- throttledMutations[key] = {mutation: m, timestamp};
310
+ throttledMutations[key] = { mutation: m, timestamp };
266
311
  }
267
- return Constant.Throttle;
312
+
313
+ return Constant.Throttle;
268
314
  }
269
315
  }
270
316
  return m.type;
@@ -272,7 +318,9 @@ function track(m: MutationRecord, timer: Timer, instance: number, timestamp: num
272
318
 
273
319
  function names(nodes: NodeList): string {
274
320
  let output: string[] = [];
275
- for (let i = 0; nodes && i < nodes.length; i++) { output.push(nodes[i].nodeName); }
321
+ for (let i = 0; nodes && i < nodes.length; i++) {
322
+ output.push(nodes[i].nodeName);
323
+ }
276
324
  return output.join();
277
325
  }
278
326
 
@@ -284,8 +332,12 @@ async function processNodeList(list: NodeList, source: Source, timer: Timer, tim
284
332
  traverse(node, timer, source, timestamp);
285
333
  } else {
286
334
  let state = task.state(timer);
287
- if (state === Task.Wait) { state = await task.suspend(timer); }
288
- if (state === Task.Stop) { break; }
335
+ if (state === Task.Wait) {
336
+ state = await task.suspend(timer);
337
+ }
338
+ if (state === Task.Stop) {
339
+ break;
340
+ }
289
341
  processNode(node, source, timestamp);
290
342
  }
291
343
  }
@@ -295,18 +347,26 @@ function processThrottledMutations(): void {
295
347
  if (throttleDelay) {
296
348
  clearTimeout(throttleDelay);
297
349
  }
298
- throttleDelay = setTimeout(() => { task.schedule(process, Priority.High) }, Setting.LookAhead);
350
+ throttleDelay = setTimeout(() => {
351
+ task.schedule(process, Priority.High);
352
+ }, Setting.LookAhead);
299
353
  }
300
354
 
301
355
  export function schedule(node: Node): Node {
302
356
  // Only schedule manual trigger for this node if it's not already in the queue
303
- if (queue.indexOf(node) < 0) { queue.push(node); }
357
+ if (queue.indexOf(node) < 0) {
358
+ queue.push(node);
359
+ }
304
360
 
305
361
  // Cancel any previous trigger before scheduling a new one.
306
362
  // It's common for a webpage to call multiple synchronous "insertRule" / "deleteRule" calls.
307
363
  // 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);
364
+ if (timeout) {
365
+ clearTimeout(timeout);
366
+ }
367
+ timeout = setTimeout(() => {
368
+ trigger();
369
+ }, Setting.LookAhead);
310
370
 
311
371
  return node;
312
372
  }
@@ -317,7 +377,9 @@ function trigger(): void {
317
377
  if (node) {
318
378
  let shadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
319
379
  // Skip re-processing shadowRoot if it was already discovered
320
- if (shadowRoot && dom.has(node)) { continue; }
380
+ if (shadowRoot && dom.has(node)) {
381
+ continue;
382
+ }
321
383
  generate(node, shadowRoot ? Constant.ChildList : Constant.CharacterData);
322
384
  }
323
385
  }
@@ -326,15 +388,17 @@ function trigger(): void {
326
388
 
327
389
  function generate(target: Node, type: MutationRecordType): void {
328
390
  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
- }]);
391
+ measure(handle)([
392
+ {
393
+ addedNodes: [target],
394
+ attributeName: null,
395
+ attributeNamespace: null,
396
+ nextSibling: null,
397
+ oldValue: null,
398
+ previousSibling: null,
399
+ removedNodes: [],
400
+ target,
401
+ type,
402
+ },
403
+ ]);
340
404
  }
@@ -1,7 +1,7 @@
1
1
  import { Event, Metric } from "@clarity-types/data";
2
2
  import { StyleSheetOperation, StyleSheetState } from "@clarity-types/layout";
3
3
  import { time } from "@src/core/time";
4
- import { shortid, data as metadataFields } from "@src/data/metadata";
4
+ import { shortid } from "@src/data/metadata";
5
5
  import encode from "@src/layout/encode";
6
6
  import { getId } from "@src/layout/dom";
7
7
  import * as core from "@src/core";
@@ -13,10 +13,10 @@ export let sheetAdoptionState: StyleSheetState[] = [];
13
13
  let replace: (text?: string) => Promise<CSSStyleSheet> = null;
14
14
  let replaceSync: (text?: string) => void = null;
15
15
  const styleSheetId = 'claritySheetId';
16
- const styleSheetPageNum = 'claritySheetNum';
17
16
  let styleSheetMap = {};
18
17
  let styleTimeMap: {[key: string]: number} = {};
19
18
  let documentNodes = [];
19
+ let createdSheetIds = [];
20
20
 
21
21
  export function start(): void {
22
22
  if (window['CSSStyleSheet'] && CSSStyleSheet.prototype) {
@@ -28,7 +28,7 @@ export function start(): void {
28
28
  // if we haven't seen this stylesheet on this page yet, wait until the checkDocumentStyles has found it
29
29
  // and attached the sheet to a document. This way the timestamp of the style sheet creation will align
30
30
  // to when it is used in the document rather than potentially being misaligned during the traverse process.
31
- if (this[styleSheetPageNum] === metadataFields.pageNum) {
31
+ if (createdSheetIds.indexOf(this[styleSheetId]) > -1) {
32
32
  trackStyleChange(time(), this[styleSheetId], StyleSheetOperation.Replace, arguments[0]);
33
33
  }
34
34
  }
@@ -44,7 +44,7 @@ export function start(): void {
44
44
  // if we haven't seen this stylesheet on this page yet, wait until the checkDocumentStyles has found it
45
45
  // and attached the sheet to a document. This way the timestamp of the style sheet creation will align
46
46
  // to when it is used in the document rather than potentially being misaligned during the traverse process.
47
- if (this[styleSheetPageNum] === metadataFields.pageNum) {
47
+ if (createdSheetIds.indexOf(this[styleSheetId]) > -1) {
48
48
  trackStyleChange(time(), this[styleSheetId], StyleSheetOperation.ReplaceSync, arguments[0]);
49
49
  }
50
50
  }
@@ -66,17 +66,17 @@ export function checkDocumentStyles(documentNode: Document, timestamp: number):
66
66
  metric.max(Metric.ConstructedStyles, 1);
67
67
  let currentStyleSheets: string[] = [];
68
68
  for (var styleSheet of documentNode.adoptedStyleSheets) {
69
- const pageNum = metadataFields.pageNum;
70
69
  // If we haven't seen this style sheet on this page yet, we create a reference to it for the visualizer.
71
70
  // For SPA or times in which Clarity restarts on a given page, our visualizer would lose context
72
71
  // on the previously created style sheet for page N-1.
73
72
  // Then we synthetically call replaceSync with its contents to bootstrap it
74
- if (styleSheet[styleSheetPageNum] !== pageNum) {
75
- styleSheet[styleSheetPageNum] = pageNum;
73
+ if (!styleSheet[styleSheetId] || createdSheetIds.indexOf(styleSheet[styleSheetId]) === -1) {
76
74
  styleSheet[styleSheetId] = shortid();
75
+ createdSheetIds.push(styleSheet[styleSheetId]);
77
76
  trackStyleChange(timestamp, styleSheet[styleSheetId], StyleSheetOperation.Create);
78
77
  trackStyleChange(timestamp, styleSheet[styleSheetId], StyleSheetOperation.ReplaceSync, getCssRules(styleSheet));
79
78
  }
79
+
80
80
  currentStyleSheets.push(styleSheet[styleSheetId]);
81
81
  }
82
82
 
@@ -109,6 +109,7 @@ export function stop(): void {
109
109
  styleSheetMap = {};
110
110
  styleTimeMap = {};
111
111
  documentNodes = [];
112
+ createdSheetIds = [];
112
113
  reset();
113
114
  }
114
115
 
package/types/core.d.ts CHANGED
@@ -101,7 +101,10 @@ export interface BrowserEvent {
101
101
  event: string;
102
102
  target: EventTarget;
103
103
  listener: EventListener;
104
- capture: boolean;
104
+ options: {
105
+ capture: boolean;
106
+ passive: boolean;
107
+ };
105
108
  }
106
109
 
107
110
  export interface Report {
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 {
@@ -1,3 +1,4 @@
1
+ import { BooleanFlag } from "@clarity-types/data";
1
2
  import { Time } from "./core";
2
3
  import { Event, Target } from "./data";
3
4
 
@@ -121,6 +122,12 @@ export interface ClickData {
121
122
  link: string;
122
123
  hash: string;
123
124
  trust: number;
125
+ isFullText: BooleanFlag;
126
+ }
127
+
128
+ export interface TextInfo {
129
+ text: string;
130
+ isFullText: BooleanFlag;
124
131
  }
125
132
 
126
133
  export interface ClipboardData {
@@ -150,6 +157,7 @@ export interface SelectionData {
150
157
 
151
158
  export interface UnloadData {
152
159
  name: string;
160
+ persisted: BooleanFlag;
153
161
  }
154
162
 
155
163
  export interface VisibilityData {