Bug 1169179 - Run mozscreenshots as a mochitest-browser-chrome test. r=felipe,glandium

This commit is contained in:
Matthew Noorenberghe 2016-01-19 22:40:34 -08:00
parent 97b9c00329
commit 244a926991
25 changed files with 1396 additions and 0 deletions

View File

@ -26,5 +26,9 @@ DIRS += [
if CONFIG['MAKENSISU']: if CONFIG['MAKENSISU']:
DIRS += ['installer/windows'] DIRS += ['installer/windows']
TEST_DIRS += [
'tools/mozscreenshots',
]
DIST_SUBDIR = 'browser' DIST_SUBDIR = 'browser'
export('DIST_SUBDIR') export('DIST_SUBDIR')

View File

@ -0,0 +1,7 @@
[DEFAULT]
subsuite = screenshots
support-files =
head.js
[browser_screenshots.js]
tags = screenshots

View File

@ -0,0 +1,18 @@
/* 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/. */
"use strict";
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
add_task(function* test() {
let { TestRunner } = Cu.import("chrome://mozscreenshots/content/TestRunner.jsm", {});
let sets = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
let setsEnv = env.get("MOZSCREENSHOTS_SETS");
if (setsEnv) {
sets = setsEnv.trim().split(",");
}
yield TestRunner.start(sets);
});

View File

@ -0,0 +1,20 @@
/* 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/. */
"use strict";
const {AddonWatcher} = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
let TestRunner;
function setup() {
requestLongerTimeout(10);
info("Checking for mozscreenshots extension");
AddonManager.getAddonByID("mozscreenshots@mozilla.org", function(aAddon) {
isnot(aAddon, null, "The mozscreenshots extension should be installed");
AddonWatcher.ignoreAddonPermanently(aAddon.id);
});
}
add_task(setup);

View File

@ -0,0 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += ['browser.ini']
TEST_DIRS += [
'mozscreenshots/extension',
]

View File

@ -0,0 +1,12 @@
# 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/.
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
XPI_PKGNAME = mozscreenshots@mozilla.org
include $(topsrcdir)/config/rules.mk
libs::
(cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - $(XPI_NAME)) | (cd $(TEST_EXTENSIONS_DIR) && tar -xf -)

View File

@ -0,0 +1,175 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Screenshot"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
const PREF_LOG_LEVEL = "extensions.mozscreenshots@mozilla.org.loglevel";
XPCOMUtils.defineLazyGetter(this, "log", () => {
let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
let consoleOptions = {
maxLogLevel: "info",
maxLogLevelPref: PREF_LOG_LEVEL,
prefix: "mozscreenshots",
};
return new ConsoleAPI(consoleOptions);
});
let Screenshot = {
_extensionPath: null,
_path: null,
_imagePrefix: "",
_imageExtension: ".png",
_screenshotFunction: null,
init(path, extensionPath, imagePrefix = "") {
this._path = path;
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
dir.initWithPath(this._path);
if (!dir.exists()) {
dir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
}
this._extensionPath = extensionPath;
this._imagePrefix = imagePrefix;
switch (Services.appinfo.OS) {
case "WINNT":
this._screenshotFunction = this._screenshotWindows;
break;
case "Darwin":
this._screenshotFunction = this._screenshotOSX;
break;
case "Linux":
this._screenshotFunction = this._screenshotLinux;
break;
default:
throw new Error("Unsupported operating system");
break;
}
},
_buildImagePath(baseName) {
return OS.Path.join(this._path, this._imagePrefix + baseName + this._imageExtension);
},
// Capture the whole screen using an external application.
captureExternal(filename) {
let imagePath = this._buildImagePath(filename);
return this._screenshotFunction(imagePath).then(() => {
log.debug("saved screenshot: " + filename);
});
},
///// helpers /////
_screenshotWindows(filename) {
return new Promise((resolve, reject) => {
let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
exe.append("screenshot.exe");
if (!exe.exists()) {
exe = this._extensionPath.QueryInterface(Ci.nsIFileURL).file;
exe.append("lib");
exe.append("screenshot.exe");
}
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(exe);
let args = [filename];
process.runAsync(args, args.length, this._processObserver(resolve, reject));
});
},
_screenshotOSX: Task.async(function*(filename) {
let screencapture = (windowID = null) => {
return new Promise((resolve, reject) => {
// Get the screencapture executable
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath("/usr/sbin/screencapture");
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(file);
// Run the process.
let args = ["-x", "-t", "png"];
// Darwin version number for OS X 10.6 is 10.x
if (windowID && Services.sysinfo.getProperty("version").indexOf("10.") !== 0) {
// Capture only that window on 10.7+
args.push("-l");
args.push(windowID);
}
args.push(filename);
process.runAsync(args, args.length, this._processObserver(resolve, reject));
});
};
function readWindowID() {
let decoder = new TextDecoder();
let promise = OS.File.read("/tmp/mozscreenshots-windowid");
return promise.then(function onSuccess(array) {
return decoder.decode(array);
});
}
let promiseWindowID = () => {
return new Promise((resolve, reject) => {
// Get the window ID of the application (assuming its front-most)
let osascript = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
osascript.initWithPath("/bin/bash");
let osascriptP = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
osascriptP.init(osascript);
let osaArgs = ["-c", "/usr/bin/osascript -e 'tell application (path to frontmost application as text) to set winID to id of window 1' > /tmp/mozscreenshots-windowid"];
osascriptP.runAsync(osaArgs, osaArgs.length, this._processObserver(resolve, reject));
});
};
yield promiseWindowID();
let windowID = yield readWindowID();
yield screencapture(windowID);
}),
_screenshotLinux(filename) {
return new Promise((resolve, reject) => {
let file = Services.dirsvc.get("GreBinD", Ci.nsIFile);
file.append("screentopng");
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(file);
let args = [filename];
process.runAsync(args, args.length, this._processObserver(resolve, reject));
});
},
_processObserver(resolve, reject) {
return {
observe(subject, topic, data) {
switch (topic) {
case "process-finished":
try {
// Wait 1s after process to resolve
setTimeout(resolve, 1000);
} catch (ex) {
reject(ex);
}
break;
default:
reject(topic);
break;
};
},
};
},
};

View File

@ -0,0 +1,256 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["TestRunner"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const defaultSetNames = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("chrome://mozscreenshots/content/Screenshot.jsm");
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
const PREF_LOG_LEVEL = "extensions.mozscreenshots@mozilla.org.loglevel";
XPCOMUtils.defineLazyGetter(this, "log", () => {
let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
let consoleOptions = {
maxLogLevel: "info",
maxLogLevelPref: PREF_LOG_LEVEL,
prefix: "mozscreenshots",
};
return new ConsoleAPI(consoleOptions);
});
this.TestRunner = {
combos: null,
completedCombos: 0,
currentComboIndex: 0,
_lastCombo: null,
_libDir: null,
init(extensionPath) {
let subDirs = ["mozscreenshots",
(new Date()).toISOString().replace(/:/g, "-") + "_" + Services.appinfo.OS];
let screenshotPath = FileUtils.getFile("TmpD", subDirs).path;
const MOZ_UPLOAD_DIR = env.get("MOZ_UPLOAD_DIR");
if (MOZ_UPLOAD_DIR) {
screenshotPath = MOZ_UPLOAD_DIR;
}
log.info("Saving screenshots to:", screenshotPath);
log.debug("TestRunner.init");
let screenshotPrefix = Services.appinfo.appBuildID + "_";
Screenshot.init(screenshotPath, extensionPath, screenshotPrefix);
this._libDir = extensionPath.QueryInterface(Ci.nsIFileURL).file.clone();
this._libDir.append("chrome");
this._libDir.append("mozscreenshots");
this._libDir.append("lib");
// Setup some prefs
Services.prefs.setCharPref("browser.aboutHomeSnippets.updateUrl", "data:");
Services.prefs.setCharPref("extensions.ui.lastCategory", "addons://list/extension");
// Don't let the caret blink since it causes false positives for image diffs
Services.prefs.setIntPref("ui.caretBlinkTime", -1);
},
/**
* Load specified sets, execute all combinations of them, and capture screenshots.
*/
start(setNames = null) {
setNames = setNames || defaultSetNames;
let sets = this.loadSets(setNames);
log.info(sets.length + " sets:", setNames);
this.combos = new LazyProduct(sets);
log.info(this.combos.length + " combinations");
this.currentComboIndex = this.completedCombos = 0;
this._lastCombo = null;
return Task.spawn(function* doStart() {
for (let i = 0; i < this.combos.length;
i++){
this.currentComboIndex = i;
yield* this._performCombo(this.combos.item(this.currentComboIndex));
}
log.info("Done: Completed " + this.completedCombos + " out of " +
this.combos.length + " configurations.");
this.cleanup();
}.bind(this));
},
/**
* Load sets of configurations from JSMs.
* @param {String[]} setNames - array of set names (e.g. ["Tabs", "WindowSize"].
* @return {Object[]} Array of sets containing `name` and `configurations` properties.
*/
loadSets(setNames) {
let sets = [];
for (let setName of setNames) {
try {
let imported = {};
Cu.import("chrome://mozscreenshots/content/configurations/" + setName + ".jsm",
imported);
imported[setName].init(this._libDir);
let configurationNames = Object.keys(imported[setName].configurations);
if (!configurationNames.length) {
throw new Error(setName + " has no configurations for this environment");
}
for (let config of configurationNames) {
// Automatically set the name property of the configuration object to
// its name from the configuration object.
imported[setName].configurations[config].name = config;
}
sets.push(imported[setName].configurations);
} catch (ex) {
log.error("Error loading set: " + setName);
log.error(ex);
throw ex;
}
}
return sets;
},
cleanup() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
while (gBrowser.tabs.length > 1) {
gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
}
gBrowser.unpinTab(gBrowser.selectedTab);
gBrowser.selectedBrowser.loadURI("data:text/html;charset=utf-8,<h1>Done!");
browserWindow.restore();
},
///// helpers /////
_performCombo: function*(combo) {
let paddedComboIndex = padLeft(this.currentComboIndex + 1, String(this.combos.length).length);
log.info("Combination " + paddedComboIndex + "/" + this.combos.length + ": " +
this._comboName(combo).substring(1));
function changeConfig(config) {
log.debug("calling " + config.name);
let promise = config.applyConfig();
log.debug("called " + config.name);
return promise;
}
try {
// First go through and actually apply all of the configs
for (let i = 0; i < combo.length; i++) {
let config = combo[i];
if (!this._lastCombo || config !== this._lastCombo[i]) {
log.debug("promising", config.name);
yield changeConfig(config);
}
}
// Update the lastCombo since it's now been applied regardless of whether it's accepted below.
log.debug("fulfilled all applyConfig so setting lastCombo.");
this._lastCombo = combo;
// Then ask configs if the current setup is valid. We can't can do this in
// the applyConfig methods of the config since it doesn't know what configs
// later in the loop will do that may invalidate the combo.
for (let i = 0; i < combo.length; i++) {
let config = combo[i];
// A configuration can specify an optional verifyConfig method to indicate
// if the current config is valid for a screenshot. This gets called even
// if the this config was used in the lastCombo since another config may
// have invalidated it.
if (config.verifyConfig) {
log.debug("checking if the combo is valid with", config.name);
yield config.verifyConfig();
}
}
} catch (ex) {
log.warn("\tskipped configuration: " + ex);
// Don't set lastCombo here so that we properly know which configurations
// need to be applied since the last screenshot
// Return so we don't take a screenshot.
return;
}
yield this._onConfigurationReady(combo);
},
_onConfigurationReady(combo) {
let delayedScreenshot = () => {
let filename = padLeft(this.currentComboIndex + 1,
String(this.combos.length).length) + this._comboName(combo);
return Screenshot.captureExternal(filename)
.then(() => {
this.completedCombos++;
});
};
log.debug("_onConfigurationReady");
return Task.spawn(delayedScreenshot);
},
_comboName(combo) {
return combo.reduce(function(a, b) {
return a + "_" + b.name;
}, "");
},
};
/**
* Helper to lazily compute the Cartesian product of all of the sets of configurations.
**/
function LazyProduct(sets) {
/**
* An entry for each set with the value being:
* [the number of permutations of the sets with lower index,
* the number of items in the set at the index]
*/
this.sets = sets;
this.lookupTable = [];
let combinations = 1;
for (let i = this.sets.length - 1; i >= 0; i--) {
let set = this.sets[i];
let setLength = Object.keys(set).length;
this.lookupTable[i] = [combinations, setLength];
combinations *= setLength;
}
}
LazyProduct.prototype = {
get length() {
let last = this.lookupTable[0];
if (!last)
return 0;
return last[0] * last[1];
},
item(n) {
// For set i, get the item from the set with the floored value of
// (n / the number of permutations of the sets already chosen from) modulo the length of set i
let result = [];
for (let i = this.sets.length - 1; i >= 0; i--) {
let priorCombinations = this.lookupTable[i][0];
let setLength = this.lookupTable[i][1];
let keyIndex = Math.floor(n / priorCombinations) % setLength;
let keys = Object.keys(this.sets[i]);
result[i] = this.sets[i][keys[keyIndex]];
}
return result;
},
};
function padLeft(number, width, padding = "0") {
return padding.repeat(Math.max(0, width - String(number).length)) + number;
}

View File

@ -0,0 +1,72 @@
/* 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/. */
/*
#if 0
Workaround a build system bug where this file doesn't get packaged if not pre-processed.
#endif
*/
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TestRunner",
"chrome://mozscreenshots/content/TestRunner.jsm");
function install(data, reason) {
if (!isAppSupported()) {
uninstallExtension(data);
return;
}
AddonManager.getAddonByID(data.id, function(addon) {
// Enable on install in case the user disabled a prior version
if (addon) {
addon.userDisabled = false;
}
});
}
function startup(data, reason) {
if (!isAppSupported()) {
uninstallExtension(data);
return;
}
AddonManager.getAddonByID(data.id, function(addon) {
let extensionPath = addon.getResourceURI();
TestRunner.init(extensionPath);
});
}
function shutdown(data, reason) { }
function uninstall(data, reason) { }
/**
* @return boolean whether the test suite applies to the application.
*/
function isAppSupported() {
return true;
}
function uninstallExtension(data) {
AddonManager.getAddonByID(data.id, function(addon) {
addon.uninstall();
});
}
function startRun() {
let env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
let setsEnv = env.get("MOZSCREENSHOTS_SETS");
let sets = setsEnv ? setsEnv.split(",") : null;
TestRunner.start(sets);
}

