From 601a98e649da9265c8fa06fccb496354f464a021 Mon Sep 17 00:00:00 2001 From: "Julien Carion (juca)" Date: Wed, 21 Jun 2023 12:58:25 +0200 Subject: [PATCH] [IMP] devtools: patch app methods only when needed This commit changes the moment when all methods that need patching to handle events are actually patched: the complete method of RootFiber is now patched at devtools startup (when the tab owl tab is opened) and every other method is patched at first activation of the event recording functionality. This makes sure that the devtools will have no impact on performance whatsoever when they are not directly put in use. --- .../devtools/src/devtools_app/store/store.js | 11 +- .../page_scripts/owl_devtools_global_hook.js | 151 +++++++----------- 2 files changed, 65 insertions(+), 97 deletions(-) diff --git a/tools/devtools/src/devtools_app/store/store.js b/tools/devtools/src/devtools_app/store/store.js index ed5236ef..cca8597c 100644 --- a/tools/devtools/src/devtools_app/store/store.js +++ b/tools/devtools/src/devtools_app/store/store.js @@ -410,12 +410,7 @@ export const store = reactive({ if (!scriptsLoaded) { await loadScripts(frame); } - evalInWindow( - `__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = ${ - store.devtoolsId - }; __OWL__DEVTOOLS_GLOBAL_HOOK__.frame = ${JSON.stringify(frame)};`, - frame - ); + evalFunctionInWindow("initDevtools", [frame], frame); if (!this.frameUrls.includes(frame)) { this.frameUrls = [...this.frameUrls, frame]; } @@ -635,7 +630,7 @@ init(); async function init() { store.devtoolsId = await getTabURL(); - evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";"); + evalFunctionInWindow("initDevtools", []); await loadSettings(); @@ -677,7 +672,7 @@ browserInstance.runtime.onConnect.addListener((port) => { if (msg.type === "Reload") { store.owlStatus = await evalInWindow("window.__OWL__DEVTOOLS_GLOBAL_HOOK__ !== undefined;"); if (store.owlStatus) { - evalInWindow("__OWL__DEVTOOLS_GLOBAL_HOOK__.devtoolsId = " + store.devtoolsId + ";"); + evalFunctionInWindow("initDevtools", []); await store.resetData(); } } diff --git a/tools/devtools/src/page_scripts/owl_devtools_global_hook.js b/tools/devtools/src/page_scripts/owl_devtools_global_hook.js index d7b48300..49d88484 100644 --- a/tools/devtools/src/page_scripts/owl_devtools_global_hook.js +++ b/tools/devtools/src/page_scripts/owl_devtools_global_hook.js @@ -22,7 +22,6 @@ // Set to keep track of the frame on which this script is loaded this.frame = "top"; // Allows to launch a message each time an iframe html element is added to the page - const self = this; const iFrameObserver = new MutationObserver(function (mutationsList) { mutationsList.forEach(function (mutation) { mutation.addedNodes.forEach(function (addedNode) { @@ -45,12 +44,7 @@ }); }); iFrameObserver.observe(document.body, { subtree: true, childList: true }); - this.appsPatched = false; - this.destroyPatched = false; this.patchAppsSetMethods(); - if (this.apps.size > 0) { - this.patchAppMethods(); - } this.recordEvents = false; this.traceRenderings = false; this.traceSubscriptions = false; @@ -170,34 +164,40 @@ }; } + initDevtools(frame = "top") { + if (!this.devtoolsInit) { + this.frame = frame; + const self = this; + // Flush the events batcher when a root render is completed + const original_Complete = self.RootFiber.prototype.complete; + self.RootFiber.prototype.complete = function () { + original_Complete.call(this, ...arguments); + const path = self.getComponentPath(this.node); + //Add a functionnality to the complete function which sends a message to the window every time it is triggered. + window.top.postMessage({ + source: "owl-devtools", + type: "Complete", + data: path, + origin: { frame: self.frame }, + }); + if (self.recordEvents) { + window.top.postMessage({ + source: "owl-devtools", + type: "Event", + data: self.eventsBatch, + }); + self.eventsBatch = []; + } + }; + this.devtoolsInit = true; + } + } // Modify the methods of the apps set in order to send a message each time it is modified. patchAppsSetMethods() { const originalAdd = this.apps.add; const originalDelete = this.apps.delete; - const self = this; this.apps.add = function () { originalAdd.call(this, ...arguments); - if (!self.destroyPatched) { - const newApp = arguments[0]; - // It is not a given that apps have a root node at creation so we need to wait - if (newApp.root) { - self.patchDestroyMethod(newApp.root); - } else { - let root = null; - Object.defineProperty(newApp, "root", { - get() { - return root; - }, - set(value) { - root = value; - if (!self.destroyPatched) { - self.patchDestroyMethod(root); - } - }, - }); - } - } - self.patchAppMethods(); window.top.postMessage({ source: "owl-devtools", type: "RefreshApps", @@ -212,53 +212,18 @@ }; } - patchDestroyMethod(root) { - if (!this.destroyPatched) { - // Signals when a component is destroyed - const originalDestroy = root.constructor.prototype._destroy; - const self = this; - root.constructor.prototype._destroy = function () { - if (self.recordEvents) { - const path = self.getComponentPath(this); - const event = { - type: "destroy", - component: this.name, - key: this.parentKey, - path: path, - time: 0, - id: self.eventId++, - }; - self.eventsBatch.push(event); - const before = performance.now(); - originalDestroy.call(this, ...arguments); - event.time = performance.now() - before; - } else { - originalDestroy.call(this, ...arguments); - } - }; - this.destroyPatched = true; - } - } - // Modify methods of each app so that it triggers messages on each flush and component render patchAppMethods() { - if (this.appsPatched) { + let app; + for (const appItem of this.apps) { + if (appItem.root) { + app = appItem; + } + } + if (!app.root) { return; } - let app = this.apps.values().next().value; const self = this; - if (app.root) { - this.patchDestroyMethod(app.root); - } else { - const originalMount = app.constructor.prototype.mount; - app.constructor.prototype.mount = async function (...args) { - const result = await originalMount.call(this, ...args); - const root = this.root; - self.patchDestroyMethod(root); - app.constructor.prototype.mount = originalMount; - return result; - }; - } const originalFlush = app.scheduler.constructor.prototype.flush; let inFlush = false; let _render = false; @@ -361,28 +326,27 @@ _render = true; original_Render.call(this, ...arguments); }; - // Flush the events batcher when a root render is completed - const original_Complete = self.RootFiber.prototype.complete; - self.RootFiber.prototype.complete = function () { - original_Complete.call(this, ...arguments); - const path = self.getComponentPath(this.node); - //Add a functionnality to the complete function which sends a message to the window every time it is triggered. - window.top.postMessage({ - source: "owl-devtools", - type: "Complete", - data: path, - origin: { frame: self.frame }, - }); + // Signals when a component is destroyed + const originalDestroy = app.root.constructor.prototype._destroy; + app.root.constructor.prototype._destroy = function () { if (self.recordEvents) { - window.top.postMessage({ - source: "owl-devtools", - type: "Event", - data: self.eventsBatch, - }); - self.eventsBatch = []; + const path = self.getComponentPath(this); + const event = { + type: "destroy", + component: this.name, + key: this.parentKey, + path: path, + time: 0, + id: self.eventId++, + }; + self.eventsBatch.push(event); + const before = performance.now(); + originalDestroy.call(this, ...arguments); + event.time = performance.now() - before; + } else { + originalDestroy.call(this, ...arguments); } }; - this.appsPatched = true; } // patch reactivity system to activate subscription tracing @@ -446,9 +410,14 @@ } toggleTracing(value) { + if (value) { + this.patchAppMethods(); + this.patchAppMethods = () => {}; // to only patch once + } this.traceRenderings = value; return this.traceRenderings; } + toggleSubscriptionTracing(value) { if (value) { this.patchReactivity(); @@ -459,6 +428,10 @@ } // Enables/disables the recording of the render/destroy events based on value toggleEventsRecording(value, index) { + if (value) { + this.patchAppMethods(); + this.patchAppMethods = () => {}; // to only patch once + } this.recordEvents = value; this.eventId = index; return this.recordEvents;