clarity-js 0.8.2 → 0.8.4-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.
Files changed (102) hide show
  1. package/README.md +26 -26
  2. package/build/clarity.extended.js +1 -1
  3. package/build/clarity.insight.js +1 -1
  4. package/build/clarity.js +5563 -5538
  5. package/build/clarity.min.js +1 -1
  6. package/build/clarity.module.js +5563 -5538
  7. package/build/clarity.performance.js +1 -1
  8. package/package.json +70 -70
  9. package/rollup.config.ts +98 -98
  10. package/src/clarity.ts +60 -60
  11. package/src/core/api.ts +8 -8
  12. package/src/core/config.ts +32 -32
  13. package/src/core/copy.ts +3 -3
  14. package/src/core/event.ts +53 -53
  15. package/src/core/hash.ts +19 -19
  16. package/src/core/history.ts +73 -73
  17. package/src/core/index.ts +83 -83
  18. package/src/core/measure.ts +19 -19
  19. package/src/core/report.ts +28 -28
  20. package/src/core/scrub.ts +196 -196
  21. package/src/core/task.ts +180 -180
  22. package/src/core/time.ts +19 -19
  23. package/src/core/timeout.ts +10 -10
  24. package/src/core/version.ts +2 -2
  25. package/src/data/baseline.ts +155 -155
  26. package/src/data/compress.ts +31 -31
  27. package/src/data/consent.ts +20 -20
  28. package/src/data/custom.ts +23 -23
  29. package/src/data/dimension.ts +53 -53
  30. package/src/data/encode.ts +140 -140
  31. package/src/data/envelope.ts +53 -53
  32. package/src/data/extract.ts +211 -211
  33. package/src/data/index.ts +46 -46
  34. package/src/data/limit.ts +44 -44
  35. package/src/data/metadata.ts +356 -356
  36. package/src/data/metric.ts +51 -51
  37. package/src/data/ping.ts +36 -36
  38. package/src/data/signal.ts +30 -30
  39. package/src/data/summary.ts +34 -34
  40. package/src/data/token.ts +39 -39
  41. package/src/data/upgrade.ts +37 -37
  42. package/src/data/upload.ts +283 -283
  43. package/src/data/variable.ts +83 -83
  44. package/src/diagnostic/encode.ts +40 -40
  45. package/src/diagnostic/fraud.ts +36 -36
  46. package/src/diagnostic/index.ts +15 -15
  47. package/src/diagnostic/internal.ts +28 -28
  48. package/src/diagnostic/script.ts +37 -37
  49. package/src/global.ts +6 -6
  50. package/src/index.ts +9 -9
  51. package/src/insight/blank.ts +14 -14
  52. package/src/insight/encode.ts +60 -60
  53. package/src/insight/snapshot.ts +114 -114
  54. package/src/interaction/change.ts +40 -40
  55. package/src/interaction/click.ts +194 -162
  56. package/src/interaction/clipboard.ts +34 -34
  57. package/src/interaction/encode.ts +196 -193
  58. package/src/interaction/index.ts +59 -59
  59. package/src/interaction/input.ts +59 -59
  60. package/src/interaction/pointer.ts +139 -139
  61. package/src/interaction/resize.ts +47 -47
  62. package/src/interaction/scroll.ts +124 -124
  63. package/src/interaction/selection.ts +68 -68
  64. package/src/interaction/submit.ts +32 -32
  65. package/src/interaction/timeline.ts +65 -65
  66. package/src/interaction/unload.ts +28 -28
  67. package/src/interaction/visibility.ts +26 -26
  68. package/src/layout/animation.ts +133 -133
  69. package/src/layout/discover.ts +31 -31
  70. package/src/layout/document.ts +48 -48
  71. package/src/layout/dom.ts +437 -437
  72. package/src/layout/encode.ts +147 -147
  73. package/src/layout/index.ts +41 -41
  74. package/src/layout/mutation.ts +409 -409
  75. package/src/layout/node.ts +292 -292
  76. package/src/layout/offset.ts +19 -19
  77. package/src/layout/region.ts +153 -153
  78. package/src/layout/schema.ts +63 -63
  79. package/src/layout/selector.ts +82 -82
  80. package/src/layout/style.ts +150 -150
  81. package/src/layout/target.ts +32 -32
  82. package/src/layout/traverse.ts +27 -27
  83. package/src/performance/blank.ts +7 -7
  84. package/src/performance/encode.ts +31 -31
  85. package/src/performance/index.ts +14 -14
  86. package/src/performance/interaction.ts +125 -125
  87. package/src/performance/navigation.ts +31 -31
  88. package/src/performance/observer.ts +108 -108
  89. package/src/queue.ts +33 -33
  90. package/test/core.test.ts +139 -139
  91. package/test/helper.ts +162 -162
  92. package/test/html/core.html +27 -27
  93. package/test/tsconfig.test.json +5 -5
  94. package/tsconfig.json +21 -21
  95. package/tslint.json +32 -32
  96. package/types/core.d.ts +153 -153
  97. package/types/data.d.ts +513 -510
  98. package/types/diagnostic.d.ts +24 -24
  99. package/types/index.d.ts +39 -39
  100. package/types/interaction.d.ts +168 -165
  101. package/types/layout.d.ts +272 -272
  102. package/types/performance.d.ts +64 -64