View File

@ -0,0 +1,84 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["AppMenu"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.AppMenu = {
init(libDir) {},
configurations: {
appMenuClosed: {
applyConfig: Task.async(function*() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.PanelUI.hide();
}),
},
appMenuMainView: {
applyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let promise = browserWindow.PanelUI.show();
browserWindow.PanelUI.showMainView();
return promise;
},
},
appMenuHistorySubview: {
applyConfig() {
// History has a footer
if (isCustomizing()) {
return Promise.reject("Can't show subviews while customizing");
}
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let promise = browserWindow.PanelUI.show();
return promise.then(() => {
browserWindow.PanelUI.showMainView();
browserWindow.document.getElementById("history-panelmenu").click();
});
},
verifyConfig: verifyConfigHelper,
},
appMenuHelpSubview: {
applyConfig() {
if (isCustomizing()) {
return Promise.reject("Can't show subviews while customizing");
}
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let promise = browserWindow.PanelUI.show();
return promise.then(() => {
browserWindow.PanelUI.showMainView();
browserWindow.document.getElementById("PanelUI-help").click();
});
},
verifyConfig: verifyConfigHelper,
},
},
};
function verifyConfigHelper() {
if (isCustomizing()) {
return Promise.reject("AppMenu verifyConfigHelper");
}
return Promise.resolve("AppMenu verifyConfigHelper");
}
function isCustomizing() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.document.documentElement.hasAttribute("customizing")) {
return true;
}
return false;
}

