Skip to content

Commit

Permalink
Fix memory leak issues with mutation observers
Browse files Browse the repository at this point in the history
  • Loading branch information
Manu Nair committed Feb 24, 2025
1 parent 656b5c0 commit 34dd132
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 9 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "0.7.69",
"version": "0.7.70",
"npmClient": "yarn",
"useWorkspaces": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "clarity",
"private": true,
"version": "0.7.69",
"version": "0.7.70",
"repository": "https://github.com/microsoft/clarity.git",
"author": "Sarvesh Nagpal <[email protected]>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-decode/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clarity-decode",
"version": "0.7.69",
"version": "0.7.70",
"description": "An analytics library that uses web page interactions to generate aggregated insights",
"author": "Microsoft Corp.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-devtools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clarity-devtools",
"version": "0.7.69",
"version": "0.7.70",
"private": true,
"description": "Adds Clarity debugging support to browser devtools",
"author": "Microsoft Corp.",
Expand Down
4 changes: 2 additions & 2 deletions packages/clarity-devtools/static/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.69",
"version_name": "0.7.69",
"version": "0.7.70",
"version_name": "0.7.70",
"minimum_chrome_version": "50",
"devtools_page": "devtools.html",
"icons": {
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clarity-js",
"version": "0.7.69",
"version": "0.7.70",
"description": "An analytics library that uses web page interactions to generate aggregated insights",
"author": "Microsoft Corp.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-js/src/core/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
let version = "0.7.69";
let version = "0.7.70";
export default version;
51 changes: 51 additions & 0 deletions packages/clarity-js/src/layout/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ let throttleDelay: number = null;
let activePeriod = null;
let history: MutationHistory = {};
let criticalPeriod = null;
let iframeObserversMap: WeakMap<Node, Set<MutationObserver>> = null;
let observedNodes: WeakMap<Node, MutationObserver> = null;

// We ignore mutations if these attributes are updated
const IGNORED_ATTRIBUTES = ["data-google-query-id", "data-load-complete", "data-google-container-id"];
Expand All @@ -47,6 +49,8 @@ export function start(): void {
activePeriod = 0;
history = {};
criticalPeriod = 0;
iframeObserversMap = new WeakMap<Node, Set<MutationObserver>>();
observedNodes = new WeakMap<Node, MutationObserver>();

// Some popular open source libraries, like styled-components, optimize performance
// by injecting CSS using insertRule API vs. appending text node. A side effect of
Expand Down Expand Up @@ -117,10 +121,30 @@ export function observe(node: Node): void {
// For this reason, we need to wire up mutations every time we see a new shadow dom.
// Also, wrap it inside a try / catch. In certain browsers (e.g. legacy Edge), observer on shadow dom can throw errors
try {
// Cleanup old observer if present. This can happen in case of nested observers within an iframe
// that was removed but we couldn't disconnect the observer as iframe contentDocument returns null
// when it is removed.
if (observedNodes.has(node)) {
observedNodes.get(node)?.disconnect();
}

let m = api(Constant.MutationObserver);
let observer = m in window ? new window[m](measure(handle) as MutationCallback) : null;
if (observer) {
observer.observe(node, { attributes: true, childList: true, characterData: true, subtree: true });
const frame = dom.iframe(node);

// Track all observers within an iframe so that they can be recursively disconnected
// when the iframe is removed to avoid memory leaks and potential duplication of mutations
// if the iframe is added back into the document
if (frame) {
if (!iframeObserversMap.has(frame)) {
iframeObserversMap.set(frame, new Set());
}
iframeObserversMap.get(frame).add(observer);
}

observedNodes.set(node, observer);
observers.push(observer);
}
} catch (e) {
Expand Down Expand Up @@ -151,6 +175,8 @@ export function stop(): void {
activePeriod = 0;
timeout = null;
criticalPeriod = 0;
iframeObserversMap = new WeakMap();
observedNodes = new WeakMap();
}

export function active(): void {
Expand Down Expand Up @@ -338,11 +364,36 @@ async function processNodeList(list: NodeList, source: Source, timer: Timer, tim
if (state === Task.Stop) {
break;
}
if (source === Source.ChildListRemove && node.nodeType === Node.ELEMENT_NODE) {
let el = node as HTMLElement;
if (el.tagName === "IFRAME") {
disconnectObserversInIFrame(node);
}
}
processNode(node, source, timestamp);
}
}
}

function disconnectObserversInIFrame(frame: Node) {
if (iframeObserversMap.has(frame)) {
const obs = iframeObserversMap.get(frame);
obs.forEach((o: MutationObserver) => {
o.disconnect();

if (observedNodes.has(frame)) {
observedNodes.delete(frame);
}
const index = observers.indexOf(o);

if (index > -1) {
observers.splice(index, 1);
}
});
iframeObserversMap.delete(frame);
}
}

function processThrottledMutations(): void {
if (throttleDelay) {
clearTimeout(throttleDelay);
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-visualize/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clarity-visualize",
"version": "0.7.69",
"version": "0.7.70",
"description": "An analytics library that uses web page interactions to generate aggregated insights",
"author": "Microsoft Corp.",
"license": "MIT",
Expand Down

0 comments on commit 34dd132

Please sign in to comment.