Bug 1753048 - [devtools] Append the addonId to the DevTools fallback window URL r=ochameau

This window does not share the regular extension principal so we cannot fetch
the addonId from the principal in the same way as we do for other documents.

Instead we append the addonId to the URL and provide a new helper in browser-context-helpers
to extract the addonId from a browsingContext.
This helper should work transparently from the parent process and the content process, and
should support browsing context for regular extension documents as well as browsing contexts
for fallback windows.

Differential Revision: https://phabricator.services.mozilla.com/D148489
This commit is contained in:
Julian Descottes 2022-06-14 06:52:49 +00:00
Родитель 45696b71c9
Коммит 7dfa0bab07
5 изменённых файлов: 147 добавлений и 17 удалений

Просмотреть файл

@ -21,6 +21,9 @@ const ADDON_NAME = "test-devtools-webextension";
const OTHER_ADDON_ID = "other-test-devtools-webextension@mozilla.org";
const OTHER_ADDON_NAME = "other-test-devtools-webextension";
const POPUPONLY_ADDON_ID = "popuponly-test-devtools-webextension@mozilla.org";
const POPUPONLY_ADDON_NAME = "popuponly-test-devtools-webextension";
/**
* This test file ensures that the webextension addon developer toolbox:
* - when the debug button is clicked on a webextension, the opened toolbox
@ -260,3 +263,76 @@ add_task(async function testWebExtensionsToolboxWebConsole() {
await removeTemporaryExtension(ADDON_NAME, document);
await removeTab(tab);
});
add_task(async function testWebExtensionNoBgScript() {
await pushPref("devtools.webconsole.filter.css", true);
await enableExtensionDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI(
{
extraProperties: {
browser_action: {
default_title: "WebExtension Popup Only",
default_popup: "popup.html",
},
},
files: {
"popup.html": `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="popup.js"></script>
</head>
<body>
Popup
</body>
</html>
`,
"popup.js": function() {
console.log("Popup-only log");
throw new Error("Popup-only exception");
},
},
id: POPUPONLY_ADDON_ID,
name: POPUPONLY_ADDON_NAME,
},
document
);
const { devtoolsTab, devtoolsWindow } = await openAboutDevtoolsToolbox(
document,
tab,
window,
POPUPONLY_ADDON_NAME
);
const toolbox = getToolbox(devtoolsWindow);
const webconsole = await toolbox.selectTool("webconsole");
const { hud } = webconsole;
info("Open the add-on popup");
const onPopupMessage = waitUntil(() => {
return findMessagesByType(hud, "Popup-only exception", ".error").length > 0;
});
clickOnAddonWidget(POPUPONLY_ADDON_ID);
await onPopupMessage;
info("Wait a bit to catch unexpected duplicates or mixed up messages");
await wait(1000);
is(
findMessagesByType(hud, "Popup-only exception", ".error").length,
1,
"We get the popup exception"
);
is(
findMessagesByType(hud, "Popup-only log", ".console-api").length,
1,
"We get the addon's popup log"
);
await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
await removeTemporaryExtension(POPUPONLY_ADDON_NAME, document);
await removeTab(tab);
});

Просмотреть файл

@ -9,6 +9,13 @@ const EventEmitter = require("devtools/shared/event-emitter");
const { Ci } = require("chrome");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"getAddonIdForWindowGlobal",
"devtools/server/actors/watcher/browsing-context-helpers.jsm",
true
);
// ms of delay to throttle updates
const BATCH_DELAY = 200;
@ -218,11 +225,8 @@ class StorageActorMock extends EventEmitter {
}
isIncludedInTargetExtension(subject) {
const { document } = subject;
return (
document.nodePrincipal.addonId &&
document.nodePrincipal.addonId === this.targetActor.addonId
);
const addonId = getAddonIdForWindowGlobal(subject.windowGlobalChild);
return addonId && addonId === this.targetActor.addonId;
}
isIncludedInTopLevelWindow(window) {

Просмотреть файл

@ -20,6 +20,12 @@ loader.lazyGetter(this, "ExtensionStorageIDB", () => {
return require("resource://gre/modules/ExtensionStorageIDB.jsm")
.ExtensionStorageIDB;
});
loader.lazyRequireGetter(
this,
"getAddonIdForWindowGlobal",
"devtools/server/actors/watcher/browsing-context-helpers.jsm",
true
);
const EXTENSION_STORAGE_ENABLED_PREF =
"devtools.storage.extensionStorage.enabled";
@ -3619,11 +3625,8 @@ const StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, {
},
isIncludedInTargetExtension(subject) {
const { document } = subject;
return (
document.nodePrincipal.addonId &&
document.nodePrincipal.addonId === this.parentActor.addonId
);
const addonId = getAddonIdForWindowGlobal(subject.windowGlobalChild);
return addonId && addonId === this.parentActor.addonId;
},
isIncludedInTopLevelWindow(window) {

Просмотреть файл

@ -227,7 +227,12 @@ webExtensionTargetPrototype._searchFallbackWindow = function() {
// specifically created for the devtools server and it is never used
// or navigated anywhere else.
this.fallbackWindow = this.chromeGlobal.content;
this.fallbackWindow.document.location.href = FALLBACK_DOC_URL;
// Add the addonId in the URL to retrieve this information in other devtools
// helpers. The addonId is usually populated in the principal, but this will
// not be the case for the fallback window because it is loaded from chrome://
// instead of moz-extension://${addonId}
this.fallbackWindow.document.location.href = `${FALLBACK_DOC_URL}#${this.addonId}`;
return this.fallbackWindow;
};

Просмотреть файл

@ -7,6 +7,7 @@
const EXPORTED_SYMBOLS = [
"isBrowsingContextPartOfContext",
"isWindowGlobalPartOfContext",
"getAddonIdForWindowGlobal",
"getAllBrowsingContextsForContext",
];
@ -23,6 +24,49 @@ const isEveryFrameTargetEnabled = Services.prefs.getBoolPref(
false
);
const WEBEXTENSION_FALLBACK_DOC_URL =
"chrome://devtools/content/shared/webextension-fallback.html";
/**
* Retrieve the addon id corresponding to a given window global.
* This is usually extracted from the principal, but in case we are dealing
* with a DevTools webextension fallback window, the addon id will be available
* in the URL.
*
* @param {WindowGlobalChild|WindowGlobalParent} windowGlobal
* The WindowGlobal from which we want to extract the addonId. Either a
* WindowGlobalParent or a WindowGlobalChild depending on where this
* helper is used from.
* @return {String} Returns the addon id if any could found, null otherwise.
*/
function getAddonIdForWindowGlobal(windowGlobal) {
const browsingContext = windowGlobal.browsingContext;
const isParent = CanonicalBrowsingContext.isInstance(browsingContext);
// documentPrincipal is only exposed on WindowGlobalParent,
// use a fallback for WindowGlobalChild.
const principal = isParent
? windowGlobal.documentPrincipal
: browsingContext.window.document.nodePrincipal;
// Most webextension documents are loaded from moz-extension://{addonId} and
// the principal provides the addon id.
if (principal.addonId) {
return principal.addonId;
}
// If no addon id was available on the principal, check if the window is the
// DevTools fallback window and extract the addon id from the URL.
const href = isParent
? windowGlobal.documentURI.displaySpec
: browsingContext.window.document.location.href;
if (href && href.startsWith(WEBEXTENSION_FALLBACK_DOC_URL)) {
const [, addonId] = href.split("#");
return addonId;
}
return null;
}
/**
* Helper function to know if a given BrowsingContext should be debugged by scope
* described by the given session context.
@ -157,6 +201,7 @@ function isBrowsingContextPartOfContext(
}
return true;
}
if (sessionContext.type == "webextension") {
// Next and last check expects a WindowGlobal.
// As we have no way to really know if this BrowsingContext is related to this add-on,
@ -164,12 +209,8 @@ function isBrowsingContextPartOfContext(
if (!windowGlobal) {
return false;
}
// documentPrincipal is only exposed on WindowGlobalParent,
// use a fallback for WindowGlobalChild.
const principal =
windowGlobal.documentPrincipal ||
browsingContext.window.document.nodePrincipal;
return principal.addonId == sessionContext.addonId;
return getAddonIdForWindowGlobal(windowGlobal) == sessionContext.addonId;
}
throw new Error("Unsupported session context type: " + sessionContext.type);
}
@ -381,6 +422,7 @@ if (typeof module == "object") {
module.exports = {
isBrowsingContextPartOfContext,
isWindowGlobalPartOfContext,
getAddonIdForWindowGlobal,
getAllBrowsingContextsForContext,
};
}