View File

@ -0,0 +1,87 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Buttons"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource:///modules/CustomizableUI.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.Buttons = {
init(libDir) {
createWidget();
},
configurations: {
navBarButtons: {
applyConfig: Task.async(() =>{
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_NAVBAR);
}),
},
tabsToolbarButtons: {
applyConfig: Task.async(() => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_TABSTRIP);
}),
},
menuPanelButtons: {
applyConfig: Task.async(() => {
CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_PANEL);
}),
verifyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.PanelUI.panel.state == "closed") {
return Promise.reject("The button isn't shown when the panel isn't open.");
}
return Promise.resolve("menuPanelButtons.verifyConfig");
},
},
custPaletteButtons: {
applyConfig: Task.async(() => {
CustomizableUI.removeWidgetFromArea("screenshot-widget");
}),
verifyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.document.documentElement.getAttribute("customizing") != "true") {
return Promise.reject("The button isn't shown when we're not in customize mode.");
}
return Promise.resolve("custPaletteButtons.verifyConfig");
},
},
},
};
function createWidget() {
let id = "screenshot-widget";
let spec = {
id: id,
label: "My Button",
removable: true,
tooltiptext: "",
type: "button",
};
CustomizableUI.createWidget(spec);
// Append a <style> for the image
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let st = browserWindow.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
let styles = "" +
"#screenshot-widget > .toolbarbutton-icon {" +
" list-style-image: url(chrome://browser/skin/Toolbar.png);" +
" -moz-image-region: rect(0px, 18px, 18px, 0px);" +
"}";
st.appendChild(browserWindow.document.createTextNode(styles));
browserWindow.document.documentElement.appendChild(st);
}

