From f6eb23ea61a14af065383293855280fc00e704ca Mon Sep 17 00:00:00 2001 From: Abdelarhman Magdy Date: Tue, 5 Nov 2024 15:08:17 +0200 Subject: [PATCH] Fixing image masking issues (#689) * Fixing image masking issues --- lerna.json | 2 +- package.json | 2 +- packages/clarity-decode/package.json | 4 +-- packages/clarity-devtools/package.json | 8 +++--- .../clarity-devtools/static/manifest.json | 4 +-- packages/clarity-js/package.json | 2 +- packages/clarity-js/src/core/version.ts | 2 +- packages/clarity-js/src/layout/dom.ts | 25 ++++++++++++++++--- packages/clarity-visualize/package.json | 4 +-- packages/clarity-visualize/src/layout.ts | 20 +++++++++++++++ 10 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lerna.json b/lerna.json index 1e45a6ae..be9bb5a4 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.7.54", + "version": "0.7.55", "npmClient": "yarn", "useWorkspaces": true } diff --git a/package.json b/package.json index ba6142a1..dbb0ef58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "clarity", "private": true, - "version": "0.7.54", + "version": "0.7.55", "repository": "https://github.com/microsoft/clarity.git", "author": "Sarvesh Nagpal ", "license": "MIT", diff --git a/packages/clarity-decode/package.json b/packages/clarity-decode/package.json index c972826c..1c4b5006 100644 --- a/packages/clarity-decode/package.json +++ b/packages/clarity-decode/package.json @@ -1,6 +1,6 @@ { "name": "clarity-decode", - "version": "0.7.54", + "version": "0.7.55", "description": "An analytics library that uses web page interactions to generate aggregated insights", "author": "Microsoft Corp.", "license": "MIT", @@ -26,7 +26,7 @@ "url": "https://github.com/Microsoft/clarity/issues" }, "dependencies": { - "clarity-js": "^0.7.54" + "clarity-js": "^0.7.55" }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.0", diff --git a/packages/clarity-devtools/package.json b/packages/clarity-devtools/package.json index 60bef005..99cb2d58 100644 --- a/packages/clarity-devtools/package.json +++ b/packages/clarity-devtools/package.json @@ -1,6 +1,6 @@ { "name": "clarity-devtools", - "version": "0.7.54", + "version": "0.7.55", "private": true, "description": "Adds Clarity debugging support to browser devtools", "author": "Microsoft Corp.", @@ -24,9 +24,9 @@ "url": "https://github.com/Microsoft/clarity/issues" }, "dependencies": { - "clarity-decode": "^0.7.54", - "clarity-js": "^0.7.54", - "clarity-visualize": "^0.7.54" + "clarity-decode": "^0.7.55", + "clarity-js": "^0.7.55", + "clarity-visualize": "^0.7.55" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.0", diff --git a/packages/clarity-devtools/static/manifest.json b/packages/clarity-devtools/static/manifest.json index 441bfaac..d53a364e 100644 --- a/packages/clarity-devtools/static/manifest.json +++ b/packages/clarity-devtools/static/manifest.json @@ -2,8 +2,8 @@ "manifest_version": 2, "name": "Microsoft Clarity Developer Tools", "description": "Clarity helps you understand how users are interacting with your website.", - "version": "0.7.54", - "version_name": "0.7.54", + "version": "0.7.55", + "version_name": "0.7.55", "minimum_chrome_version": "50", "devtools_page": "devtools.html", "icons": { diff --git a/packages/clarity-js/package.json b/packages/clarity-js/package.json index 94ec1e11..6dbf6d71 100644 --- a/packages/clarity-js/package.json +++ b/packages/clarity-js/package.json @@ -1,6 +1,6 @@ { "name": "clarity-js", - "version": "0.7.54", + "version": "0.7.55", "description": "An analytics library that uses web page interactions to generate aggregated insights", "author": "Microsoft Corp.", "license": "MIT", diff --git a/packages/clarity-js/src/core/version.ts b/packages/clarity-js/src/core/version.ts index 73bc1719..afe1ba02 100644 --- a/packages/clarity-js/src/core/version.ts +++ b/packages/clarity-js/src/core/version.ts @@ -1,2 +1,2 @@ -let version = "0.7.54"; +let version = "0.7.55"; export default version; diff --git a/packages/clarity-js/src/layout/dom.ts b/packages/clarity-js/src/layout/dom.ts index 61955073..92171982 100644 --- a/packages/clarity-js/src/layout/dom.ts +++ b/packages/clarity-js/src/layout/dom.ts @@ -2,7 +2,9 @@ import { Privacy } from "@clarity-types/core"; import { Code, Setting, Severity } from "@clarity-types/data"; import { Constant, Mask, NodeInfo, NodeMeta, NodeValue, Selector, SelectorInput, Source } from "@clarity-types/layout"; import config from "@src/core/config"; +import { bind } from "@src/core/event"; import hash from "@src/core/hash"; +import { shortid } from "@src/data/metadata"; import * as internal from "@src/diagnostic/internal"; import * as region from "@src/layout/region"; import * as selector from "@src/layout/selector"; @@ -120,7 +122,7 @@ export function add(node: Node, parent: Node, data: NodeInfo, source: Source): v privacy(node, values[id], parentValue); updateSelector(values[id]); - size(values[id]); + updateImageSize(values[id]); track(id, source); } @@ -250,6 +252,12 @@ function privacy(node: Node, value: NodeValue, parent: NodeValue): void { // In a mode where we mask sensitive information by default, look through class names to aggressively mask content metadata.privacy = inspect(attributes[Constant.Class], maskText, metadata); break; + case tag === Constant.ImageTag: + // Mask images with blob src as it is not publicly available anyway. + if(attributes.src?.startsWith('blob:')){ + metadata.privacy = Privacy.TextImage; + } + break; } } @@ -361,10 +369,21 @@ function removeNodeFromNodesMap(id: number) { } } -function size(value: NodeValue): void { +function updateImageSize(value: NodeValue): void { // If this element is a image node, and is masked, then track box model for the current element - if (value.data.tag === Constant.ImageTag && value.metadata.privacy === Privacy.TextImage) { value.metadata.size = []; } + if (value.data.tag === Constant.ImageTag && value.metadata.privacy === Privacy.TextImage) { + let img = getNode(value.id) as HTMLImageElement; + // We will not capture the natural image dimensions until it loads. + if(img && (!img.complete || img.naturalWidth === 0)){ + // This will trigger mutation to update the original width and height after image loads. + bind(img, 'load', () => { + img.setAttribute('data-clarity-loaded', `${shortid()}`); + }) + } + value.metadata.size = []; + } } + function getPreviousId(node: Node): number { let id = null; diff --git a/packages/clarity-visualize/package.json b/packages/clarity-visualize/package.json index d0a41196..63184e29 100644 --- a/packages/clarity-visualize/package.json +++ b/packages/clarity-visualize/package.json @@ -1,6 +1,6 @@ { "name": "clarity-visualize", - "version": "0.7.54", + "version": "0.7.55", "description": "An analytics library that uses web page interactions to generate aggregated insights", "author": "Microsoft Corp.", "license": "MIT", @@ -27,7 +27,7 @@ "url": "https://github.com/Microsoft/clarity/issues" }, "dependencies": { - "clarity-decode": "^0.7.54" + "clarity-decode": "^0.7.55" }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.0", diff --git a/packages/clarity-visualize/src/layout.ts b/packages/clarity-visualize/src/layout.ts index 83e92e1b..4785fc12 100644 --- a/packages/clarity-visualize/src/layout.ts +++ b/packages/clarity-visualize/src/layout.ts @@ -3,6 +3,7 @@ import type { Layout as DecodedLayout } from "clarity-decode"; import { Asset, Constant, LinkHandler, NodeType, PlaybackState, Setting } from "@clarity-types/visualize"; import { StyleSheetOperation } from "clarity-js/types/layout"; import { AnimationOperation } from "clarity-js/types/layout"; +import { Constant as LayoutConstants } from "clarity-js/types/layout"; export class LayoutHelper { static TIMEOUT = 3000; @@ -19,6 +20,9 @@ export class LayoutHelper { animations = {}; state: PlaybackState = null; stylesToApply: { [id: string] : string[] } = {}; + BackgroundImageEligibleElements = ['DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'ASIDE', 'NAV', 'SPAN', 'P', 'MAIN']; + MaskedBackgroundImageStyle = `#CCC no-repeat center url("${Asset.Hide}")`; + constructor(state: PlaybackState, isMobile: boolean = false) { this.state = state; @@ -422,6 +426,21 @@ export class LayoutHelper { } return child; } + + + // Mask images within a masked ancestor element in the node has a background image. + private mask = (node: HTMLElement) => { + if (node && this.BackgroundImageEligibleElements.includes(node.nodeName) && 'getComputedStyle' in window && 'closest' in node) { + const urlPattern = /url\(['"]?([^'")]+)['"]?\)/; + const computedStyles = window.getComputedStyle(node); + const hasBackgroundImage = computedStyles.backgroundImage?.match(urlPattern) || computedStyles.background?.match(urlPattern); + const masked = node.closest?.(`[${LayoutConstants.MaskData}]`); + + if (hasBackgroundImage && masked) { + node.style.background = this.MaskedBackgroundImageStyle; + } + } + }; private insertBefore = (data: DecodedLayout.DomData, parent: Node, node: Node, next: Node): void => { if (parent !== null) { @@ -429,6 +448,7 @@ export class LayoutHelper { next = next && (next.parentElement !== parent && next.parentNode !== parent) ? null : next; try { parent.insertBefore(node, next); + this.mask(node as HTMLElement); } catch (ex) { console.warn("Node: " + node + " | Parent: " + parent + " | Data: " + JSON.stringify(data)); console.warn("Exception encountered while inserting node: " + ex);