Bug 1233127 - Use createProcessingIntruction instead of addon-sdk to load stylesheets in tools;r=pbrosset
--- a/devtools/client/framework/test/browser_toolbox_theme_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_theme_registration.js
@@ -1,113 +1,98 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/framework/test/";
var toolbox;
-function test()
-{
- gBrowser.selectedTab = gBrowser.addTab();
- let target = TargetFactory.forTab(gBrowser.selectedTab);
-
- gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
- gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
- gDevTools.showToolbox(target).then(testRegister);
- }, true);
-
- content.location = "data:text/html,test for dynamically registering and unregistering themes";
-}
+add_task(function* themeRegistration() {
+ let tab = yield addTab("data:text/html,test for dynamically registering and unregistering themes");
+ let target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target);
-function testRegister(aToolbox)
-{
- toolbox = aToolbox
- gDevTools.once("theme-registered", themeRegistered);
+ let themeId = yield new Promise(resolve => {
+ gDevTools.once("theme-registered", (e,themeId) => {
+ resolve(themeId);
+ });
- gDevTools.registerTheme({
- id: "test-theme",
- label: "Test theme",
- stylesheets: [CHROME_URL + "doc_theme.css"],
- classList: ["theme-test"],
+ gDevTools.registerTheme({
+ id: "test-theme",
+ label: "Test theme",
+ stylesheets: [CHROME_URL + "doc_theme.css"],
+ classList: ["theme-test"],
+ });
});
-}
-function themeRegistered(event, themeId)
-{
is(themeId, "test-theme", "theme-registered event handler sent theme id");
ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
+});
- // Test that new theme appears in the Options panel
- let target = TargetFactory.forTab(gBrowser.selectedTab);
- gDevTools.showToolbox(target, "options").then(() => {
- let panel = toolbox.getCurrentPanel();
- let doc = panel.panelWin.frameElement.contentDocument;
- let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
+add_task(function* themeInOptionsPanel() {
- ok(themeOption, "new theme exists in the Options panel");
+ yield toolbox.selectTool("options");
- // Apply the new theme.
- applyTheme();
- });
-}
-
-function applyTheme()
-{
+ let panel = toolbox.getCurrentPanel();
let panelWin = toolbox.getCurrentPanel().panelWin;
let doc = panelWin.frameElement.contentDocument;
+ let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
+
+ ok(themeOption, "new theme exists in the Options panel");
+
let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
let color = panelWin.getComputedStyle(testThemeOption).color;
isnot(color, "rgb(255, 0, 0)", "style unapplied");
// Select test theme.
testThemeOption.click();
+ info("Waiting for theme to finish loading");
+ yield new Promise(resolve => {
+ panelWin.addEventListener("theme-switch-complete", function switchDone() {
+ panelWin.removeEventListener("theme-switch-complete", switchDone);
+ resolve();
+ });
+ });
+
color = panelWin.getComputedStyle(testThemeOption).color;
is(color, "rgb(255, 0, 0)", "style applied");
// Select light theme
lightThemeOption.click();
+ info("Waiting for theme to finish loading");
+ yield new Promise(resolve => {
+ panelWin.addEventListener("theme-switch-complete", function switchDone() {
+ panelWin.removeEventListener("theme-switch-complete", switchDone);
+ resolve();
+ });
+ });
+
color = panelWin.getComputedStyle(testThemeOption).color;
isnot(color, "rgb(255, 0, 0)", "style unapplied");
// Select test theme again.
testThemeOption.click();
+});
- // Then unregister the test theme.
- testUnregister();
-}
-
-function testUnregister()
-{
+add_task(function* themeUnregistration() {
gDevTools.unregisterTheme("test-theme");
ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
let panelWin = toolbox.getCurrentPanel().panelWin;
let doc = panelWin.frameElement.contentDocument;
let themeBox = doc.querySelector("#devtools-theme-box");
// The default light theme must be selected now.
is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
"theme light must be selected");
-
- // Make sure the tab-attaching process is done before we destroy the toolbox.
- let target = TargetFactory.forTab(gBrowser.selectedTab);
- let actor = target.activeTab.actor;
- target.client.attachTab(actor, (response) => {
- cleanup();
- });
-}
+});
-function cleanup()
-{
- toolbox.destroy().then(function() {
- toolbox = null;
- gBrowser.removeCurrentTab();
- finish();
- });
-}
+add_task(function* cleanup() {
+ yield toolbox.destroy();
+ toolbox = null;
+});
--- a/devtools/client/shared/test/browser_theme_switching.js
+++ b/devtools/client/shared/test/browser_theme_switching.js
@@ -2,25 +2,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var toolbox;
add_task(function*() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target);
- let root = toolbox.frame.contentDocument.documentElement;
+ let doc = toolbox.frame.contentDocument;
+ let root = doc.documentElement;
let platform = root.getAttribute("platform");
let expectedPlatform = getPlatform();
is(platform, expectedPlatform, ":root[platform] is correct");
let theme = Services.prefs.getCharPref("devtools.theme");
let className = "theme-" + theme;
- ok(root.classList.contains(className), ":root has " + className + " class (current theme)");
+ ok(root.classList.contains(className),":root has " + className + " class (current theme)");
+
+ // Convert the xpath result into an array of strings
+ // like `href="{URL}" type="text/css"`
+ let sheetsIterator = doc.evaluate("processing-instruction('xml-stylesheet')",
+ doc, null, XPathResult.ANY_TYPE, null);
+ let sheetsInDOM = [];
+ let sheet;
+ while (sheet = sheetsIterator.iterateNext()) {
+ sheetsInDOM.push(sheet.data);
+ }
+
+ let sheetsFromTheme = gDevTools.getThemeDefinition(theme).stylesheets;
+ info ("Checking for existence of " + sheetsInDOM.length + " sheets");
+ for (let sheet of sheetsFromTheme) {
+ ok(sheetsInDOM.some(s=>s.includes(sheet)), "There is a stylesheet for " + sheet);
+ }
yield toolbox.destroy();
});
function getPlatform() {
let {OS} = Services.appinfo;
if (OS == "WINNT") {
return "win";
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -1,78 +1,111 @@
/* 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/. */
(function() {
- const DEVTOOLS_SKIN_URL = "chrome://devtools/skin/";
+ const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-light.css";
let documentElement = document.documentElement;
+ let devtoolsStyleSheets = new WeakMap();
function forceStyle() {
let computedStyle = window.getComputedStyle(documentElement);
if (!computedStyle) {
// Null when documentElement is not ready. This method is anyways not
// required then as scrollbars would be in their state without flushing.
return;
}
let display = computedStyle.display; // Save display value
documentElement.style.display = "none";
window.getComputedStyle(documentElement).display; // Flush
documentElement.style.display = display; // Restore
}
+ /*
+ * Append a new processing instruction and return an object with
+ * - styleSheet: DOMNode
+ * - loadPromise: Promise that resolves once the sheets loads or errors
+ */
+ function appendStyleSheet(url) {
+ let styleSheetAttr = `href="${url}" type="text/css"`;
+ let styleSheet = document.createProcessingInstruction(
+ "xml-stylesheet", styleSheetAttr);
+ let loadPromise = new Promise(resolve => {
+ styleSheet.addEventListener("load", function onload() {
+ styleSheet.removeEventListener("load", onload);
+ resolve();
+ });
+ styleSheet.addEventListener("error", function onerror() {
+ styleSheet.removeEventListener("error", onerror);
+ resolve();
+ });
+ });
+ document.insertBefore(styleSheet, documentElement);
+ return {styleSheet,loadPromise};
+ }
+
+ /*
+ * Notify the window that a theme switch finished so tests can check the DOM
+ */
+ function notifyWindow() {
+ window.dispatchEvent(new CustomEvent("theme-switch-complete", {}));
+ }
+
+ /*
+ * Apply all the sheets from `newTheme` and remove all of the sheets
+ * from `oldTheme`
+ */
function switchTheme(newTheme, oldTheme) {
if (newTheme === oldTheme) {
return;
}
let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
// Unload all theme stylesheets related to the old theme.
if (oldThemeDef) {
- for (let url of oldThemeDef.stylesheets) {
- StylesheetUtils.removeSheet(window, url, "author");
+ for (let sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
+ sheet.remove();
}
}
// Load all stylesheets associated with the new theme.
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
// The theme might not be available anymore (e.g. uninstalled)
// Use the default one.
if (!newThemeDef) {
newThemeDef = gDevTools.getThemeDefinition("light");
}
+ // Store the sheets in a WeakMap for access later when the theme gets
+ // unapplied. It's hard to query for processing instructions so this
+ // is an easy way to access them later without storing a property on
+ // the window
+ devtoolsStyleSheets.set(newThemeDef, []);
+
+ let loadEvents = [];
for (let url of newThemeDef.stylesheets) {
- StylesheetUtils.loadSheet(window, url, "author");
+ let {styleSheet,loadPromise} = appendStyleSheet(url);
+ devtoolsStyleSheets.get(newThemeDef).push(styleSheet);
+ loadEvents.push(loadPromise);
}
// Floating scroll-bars like in OSX
let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService)
.hiddenDOMWindow;
// TODO: extensions might want to customize scrollbar styles too.
if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
- let scrollbarsUrl = Services.io.newURI(
- DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
-
if (newTheme == "dark") {
- StylesheetUtils.loadSheet(
- window,
- scrollbarsUrl,
- "agent"
- );
+ StylesheetUtils.loadSheet(window, SCROLLBARS_URL, "agent");
} else if (oldTheme == "dark") {
- StylesheetUtils.removeSheet(
- window,
- scrollbarsUrl,
- "agent"
- );
+ StylesheetUtils.removeSheet(window, SCROLLBARS_URL, "agent");
}
forceStyle();
}
if (oldThemeDef) {
for (let name of oldThemeDef.classList) {
documentElement.classList.remove(name);
}
@@ -87,26 +120,27 @@
}
if (newThemeDef.onApply) {
newThemeDef.onApply(window, oldTheme);
}
// Final notification for further theme-switching related logic.
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
+
+ Promise.all(loadEvents).then(notifyWindow);
}
function handlePrefChange(event, data) {
if (data.pref == "devtools.theme") {
switchTheme(data.newValue, data.oldValue);
}
}
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const StylesheetUtils = require("sdk/stylesheet/utils");
let os;
let platform = navigator.platform;
if (platform.startsWith("Win")) {