View File

@ -0,0 +1,61 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["CustomizeMode"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
this.CustomizeMode = {
init(libDir) {},
configurations: {
notCustomizing: {
applyConfig() {
return new Promise((resolve) => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (!browserWindow.document.documentElement.hasAttribute("customizing")) {
resolve("notCustomizing: already not customizing");
return;
}
function onCustomizationEnds() {
browserWindow.gNavToolbox.removeEventListener("aftercustomization",
onCustomizationEnds);
// Wait for final changes
setTimeout(() => resolve("notCustomizing: onCustomizationEnds"), 500);
}
browserWindow.gNavToolbox.addEventListener("aftercustomization",
onCustomizationEnds);
browserWindow.gCustomizeMode.exit();
});
},
},
customizing: {
applyConfig() {
return new Promise((resolve) => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.document.documentElement.hasAttribute("customizing")) {
resolve("customizing: already customizing");
return;
}
function onCustomizing() {
browserWindow.gNavToolbox.removeEventListener("customizationready",
onCustomizing);
// Wait for final changes
setTimeout(() => resolve("customizing: onCustomizing"), 500);
}
browserWindow.gNavToolbox.addEventListener("customizationready",
onCustomizing);
browserWindow.gCustomizeMode.enter();
});
},
},
},
};

