Bug 1525179 - Use aboutaddons.html at the root of about:addons. r=robwu

- Moved aboutaddons.html at the root of the about:addons page
- renamed aboutaddons.js `initialize` global function to `initializeView` (and renamed `show` and `hide` global functions accordingly)
- Simplify initial load logic a bit more:
  - remove gCategories
  - moved responsability of the CategoriesBox initialization to the `initializationView` function
  - replaced initialization logic based on `gPendingInitializations` and `notifyInitialized` with
    a `promiseInitialized` global resolved when `initializeView` method has been called and its return value resolved
- Fix test helpers and test failures
  - InlineOptionsBrowser: take into account that browser-custom-element does opt-in the delayedConnectedCallback behavior
    and browser.loadURI may throw if called while the document is still loading.

Differential Revision: https://phabricator.services.mozilla.com/D96472
This commit is contained in:
Luca Greco 2021-02-03 17:08:14 +00:00
Родитель bedfe8bf76
Коммит e1d14d5183
35 изменённых файлов: 215 добавлений и 374 удалений

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

@ -39,11 +39,9 @@ function promiseEvent(eventEmitter, event) {
}
function getAddonElement(managerWindow, addonId) {
const { contentDocument: doc } = managerWindow.document.getElementById(
"html-view-browser"
);
return BrowserTestUtils.waitForCondition(
() => doc.querySelector(`addon-card[addon-id="${addonId}"]`),
() =>
managerWindow.document.querySelector(`addon-card[addon-id="${addonId}"]`),
`Found entry for sideload extension addon "${addonId}" in HTML about:addons`
);
}
@ -59,7 +57,7 @@ function assertSideloadedAddonElementState(addonElement, checked) {
is(enableBtn.type, "checkbox", "It's a checkbox");
}
function clickEnableExtension(managerWindow, addonElement) {
function clickEnableExtension(addonElement) {
addonElement.querySelector('[action="toggle-disabled"]').click();
}
@ -218,7 +216,7 @@ add_task(async function test_sideloading() {
);
}
// XUL or HTML about:addons addon entry element.
// about:addons addon entry element.
const addonElement = await getAddonElement(win, ID2);
assertSideloadedAddonElementState(addonElement, false);
@ -227,7 +225,7 @@ add_task(async function test_sideloading() {
// When clicking enable we should see the permissions notification
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
clickEnableExtension(win, addonElement);
clickEnableExtension(addonElement);
panel = await popupPromise;
checkNotification(panel, DEFAULT_ICON_URL, [
["webextPerms.hostDescription.allUrls"],

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

@ -14,13 +14,11 @@ async function installFile(filename) {
MockFilePicker.setFiles([file]);
MockFilePicker.afterOpenCallback = MockFilePicker.cleanup;
let managerWin = await BrowserOpenAddonsMgr("addons://list/extension");
let { document } = await BrowserOpenAddonsMgr("addons://list/extension");
// Do the install...
await BrowserTestUtils.waitForEvent(managerWin.document, "ViewChanged");
let installButton = managerWin
.getHtmlBrowser()
.contentDocument.querySelector('[action="install-from-file"]');
await BrowserTestUtils.waitForEvent(document, "ViewChanged");
let installButton = document.querySelector('[action="install-from-file"]');
installButton.click();
}

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

@ -179,10 +179,7 @@ async function waitForUpdate(addon) {
* Trigger an action from the page options menu.
*/
function triggerPageOptionsAction(win, action) {
win
.getHtmlBrowser()
.contentDocument.querySelector(`#page-options [action="${action}"]`)
.click();
win.document.querySelector(`#page-options [action="${action}"]`).click();
}
function isDefaultIcon(icon) {
@ -484,16 +481,13 @@ async function interactiveUpdateTest(autoUpdate, checkFn) {
if (manualUpdatePromise) {
await manualUpdatePromise;
let doc = win.getHtmlBrowser().contentDocument;
let doc = win.document;
if (win.gViewController.currentViewId !== "addons://updates/available") {
let showUpdatesBtn = doc.querySelector("addon-updates-message").button;
await TestUtils.waitForCondition(() => {
return !showUpdatesBtn.hidden;
}, "Wait for show updates button");
let viewChanged = BrowserTestUtils.waitForEvent(
win.document,
"ViewChanged"
);
let viewChanged = BrowserTestUtils.waitForEvent(doc, "ViewChanged");
showUpdatesBtn.click();
await viewChanged;
}

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

@ -31,8 +31,8 @@ add_task(async function() {
),
]);
let addonsPage = gBrowser.selectedBrowser.contentWindow.document.getElementById(
"addons-page"
let addonsPage = gBrowser.selectedBrowser.contentWindow.document.querySelector(
"title[data-l10n-id='addons-page-title']"
);
ok(addonsPage, "Add-ons page was opened");
});

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

@ -7,10 +7,8 @@ const BASE_URL =
"http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser";
async function isExtensionLocked(win, addonID) {
let addonCard = await BrowserTestUtils.waitForCondition(async () => {
let doc = win.getHtmlBrowser().contentDocument;
await win.htmlBrowserLoaded;
return doc.querySelector(`addon-card[addon-id="${addonID}"]`);
let addonCard = await BrowserTestUtils.waitForCondition(() => {
return win.document.querySelector(`addon-card[addon-id="${addonID}"]`);
}, `Get addon-card for "${addonID}"`);
let disableBtn = addonCard.querySelector('[action="toggle-disabled"]');
let removeBtn = addonCard.querySelector('panel-item[action="remove"]');

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

@ -645,8 +645,13 @@ add_task(async function browseraction_contextmenu_report_extension() {
"Got about:addons tab selected"
);
await BrowserTestUtils.browserLoaded(browser);
// Do not wait for the about:addons tab to be loaded if its
// document is already readyState==complete.
// This prevents intermittent timeout failures while running
// this test in optimized builds.
if (browser.contentDocument?.readyState != "complete") {
await BrowserTestUtils.browserLoaded(browser);
}
await testReportDialog();
// Close the new about:addons tab when running in customize mode,

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

@ -138,12 +138,8 @@ let img =
var imageBuffer = imageBufferFromDataURI(img);
function getInlineOptionsBrowser(aboutAddonsBrowser) {
let { contentWindow } = aboutAddonsBrowser;
let htmlBrowser = contentWindow.document.getElementById("html-view-browser");
return (
htmlBrowser.contentDocument.getElementById("addon-inline-options") ||
contentWindow.document.getElementById("addon-options")
);
let { contentDocument } = aboutAddonsBrowser;
return contentDocument.getElementById("addon-inline-options");
}
function getListStyleImage(button) {

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

@ -64,7 +64,7 @@ class CrashChannel final : public nsBaseChannel {
*/
static const RedirEntry kRedirMap[] = {
{"about", "chrome://global/content/aboutAbout.html", 0},
{"addons", "chrome://mozapps/content/extensions/extensions.xhtml",
{"addons", "chrome://mozapps/content/extensions/aboutaddons.html",
nsIAboutModule::ALLOW_SCRIPT},
{"buildconfig", "chrome://global/content/buildconfig.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},

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

@ -17,6 +17,8 @@
<link rel="stylesheet" href="chrome://mozapps/content/extensions/aboutaddons.css">
<link rel="stylesheet" href="chrome://mozapps/content/extensions/shortcuts.css">
<link rel="shortcut icon" href="chrome://mozapps/skin/extensions/extension.svg">
<link rel="localization" href="branding/brand.ftl">
<link rel="localization" href="toolkit/about/aboutAddons.ftl">
<link rel="localization" href="toolkit/about/abuseReports.ftl">
@ -29,6 +31,7 @@
<script defer src="chrome://mozapps/content/extensions/shortcuts.js"></script>
<script defer src="chrome://mozapps/content/extensions/drag-drop-addon-installer.js"></script>
<script defer src="chrome://mozapps/content/extensions/aboutaddons.js"></script>
<script defer src="chrome://mozapps/content/extensions/view-controller.js"></script>
</head>
<body>
<drag-drop-addon-installer></drag-drop-addon-installer>

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

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint max-len: ["error", 80] */
/* exported hide, initialize, show */
/* exported hideView, initializeView, showView */
/* import-globals-from aboutaddonsCommon.js */
/* import-globals-from abuse-reports.js */
/* global MozXULElement, MessageBarStackElement, windowRoot */
@ -1388,8 +1388,7 @@ class AddonPageHeader extends HTMLElement {
this.heading.hidden = viewType === "detail";
this.backButton.hidden = viewType !== "detail" && viewType !== "shortcuts";
let { contentWindow } = getBrowserElement();
this.backButton.disabled = !contentWindow.history.state?.previousView;
this.backButton.disabled = !history.state?.previousView;
if (viewType !== "detail") {
document.l10n.setAttributes(this.heading, `${viewType}-heading`);
@ -1763,12 +1762,6 @@ class CategoriesBox extends customElements.get("button-group") {
this.promiseRendered = new Promise(resolve => {
this._resolveRendered = resolve;
});
// This will resolve when the final category states have been set by
// checking the AddonManager state and showing/hiding categories. The page
// won't be "initialized" until this resolves.
this.promiseInitialized = new Promise(resolve => {
this._resolveInitialized = resolve;
});
}
async initialize() {
@ -1811,7 +1804,6 @@ class CategoriesBox extends customElements.get("button-group") {
this._resolveRendered();
await hiddenUpdated;
this._resolveInitialized();
}
get initialViewId() {
@ -2436,11 +2428,27 @@ class InlineOptionsBrowser extends HTMLElement {
mm.sendAsyncMessage("Extension:InitBrowser", browserOptions);
// prettier-ignore
browser.loadURI(optionsURL, {
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
});
if (browser.isConnectedAndReady) {
this.loadURI(optionsURL);
} else {
// browser custom element does opt-in the delayConnectedCallback
// behavior (see connectedCallback in the custom element definition
// from browser-custom-element.js) and so calling browser.loadURI
// would fail if the about:addons document is not yet fully loaded.
promiseEvent("DOMContentLoaded", document).then(() => {
this.loadURI(optionsURL);
});
}
});
}
loadURI(uri) {
if (!this.browser || !this.browser.isConnectedAndReady) {
throw new Error("Fail to loadURI");
}
this.browser.loadURI(uri, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
}
}
@ -4721,9 +4729,9 @@ var ScrollOffsets = {
};
/**
* Called from extensions.js once, when about:addons is loading.
* Called automatically when about:addons is loading by view-controller.js.
*/
function initialize(opts) {
async function initializeView(opts) {
mainEl = document.getElementById("main");
addonPageHeader = document.getElementById("page-header");
categoriesBox = document.querySelector("categories-box");
@ -4731,9 +4739,6 @@ function initialize(opts) {
loadViewFn = opts.loadViewFn;
replaceWithDefaultViewFn = opts.replaceWithDefaultViewFn;
if (opts.shouldLoadInitialView) {
opts.loadInitialViewFn(categoriesBox.initialViewId);
}
categoriesBox.initialize();
AddonManagerListenerHandler.startup();
@ -4755,7 +4760,7 @@ function initialize(opts) {
* resolve once the view has been updated to conform with other about:addons
* views.
*/
async function show(type, param, { historyEntryId }) {
async function showView(type, param, { historyEntryId }) {
let container = document.createElement("div");
container.setAttribute("current-view", type);
addonPageHeader.setViewInfo({ type, param });
@ -4804,7 +4809,10 @@ async function show(type, param, { historyEntryId }) {
});
}
function hide() {
async function hideView() {
if (!mainEl) {
return;
}
ScrollOffsets.save();
ScrollOffsets.setView(null);
mainEl.textContent = "";

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

@ -1,23 +0,0 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
csp="default-src chrome:; frame-src chrome:; object-src 'none'"
id="addons-page" data-l10n-id="addons-window"
role="application" windowtype="Addons:Manager">
<xhtml:link rel="shortcut icon"
href="chrome://mozapps/skin/extensions/extension.svg"/>
<linkset>
<xhtml:link rel="localization" href="branding/brand.ftl"/>
<xhtml:link rel="localization" href="toolkit/about/aboutAddons.ftl"/>
</linkset>
<script src="chrome://mozapps/content/extensions/aboutaddonsCommon.js"/>
<script src="chrome://mozapps/content/extensions/extensions.js"/>
<browser id="html-view-browser" type="content" flex="1" disablehistory="true"/>
</window>

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

@ -6,6 +6,7 @@
/* import-globals-from ../../../content/customElements.js */
/* import-globals-from aboutaddonsCommon.js */
/* import-globals-from aboutaddons.js */
/* exported loadView */
const { AddonManager } = ChromeUtils.import(
@ -18,50 +19,32 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/AddonManager.jsm"
);
document.addEventListener("load", initialize, true);
window.addEventListener("unload", shutdown);
var gPendingInitializations = 1;
Object.defineProperty(this, "gIsInitializing", {
get: () => gPendingInitializations > 0,
window.promiseInitialized = new Promise(resolve => {
window.addEventListener("load", () => initialize(resolve), { once: true });
});
function initialize(event) {
document.removeEventListener("load", initialize, true);
// Support focusing the search bar from the XUL document.
document.addEventListener("keypress", e => {
getHtmlBrowser()
.contentDocument.querySelector("search-addons")
.handleEvent(e);
});
gViewController.initialize();
async function initialize(resolvePromiseInitialized) {
Services.obs.addObserver(sendEMPong, "EM-ping");
Services.obs.notifyObservers(window, "EM-loaded");
await gViewController.initialize().then(resolvePromiseInitialized);
// If the initial view has already been selected (by a call to loadView from
// the above notifications) then bail out now
if (gViewController.initialViewSelected) {
return;
}
// If there is a history state to restore then use that
if (history.state) {
// If there is a history state to restore then use that
gViewController.updateState(history.state);
}
}
function notifyInitialized() {
if (!gIsInitializing) {
return;
}
gPendingInitializations--;
if (!gIsInitializing) {
var event = document.createEvent("Events");
event.initEvent("Initialized", true, true);
document.dispatchEvent(event);
} else if (!gViewController.currentViewId) {
// Fallback to the last category or first valid category view otherwise.
gViewController.loadInitialView(
document.querySelector("categories-box").initialViewId
);
}
}
@ -97,7 +80,11 @@ async function recordViewTelemetry(param) {
}
// Used by external callers to load a specific view into the manager
function loadView(aViewId) {
async function loadView(aViewId) {
// Make sure to wait about:addons initialization before loading
// a view triggered by external callers.
await window.promiseInitialized;
if (!gViewController.initialViewSelected) {
// The caller opened the window and immediately loaded the view so it
// should be the initial history entry
@ -109,8 +96,14 @@ function loadView(aViewId) {
}
var gViewController = {
defaultViewId: "addons://discover/",
currentViewId: "",
get defaultViewId() {
if (!isDiscoverEnabled()) {
return "addons://list/extension";
}
return "addons://discover/";
},
initialViewSelected: false,
isLoading: true,
// All historyEntryId values must be unique within one session, because the
// IDs are used to map history entries to page state. It is not possible to
@ -118,14 +111,17 @@ var gViewController = {
// was loaded, so start counting from a random value to avoid collisions.
// This is used for scroll offsets in aboutaddons.js
nextHistoryEntryId: Math.floor(Math.random() * 2 ** 32),
initialViewSelected: false,
initialize() {
if (!isDiscoverEnabled()) {
this.defaultViewId = "addons://list/extension";
}
gCategories.initialize();
async initialize() {
await initializeView({
loadViewFn: async view => {
let viewId = view.startsWith("addons://") ? view : `addons://${view}`;
await this.loadView(viewId);
},
replaceWithDefaultViewFn: async () => {
await this.replaceView(this.defaultViewId);
},
});
window.addEventListener("popstate", e => {
this.updateState(e.state);
@ -173,15 +169,13 @@ var gViewController = {
},
loadInitialView(aViewId) {
var state = {
let state = {
view: aViewId,
previousView: null,
historyEntryId: ++this.nextHistoryEntryId,
};
history.replaceState(state, "");
this.loadViewInternal(aViewId, null, state);
notifyInitialized();
},
loadViewInternal(aViewId, aPreviousView, aState) {
@ -193,9 +187,7 @@ var gViewController = {
}
if (aViewId != aPreviousView) {
promiseHtmlBrowserLoaded()
.then(browser => browser.contentWindow.hide())
.catch(err => Cu.reportError(err));
hideView().catch(err => Cu.reportError(err));
}
this.currentViewId = aViewId;
@ -203,98 +195,19 @@ var gViewController = {
recordViewTelemetry(view.param);
let promiseLoad;
if (aViewId != aPreviousView) {
promiseHtmlBrowserLoaded()
.then(browser =>
browser.contentWindow.show(view.type, view.param, aState)
)
.then(() => {
this.isLoading = false;
promiseLoad = showView(view.type, view.param, aState).then(() => {
this.isLoading = false;
var event = document.createEvent("Events");
event.initEvent("ViewChanged", true, true);
document.dispatchEvent(event);
});
var event = document.createEvent("Events");
event.initEvent("ViewChanged", true, true);
document.dispatchEvent(event);
});
}
this.initialViewSelected = true;
return promiseLoad;
},
};
var gCategories = {
initialize() {
gPendingInitializations++;
promiseHtmlBrowserLoaded().then(async browser => {
await browser.contentWindow.customElements.whenDefined("categories-box");
let categoriesBox = browser.contentDocument.getElementById("categories");
await categoriesBox.promiseInitialized;
notifyInitialized();
});
},
};
const htmlViewOpts = {
loadViewFn(view) {
let viewId = view.startsWith("addons://") ? view : `addons://${view}`;
gViewController.loadView(viewId);
},
loadInitialViewFn(viewId) {
gViewController.loadInitialView(viewId);
},
replaceWithDefaultViewFn() {
gViewController.replaceView(gViewController.defaultViewId);
},
get shouldLoadInitialView() {
// Let the HTML document load the view if `loadView` hasn't been called
// externally and we don't have history to refresh from.
return !gViewController.currentViewId && !window.history.state;
},
};
// View wrappers for the HTML version of about:addons. These delegate to an
// HTML browser that renders the actual views.
let htmlBrowser;
let _htmlBrowserLoaded;
function getHtmlBrowser() {
if (!htmlBrowser) {
gPendingInitializations++;
htmlBrowser = document.getElementById("html-view-browser");
htmlBrowser.loadURI(
"chrome://mozapps/content/extensions/aboutaddons.html",
{
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
}
);
_htmlBrowserLoaded = new Promise(resolve =>
htmlBrowser.addEventListener("load", function loadListener() {
if (htmlBrowser.contentWindow.location.href != "about:blank") {
htmlBrowser.removeEventListener("load", loadListener);
resolve();
}
})
).then(() => {
htmlBrowser.contentWindow.initialize(htmlViewOpts);
notifyInitialized();
});
}
return htmlBrowser;
}
async function promiseHtmlBrowserLoaded() {
// Call getHtmlBrowser() first to ensure _htmlBrowserLoaded has been defined.
let browser = getHtmlBrowser();
await _htmlBrowserLoaded;
return browser;
}
// Helper method exported into the about:addons global, used to open the
// abuse report panel from outside of the about:addons page
// (e.g. triggered from the browserAction context menu).
window.openAbuseReport = ({ addonId, reportEntryPoint }) => {
promiseHtmlBrowserLoaded().then(browser => {
browser.contentWindow.openAbuseReport({
addonId,
reportEntryPoint,
});
});
};

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

@ -5,10 +5,6 @@
toolkit.jar:
% content mozapps %content/mozapps/
#ifndef MOZ_FENNEC
content/mozapps/extensions/extensions.xhtml (content/extensions.xhtml)
content/mozapps/extensions/extensions.js (content/extensions.js)
content/mozapps/extensions/blocklist.xhtml (content/blocklist.xhtml)
content/mozapps/extensions/blocklist.js (content/blocklist.js)
content/mozapps/extensions/OpenH264-license.txt (content/OpenH264-license.txt)
content/mozapps/extensions/aboutaddons.html (content/aboutaddons.html)
content/mozapps/extensions/aboutaddons.js (content/aboutaddons.js)
@ -18,6 +14,8 @@ toolkit.jar:
content/mozapps/extensions/abuse-report-frame.html (content/abuse-report-frame.html)
content/mozapps/extensions/abuse-report-panel.css (content/abuse-report-panel.css)
content/mozapps/extensions/abuse-report-panel.js (content/abuse-report-panel.js)
content/mozapps/extensions/blocklist.xhtml (content/blocklist.xhtml)
content/mozapps/extensions/blocklist.js (content/blocklist.js)
content/mozapps/extensions/default-theme.svg (content/default-theme.svg)
content/mozapps/extensions/drag-drop-addon-installer.js (content/drag-drop-addon-installer.js)
content/mozapps/extensions/firefox-compact-dark.svg (content/firefox-compact-dark.svg)
@ -31,6 +29,7 @@ toolkit.jar:
content/mozapps/extensions/rating-star.css (content/rating-star.css)
content/mozapps/extensions/shortcuts.css (content/shortcuts.css)
content/mozapps/extensions/shortcuts.js (content/shortcuts.js)
content/mozapps/extensions/view-controller.js (content/view-controller.js)
% resource default-theme %content/mozapps/extensions/default-theme/
content/mozapps/extensions/default-theme/icon.svg (default-theme/icon.svg)

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

@ -69,7 +69,6 @@ function check_state(canGoBack, canGoForward) {
}
function is_in_list(aManager, view, canGoBack, canGoForward) {
var doc = aManager.document;
var categoryUtils = new CategoryUtilities(aManager);
is(
@ -78,17 +77,15 @@ function is_in_list(aManager, view, canGoBack, canGoForward) {
"Should be on the right category"
);
doc = aManager.getHtmlBrowser().contentDocument;
ok(
doc.querySelector("addon-list"),
"Got a list-view in the HTML about:addons browser"
aManager.document.querySelector("addon-list"),
"Got a list-view in about:addons"
);
check_state(canGoBack, canGoForward);
}
function is_in_detail(aManager, view, canGoBack, canGoForward) {
var doc = aManager.document;
var categoryUtils = new CategoryUtilities(aManager);
is(
@ -97,20 +94,18 @@ function is_in_detail(aManager, view, canGoBack, canGoForward) {
"Should be on the right category"
);
doc = aManager.getHtmlBrowser().contentDocument;
is(
doc.querySelectorAll("addon-card").length,
aManager.document.querySelectorAll("addon-card").length,
1,
"Got a detail-view in the HTML about:addons browser"
"Got a detail-view in about:addons"
);
check_state(canGoBack, canGoForward);
}
function is_in_discovery(aManager, canGoBack, canGoForward) {
const doc = aManager.getHtmlBrowser().contentDocument;
ok(
doc.querySelector("discovery-pane"),
aManager.document.querySelector("discovery-pane"),
"Got a discovery panel in the HTML about:addons browser"
);
@ -190,7 +185,9 @@ add_task(async function test_navigate_between_webpage_and_aboutaddons() {
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:addons");
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let manager = await wait_for_manager_load(gBrowser.contentWindow);
let manager = await wait_for_manager_load(
gBrowser.selectedBrowser.contentWindow
);
info("Part 3");
is_in_list(manager, "addons://list/extension", true, false);
@ -501,8 +498,7 @@ add_task(async function test_history_on_detailview_extension_removed() {
info("Part 2");
is_in_detail(aManager, "addons://list/extension", true, false);
const doc = aManager.getHtmlBrowser().contentDocument;
const addonCard = doc.querySelector(
const addonCard = aManager.document.querySelector(
'addon-card[addon-id="test1@tests.mozilla.org"]'
);
const promptService = mockPromptService();
@ -604,7 +600,6 @@ add_task(async function test_initialSelectedView_on_aboutaddons_reload() {
managerWindow.gViewController.initialViewSelected,
"initialViewSelected is true as expected on first about:addons load"
);
is(managerWindow.gPendingInitializations, 0, "No pending initializations");
await close_manager(managerWindow);
});

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

@ -543,13 +543,10 @@ add_task(async function test_abusereport_from_aboutaddons_menu() {
const extension = await installTestExtension(EXT_ID);
await openAboutAddons();
await gManagerWindow.promiseHtmlBrowserLoaded();
AbuseReportTestUtils.assertReportPanelHidden();
const { contentDocument: doc } = gManagerWindow.getHtmlBrowser();
const addonCard = doc.querySelector(
const addonCard = gManagerWindow.document.querySelector(
`addon-list addon-card[addon-id="${extension.id}"]`
);
ok(addonCard, "Got the addon-card for the test extension");
@ -580,13 +577,10 @@ add_task(async function test_abusereport_from_aboutaddons_remove() {
const extension = await installTestExtension(EXT_ID, "theme");
await openAboutAddons("theme");
await gManagerWindow.promiseHtmlBrowserLoaded();
AbuseReportTestUtils.assertReportPanelHidden();
const { contentDocument: doc } = gManagerWindow.getHtmlBrowser();
const addonCard = doc.querySelector(
const addonCard = gManagerWindow.document.querySelector(
`addon-list addon-card[addon-id="${extension.id}"]`
);
ok(addonCard, "Got the addon-card for the test theme extension");
@ -691,6 +685,7 @@ add_task(async function test_abusereport_from_browserAction_remove() {
menu.hidePopup();
let panelEl = await onceReportOpened;
await AbuseReportTestUtils.closeReportPanel(panelEl);
let onceExtStarted = AddonTestUtils.promiseWebExtensionStartup(EXT_ID);
@ -862,11 +857,8 @@ add_task(async function test_abusereport_open_author_url() {
add_task(async function test_no_report_checkbox_for_unsupported_addon_types() {
async function test_report_checkbox_hidden(addon) {
await openAboutAddons(addon.type);
await gManagerWindow.promiseHtmlBrowserLoaded();
const { contentDocument: doc } = gManagerWindow.getHtmlBrowser();
const addonCard = doc.querySelector(
const addonCard = gManagerWindow.document.querySelector(
`addon-list addon-card[addon-id="${addon.id}"]`
);
ok(addonCard, "Got the addon-card for the test extension");

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

@ -412,7 +412,7 @@ add_task(async function testPermissionsViewStates() {
function wait_for_addon_item_updated(addonId) {
return BrowserTestUtils.waitForEvent(
get_addon_element(view.managerWindow, addonId),
get_addon_element(view, addonId),
"update"
);
}

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

@ -123,11 +123,8 @@ async function hasPrivateAllowed(id) {
}
async function assertBackButtonIsDisabled(win) {
await win.htmlBrowserLoaded;
let backButton = await BrowserTestUtils.waitForCondition(async () => {
let doc = win.getHtmlBrowser().contentDocument;
let backButton = doc.querySelector(".back-button");
let backButton = win.document.querySelector(".back-button");
// Wait until the button is visible in the page.
return backButton && !backButton.hidden ? backButton : false;

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

@ -158,8 +158,8 @@ async function switchToNonDiscoView(win) {
// Listeners registered while the discopane was the active view continue to be
// active when the view switches to the extensions list, because both views
// share the same document.
win.managerWindow.gViewController.loadView("addons://list/extension");
await wait_for_view_load(win.managerWindow);
win.gViewController.loadView("addons://list/extension");
await wait_for_view_load(win);
ok(
win.document.querySelector("addon-list"),
"Should be at the extension list view"
@ -174,8 +174,8 @@ async function switchToDiscoView(win) {
null,
"Cannot switch to discopane when the discopane is already shown"
);
win.managerWindow.gViewController.loadView("addons://discover/");
await wait_for_view_load(win.managerWindow);
win.gViewController.loadView("addons://discover/");
await wait_for_view_load(win);
await promiseDiscopaneUpdate(win);
}

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

@ -74,7 +74,7 @@ add_task(async function testRecentUpdatesList() {
// Load extension view first so we can mock the startOfDay property.
let win = await loadInitialView("extension");
let doc = win.document;
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
const RECENT_URL = "addons://updates/recent";
let recentCat = categoryUtils.get("recent-updates");

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

@ -62,7 +62,7 @@ add_task(async function setup() {
async function switchToView(win, type, param = "") {
let loaded = waitForViewLoad(win);
win.managerWindow.gViewController.loadView(`addons://${type}/${param}`);
win.gViewController.loadView(`addons://${type}/${param}`);
await loaded;
await waitForStableLayout(win);
}
@ -71,10 +71,10 @@ async function switchToView(win, type, param = "") {
// delta = +1 = go forwards.
async function historyGo(win, delta, expectedViewType) {
let loaded = waitForViewLoad(win);
win.managerWindow.history.go(delta);
win.history.go(delta);
await loaded;
is(
win.managerWindow.gViewController.currentViewId,
win.gViewController.currentViewId,
expectedViewType,
"Expected view after history navigation"
);

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

@ -606,7 +606,7 @@ add_task(async function testAvailableUpdates() {
let win = await loadInitialView("extension");
let doc = win.document;
let updatesMessage = doc.getElementById("updates-message");
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
let availableCat = categoryUtils.get("available-updates");
@ -727,7 +727,7 @@ add_task(async function testUpdatesShownOnLoad() {
await findUpdatesForAddonId(id);
let win = await loadInitialView("extension");
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
let updatesButton = categoryUtils.get("available-updates");
ok(!updatesButton.hidden, "The updates button is shown");
@ -752,7 +752,7 @@ add_task(async function testUpdatesShownOnLoad() {
info("Check that the updates section is hidden when re-opened");
await closeView(win);
win = await loadInitialView("extension");
categoryUtils = new CategoryUtilities(win.managerWindow);
categoryUtils = new CategoryUtilities(win);
updatesButton = categoryUtils.get("available-updates");
ok(updatesButton.hidden, "Available updates is hidden");

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

@ -131,8 +131,7 @@ async function init(startPage) {
await gCategoryUtilities.openType(startPage);
return gManagerWindow.document.getElementById("html-view-browser")
.contentDocument;
return gManagerWindow.document;
}
/* Test functions start here. */

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

@ -18,7 +18,7 @@ async function loadShortcutsView() {
// Load the theme view initially so we can verify that the category is switched
// to "extension" when the shortcuts view is loaded.
let win = await loadInitialView("theme");
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
is(
categoryUtils.getSelectedViewId(),
@ -86,11 +86,24 @@ add_task(async function testUpdatingCommands() {
);
}
// Load the about:addons shortcut view before verify that emitting
// the key events does trigger the expected extension commands.
// There is apparently a race (more likely to be triggered on an
// optimized build) between:
// - the new opened browser window to be ready to listen for the
// keyboard events that are expected to triggered one of the key
// in the extension keyset
// - and the test calling EventUtils.syntesizeKey to test that
// the expected extension command listener is notified.
//
// Loading the shortcut view before calling checkShortcut seems to be
// enough to consistently avoid that race condition.
let win = await loadShortcutsView();
// Check that the original shortcuts work.
await checkShortcut("commandOne", "7", { shiftKey: true, altKey: true });
await checkShortcut("commandTwo", "4", { altKey: true });
let win = await loadShortcutsView();
let doc = win.document;
let card = doc.querySelector(`.card[addon-id="${extension.id}"]`);

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

@ -5,8 +5,7 @@ async function loadShortcutsView() {
let managerWin = await open_manager(null);
managerWin.gViewController.loadView("addons://shortcuts/shortcuts");
await wait_for_view_load(managerWin);
return managerWin.document.getElementById("html-view-browser")
.contentDocument;
return managerWin.document;
}
async function closeShortcutsView(doc) {

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

@ -4,8 +4,7 @@ async function loadShortcutsView() {
let managerWin = await open_manager(null);
managerWin.gViewController.loadView("addons://shortcuts/shortcuts");
await wait_for_view_load(managerWin);
return managerWin.document.getElementById("html-view-browser")
.contentDocument;
return managerWin.document;
}
async function closeShortcutsView(doc) {

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

@ -40,7 +40,7 @@ function assertEnabledSideloadedExtensionElement(managerWindow, addonElement) {
ok(!toggleDisabled.checked, "toggle-disable isn't checked");
}
function clickEnableExtension(managerWindow, addonElement) {
function clickEnableExtension(addonElement) {
addonElement.querySelector('[action="toggle-disabled"]').click();
}
@ -80,11 +80,11 @@ add_task(async function test_theme_enable() {
// enable fresh installed theme
let manager = await open_manager("addons://list/theme");
let customTheme = get_addon_element(manager, CUSTOM_THEME_ID);
clickEnableExtension(manager, customTheme);
clickEnableExtension(customTheme);
// enable default theme again
let defaultTheme = get_addon_element(manager, DEFAULT_THEME_ID);
clickEnableExtension(manager, defaultTheme);
clickEnableExtension(defaultTheme);
let addon = await AddonManager.getAddonByID(CUSTOM_THEME_ID);
await close_manager(manager);
@ -128,7 +128,7 @@ add_task(async function test_sideloaded_extension_permissions_prompt() {
assertDisabledSideloadedExtensionElement(manager, addon);
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
clickEnableExtension(manager, addon);
clickEnableExtension(addon);
let panel = await popupPromise;
ok(PopupNotifications.isPanelOpen, "Permission popup should be visible");
@ -151,7 +151,7 @@ add_task(async function test_sideloaded_extension_permissions_prompt() {
assertEnabledSideloadedExtensionElement(manager, addon);
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
clickEnableExtension(manager, addon);
clickEnableExtension(addon);
panel = await popupPromise;
ok(PopupNotifications.isPanelOpen, "Permission popup should be visible");

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

@ -15,8 +15,7 @@ const testIdSuffix = "@tests.mozilla.org";
let gManagerWindow, xpi1, xpi2;
function htmlDoc() {
return gManagerWindow.document.getElementById("html-view-browser")
.contentDocument;
return gManagerWindow.document;
}
function get_list_item_count() {

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

@ -25,18 +25,13 @@ function testKeys(win, searchBox) {
searchBox.blur();
}
// Get a stack frame with the expected browser type.
const testHtmlKeys = (...args) => testKeys(...args);
const testXulKeys = (...args) => testKeys(...args);
add_task(async function testSearchBarKeyboardAccess() {
let win = await loadInitialView("extension");
let doc = win.document;
let searchBox = doc.querySelector("search-addons").input;
testHtmlKeys(win, searchBox);
testXulKeys(win.managerWindow, searchBox);
testKeys(win, searchBox);
await closeView(win);
});

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

@ -54,8 +54,7 @@ add_task(async function testClickingSidebarEntriesChangesView() {
add_task(async function testClickingSidebarPaddingNoChange() {
let win = await loadInitialView("theme");
let { managerWindow } = win;
let categoryUtils = new CategoryUtilities(managerWindow);
let categoryUtils = new CategoryUtilities(win);
let themeCategory = categoryUtils.get("theme");
let loadDetailView = async () => {
@ -64,7 +63,7 @@ add_task(async function testClickingSidebarPaddingNoChange() {
await loaded;
is(
managerWindow.gViewController.currentViewId,
win.gViewController.currentViewId,
`addons://detail/${THEME_ID}`,
"The detail view loaded"
);
@ -76,7 +75,7 @@ add_task(async function testClickingSidebarPaddingNoChange() {
EventUtils.synthesizeMouseAtCenter(themeCategory, {}, win);
await loaded;
is(
managerWindow.gViewController.currentViewId,
win.gViewController.currentViewId,
`addons://list/theme`,
"The detail view loaded"
);
@ -84,7 +83,7 @@ add_task(async function testClickingSidebarPaddingNoChange() {
// Confirm that clicking on the padding beside it does nothing.
await loadDetailView();
EventUtils.synthesizeMouse(themeCategory, -5, -5, {}, win);
ok(!managerWindow.gViewController.isLoading, "No view is loading");
ok(!win.gViewController.isLoading, "No view is loading");
await closeView(win);
});
@ -108,8 +107,6 @@ add_task(async function testKeyboardUsage() {
ok(!isFocusInCategories(), "Focus is not in the category list");
// Tab into the HTML browser.
await sendTabKey();
// Tab to the first focusable element.
await sendTabKey();
@ -148,7 +145,7 @@ add_task(async function testKeyboardUsage() {
await sendKey("VK_DOWN");
is(win.document.activeElement, pluginCategory, "Plugins is still focused");
ok(!win.managerWindow.gViewController.isLoading, "No view is loading");
ok(!win.gViewController.isLoading, "No view is loading");
loaded = waitForViewLoad(win);
await sendKey("VK_UP");

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

@ -193,7 +193,7 @@ add_task(async function testLocalesHiddenIfPreviousViewAndNoLocales() {
});
let win = await viewLoaded;
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
await TestUtils.waitForCondition(
() => categoryUtils.selectedCategory != "locale"
@ -206,7 +206,7 @@ add_task(async function testLocalesHiddenIfPreviousViewAndNoLocales() {
is(
categoryUtils.getSelectedViewId(),
win.managerWindow.gViewController.defaultViewId,
win.gViewController.defaultViewId,
"default view is selected"
);

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

@ -6,7 +6,7 @@
add_task(async function testCategoryRestore() {
let win = await loadInitialView("extension");
let utils = new CategoryUtilities(win.managerWindow);
let utils = new CategoryUtilities(win);
// Open the plugins category
await utils.openType("plugin");
@ -14,7 +14,7 @@ add_task(async function testCategoryRestore() {
// Re-open the manager
await closeView(win);
win = await loadInitialView();
utils = new CategoryUtilities(win.managerWindow);
utils = new CategoryUtilities(win);
is(
utils.selectedCategory,
@ -28,7 +28,7 @@ add_task(async function testCategoryRestore() {
// Re-open the manager
await closeView(win);
win = await loadInitialView();
utils = new CategoryUtilities(win.managerWindow);
utils = new CategoryUtilities(win);
is(
utils.selectedCategory,
@ -42,15 +42,15 @@ add_task(async function testCategoryRestore() {
add_task(async function testInvalidAddonType() {
let win = await loadInitialView("invalid");
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
is(
categoryUtils.getSelectedViewId(),
win.managerWindow.gViewController.defaultViewId,
win.gViewController.defaultViewId,
"default view is selected"
);
is(
win.managerWindow.gViewController.currentViewId,
win.managerWindow.gViewController.defaultViewId,
win.gViewController.currentViewId,
win.gViewController.defaultViewId,
"default view is shown"
);
@ -60,15 +60,15 @@ add_task(async function testInvalidAddonType() {
add_task(async function testInvalidViewId() {
let win = await loadInitialView("addons://invalid/view");
let categoryUtils = new CategoryUtilities(win.managerWindow);
let categoryUtils = new CategoryUtilities(win);
is(
categoryUtils.getSelectedViewId(),
win.managerWindow.gViewController.defaultViewId,
win.gViewController.defaultViewId,
"default view is selected"
);
is(
win.managerWindow.gViewController.currentViewId,
win.managerWindow.gViewController.defaultViewId,
win.gViewController.currentViewId,
win.gViewController.defaultViewId,
"default view is shown"
);

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

@ -19,9 +19,7 @@ AddonTestUtils.initMochitest(this);
function get_test_items() {
var items = {};
for (let item of gManagerWindow
.getHtmlBrowser()
.contentDocument.querySelectorAll("addon-card")) {
for (let item of gManagerWindow.document.querySelectorAll("addon-card")) {
items[item.getAttribute("addon-id")] = item;
}
@ -29,9 +27,7 @@ function get_test_items() {
}
function getHtmlElem(selector) {
return gManagerWindow
.getHtmlBrowser()
.contentDocument.querySelector(selector);
return gManagerWindow.document.querySelector(selector);
}
function getPrivateBrowsingBadge(card) {

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

@ -295,21 +295,28 @@ function check_all_in_list(aManager, aIds, aIgnoreExtras) {
}
}
// XXX(rpl): merge both get_addon_element and getAddonCard into a single helper function
function get_addon_element(aManager, aId) {
const win = aManager.getHtmlBrowser().contentWindow;
return getAddonCard(win, aId);
return getAddonCard(aManager, aId);
}
function getAddonCard(win, id) {
return win.document.querySelector(`addon-card[addon-id="${id}"]`);
}
function wait_for_view_load(
async function wait_for_view_load(
aManagerWindow,
aCallback,
aForceWait,
aLongerTimeout
) {
// Wait one tick to make sure that the microtask related to an
// async loadView call originated from outsite about:addons
// is already executing (otherwise isLoading would be still false
// and we wouldn't be waiting for that load before resolving
// the promise returned by this test helper function).
await Promise.resolve();
let p = new Promise(resolve => {
requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
@ -331,23 +338,11 @@ function wait_for_view_load(
}
function wait_for_manager_load(aManagerWindow, aCallback) {
let p = new Promise(resolve => {
if (!aManagerWindow.gIsInitializing) {
resolve(aManagerWindow);
return;
}
info("Waiting for initialization");
aManagerWindow.document.addEventListener(
"Initialized",
function() {
resolve(aManagerWindow);
},
{ once: true }
);
});
return log_callback(p, aCallback);
info("Waiting for initialization");
return log_callback(
aManagerWindow.promiseInitialized.then(() => aManagerWindow),
aCallback
);
}
function open_manager(
@ -537,22 +532,14 @@ async function install_addon(path, cb, pathPrefix = TESTROOT) {
}
function CategoryUtilities(aManagerWindow) {
this.window = aManagerWindow.getHtmlBrowser().contentWindow;
this.managerWindow = aManagerWindow;
this.window = aManagerWindow;
this.window.addEventListener("unload", () => (this.window = null), {
once: true,
});
this.managerWindow.addEventListener(
"unload",
() => (this.managerWindow = null),
{ once: true }
);
}
CategoryUtilities.prototype = {
window: null,
managerWindow: null,
get _categoriesBox() {
return this.window.document.querySelector("categories-box");
@ -571,7 +558,7 @@ CategoryUtilities.prototype = {
"Should not get selected category when manager window is not loaded"
);
let viewId = this.getSelectedViewId();
let view = this.managerWindow.gViewController.parseViewId(viewId);
let view = this.window.gViewController.parseViewId(viewId);
return view.type == "list" ? view.param : view.type;
},
@ -628,7 +615,7 @@ CategoryUtilities.prototype = {
EventUtils.synthesizeMouseAtCenter(categoryButton, {}, this.window);
// Use wait_for_view_load until all open_manager calls are gone.
return wait_for_view_load(this.managerWindow);
return wait_for_view_load(this.window);
},
openType(categoryType) {
@ -1631,27 +1618,18 @@ async function loadInitialView(type, opts) {
let loadCallbackDone = Promise.resolve();
if (opts && opts.loadCallback) {
// Make sure the HTML browser is loaded and pass its window to the callback
// function instead of the XUL window.
loadCallback = managerWindow => {
loadCallbackDone = managerWindow
.promiseHtmlBrowserLoaded()
.then(async browser => {
let win = browser.contentWindow;
win.managerWindow = managerWindow;
// Wait for the test code to finish running before proceeding.
await opts.loadCallback(win);
});
loadCallback = win => {
loadCallbackDone = (async () => {
// Wait for the test code to finish running before proceeding.
await opts.loadCallback(win);
})();
};
}
let managerWindow = await open_manager(null, null, loadCallback);
let browser = managerWindow.document.getElementById("html-view-browser");
let win = browser.contentWindow;
let win = await open_manager(null, null, loadCallback);
if (!opts || !opts.withAnimations) {
win.document.body.setAttribute("skip-animations", "");
}
win.managerWindow = managerWindow;
// Let any load callback code to run before the rest of the test continues.
await loadCallbackDone;
@ -1660,19 +1638,19 @@ async function loadInitialView(type, opts) {
}
function waitForViewLoad(win) {
return wait_for_view_load(win.managerWindow, undefined, true);
return wait_for_view_load(win, undefined, true);
}
function closeView(win) {
return close_manager(win.managerWindow);
return close_manager(win);
}
function switchView(win, type) {
return new CategoryUtilities(win.managerWindow).openType(type);
return new CategoryUtilities(win).openType(type);
}
function isCategoryVisible(win, type) {
return new CategoryUtilities(win.managerWindow).isTypeVisible(type);
return new CategoryUtilities(win).isTypeVisible(type);
}
function mockPromptService() {

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

@ -43,21 +43,17 @@ const EXT_SYSTEM_ADDON_ID = "test-system-addon@mochi.test";
const EXT_UNSUPPORTED_TYPE_ADDON_ID = "report-unsupported-type@mochi.test";
const THEME_NO_UNINSTALL_ID = "theme-without-perm-can-uninstall@mochi.test";
let gHtmlAboutAddonsWindow;
let gManagerWindow;
AddonTestUtils.initMochitest(this);
async function openAboutAddons(type = "extension") {
const win = await loadInitialView(type);
gHtmlAboutAddonsWindow = win;
gManagerWindow = win.managerWindow;
gManagerWindow = await loadInitialView(type);
}
async function closeAboutAddons() {
if (gHtmlAboutAddonsWindow) {
await closeView(gHtmlAboutAddonsWindow);
gHtmlAboutAddonsWindow = null;
if (gManagerWindow) {
await closeView(gManagerWindow);
gManagerWindow = null;
}
}
@ -314,14 +310,14 @@ const AbuseReportTestUtils = {
}
}
function cleanup() {
if (gHtmlAboutAddonsWindow) {
gHtmlAboutAddonsWindow.document.removeEventListener(
if (gManagerWindow) {
gManagerWindow.document.removeEventListener(
"abuse-report:new-message-bar",
listener
);
}
}
gHtmlAboutAddonsWindow.document.addEventListener(
gManagerWindow.document.addEventListener(
"abuse-report:new-message-bar",
listener
);
@ -331,10 +327,7 @@ const AbuseReportTestUtils = {
// Assert that the report action is hidden on the addon card
// for the given about:addons windows and extension id.
async assertReportActionHidden(gManagerWindow, extId) {
await gManagerWindow.promiseHtmlBrowserLoaded();
const { contentDocument: doc } = gManagerWindow.getHtmlBrowser();
let addonCard = doc.querySelector(
let addonCard = gManagerWindow.document.querySelector(
`addon-list addon-card[addon-id="${extId}"]`
);
ok(addonCard, `Got the addon-card for the ${extId} test extension`);
@ -364,7 +357,7 @@ const AbuseReportTestUtils = {
},
triggerNewReport(addonId, reportEntryPoint) {
gHtmlAboutAddonsWindow.openAbuseReport({ addonId, reportEntryPoint });
gManagerWindow.openAbuseReport({ addonId, reportEntryPoint });
},
triggerSubmit(reason, message) {
@ -379,7 +372,7 @@ const AbuseReportTestUtils = {
async openReport(addonId, reportEntryPoint = REPORT_ENTRY_POINT) {
// Close the current about:addons window if it has been leaved open from
// a previous test case failure.
if (gHtmlAboutAddonsWindow) {
if (gManagerWindow) {
await closeAboutAddons();
}

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

@ -12,7 +12,7 @@ const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/";
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR;
const PROMPT_URL = "chrome://global/content/commonDialog.xhtml";
const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xhtml";
const ADDONS_URL = "chrome://mozapps/content/extensions/aboutaddons.html";
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const PREF_INSTALL_REQUIREBUILTINCERTS =
"extensions.install.requireBuiltInCerts";