Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
// Like most of the mochitest-browser devtools test,
// on debug test machine, it takes about 50s to run the test.
requestLongerTimeout(4);
loadTestSubscript("head_devtools.js");
ChromeUtils.defineESModuleGetters(this, {
});
const DEVTOOLS_THEME_PREF = "devtools.theme";
/**
* This test file ensures that:
*
* - devtools.panels.themeName returns the correct value,
* both from a page and a panel.
* - devtools.panels.onThemeChanged fires for theme changes,
* both from a page and a panel.
* - devtools.panels.create is able to create a devtools panel.
*/
function createPage(jsScript, bodyText = "") {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
${bodyText}
<script src="${jsScript}"></script>
</body>
</html>`;
}
async function test_theme_name(testWithPanel = false) {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
);
function switchTheme(theme) {
const waitforThemeChanged = gDevTools.once("theme-changed");
Preferences.set(DEVTOOLS_THEME_PREF, theme);
return waitforThemeChanged;
}
async function testThemeSwitching(extension, locations = ["page"]) {
for (let newTheme of ["dark", "light"]) {
await switchTheme(newTheme);
for (let location of locations) {
is(
await extension.awaitMessage(`devtools_theme_changed_${location}`),
newTheme,
`The onThemeChanged event listener fired for the ${location}.`
);
is(
await extension.awaitMessage(`current_theme_${location}`),
newTheme,
`The current theme is reported as expected for the ${location}.`
);
}
}
}
async function devtools_page(createPanel) {
if (createPanel) {
await browser.devtools.panels.create(
"Test Panel Theme",
"fake-icon.png",
"devtools_panel.html"
);
}
browser.devtools.panels.onThemeChanged.addListener(themeName => {
browser.test.sendMessage("devtools_theme_changed_page", themeName);
browser.test.sendMessage(
"current_theme_page",
browser.devtools.panels.themeName
);
});
browser.test.sendMessage(
"initial_theme_page",
browser.devtools.panels.themeName
);
}
async function devtools_panel() {
browser.devtools.panels.onThemeChanged.addListener(themeName => {
browser.test.sendMessage("devtools_theme_changed_panel", themeName);
browser.test.sendMessage(
"current_theme_panel",
browser.devtools.panels.themeName
);
});
browser.test.sendMessage(
"initial_theme_panel",
browser.devtools.panels.themeName
);
}
let files = {
"devtools_page.html": createPage("devtools_page.js"),
"devtools_page.js": `(${devtools_page})(${testWithPanel})`,
};
if (testWithPanel) {
files["devtools_panel.js"] = devtools_panel;
files["devtools_panel.html"] = createPage(
"devtools_panel.js",
"Test Panel Theme"
);
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
devtools_page: "devtools_page.html",
},
files,
});
// Ensure that the initial value of the devtools theme is "light".
await SpecialPowers.pushPrefEnv({ set: [[DEVTOOLS_THEME_PREF, "light"]] });
registerCleanupFunction(async function () {
await SpecialPowers.popPrefEnv();
});
await extension.startup();
const toolbox = await openToolboxForTab(tab);
info("Waiting initial theme from devtools_page");
is(
await extension.awaitMessage("initial_theme_page"),
"light",
"The initial theme is reported as expected."
);
if (testWithPanel) {
let toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
1,
"Got the expected number of toolbox specific panel registered."
);
let panelId = toolboxAdditionalTools[0].id;
await gDevTools.showToolboxForTab(tab, { toolId: panelId });
is(
await extension.awaitMessage("initial_theme_panel"),
"light",
"The initial theme is reported as expected from a devtools panel."
);
await testThemeSwitching(extension, ["page", "panel"]);
} else {
await testThemeSwitching(extension);
}
await closeToolboxForTab(tab);
await extension.unload();
BrowserTestUtils.removeTab(tab);
}
add_task(async function test_devtools_page_theme() {
await test_theme_name(false);
});
add_task(async function test_devtools_panel_theme() {
await test_theme_name(true);
});
add_task(async function test_devtools_page_panels_create() {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
);
async function devtools_page() {
const result = {
devtoolsPageTabId: browser.devtools.inspectedWindow.tabId,
panelCreated: 0,
panelShown: 0,
panelHidden: 0,
};
try {
const panel = await browser.devtools.panels.create(
"Test Panel Create",
"fake-icon.png",
"devtools_panel.html"
);
result.panelCreated++;
panel.onShown.addListener(contentWindow => {
result.panelShown++;
browser.test.assertEq(
"complete",
contentWindow.document.readyState,
"Got the expected 'complete' panel document readyState"
);
browser.test.assertEq(
"test_panel_global",
contentWindow.TEST_PANEL_GLOBAL,
"Got the expected global in the panel contentWindow"
);
browser.test.sendMessage("devtools_panel_shown", result);
});
panel.onHidden.addListener(() => {
result.panelHidden++;
browser.test.sendMessage("devtools_panel_hidden", result);
});
browser.test.sendMessage("devtools_panel_created");
} catch (err) {
// Make the test able to fail fast when it is going to be a failure.
browser.test.sendMessage("devtools_panel_created");
throw err;
}
}
function devtools_panel() {
// Set a property in the global and check that it is defined
// and accessible from the devtools_page when the panel.onShown
// event has been received.
window.TEST_PANEL_GLOBAL = "test_panel_global";
browser.test.sendMessage(
"devtools_panel_inspectedWindow_tabId",
browser.devtools.inspectedWindow.tabId
);
}
const longPrefix = new Array(80).fill("x").join("");
// Extension ID includes "inspector" to verify Bug 1474379 doesn't regress.
const EXTENSION_ID = `${longPrefix}-inspector@create-devtools-panel.test`;
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
devtools_page: "devtools_page.html",
browser_specific_settings: {
gecko: { id: EXTENSION_ID },
},
},
files: {
"devtools_page.html": createPage("devtools_page.js"),
"devtools_page.js": devtools_page,
"devtools_panel.html": createPage(
"devtools_panel.js",
"Test Panel Create"
),
"devtools_panel.js": devtools_panel,
},
});
await extension.startup();
const extensionPrefBranch = `devtools.webextensions.${EXTENSION_ID}.`;
const extensionPrefName = `${extensionPrefBranch}enabled`;
let prefBranch = Services.prefs.getBranch(extensionPrefBranch);
ok(
prefBranch,
"The preference branch for the extension should have been created"
);
is(
prefBranch.getBoolPref("enabled", false),
true,
"The 'enabled' bool preference for the extension should be initially true"
);
// Get the devtools panel info for the first item in the toolbox additional tools array.
const getPanelInfo = toolbox => {
let toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
1,
"Got the expected number of toolbox specific panel registered."
);
return toolboxAdditionalTools[0];
};
// Test the devtools panel shown and hide events.
const testPanelShowAndHide = async ({
tab,
panelId,
isFirstPanelLoad,
expectedResults,
}) => {
info("Wait Addon Devtools Panel to be shown");
await gDevTools.showToolboxForTab(tab, { toolId: panelId });
const { devtoolsPageTabId } = await extension.awaitMessage(
"devtools_panel_shown"
);
// If the panel is loaded for the first time, we expect to also
// receive the test messages and assert that both the page and the panel
// have the same devtools.inspectedWindow.tabId value.
if (isFirstPanelLoad) {
const devtoolsPanelTabId = await extension.awaitMessage(
"devtools_panel_inspectedWindow_tabId"
);
is(
devtoolsPanelTabId,
devtoolsPageTabId,
"Got the same devtools.inspectedWindow.tabId from devtools page and panel"
);
}
info("Wait Addon Devtools Panel to be shown");
await gDevTools.showToolboxForTab(tab, { toolId: "testBlankPanel" });
const results = await extension.awaitMessage("devtools_panel_hidden");
// We already checked the tabId, remove it from the results, so that we can check
// the remaining properties using a single Assert.deepEqual.
delete results.devtoolsPageTabId;
Assert.deepEqual(
results,
expectedResults,
"Got the expected number of created panels and shown/hidden events"
);
};
// Test the extension devtools_page enabling/disabling through the related
// about:config preference.
const testExtensionDevToolsPref = async ({
prefValue,
toolbox,
oldPanelId,
}) => {
if (!prefValue) {
// Test that the extension devtools_page is shutting down when the related
// about:config preference has been set to false, and the panel on its left
// is being selected.
info(
"Turning off the extension devtools page from its about:config preference"
);
let waitToolSelected = toolbox.once("select");
Services.prefs.setBoolPref(extensionPrefName, false);
const selectedTool = await waitToolSelected;
isnot(
selectedTool,
oldPanelId,
"Expect a different panel to be selected"
);
let toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
0,
"Extension devtools panel unregistered"
);
is(
toolbox.visibleAdditionalTools.filter(toolId => toolId == oldPanelId)
.length,
0,
"Removed panel should not be listed in the visible additional tools"
);
} else {
// Test that the extension devtools_page and panel are being created again when
// the related about:config preference has been set to true.
info(
"Turning on the extension devtools page from its about:config preference"
);
Services.prefs.setBoolPref(extensionPrefName, true);
await extension.awaitMessage("devtools_panel_created");
let toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
1,
"Got one extension devtools panel registered"
);
let newPanelId = getPanelInfo(toolbox).id;
is(
toolbox.visibleAdditionalTools.filter(toolId => toolId == newPanelId)
.length,
1,
"Extension panel is listed in the visible additional tools"
);
}
};
// Wait that the devtools_page has created its devtools panel and retrieve its
// panel id.
let toolbox = await openToolboxForTab(tab);
await extension.awaitMessage("devtools_panel_created");
let panelId = getPanelInfo(toolbox).id;
info("Test panel show and hide - first cycle");
await testPanelShowAndHide({
tab,
panelId,
isFirstPanelLoad: true,
expectedResults: {
panelCreated: 1,
panelShown: 1,
panelHidden: 1,
},
});
info("Test panel show and hide - second cycle");
await testPanelShowAndHide({
tab,
panelId,
isFirstPanelLoad: false,
expectedResults: {
panelCreated: 1,
panelShown: 2,
panelHidden: 2,
},
});
// Go back to the extension devtools panel.
await gDevTools.showToolboxForTab(tab, { toolId: panelId });
await extension.awaitMessage("devtools_panel_shown");
// Check that the aria-label has been set on the devtools panel.
const panelFrame = toolbox.doc.getElementById(
`toolbox-panel-iframe-${panelId}`
);
const panelInfo = getPanelInfo(toolbox);
ok(
panelInfo.panelLabel && !!panelInfo.panelLabel.length,
"Expect the registered panel to include a non empty panelLabel property"
);
is(
panelFrame && panelFrame.getAttribute("aria-label"),
panelInfo.panelLabel,
"Got the expected aria-label on the extension panel frame"
);
// Turn off the extension devtools page using the preference that enable/disable the
// devtools page for a given installed WebExtension.
await testExtensionDevToolsPref({
toolbox,
prefValue: false,
oldPanelId: panelId,
});
// Close and Re-open the toolbox to verify that the toolbox doesn't load the
// devtools_page and the devtools panel.
info("Re-open the toolbox and expect no extension devtools panel");
await closeToolboxForTab(tab);
toolbox = await openToolboxForTab(tab);
let toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
0,
"Got no extension devtools panel on the opened toolbox as expected."
);
// Close and Re-open the toolbox to verify that the toolbox does load the
// devtools_page and the devtools panel again.
info("Restart the toolbox and enable the extension devtools panel");
await closeToolboxForTab(tab);
toolbox = await openToolboxForTab(tab);
// Turn the addon devtools panel back on using the preference that enable/disable the
// devtools page for a given installed WebExtension.
await testExtensionDevToolsPref({
toolbox,
prefValue: true,
});
// Test devtools panel is loaded correctly after being toggled and
// devtools panel events has been fired as expected.
panelId = getPanelInfo(toolbox).id;
info("Test panel show and hide - after disabling/enabling devtools_page");
await testPanelShowAndHide({
tab,
panelId,
isFirstPanelLoad: true,
expectedResults: {
panelCreated: 1,
panelShown: 1,
panelHidden: 1,
},
});
await closeToolboxForTab(tab);
await extension.unload();
// Verify that the extension preference branch has been removed once the extension
// has been uninstalled.
prefBranch = Services.prefs.getBranch(extensionPrefBranch);
is(
prefBranch.getPrefType("enabled"),
prefBranch.PREF_INVALID,
"The preference branch for the extension should have been removed"
);
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_devtools_page_panels_switch_toolbox_host() {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
);
function devtools_panel() {
const hasDevToolsAPINamespace = "devtools" in browser;
browser.test.sendMessage("devtools_panel_loaded", {
hasDevToolsAPINamespace,
panelLoadedURL: window.location.href,
});
}
async function devtools_page() {
const panel = await browser.devtools.panels.create(
"Test Panel Switch Host",
"fake-icon.png",
"devtools_panel.html"
);
panel.onShown.addListener(panelWindow => {
browser.test.sendMessage(
"devtools_panel_shown",
panelWindow.location.href
);
});
panel.onHidden.addListener(() => {
browser.test.sendMessage("devtools_panel_hidden");
});
browser.test.sendMessage("devtools_panel_created");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
devtools_page: "devtools_page.html",
},
files: {
"devtools_page.html": createPage("devtools_page.js"),
"devtools_page.js": devtools_page,
"devtools_panel.html": createPage("devtools_panel.js", "DEVTOOLS PANEL"),
"devtools_panel.js": devtools_panel,
},
});
await extension.startup();
let toolbox = await openToolboxForTab(tab);
await extension.awaitMessage("devtools_panel_created");
const toolboxAdditionalTools = toolbox.getAdditionalTools();
is(
toolboxAdditionalTools.length,
1,
"Got the expected number of toolbox specific panel registered."
);
const panelDef = toolboxAdditionalTools[0];
const panelId = panelDef.id;
info("Selecting the addon devtools panel");
await gDevTools.showToolboxForTab(tab, { toolId: panelId });
info("Wait for the panel to show and load for the first time");
const panelShownURL = await extension.awaitMessage("devtools_panel_shown");
const { panelLoadedURL, hasDevToolsAPINamespace } =
await extension.awaitMessage("devtools_panel_loaded");
is(
panelShownURL,
panelLoadedURL,
"Got the expected panel URL on the first load"
);
ok(
hasDevToolsAPINamespace,
"The devtools panel has the devtools API on the first load"
);
const originalToolboxHostType = toolbox.hostType;
info("Switch the toolbox from docked on bottom to docked on right");
toolbox.switchHost("right");
info(
"Wait for the panel to emit hide, show and load messages once docked on side"
);
await extension.awaitMessage("devtools_panel_hidden");
const dockedOnSideShownURL = await extension.awaitMessage(
"devtools_panel_shown"
);
is(
dockedOnSideShownURL,
panelShownURL,
"Got the expected panel url once the panel shown event has been emitted on toolbox host changed"
);
const dockedOnSideLoaded = await extension.awaitMessage(
"devtools_panel_loaded"
);
is(
dockedOnSideLoaded.panelLoadedURL,
panelShownURL,
"Got the expected panel url once the panel has been reloaded on toolbox host changed"
);
ok(
dockedOnSideLoaded.hasDevToolsAPINamespace,
"The devtools panel has the devtools API once the toolbox host has been changed"
);
info("Switch the toolbox from docked on bottom to the original dock mode");
toolbox.switchHost(originalToolboxHostType);
info(
"Wait for the panel test messages once toolbox dock mode has been restored"
);
await extension.awaitMessage("devtools_panel_hidden");
await extension.awaitMessage("devtools_panel_shown");
await extension.awaitMessage("devtools_panel_loaded");
await closeToolboxForTab(tab);
await extension.unload();
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_devtools_page_invalid_panel_urls() {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
);
async function devtools_page() {
const matchInvalidPanelURL = /must be a relative URL/;
const matchInvalidIconURL =
/be one of \[""\], or match the format "strictRelativeUrl"/;
// Invalid panel urls (validated by the schema wrappers, throws on invalid urls).
const invalid_panels = [
{
panel: "about:about",
icon: "icon.png",
expectError: matchInvalidPanelURL,
},
{
panel: "about:addons",
icon: "icon.png",
expectError: matchInvalidPanelURL,
},
{
icon: "icon.png",
expectError: matchInvalidPanelURL,
},
// Invalid icon urls (validated inside the API method because of the empty icon string
// which have to be resolved to the default icon, reject the returned promise).
{
panel: "panel.html",
icon: "about:about",
expectError: matchInvalidIconURL,
},
{
panel: "panel.html",
expectError: matchInvalidIconURL,
},
];
const valid_panels = [
{ panel: "panel.html", icon: "icon.png" },
{ panel: "./panel.html", icon: "icon.png" },
{ panel: "/panel.html", icon: "icon.png" },
{ panel: "/panel.html", icon: "" },
];
let valid_panels_length = valid_panels.length;
const test_cases = [].concat(invalid_panels, valid_panels);
browser.test.onMessage.addListener(async msg => {
if (msg !== "start_test_panel_create") {
return;
}
for (let { panel, icon, expectError } of test_cases) {
browser.test.log(
`Testing devtools.panels.create for ${JSON.stringify({
panel,
icon,
})}`
);
if (expectError) {
// Verify that invalid panel urls throw.
browser.test.assertThrows(
() => browser.devtools.panels.create("Test Panel", icon, panel),
expectError,
"Got the expected rejection on creating a devtools panel with " +
`panel url ${panel} and icon ${icon}`
);
} else {
// Verify that with valid panel and icon urls the panel is created and loaded
// as expected.
try {
const pane = await browser.devtools.panels.create(
"Test Panel",
icon,
panel
);
valid_panels_length--;
// Wait the panel to be loaded.
const oncePanelLoaded = new Promise(resolve => {
pane.onShown.addListener(paneWin => {
browser.test.assertTrue(
paneWin.location.href.endsWith("/panel.html"),
`The panel has loaded the expected extension URL with ${panel}`
);
resolve();
});
});
// Ask the privileged code to select the last created panel.
const done = valid_panels_length === 0;
browser.test.sendMessage("select-devtools-panel", done);
await oncePanelLoaded;
} catch (err) {
browser.test.fail(
"Unexpected failure on creating a devtools panel with " +
`panel url ${panel} and icon ${icon}`
);
throw err;
}
}
}
browser.test.sendMessage("test_invalid_devtools_panel_urls_done");
});
browser.test.sendMessage("devtools_page_ready");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
devtools_page: "devtools_page.html",
icons: {
32: "icon.png",
},
},
files: {
"devtools_page.html": createPage("devtools_page.js"),
"devtools_page.js": devtools_page,
"panel.html": createPage("panel.js", "DEVTOOLS PANEL"),
"panel.js": "",
"icon.png": imageBuffer,
"default-icon.png": imageBuffer,
},
});
await extension.startup();
let toolbox = await openToolboxForTab(tab);
info("developer toolbox opened");
await extension.awaitMessage("devtools_page_ready");
extension.sendMessage("start_test_panel_create");
let done = false;
while (!done) {
info("Waiting test extension request to select the last created panel");
done = await extension.awaitMessage("select-devtools-panel");
const toolboxAdditionalTools = toolbox.getAdditionalTools();
const lastTool = toolboxAdditionalTools[toolboxAdditionalTools.length - 1];
gDevTools.showToolboxForTab(tab, { toolId: lastTool.id });
info("Last created panel selected");
}
await extension.awaitMessage("test_invalid_devtools_panel_urls_done");
await closeToolboxForTab(tab);
await extension.unload();
BrowserTestUtils.removeTab(tab);
});