View File

@ -0,0 +1,42 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["DevEdition"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const THEME_ID = "firefox-devedition@mozilla.org";
Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.DevEdition = {
init(libDir) {},
configurations: {
devEditionLight: {
applyConfig: Task.async(() => {
Services.prefs.setCharPref("devtools.theme", "light");
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(THEME_ID);
Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
}),
},
devEditionDark: {
applyConfig: Task.async(() => {
Services.prefs.setCharPref("devtools.theme", "dark");
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(THEME_ID);
Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
}),
},
devEditionOff: {
applyConfig: Task.async(() => {
Services.prefs.clearUserPref("devtools.theme");
LightweightThemeManager.currentTheme = null;
Services.prefs.clearUserPref("browser.devedition.theme.showCustomizeButton");
}),
},
},
};

View File

@ -0,0 +1,50 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["DevTools"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let TargetFactory = devtools.TargetFactory;
function getTargetForSelectedTab() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let target = TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
return target;
}
this.DevTools = {
init(libDir) {
let panels = ["options", "webconsole", "inspector", "jsdebugger", "netmonitor"];
panels.forEach(panel => {
this.configurations[panel] = {};
this.configurations[panel].applyConfig = () => {
return gDevTools.showToolbox(getTargetForSelectedTab(), panel, "bottom");
};
});
},
configurations: {
bottomToolbox: {
applyConfig() {
return gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "bottom");
},
},
sideToolbox: {
applyConfig() {
return gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "side");
},
},
undockedToolbox: {
applyConfig() {
return gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "window");
},
}
},
};

