diff --git a/packages/clarity-js/src/layout/discover.ts b/packages/clarity-js/src/layout/discover.ts index f7f9539..72a4e8d 100644 --- a/packages/clarity-js/src/layout/discover.ts +++ b/packages/clarity-js/src/layout/discover.ts @@ -23,8 +23,8 @@ async function discover(): Promise { let timer: Timer = { id: id(), cost: Metric.LayoutCost }; task.start(timer); - await traverse(document, timer, Source.Discover); - checkDocumentStyles(document); + await traverse(document, timer, Source.Discover, ts); + checkDocumentStyles(document, ts); await encode(Event.Discover, timer, ts); task.stop(timer); } diff --git a/packages/clarity-js/src/layout/mutation.ts b/packages/clarity-js/src/layout/mutation.ts index d5e4323..0e46ebe 100644 --- a/packages/clarity-js/src/layout/mutation.ts +++ b/packages/clarity-js/src/layout/mutation.ts @@ -129,19 +129,19 @@ async function process(): Promise { if (state === Task.Wait) { state = await task.suspend(timer); } if (state === Task.Stop) { break; } let target = mutation.target; - let type = track(mutation, timer, instance); + let type = track(mutation, timer, instance, record.time); if (type && target && target.ownerDocument) { dom.parse(target.ownerDocument); } if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) { dom.parse(target as ShadowRoot); } switch (type) { case Constant.Attributes: - processNode(target, Source.Attributes); + processNode(target, Source.Attributes, record.time); break; case Constant.CharacterData: - processNode(target, Source.CharacterData); + processNode(target, Source.CharacterData, record.time); break; case Constant.ChildList: - processNodeList(mutation.addedNodes, Source.ChildListAdd, timer); - processNodeList(mutation.removedNodes, Source.ChildListRemove, timer); + processNodeList(mutation.addedNodes, Source.ChildListAdd, timer, record.time); + processNodeList(mutation.removedNodes, Source.ChildListRemove, timer, record.time); break; case Constant.Suspend: let value = dom.get(target); @@ -156,7 +156,7 @@ async function process(): Promise { task.stop(timer); } -function track(m: MutationRecord, timer: Timer, instance: number): string { +function track(m: MutationRecord, timer: Timer, instance: number, timestamp: number): string { let value = m.target ? dom.get(m.target.parentNode) : null; // Check if the parent is already discovered and that the parent is not the document root if (value && value.data.tag !== Constant.HTML) { @@ -172,7 +172,7 @@ function track(m: MutationRecord, timer: Timer, instance: number): string { history[key] = key in history ? history[key] : [0, instance]; let h = history[key]; // Lookup any pending nodes queued up for removal, and process them now if we suspended a mutation before - if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) { processNodeList(h[2], Source.ChildListRemove, timer); } + if (inactive === false && h[0] >= Setting.MutationSuspendThreshold) { processNodeList(h[2], Source.ChildListRemove, timer, timestamp); } // Update the counter h[0] = inactive ? (h[1] === instance ? h[0] : h[0] + 1) : 1; h[1] = instance; @@ -195,16 +195,16 @@ function names(nodes: NodeList): string { return output.join(); } -async function processNodeList(list: NodeList, source: Source, timer: Timer): Promise { +async function processNodeList(list: NodeList, source: Source, timer: Timer, timestamp: number): Promise { let length = list ? list.length : 0; for (let i = 0; i < length; i++) { if (source === Source.ChildListAdd) { - traverse(list[i], timer, source); + traverse(list[i], timer, source, timestamp); } else { let state = task.state(timer); if (state === Task.Wait) { state = await task.suspend(timer); } if (state === Task.Stop) { break; } - processNode(list[i], source); + processNode(list[i], source, timestamp); } } } diff --git a/packages/clarity-js/src/layout/node.ts b/packages/clarity-js/src/layout/node.ts index c6f0544..bfe1446 100644 --- a/packages/clarity-js/src/layout/node.ts +++ b/packages/clarity-js/src/layout/node.ts @@ -12,7 +12,7 @@ import { electron } from "@src/data/metadata"; const IGNORE_ATTRIBUTES = ["title", "alt", "onload", "onfocus", "onerror", "data-drupal-form-submit-last"]; const newlineRegex = /[\r\n]+/g; -export default function (node: Node, source: Source): Node { +export default function (node: Node, source: Source, timestamp: number): Node { let child: Node = null; // Do not track this change if we are attempting to remove a node before discovering it @@ -43,7 +43,7 @@ export default function (node: Node, source: Source): Node { // We check for regions in the beginning when discovering document and // later whenever there are new additions or modifications to DOM (mutations) if (node === document) dom.parse(document); - checkDocumentStyles(node as Document); + checkDocumentStyles(node as Document, timestamp); observe(node); break; case Node.DOCUMENT_FRAGMENT_NODE: @@ -67,7 +67,7 @@ export default function (node: Node, source: Source): Node { // the same way we observe real shadow DOM nodes (encapsulation provided by the browser). dom[call](node, shadowRoot.host, { tag: Constant.PolyfillShadowDomTag, attributes: {} }, source); } - checkDocumentStyles(node as Document); + checkDocumentStyles(node as Document, timestamp); } break; case Node.TEXT_NODE: diff --git a/packages/clarity-js/src/layout/style.ts b/packages/clarity-js/src/layout/style.ts index 1cdb813..e7b9ca8 100644 --- a/packages/clarity-js/src/layout/style.ts +++ b/packages/clarity-js/src/layout/style.ts @@ -14,6 +14,7 @@ let replaceSync: (text?: string) => void = null; const styleSheetId = 'claritySheetId'; const styleSheetPageNum = 'claritySheetNum'; let styleSheetMap = {}; +let styleTimeMap: {[key: string]: number} = {}; export function start(): void { reset(); @@ -56,7 +57,8 @@ function bootStrapStyleSheet(styleSheet: CSSStyleSheet): void { } } -export function checkDocumentStyles(documentNode: Document): void { +export function checkDocumentStyles(documentNode: Document, timestamp: number): void { + timestamp = timestamp || time(); if (!documentNode?.adoptedStyleSheets) { // if we don't have adoptedStyledSheets on the Node passed to us, we can short circuit. return; @@ -69,8 +71,8 @@ export function checkDocumentStyles(documentNode: Document): void { if (styleSheet[styleSheetPageNum] !== pageNum) { styleSheet[styleSheetPageNum] = pageNum; styleSheet[styleSheetId] = shortid(); - trackStyleChange(time(), styleSheet[styleSheetId], StyleSheetOperation.Create); - trackStyleChange(time(), styleSheet[styleSheetId], StyleSheetOperation.ReplaceSync, getCssRules(styleSheet)); + trackStyleChange(timestamp, styleSheet[styleSheetId], StyleSheetOperation.Create); + trackStyleChange(timestamp, styleSheet[styleSheetId], StyleSheetOperation.ReplaceSync, getCssRules(styleSheet)); } currentStyleSheets.push(styleSheet[styleSheetId]); } @@ -81,14 +83,16 @@ export function checkDocumentStyles(documentNode: Document): void { } if (!arraysEqual(currentStyleSheets, styleSheetMap[documentId])) { // Using -1 to signify the root document node as we don't track that as part of our nodeMap - trackStyleAdoption(time(), documentNode == document ? -1 : getId(documentNode), StyleSheetOperation.SetAdoptedStyles, currentStyleSheets); + trackStyleAdoption(timestamp, documentNode == document ? -1 : getId(documentNode), StyleSheetOperation.SetAdoptedStyles, currentStyleSheets); styleSheetMap[documentId] = currentStyleSheets; + styleTimeMap[documentId] = timestamp; } } export function compute(): void { - checkDocumentStyles(document); - Object.keys(styleSheetMap).forEach((x) => checkDocumentStyles(getNode(parseInt(x, 10)) as Document)); + let ts = -1 in styleTimeMap ? styleTimeMap[-1] : null; + checkDocumentStyles(document, ts); + Object.keys(styleSheetMap).forEach((x) => checkDocumentStyles(getNode(parseInt(x, 10)) as Document, styleTimeMap[x])); } export function reset(): void { @@ -97,6 +101,7 @@ export function reset(): void { export function stop(): void { styleSheetMap = {}; + styleTimeMap = {}; reset(); } diff --git a/packages/clarity-js/src/layout/traverse.ts b/packages/clarity-js/src/layout/traverse.ts index ebdf6ff..72ee6da 100644 --- a/packages/clarity-js/src/layout/traverse.ts +++ b/packages/clarity-js/src/layout/traverse.ts @@ -3,7 +3,7 @@ import { Source } from "@clarity-types/layout"; import * as task from "@src/core/task"; import node from "@src/layout/node"; -export default async function(root: Node, timer: Timer, source: Source): Promise { +export default async function(root: Node, timer: Timer, source: Source, timestamp: number): Promise { let queue = [root]; while (queue.length > 0) { let entry = queue.shift(); @@ -22,7 +22,7 @@ export default async function(root: Node, timer: Timer, source: Source): Promise // Check if processing a node gives us a pointer to one of its sub nodes for traversal // E.g. an element node may give us a pointer to traverse shadowDom if shadowRoot property is set // Or, an iframe from the same origin could give a pointer to it's document for traversing contents of iframe. - let subnode = node(entry, source); + let subnode = node(entry, source, timestamp); if (subnode) { queue.push(subnode); } } } \ No newline at end of file diff --git a/packages/clarity-visualize/src/visualizer.ts b/packages/clarity-visualize/src/visualizer.ts index ba1b217..e17b8a1 100644 --- a/packages/clarity-visualize/src/visualizer.ts +++ b/packages/clarity-visualize/src/visualizer.ts @@ -185,6 +185,7 @@ export class Visualizer implements VisualizerType { case Data.Event.StyleSheetAdoption: case Data.Event.StyleSheetUpdate: this.layout.styleChange(entry as Layout.StyleSheetEvent); + break; case Data.Event.Animation: this.layout.animateChange(entry as Layout.AnimationEvent); break;