Bug 1468550 - Track webextension activity - r?kmag
Adds a performance counter in ParentAPIManager to track the
number and duration of API calls by webextensions.
MozReview-Commit-ID: PTpaSCkE6A
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4964,16 +4964,19 @@ pref("extensions.webextensions.protocol.
// Enable tab hiding API by default.
pref("extensions.webextensions.tabhide.enabled", true);
pref("extensions.webextensions.background-delayed-startup", false);
// Whether or not the installed extensions should be migrated to the storage.local IndexedDB backend.
pref("extensions.webextensions.ExtensionStorageIDB.enabled", false);
+// if enabled, store execution times for API calls
+pref("extensions.webextensions.enablePerformanceCounters", false);
+
// Report Site Issue button
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
pref("extensions.webcompat-reporter.enabled", true);
#else
pref("extensions.webcompat-reporter.enabled", false);
#endif
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -55,16 +55,17 @@ var {
promiseEvent,
promiseObserved,
} = ExtensionUtils;
const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
const CATEGORY_EXTENSION_MODULES = "webextension-modules";
const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
+const TIMING_ENABLED_PREF = "extensions.webextensions.enablePerformanceCounters";
let schemaURLs = new Set();
schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
let GlobalManager;
let ParentAPIManager;
let ProxyMessenger;
@@ -744,26 +745,30 @@ class DevToolsExtensionPageContextParent
this._devToolsToolbox = null;
super.shutdown();
}
}
ParentAPIManager = {
+ // stores dispatches counts per web extension and API
+ performanceCounters: new DefaultMap(() => new DefaultMap(() => ({duration: 0, calls: 0}))),
+
proxyContexts: new Map(),
init() {
Services.obs.addObserver(this, "message-manager-close");
Services.mm.addMessageListener("API:CreateProxyContext", this);
Services.mm.addMessageListener("API:CloseProxyContext", this, true);
Services.mm.addMessageListener("API:Call", this);
Services.mm.addMessageListener("API:AddListener", this);
Services.mm.addMessageListener("API:RemoveListener", this);
+ XPCOMUtils.defineLazyPreferenceGetter(this, "_timingEnabled", TIMING_ENABLED_PREF, false);
},
attachMessageManager(extension, processMessageManager) {
extension.parentMessageManager = processMessageManager;
},
async observe(subject, topic, data) {
if (topic === "message-manager-close") {
@@ -871,16 +876,36 @@ ParentAPIManager = {
closeProxyContext(childId) {
let context = this.proxyContexts.get(childId);
if (context) {
context.unload();
this.proxyContexts.delete(childId);
}
},
+ storeExecutionTime(webExtensionId, apiPath, duration) {
+ let apiCounter = this.performanceCounters.get(webExtensionId).get(apiPath);
+ apiCounter.duration += duration;
+ apiCounter.calls += 1;
+ },
+
+ async withTiming(data, callable) {
+ if (!this._timingEnabled) {
+ return callable();
+ }
+ let webExtId = data.childId.split(".")[0];
+ let start = Cu.now();
+ try {
+ return callable();
+ } finally {
+ let end = Cu.now();
+ this.storeExecutionTime(webExtId, data.path, end - start);
+ }
+ },
+
async call(data, target) {
let context = this.getContextById(data.childId);
if (context.parentMessageManager !== target.messageManager) {
throw new Error("Got message on unexpected message manager");
}
let reply = result => {
if (!context.parentMessageManager) {
@@ -896,18 +921,21 @@ ParentAPIManager = {
callId: data.callId,
}, result));
};
try {
let args = data.args;
let pendingBrowser = context.pendingEventBrowser;
let fun = await context.apiCan.asyncFindAPIPath(data.path);
- let result = context.withPendingBrowser(pendingBrowser,
- () => fun(...args));
+ let result = this.withTiming(data, () => {
+ return context.withPendingBrowser(pendingBrowser,
+ () => fun(...args));
+ });
+
if (data.callId) {
result = result || Promise.resolve();
result.then(result => {
result = result instanceof SpreadArgs ? [...result] : [result];
let holder = new StructuredCloneHolder(result);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_performance_counters.js
@@ -0,0 +1,43 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
+const ENABLE_COUNTER_PREF = "extensions.webextensions.enablePerformanceCounters";
+
+let {
+ ParentAPIManager,
+} = ExtensionParent;
+
+
+async function test_counter() {
+ async function background() {
+ // creating a bookmark
+ let folder = await browser.bookmarks.create({title: "Folder"});
+ await browser.bookmarks.create({title: "Bookmark", url: "http://example.com",
+ parentId: folder.id});
+ browser.test.sendMessage("done");
+ }
+
+ let extensionData = {
+ background,
+ manifest: {
+ permissions: ["bookmarks"],
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ await extension.awaitMessage("done");
+ await extension.unload();
+
+ // check that the bookmarks.create API was tracked
+ let counters = ParentAPIManager.performanceCounters;
+ let counter = counters.get(extension.id).get("bookmarks.create");
+ ok(counter.calls > 0);
+ ok(counter.duration > 0);
+}
+
+add_task(function test_performance_counter() {
+ return runWithPrefs([[ENABLE_COUNTER_PREF, true]], test_counter);
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -129,8 +129,10 @@ skip-if = true # Too frequent intermitte
subprocess = true
skip-if = os == "android"
[test_ext_permissions.js]
skip-if = os == "android" # Bug 1350559
[test_proxy_listener.js]
[test_proxy_scripts.js]
[test_proxy_scripts_results.js]
[test_ext_brokenlinks.js]
+[test_ext_performance_counters.js]
+skip-if = os == "android"