View File

@ -0,0 +1,92 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["LightweightThemes"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
this.LightweightThemes = {
init(libDir) {
// convert -size 3000x200 canvas:black black_theme.png
let blackImage = libDir.clone();
blackImage.append("black_theme.png");
this._blackImageURL = Services.io.newFileURI(blackImage).spec;
// convert -size 3000x200 canvas:white white_theme.png
let whiteImage = libDir.clone();
whiteImage.append("white_theme.png");
this._whiteImageURL = Services.io.newFileURI(whiteImage).spec;
},
configurations: {
noLWT: {
applyConfig: Task.async(function*() {
LightweightThemeManager.currentTheme = null;
}),
},
darkLWT: {
applyConfig() {
LightweightThemeManager.setLocalTheme({
id: "black",
name: "black",
headerURL: LightweightThemes._blackImageURL,
footerURL: LightweightThemes._blackImageURL,
textcolor: "#ffffff",
accentcolor: "#111111",
});
// Wait for LWT listener
return new Promise(resolve => {
setTimeout(() => {
resolve("darkLWT");
}, 500);
});
},
verifyConfig: verifyConfigHelper,
},
lightLWT: {
applyConfig() {
LightweightThemeManager.setLocalTheme({
id: "white",
name: "white",
headerURL: LightweightThemes._whiteImageURL,
footerURL: LightweightThemes._whiteImageURL,
textcolor: "#000000",
accentcolor: "#eeeeee",
});
// Wait for LWT listener
return new Promise(resolve => {
setTimeout(() => {
resolve("lightLWT");
}, 500);
});
},
verifyConfig: verifyConfigHelper,
},
},
};
function verifyConfigHelper() {
return new Promise((resolve, reject) => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.document.documentElement.hasAttribute("lwtheme")) {
resolve("verifyConfigHelper");
} else {
reject("The @lwtheme attribute wasn't present so themes may not be available");
}
});
}

View File

@ -0,0 +1,53 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Preferences"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
this.Preferences = {
init(libDir) {
Services.prefs.setBoolPref("browser.preferences.inContent", true);
let panes = [
["paneGeneral", null],
["paneSearch", null],
["paneContent", null],
["paneApplications", null],
["panePrivacy", null],
["paneSecurity", null],
["paneSync", null],
["paneAdvanced", "generalTab"],
["paneAdvanced", "dataChoicesTab"],
["paneAdvanced", "networkTab"],
["paneAdvanced", "updateTab"],
["paneAdvanced", "encryptionTab"],
];
for (let [primary, advanced] of panes) {
let configName = primary + ("-" + advanced || "");
this.configurations[configName] = {};
this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced);
}
},
configurations: {},
};
function prefHelper(primary, advanced) {
return new Promise((resolve) => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (primary == "paneAdvanced") {
browserWindow.openAdvancedPreferences(advanced);
} else {
browserWindow.openPreferences(primary);
}
setTimeout(resolve, 50);
});
}