@@ -1,292 +1,292 @@
1
- import { Constant, Source } from "@clarity-types/layout";
2
- import { Code, Dimension, Severity } from "@clarity-types/data";
3
- import * as dom from "./dom";
4
- import * as event from "@src/core/event";
5
- import * as dimension from "@src/data/dimension";
6
- import * as internal from "@src/diagnostic/internal";
7
- import * as interaction from "@src/interaction";
8
- import * as mutation from "@src/layout/mutation";
9
- import * as schema from "@src/layout/schema";
10
- import { checkDocumentStyles } from "@src/layout/style";
11
- import { electron } from "@src/data/metadata";
12
-
13
- const IGNORE_ATTRIBUTES = ["title", "alt", "onload", "onfocus", "onerror", "data-drupal-form-submit-last", "aria-label"];
14
- const newlineRegex = /[\r\n]+/g;
15
-
16
- export default function (node: Node, source: Source, timestamp: number): Node {
17
- let child: Node = null;
18
-
19
- // Do not track this change if we are attempting to remove a node before discovering it
20
- if (source === Source.ChildListRemove && dom.has(node) === false) { return child; }
21
-
22
- // Special handling for text nodes that belong to style nodes
23
- if (source !== Source.Discover &&
24
- node.nodeType === Node.TEXT_NODE &&
25
- node.parentElement &&
26
- node.parentElement.tagName === "STYLE") {
27
- node = node.parentNode;
28
- }
29
-
30
- let add = dom.has(node) === false;
31
- let call = add ? "add" : "update";
32
- let parent = node.parentElement ? node.parentElement : null;
33
- let insideFrame = node.ownerDocument !== document;
34
- switch (node.nodeType) {
35
- case Node.DOCUMENT_TYPE_NODE:
36
- parent = insideFrame && node.parentNode ? dom.iframe(node.parentNode) : parent;
37
- let docTypePrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
38
- let doctype = node as DocumentType;
39
- let docName = doctype.name ? doctype.name : Constant.HTML;
40
- let docAttributes = { name: docName, publicId: doctype.publicId, systemId: doctype.systemId };
41
- let docData = { tag: docTypePrefix + Constant.DocumentTag, attributes: docAttributes };
42
- dom[call](node, parent, docData, source);
43
- break;
44
- case Node.DOCUMENT_NODE:
45
- // We check for regions in the beginning when discovering document and
46
- // later whenever there are new additions or modifications to DOM (mutations)
47
- if (node === document) {
48
- dom.parse(document);
49
- }
50
- checkDocumentStyles(node as Document, timestamp);
51
- observe(node);
52
- break;
53
- case Node.DOCUMENT_FRAGMENT_NODE:
54
- let shadowRoot = (node as ShadowRoot);
55
- if (shadowRoot.host) {
56
- dom.parse(shadowRoot);
57
- let type = typeof (shadowRoot.constructor);
58
- if (type === Constant.Function && shadowRoot.constructor.toString().indexOf(Constant.NativeCode) >= 0) {
59
- observe(shadowRoot);
60
-
61
- // See: https://wicg.github.io/construct-stylesheets/ for more details on adoptedStyleSheets.
62
- // At the moment, we are only able to capture "open" shadow DOM nodes. If they are closed, they are not accessible.
63
- // In future we may decide to proxy "attachShadow" call to gain access, but at the moment, we don't want to
64
- // cause any unintended side effect to the page. We will re-evaluate after we gather more real world data on this.
65
- let style = Constant.Empty as string;
66
- let fragmentData = { tag: Constant.ShadowDomTag, attributes: { style } };
67
- dom[call](node, shadowRoot.host, fragmentData, source);
68
- } else {
69
- // If the browser doesn't support shadow DOM natively, we detect that, and send appropriate tag back.
70
- // The differentiation is important because we don't have to observe pollyfill shadow DOM nodes,
71
- // the same way we observe real shadow DOM nodes (encapsulation provided by the browser).
72
- dom[call](node, shadowRoot.host, { tag: Constant.PolyfillShadowDomTag, attributes: {} }, source);
73
- }
74
- checkDocumentStyles(node as Document, timestamp);
75
- }
76
- break;
77
- case Node.TEXT_NODE:
78
- // In IE11 TEXT_NODE doesn't expose a valid parentElement property. Instead we need to lookup parentNode property.
79
- parent = parent ? parent : node.parentNode as HTMLElement;
80
- // Account for this text node only if we are tracking the parent node
81
- // We do not wish to track text nodes for ignored parent nodes, like script tags
82
- // Also, we do not track text nodes for STYLE tags
83
- // The only exception is when we receive a mutation to remove the text node, in that case
84
- // parent will be null, but we can still process the node by checking it's an update call.
85
- if (call === "update" || (parent && dom.has(parent) && parent.tagName !== "STYLE" && parent.tagName !== "NOSCRIPT")) {
86
- let textData = { tag: Constant.TextTag, value: node.nodeValue };
87
- dom[call](node, parent, textData, source);
88
- }
89
- break;
90
- case Node.ELEMENT_NODE:
91
- let element = (node as HTMLElement);
92
- let tag = element.tagName;
93
- let attributes = getAttributes(element);
94
- // In some cases, external libraries like vue-fragment, can modify parentNode property to not be in sync with the DOM
95
- // For correctness, we first look at parentElement and if it not present then fall back to using parentNode
96
- parent = node.parentElement ? node.parentElement : (node.parentNode ? node.parentNode as HTMLElement : null);
97
- // If we encounter a node that is part of SVG namespace, prefix the tag with SVG_PREFIX
98
- if (element.namespaceURI === Constant.SvgNamespace) { tag = Constant.SvgPrefix + tag; }
99
-
100
- switch (tag) {
101
- case "HTML":
102
- parent = insideFrame && parent ? dom.iframe(parent) : parent;
103
- let htmlPrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
104
- let htmlData = { tag: htmlPrefix + tag, attributes };
105
- dom[call](node, parent, htmlData, source);
106
- break;
107
- case "SCRIPT":
108
- if (Constant.Type in attributes && attributes[Constant.Type] === Constant.JsonLD) {
109
- try {
110
- schema.ld(JSON.parse((element as HTMLScriptElement).text.replace(newlineRegex, Constant.Empty)));
111
- } catch { /* do nothing */ }
112
- }
113
- break;
114
- case "NOSCRIPT":
115
- // keeping the noscript tag but ignoring its contents. Some HTML markup relies on having these tags
116
- // to maintain parity with the original css view, but we don't want to execute any noscript in Clarity
117
- let noscriptData = { tag, attributes: {}, value: '' };
118
- dom[call](node, parent, noscriptData, source);
119
- break;
120
- case "META":
121
- var key = (Constant.Property in attributes ?
122
- Constant.Property :
123
- (Constant.Name in attributes ? Constant.Name : null));
124
- if (key && Constant.Content in attributes) {
125
- let content = attributes[Constant.Content]
126
- switch(attributes[key]) {
127
- case Constant.ogTitle:
128
- dimension.log(Dimension.MetaTitle, content)
129
- break;
130
- case Constant.ogType:
131
- dimension.log(Dimension.MetaType, content)
132
- break;
133
- case Constant.Generator:
134
- dimension.log(Dimension.Generator, content)
135
- break;
136
- }
137
- }
138
- break;
139
- case "HEAD":
140
- let head = { tag, attributes };
141
- let l = insideFrame && node.ownerDocument?.location ? node.ownerDocument.location : location;
142
- head.attributes[Constant.Base] = l.protocol + "//" + l.host + l.pathname;
143
- dom[call](node, parent, head, source);
144
- break;
145
- case "BASE":
146
- // Override the auto detected base path to explicit value specified in this tag
147
- let baseHead = dom.get(node.parentElement);
148
- if (baseHead) {
149
- // We create "a" element so we can generate protocol and hostname for relative paths like "/path/"
150
- let a = document.createElement("a");
151
- a.href = attributes["href"];
152
- baseHead.data.attributes[Constant.Base] = a.protocol + "//" + a.host + a.pathname;
153
- }
154
- break;
155
- case "STYLE":
156
- let styleData = { tag, attributes, value: getStyleValue(element as HTMLStyleElement) };
157
- dom[call](node, parent, styleData, source);
158
- break;
159
- case "IFRAME":
160
- let iframe = node as HTMLIFrameElement;
161
- let frameData = { tag, attributes };
162
- if (dom.sameorigin(iframe)) {
163
- mutation.monitor(iframe);
164
- frameData.attributes[Constant.SameOrigin] = "true";
165
- if (iframe.contentDocument && iframe.contentWindow && iframe.contentDocument.readyState !== "loading") {
166
- child = iframe.contentDocument;
167
- }
168
- }
169
- if (source === Source.ChildListRemove) {
170
- removeObserver(iframe);
171
- }
172
- dom[call](node, parent, frameData, source);
173
- break;
174
- case "LINK":
175
- // electron stylesheets reference the local file system - translating those
176
- // to inline styles so playback can work
177
- if (electron && attributes['rel'] === Constant.StyleSheet) {
178
- for (var styleSheetIndex in Object.keys(document.styleSheets)) {
179
- var currentStyleSheet = document.styleSheets[styleSheetIndex];
180
- if (currentStyleSheet.ownerNode == element) {
181
- let syntheticStyleData = { tag: "STYLE", attributes, value: getCssRules(currentStyleSheet) };
182
- dom[call](node, parent, syntheticStyleData, source);
183
- break;
184
- }
185
- }
186
- break;
187
- }
188
- // for links that aren't electron style sheets we can process them normally
189
- let linkData = { tag, attributes };
190
- dom[call](node, parent, linkData, source);
191
- break;
192
- case "VIDEO":
193
- case "AUDIO":
194
- case "SOURCE":
195
- // Ignoring any base64 src attribute for media elements to prevent big unused tokens to be sent and shock the network
196
- if (Constant.Src in attributes && attributes[Constant.Src].startsWith("data:")) {
197
- attributes[Constant.Src] = "";
198
- }
199
- let mediaTag = { tag, attributes };
200
- dom[call](node, parent, mediaTag, source);
201
- break;
202
- default:
203
- let data = { tag, attributes };
204
- if (element.shadowRoot) { child = element.shadowRoot; }
205
- dom[call](node, parent, data, source);
206
- break;
207
- }
208
- break;
209
- default:
210
- break;
211
- }
212
- return child;
213
- }
214
-
215
- function observe(root: Node): void {
216
- if (dom.has(root) || event.has(root)) { return; }
217
- mutation.observe(root); // Observe mutations for this root node
218
- interaction.observe(root); // Observe interactions for this root node
219
- }
220
-
221
- export function removeObserver(root: HTMLIFrameElement): void {
222
- // iframes will have load event listeners and they should be removed when iframe is removed
223
- // from the document
224
- event.unbind(root);
225
- const { doc = null, win = null } = dom.iframeContent(root) || {};
226
-
227
- if (win) {
228
- // For iframes, scroll event is observed on content window and this needs to be removed as well
229
- event.unbind(win);
230
- }
231
-
232
- if (doc) {
233
- // When an iframe is removed, we should also remove all listeners attached to its document
234
- // to avoid memory leaks.
235
- event.unbind(doc);
236
- mutation.disconnect(doc);
237
-
238
- // Remove iframe and content document from maps tracking them
239
- dom.removeIFrame(root, doc);
240
- }
241
- }
242
-
243
- function getStyleValue(style: HTMLStyleElement): string {
244
- // Call trim on the text content to ensure we do not process white spaces ( , \n, \r\n, \t, etc.)
245
- // Also, check if stylesheet has any data-* attribute, if so process rules instead of looking up text
246
- // Additionally, check if style node has an id - if so it's at a high risk to have experienced dynamic
247
- // style updates which would make the textContent out of date with its true style contribution.
248
- let value = style.textContent ? style.textContent.trim() : Constant.Empty;
249
- let dataset = style.dataset ? Object.keys(style.dataset).length : 0;
250
- if (value.length === 0 || dataset > 0 || style.id.length > 0) {
251
- value = getCssRules(style.sheet as CSSStyleSheet);
252
- }
253
- return value;
254
- }
255
-
256
- export function getCssRules(sheet: CSSStyleSheet): string {
257
- let value = Constant.Empty as string;
258
- let cssRules = null;
259
- // Firefox throws a SecurityError when trying to access cssRules of a stylesheet from a different domain
260
- try { cssRules = sheet ? sheet.cssRules : []; } catch (e) {
261
- internal.log(Code.CssRules, Severity.Warning, e ? e.name : null);
262
- if (e && e.name !== "SecurityError") { throw e; }
263
- }
264
-
265
- if (cssRules !== null) {
266
- for (let i = 0; i < cssRules.length; i++) {
267
- value += cssRules[i].cssText;
268
- }
269
- }
270
-
271
- return value;
272
- }
273
-
274
- function getAttributes(element: HTMLElement): { [key: string]: string } {
275
- let output = {};
276
- let attributes = element.attributes;
277
- if (attributes && attributes.length > 0) {
278
- for (let i = 0; i < attributes.length; i++) {
279
- let name = attributes[i].name;
280
- if (IGNORE_ATTRIBUTES.indexOf(name) < 0) {
281
- output[name] = attributes[i].value;
282
- }
283
- }
284
- }
285
-
286
- // For INPUT tags read the dynamic "value" property if an explicit "value" attribute is not set
287
- if (element.tagName === Constant.InputTag && !(Constant.Value in output) && (element as HTMLInputElement).value) {
288
- output[Constant.Value] = (element as HTMLInputElement).value;
289
- }
290
-
291
- return output;
292
- }
1
+ import { Constant, Source } from "@clarity-types/layout";
2
+ import { Code, Dimension, Severity } from "@clarity-types/data";
3
+ import * as dom from "./dom";
4
+ import * as event from "@src/core/event";
5
+ import * as dimension from "@src/data/dimension";
6
+ import * as internal from "@src/diagnostic/internal";
7
+ import * as interaction from "@src/interaction";
8
+ import * as mutation from "@src/layout/mutation";
9
+ import * as schema from "@src/layout/schema";
10
+ import { checkDocumentStyles } from "@src/layout/style";
11
+ import { electron } from "@src/data/metadata";
12
+
13
+ const IGNORE_ATTRIBUTES = ["title", "alt", "onload", "onfocus", "onerror", "data-drupal-form-submit-last", "aria-label"];
14
+ const newlineRegex = /[\r\n]+/g;
15
+
16
+ export default function (node: Node, source: Source, timestamp: number): Node {
17
+ let child: Node = null;
18
+
19
+ // Do not track this change if we are attempting to remove a node before discovering it
20
+ if (source === Source.ChildListRemove && dom.has(node) === false) { return child; }
21
+
22
+ // Special handling for text nodes that belong to style nodes
23
+ if (source !== Source.Discover &&
24
+ node.nodeType === Node.TEXT_NODE &&
25
+ node.parentElement &&
26
+ node.parentElement.tagName === "STYLE") {
27
+ node = node.parentNode;
28
+ }
29
+
30
+ let add = dom.has(node) === false;
31
+ let call = add ? "add" : "update";
32
+ let parent = node.parentElement ? node.parentElement : null;
33
+ let insideFrame = node.ownerDocument !== document;
34
+ switch (node.nodeType) {
35
+ case Node.DOCUMENT_TYPE_NODE:
36
+ parent = insideFrame && node.parentNode ? dom.iframe(node.parentNode) : parent;
37
+ let docTypePrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
38
+ let doctype = node as DocumentType;
39
+ let docName = doctype.name ? doctype.name : Constant.HTML;
40
+ let docAttributes = { name: docName, publicId: doctype.publicId, systemId: doctype.systemId };
41
+ let docData = { tag: docTypePrefix + Constant.DocumentTag, attributes: docAttributes };
42
+ dom[call](node, parent, docData, source);
43
+ break;
44
+ case Node.DOCUMENT_NODE:
45
+ // We check for regions in the beginning when discovering document and
46
+ // later whenever there are new additions or modifications to DOM (mutations)
47
+ if (node === document) {
48
+ dom.parse(document);
49
+ }
50
+ checkDocumentStyles(node as Document, timestamp);
51
+ observe(node);
52
+ break;
53
+ case Node.DOCUMENT_FRAGMENT_NODE:
54
+ let shadowRoot = (node as ShadowRoot);
55
+ if (shadowRoot.host) {
56
+ dom.parse(shadowRoot);
57
+ let type = typeof (shadowRoot.constructor);
58
+ if (type === Constant.Function && shadowRoot.constructor.toString().indexOf(Constant.NativeCode) >= 0) {
59
+ observe(shadowRoot);
60
+
61
+ // See: https://wicg.github.io/construct-stylesheets/ for more details on adoptedStyleSheets.
62
+ // At the moment, we are only able to capture "open" shadow DOM nodes. If they are closed, they are not accessible.
63
+ // In future we may decide to proxy "attachShadow" call to gain access, but at the moment, we don't want to
64
+ // cause any unintended side effect to the page. We will re-evaluate after we gather more real world data on this.
65
+ let style = Constant.Empty as string;
66
+ let fragmentData = { tag: Constant.ShadowDomTag, attributes: { style } };
67
+ dom[call](node, shadowRoot.host, fragmentData, source);
68
+ } else {
69
+ // If the browser doesn't support shadow DOM natively, we detect that, and send appropriate tag back.
70
+ // The differentiation is important because we don't have to observe pollyfill shadow DOM nodes,
71
+ // the same way we observe real shadow DOM nodes (encapsulation provided by the browser).
72
+ dom[call](node, shadowRoot.host, { tag: Constant.PolyfillShadowDomTag, attributes: {} }, source);
73
+ }
74
+ checkDocumentStyles(node as Document, timestamp);
75
+ }
76
+ break;
77
+ case Node.TEXT_NODE:
78
+ // In IE11 TEXT_NODE doesn't expose a valid parentElement property. Instead we need to lookup parentNode property.
79
+ parent = parent ? parent : node.parentNode as HTMLElement;
80
+ // Account for this text node only if we are tracking the parent node
81
+ // We do not wish to track text nodes for ignored parent nodes, like script tags
82
+ // Also, we do not track text nodes for STYLE tags
83
+ // The only exception is when we receive a mutation to remove the text node, in that case
84
+ // parent will be null, but we can still process the node by checking it's an update call.
85
+ if (call === "update" || (parent && dom.has(parent) && parent.tagName !== "STYLE" && parent.tagName !== "NOSCRIPT")) {
86
+ let textData = { tag: Constant.TextTag, value: node.nodeValue };
87
+ dom[call](node, parent, textData, source);
88
+ }
89
+ break;
90
+ case Node.ELEMENT_NODE:
91
+ let element = (node as HTMLElement);
92
+ let tag = element.tagName;
93
+ let attributes = getAttributes(element);
94
+ // In some cases, external libraries like vue-fragment, can modify parentNode property to not be in sync with the DOM
95
+ // For correctness, we first look at parentElement and if it not present then fall back to using parentNode
96
+ parent = node.parentElement ? node.parentElement : (node.parentNode ? node.parentNode as HTMLElement : null);
97
+ // If we encounter a node that is part of SVG namespace, prefix the tag with SVG_PREFIX
98
+ if (element.namespaceURI === Constant.SvgNamespace) { tag = Constant.SvgPrefix + tag; }
99
+
100
+ switch (tag) {
101
+ case "HTML":
102
+ parent = insideFrame && parent ? dom.iframe(parent) : parent;
103
+ let htmlPrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
104
+ let htmlData = { tag: htmlPrefix + tag, attributes };
105
+ dom[call](node, parent, htmlData, source);
106
+ break;
107
+ case "SCRIPT":
108
+ if (Constant.Type in attributes && attributes[Constant.Type] === Constant.JsonLD) {
109
+ try {
110
+ schema.ld(JSON.parse((element as HTMLScriptElement).text.replace(newlineRegex, Constant.Empty)));
111
+ } catch { /* do nothing */ }
112
+ }
113
+ break;
114
+ case "NOSCRIPT":
115
+ // keeping the noscript tag but ignoring its contents. Some HTML markup relies on having these tags
116
+ // to maintain parity with the original css view, but we don't want to execute any noscript in Clarity
117
+ let noscriptData = { tag, attributes: {}, value: '' };
118
+ dom[call](node, parent, noscriptData, source);
119
+ break;
120
+ case "META":
121
+ var key = (Constant.Property in attributes ?
122
+ Constant.Property :
123
+ (Constant.Name in attributes ? Constant.Name : null));
124
+ if (key && Constant.Content in attributes) {
125
+ let content = attributes[Constant.Content]
126
+ switch(attributes[key]) {
127
+ case Constant.ogTitle:
128
+ dimension.log(Dimension.MetaTitle, content)
129
+ break;
130
+ case Constant.ogType:
131
+ dimension.log(Dimension.MetaType, content)
132
+ break;
133
+ case Constant.Generator:
134
+ dimension.log(Dimension.Generator, content)
135
+ break;
136
+ }
137
+ }
138
+ break;
139
+ case "HEAD":
140
+ let head = { tag, attributes };
141
+ let l = insideFrame && node.ownerDocument?.location ? node.ownerDocument.location : location;
142
+ head.attributes[Constant.Base] = l.protocol + "//" + l.host + l.pathname;
143
+ dom[call](node, parent, head, source);
144
+ break;
145
+ case "BASE":
146
+ // Override the auto detected base path to explicit value specified in this tag
147
+ let baseHead = dom.get(node.parentElement);
148
+ if (baseHead) {
149
+ // We create "a" element so we can generate protocol and hostname for relative paths like "/path/"
150
+ let a = document.createElement("a");
151
+ a.href = attributes["href"];
152
+ baseHead.data.attributes[Constant.Base] = a.protocol + "//" + a.host + a.pathname;
153
+ }
154
+ break;
155
+ case "STYLE":
156
+ let styleData = { tag, attributes, value: getStyleValue(element as HTMLStyleElement) };
157
+ dom[call](node, parent, styleData, source);
158
+ break;
159
+ case "IFRAME":
160
+ let iframe = node as HTMLIFrameElement;
161
+ let frameData = { tag, attributes };
162
+ if (dom.sameorigin(iframe)) {
163
+ mutation.monitor(iframe);
164
+ frameData.attributes[Constant.SameOrigin] = "true";
165
+ if (iframe.contentDocument && iframe.contentWindow && iframe.contentDocument.readyState !== "loading") {
166
+ child = iframe.contentDocument;
167
+ }
168
+ }
169
+ if (source === Source.ChildListRemove) {
170
+ removeObserver(iframe);
171
+ }
172
+ dom[call](node, parent, frameData, source);
173
+ break;
174
+ case "LINK":
175
+ // electron stylesheets reference the local file system - translating those
176
+ // to inline styles so playback can work
177
+ if (electron && attributes['rel'] === Constant.StyleSheet) {
178
+ for (var styleSheetIndex in Object.keys(document.styleSheets)) {
179
+ var currentStyleSheet = document.styleSheets[styleSheetIndex];
180
+ if (currentStyleSheet.ownerNode == element) {
181
+ let syntheticStyleData = { tag: "STYLE", attributes, value: getCssRules(currentStyleSheet) };
182
+ dom[call](node, parent, syntheticStyleData, source);
183
+ break;
184
+ }
185
+ }
186
+ break;
187
+ }
188
+ // for links that aren't electron style sheets we can process them normally
189
+ let linkData = { tag, attributes };
190
+ dom[call](node, parent, linkData, source);
191
+ break;
192
+ case "VIDEO":
193
+ case "AUDIO":
194
+ case "SOURCE":
195
+ // Ignoring any base64 src attribute for media elements to prevent big unused tokens to be sent and shock the network
196
+ if (Constant.Src in attributes && attributes[Constant.Src].startsWith("data:")) {
197
+ attributes[Constant.Src] = "";
198
+ }
199
+ let mediaTag = { tag, attributes };
200
+ dom[call](node, parent, mediaTag, source);
201
+ break;
202
+ default:
203
+ let data = { tag, attributes };
204
+ if (element.shadowRoot) { child = element.shadowRoot; }
205
+ dom[call](node, parent, data, source);
206
+ break;
207
+ }
208
+ break;
209
+ default:
210
+ break;
211
+ }
212
+ return child;
213
+ }
214
+
215
+ function observe(root: Node): void {
216
+ if (dom.has(root) || event.has(root)) { return; }
217
+ mutation.observe(root); // Observe mutations for this root node
218
+ interaction.observe(root); // Observe interactions for this root node
219
+ }
220
+
221
+ export function removeObserver(root: HTMLIFrameElement): void {
222
+ // iframes will have load event listeners and they should be removed when iframe is removed
223
+ // from the document
224
+ event.unbind(root);
225
+ const { doc = null, win = null } = dom.iframeContent(root) || {};
226
+
227
+ if (win) {
228
+ // For iframes, scroll event is observed on content window and this needs to be removed as well
229
+ event.unbind(win);
230
+ }
231
+
232
+ if (doc) {
233
+ // When an iframe is removed, we should also remove all listeners attached to its document
234
+ // to avoid memory leaks.
235
+ event.unbind(doc);
236
+ mutation.disconnect(doc);
237
+
238
+ // Remove iframe and content document from maps tracking them
239
+ dom.removeIFrame(root, doc);
240
+ }
241
+ }
242
+
243
+ function getStyleValue(style: HTMLStyleElement): string {
244
+ // Call trim on the text content to ensure we do not process white spaces ( , \n, \r\n, \t, etc.)
245
+ // Also, check if stylesheet has any data-* attribute, if so process rules instead of looking up text
246
+ // Additionally, check if style node has an id - if so it's at a high risk to have experienced dynamic
247
+ // style updates which would make the textContent out of date with its true style contribution.
248
+ let value = style.textContent ? style.textContent.trim() : Constant.Empty;
249
+ let dataset = style.dataset ? Object.keys(style.dataset).length : 0;
250
+ if (value.length === 0 || dataset > 0 || style.id.length > 0) {
251
+ value = getCssRules(style.sheet as CSSStyleSheet);
252
+ }
253
+ return value;
254
+ }
255
+
256
+ export function getCssRules(sheet: CSSStyleSheet): string {
257
+ let value = Constant.Empty as string;
258
+ let cssRules = null;
259
+ // Firefox throws a SecurityError when trying to access cssRules of a stylesheet from a different domain
260
+ try { cssRules = sheet ? sheet.cssRules : []; } catch (e) {
261
+ internal.log(Code.CssRules, Severity.Warning, e ? e.name : null);
262
+ if (e && e.name !== "SecurityError") { throw e; }
263
+ }
264
+
265
+ if (cssRules !== null) {
266
+ for (let i = 0; i < cssRules.length; i++) {
267
+ value += cssRules[i].cssText;
268
+ }
269
+ }
270
+
271
+ return value;
272
+ }
273
+
274
+ function getAttributes(element: HTMLElement): { [key: string]: string } {
275
+ let output = {};
276
+ let attributes = element.attributes;
277
+ if (attributes && attributes.length > 0) {
278
+ for (let i = 0; i < attributes.length; i++) {
279
+ let name = attributes[i].name;
280
+ if (IGNORE_ATTRIBUTES.indexOf(name) < 0) {
281
+ output[name] = attributes[i].value;
282
+ }
283
+ }
284
+ }
285
+
286
+ // For INPUT tags read the dynamic "value" property if an explicit "value" attribute is not set
287
+ if (element.tagName === Constant.InputTag && !(Constant.Value in output) && (element as HTMLInputElement).value) {
288
+ output[Constant.Value] = (element as HTMLInputElement).value;
289
+ }
290
+
291
+ return output;
292
+ }
@@ -1,19 +1,19 @@
1
- import { OffsetDistance } from "@clarity-types/core";
2
- import { iframe } from "@src/layout/dom";
3
-
4
- export function offset(element: HTMLElement): OffsetDistance {
5
- let output: OffsetDistance = { x: 0, y: 0 };
6
-
7
- // Walk up the chain to ensure we compute offset distance correctly
8
- // In case where we may have nested IFRAMEs, we keep walking up until we get to the top most parent page
9
- if (element && element.offsetParent) {
10
- do {
11
- let parent = element.offsetParent as HTMLElement;
12
- let frame = parent === null ? iframe(element.ownerDocument) : null;
13
- output.x += element.offsetLeft;
14
- output.y += element.offsetTop;
15
- element = frame ? frame : parent;
16
- } while (element);
17
- }
18
- return output;
19
- }
1
+ import { OffsetDistance } from "@clarity-types/core";
2
+ import { iframe } from "@src/layout/dom";
3
+
4
+ export function offset(element: HTMLElement): OffsetDistance {
5
+ let output: OffsetDistance = { x: 0, y: 0 };
6
+
7
+ // Walk up the chain to ensure we compute offset distance correctly
8
+ // In case where we may have nested IFRAMEs, we keep walking up until we get to the top most parent page
9
+ if (element && element.offsetParent) {
10
+ do {
11
+ let parent = element.offsetParent as HTMLElement;
12
+ let frame = parent === null ? iframe(element.ownerDocument) : null;
13
+ output.x += element.offsetLeft;
14
+ output.y += element.offsetTop;
15
+ element = frame ? frame : parent;
16
+ } while (element);
17
+ }
18
+ return output;
19
+ }