View File

@ -0,0 +1,143 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Tabs"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const CUST_TAB = "chrome://browser/skin/customizableui/customizeFavicon.ico";
const PREFS_TAB = "chrome://browser/skin/preferences/in-content/favicon.ico";
const DEFAULT_FAVICON_TAB = `data:text/html,<meta charset="utf-8">
<title>No favicon</title>`;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.Tabs = {
init(libDir) {},
configurations: {
fiveTabs: {
applyConfig: Task.async(function*() {
fiveTabsHelper();
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
hoverTab(browserWindow.gBrowser.tabs[3]);
}),
},
fourPinned: {
applyConfig: Task.async(function*() {
fiveTabsHelper();
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let tab = browserWindow.gBrowser.addTab(PREFS_TAB);
browserWindow.gBrowser.pinTab(tab);
tab = browserWindow.gBrowser.addTab(CUST_TAB);
browserWindow.gBrowser.pinTab(tab);
tab = browserWindow.gBrowser.addTab("about:privatebrowsing");
browserWindow.gBrowser.pinTab(tab);
tab = browserWindow.gBrowser.addTab("about:home");
browserWindow.gBrowser.pinTab(tab);
browserWindow.gBrowser.selectTabAtIndex(5);
hoverTab(browserWindow.gBrowser.tabs[2]);
// also hover the new tab button
let newTabButton = browserWindow.document.getAnonymousElementByAttribute(browserWindow.
gBrowser.tabContainer, "class", "tabs-newtab-button");
hoverTab(newTabButton);
browserWindow.gBrowser.tabs[browserWindow.gBrowser.tabs.length - 1].
setAttribute("beforehovered", true);
}),
},
twoPinnedWithOverflow: {
applyConfig: Task.async(function*() {
fiveTabsHelper();
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.gBrowser.loadTabs([
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
], true, true);
let tab = browserWindow.gBrowser.addTab(PREFS_TAB);
browserWindow.gBrowser.pinTab(tab);
tab = browserWindow.gBrowser.addTab(CUST_TAB);
browserWindow.gBrowser.pinTab(tab);
browserWindow.gBrowser.selectTabAtIndex(4);
hoverTab(browserWindow.gBrowser.tabs[6]);
}),
},
},
};
/* helpers */
function fiveTabsHelper() {
// some with no favicon and some with. Selected tab in middle.
closeAllButOneTab("about:addons");
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.gBrowser.loadTabs([
"about:addons",
"about:home",
DEFAULT_FAVICON_TAB,
"about:newtab",
CUST_TAB,
], true, true);
browserWindow.gBrowser.selectTabAtIndex(1);
}
function closeAllButOneTab(url = "about:blank") {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
// Close all tabs except the last so we don't quit the browser.
while (gBrowser.tabs.length > 1)
gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
gBrowser.selectedBrowser.loadURI(url);
if (gBrowser.selectedTab.pinned)
gBrowser.unpinTab(gBrowser.selectedTab);
let newTabButton = browserWindow.document.getAnonymousElementByAttribute(browserWindow.gBrowser.tabContainer, "class", "tabs-newtab-button");
hoverTab(newTabButton, false);
}
function hoverTab(tab, hover = true) {
const inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
if (hover) {
inIDOMUtils.addPseudoClassLock(tab, ":hover");
} else {
inIDOMUtils.clearPseudoClassLocks(tab);
}
// XXX TODO: this isn't necessarily testing what we ship
if (tab.nextElementSibling)
tab.nextElementSibling.setAttribute("afterhovered", hover || null);
if (tab.previousElementSibling)
tab.previousElementSibling.setAttribute("beforehovered", hover || null);
}

View File

@ -0,0 +1,37 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["TabsInTitlebar"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const PREF_TABS_IN_TITLEBAR = "browser.tabs.drawInTitlebar";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.TabsInTitlebar = {
init(libDir) {},
configurations: {
tabsInTitlebar: {
applyConfig: Task.async(function*() {
if (Services.appinfo.OS == "Linux") {
return Promise.reject("TabsInTitlebar isn't supported on Linux");
}
Services.prefs.setBoolPref(PREF_TABS_IN_TITLEBAR, true);
}),
},
tabsOutsideTitlebar: {
applyConfig: Task.async(function*() {
Services.prefs.setBoolPref(PREF_TABS_IN_TITLEBAR, false);
}),
},
},
};

View File

@ -0,0 +1,56 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Toolbars"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.Toolbars = {
init(libDir) {},
configurations: {
onlyNavBar: {
applyConfig: Task.async(function*() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let personalToolbar = browserWindow.document.getElementById("PersonalToolbar");
browserWindow.setToolbarVisibility(personalToolbar, false);
toggleMenubarIfNecessary(false);
}),
},
allToolbars: {
applyConfig: Task.async(function*() { // Boookmarks and menubar
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let personalToolbar = browserWindow.document.getElementById("PersonalToolbar");
browserWindow.setToolbarVisibility(personalToolbar, true);
toggleMenubarIfNecessary(true);
}),
verifyConfig: Task.async(function*() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWindow.fullScreen) {
return Promise.reject("The bookmark toolbar and menubar are not shown in fullscreen.");
}
}),
},
},
};
///// helpers /////
function toggleMenubarIfNecessary(visible) {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
// The menubar is not shown on OS X or while in fullScreen
if (Services.appinfo.OS != "Darwin" /*&& !browserWindow.fullScreen*/) {
let menubar = browserWindow.document.getElementById("toolbar-menubar");
browserWindow.setToolbarVisibility(menubar, visible);
}
}

View File

@ -0,0 +1,60 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["WindowSize"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
this.WindowSize = {
init(libDir) {
Services.prefs.setBoolPref("browser.fullscreen.autohide", false);
},
configurations: {
maximized: {
applyConfig: Task.async(function*() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.fullScreen = false;
// Wait for the Lion fullscreen transition to end as there doesn't seem to be an event
// and trying to maximize while still leaving fullscreen doesn't work.
yield new Promise((resolve, reject) => {
setTimeout(function waitToLeaveFS() {
browserWindow.maximize();
resolve();
}, Services.appinfo.OS == "Darwin" ? 1500 : 0);
});
}),
},
normal: {
applyConfig: Task.async(() => {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.fullScreen = false;
browserWindow.restore();
}),
},
fullScreen: {
applyConfig: Task.async(function*() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
browserWindow.fullScreen = true;
// OS X Lion fullscreen transition takes a while
yield new Promise((resolve, reject) => {
setTimeout(function waitAfterEnteringFS() {
resolve();
}, Services.appinfo.OS == "Darwin" ? 1500 : 0);
});
}),
},
},
};

View File

@ -0,0 +1,33 @@
<?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/. -->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>mozscreenshots@mozilla.org</em:id>
#expand <em:version>__MOZILLA_VERSION_U__</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- for running custom screenshot binaries -->
<em:unpack>true</em:unpack>
<!-- Front End MetaData -->
<em:name>mozscreenshots</em:name>
<em:description>Take screenshots of Mozilla applications in various UI configurations.</em:description>
<em:creator>Mozilla</em:creator>
<em:targetApplication>
<Description>
<!-- Firefox -->
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
#expand <em:minVersion>__MOZILLA_VERSION_U__</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,6 @@
mozscreenshots.jar:
% content mozscreenshots chrome/mozscreenshots/
Screenshot.jsm
TestRunner.jsm
configurations/ (configurations/*.jsm)
lib/ (lib/*.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

View File

@ -0,0 +1,17 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPI_NAME = 'mozscreenshots'
JAR_MANIFESTS += ['jar.mn']
USE_EXTENSION_MANIFEST = True
NO_JS_MANIFEST = True
FINAL_TARGET_PP_FILES += [
'bootstrap.js',
'install.rdf',
]