diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index 9f9d4a23b83c..7d9d028ff1fb 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1166,6 +1166,9 @@ pref("devtools.scratchpad.recentFilesMax", 10);
pref("devtools.styleeditor.enabled", true);
pref("devtools.styleeditor.transitions", true);
+// Enable the Shader Editor.
+pref("devtools.shadereditor.enabled", false);
+
// Enable tools for Chrome development.
pref("devtools.chrome.enabled", false);
diff --git a/browser/base/content/newtab/newTab.js b/browser/base/content/newtab/newTab.js
index 28c151bb8642..2ca2ff433c01 100644
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -10,9 +10,7 @@ let Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PageThumbs.jsm");
-#ifndef RELEASE_BUILD
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
-#endif
Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
diff --git a/browser/base/content/newtab/sites.js b/browser/base/content/newtab/sites.js
index 3406021d5763..f7c16ab0ae48 100644
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -131,12 +131,10 @@ Site.prototype = {
if (this.isPinned())
this._updateAttributes(true);
-#ifndef RELEASE_BUILD
- // request a staleness check for the thumbnail, which will cause page.js
+ // Capture the page if the thumbnail is missing, which will cause page.js
// to be notified and call our refreshThumbnail() method.
BackgroundPageThumbs.captureIfMissing(this.url);
// but still display whatever thumbnail might be available now.
-#endif
this.refreshThumbnail();
},
diff --git a/browser/components/preferences/permissions.js b/browser/components/preferences/permissions.js
index 7ad74b992318..272526c4931d 100644
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -6,6 +6,8 @@
const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
const nsICookiePermission = Components.interfaces.nsICookiePermission;
+const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
+
function Permission(host, rawHost, type, capability, perm)
{
this.host = host;
@@ -183,6 +185,7 @@ var gPermissionManager = {
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
os.addObserver(this, "perm-changed", false);
this._loadPermissions();
diff --git a/browser/devtools/app-manager/content/manifest-editor.js b/browser/devtools/app-manager/content/manifest-editor.js
index 66acf64b8d72..c2bde66d20dd 100644
--- a/browser/devtools/app-manager/content/manifest-editor.js
+++ b/browser/devtools/app-manager/content/manifest-editor.js
@@ -42,9 +42,13 @@ ManifestEditor.prototype = {
_onContainerReady: function(varWindow) {
let variablesContainer = varWindow.document.querySelector("#variables");
+ variablesContainer.classList.add("manifest-editor");
+
let editor = this.editor = new VariablesView(variablesContainer);
editor.onlyEnumVisible = true;
+ editor.alignedValues = true;
+ editor.actionsFirst = true;
if (this.editable) {
editor.eval = this._onEval;
diff --git a/browser/devtools/debugger/moz.build b/browser/devtools/debugger/moz.build
index 267cbbe712f2..ae60acb34f6d 100644
--- a/browser/devtools/debugger/moz.build
+++ b/browser/devtools/debugger/moz.build
@@ -1,4 +1,3 @@
-# -*- 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
diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn
index 394c68bf03b1..265d9e81d504 100644
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -51,6 +51,8 @@ browser.jar:
content/browser/devtools/debugger-view.js (debugger/debugger-view.js)
content/browser/devtools/debugger-toolbar.js (debugger/debugger-toolbar.js)
content/browser/devtools/debugger-panes.js (debugger/debugger-panes.js)
+ content/browser/devtools/shadereditor.xul (shadereditor/shadereditor.xul)
+ content/browser/devtools/shadereditor.js (shadereditor/shadereditor.js)
content/browser/devtools/profiler.xul (profiler/profiler.xul)
content/browser/devtools/cleopatra.html (profiler/cleopatra/cleopatra.html)
content/browser/devtools/profiler/cleopatra/css/ui.css (profiler/cleopatra/css/ui.css)
diff --git a/browser/devtools/main.js b/browser/devtools/main.js
index d7ebf7d7e782..ed33aac989e8 100644
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -27,6 +27,7 @@ loader.lazyGetter(this, "InspectorPanel", () => require("devtools/inspector/insp
loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/webconsole/panel").WebConsolePanel);
loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/debugger/debugger-panel").DebuggerPanel);
loader.lazyImporter(this, "StyleEditorPanel", "resource:///modules/devtools/StyleEditorPanel.jsm");
+loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel);
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel"));
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/netmonitor-panel").NetMonitorPanel);
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel);
@@ -36,6 +37,7 @@ const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties";
const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
+const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties";
const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
@@ -44,6 +46,7 @@ loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(to
loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
+loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps));
loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps));
loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
@@ -166,11 +169,30 @@ Tools.styleEditor = {
}
};
+Tools.shaderEditor = {
+ id: "shadereditor",
+ ordinal: 5,
+ visibilityswitch: "devtools.shadereditor.enabled",
+ icon: "chrome://browser/skin/devtools/tool-styleeditor.png",
+ url: "chrome://browser/content/devtools/shadereditor.xul",
+ label: l10n("ToolboxShaderEditor.label", shaderEditorStrings),
+ tooltip: l10n("ToolboxShaderEditor.tooltip", shaderEditorStrings),
+
+ isTargetSupported: function(target) {
+ return true;
+ },
+
+ build: function(iframeWindow, toolbox) {
+ let panel = new ShaderEditorPanel(iframeWindow, toolbox);
+ return panel.open();
+ }
+};
+
Tools.jsprofiler = {
id: "jsprofiler",
accesskey: l10n("profiler.accesskey", profilerStrings),
key: l10n("profiler2.commandkey", profilerStrings),
- ordinal: 5,
+ ordinal: 6,
modifiers: "shift",
visibilityswitch: "devtools.profiler.enabled",
icon: "chrome://browser/skin/devtools/tool-profiler.png",
@@ -193,7 +215,7 @@ Tools.netMonitor = {
id: "netmonitor",
accesskey: l10n("netmonitor.accesskey", netMonitorStrings),
key: l10n("netmonitor.commandkey", netMonitorStrings),
- ordinal: 6,
+ ordinal: 7,
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
visibilityswitch: "devtools.netmonitor.enabled",
icon: "chrome://browser/skin/devtools/tool-network.png",
@@ -214,7 +236,7 @@ Tools.netMonitor = {
Tools.scratchpad = {
id: "scratchpad",
- ordinal: 7,
+ ordinal: 8,
visibilityswitch: "devtools.scratchpad.enabled",
icon: "chrome://browser/skin/devtools/tool-scratchpad.png",
url: "chrome://browser/content/devtools/scratchpad.xul",
@@ -234,10 +256,11 @@ Tools.scratchpad = {
let defaultTools = [
Tools.options,
- Tools.styleEditor,
Tools.webConsole,
- Tools.jsdebugger,
Tools.inspector,
+ Tools.jsdebugger,
+ Tools.styleEditor,
+ Tools.shaderEditor,
Tools.jsprofiler,
Tools.netMonitor,
Tools.scratchpad
diff --git a/browser/devtools/moz.build b/browser/devtools/moz.build
index 92116b82765a..87b84bce02d4 100644
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -5,24 +5,25 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
- 'inspector',
- 'markupview',
- 'webconsole',
+ 'app-manager',
'commandline',
+ 'debugger',
+ 'fontinspector',
+ 'framework',
+ 'inspector',
+ 'layoutview',
+ 'markupview',
+ 'netmonitor',
+ 'profiler',
+ 'responsivedesign',
+ 'scratchpad',
+ 'shadereditor',
+ 'shared',
'sourceeditor',
'styleeditor',
'styleinspector',
'tilt',
- 'scratchpad',
- 'debugger',
- 'netmonitor',
- 'layoutview',
- 'shared',
- 'responsivedesign',
- 'framework',
- 'profiler',
- 'fontinspector',
- 'app-manager',
+ 'webconsole',
]
EXTRA_COMPONENTS += [
diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js
index 638d1afa5286..09d29c01c258 100644
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -2044,6 +2044,7 @@ NetworkDetailsView.prototype = {
* DOM query helper.
*/
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
+function $all(aSelector, aTarget = document) aTarget.querySelectorAll(aSelector);
/**
* Helper for getting an nsIURL instance out of a string.
diff --git a/browser/devtools/netmonitor/test/browser_net_sort-01.js b/browser/devtools/netmonitor/test/browser_net_sort-01.js
index 329ff461c32c..4f919fbdf3c8 100644
--- a/browser/devtools/netmonitor/test/browser_net_sort-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-01.js
@@ -9,7 +9,7 @@ function test() {
initNetMonitor(STATUS_CODES_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
- let { L10N, NetMonitorView } = aMonitor.panelWin;
+ let { $all, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
@@ -182,6 +182,8 @@ function test() {
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
"There should be a total of 5 visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, 5,
+ "The visible items in the requests menu are, in fact, visible!");
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
"The requests menu items aren't ordered correctly. First item is misplaced.");
diff --git a/browser/devtools/netmonitor/test/browser_net_sort-02.js b/browser/devtools/netmonitor/test/browser_net_sort-02.js
index 11d2288b3045..ffcdfe13d9eb 100644
--- a/browser/devtools/netmonitor/test/browser_net_sort-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-02.js
@@ -13,7 +13,7 @@ function test() {
// of the heavy dom manipulation associated with sorting.
requestLongerTimeout(2);
- let { $, L10N, NetMonitorView } = aMonitor.panelWin;
+ let { $, $all, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
@@ -178,6 +178,8 @@ function test() {
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
"There should be a total of 5 visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, 5,
+ "The visible items in the requests menu are, in fact, visible!");
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
"The requests menu items aren't ordered correctly. First item is misplaced.");
diff --git a/browser/devtools/netmonitor/test/browser_net_sort-03.js b/browser/devtools/netmonitor/test/browser_net_sort-03.js
index 93b1f4af7c61..0cffd484d5ef 100644
--- a/browser/devtools/netmonitor/test/browser_net_sort-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-03.js
@@ -13,7 +13,7 @@ function test() {
// of the heavy dom manipulation associated with sorting.
requestLongerTimeout(2);
- let { $, L10N, NetMonitorView } = aMonitor.panelWin;
+ let { $, $all, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
@@ -114,6 +114,8 @@ function test() {
"There should be a specific number of items in the requests menu.");
is(RequestsMenu.visibleItems.length, aOrder.length,
"There should be a specific number of visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, aOrder.length,
+ "The visible items in the requests menu are, in fact, visible!");
for (let i = 0; i < aOrder.length; i++) {
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
diff --git a/browser/devtools/shadereditor/moz.build b/browser/devtools/shadereditor/moz.build
new file mode 100644
index 000000000000..1978c0d9f9bf
--- /dev/null
+++ b/browser/devtools/shadereditor/moz.build
@@ -0,0 +1,13 @@
+# 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/.
+
+TEST_DIRS += ['test']
+
+JS_MODULES_PATH = 'modules/devtools/shadereditor'
+
+EXTRA_JS_MODULES += [
+ 'panel.js'
+]
+
diff --git a/browser/devtools/shadereditor/panel.js b/browser/devtools/shadereditor/panel.js
new file mode 100644
index 000000000000..102df56e9d2c
--- /dev/null
+++ b/browser/devtools/shadereditor/panel.js
@@ -0,0 +1,65 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
+const promise = require("sdk/core/promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+const { WebGLFront } = require("devtools/server/actors/webgl");
+
+function ShaderEditorPanel(iframeWindow, toolbox) {
+ this.panelWin = iframeWindow;
+ this._toolbox = toolbox;
+ this._destroyer = null;
+
+ EventEmitter.decorate(this);
+};
+
+exports.ShaderEditorPanel = ShaderEditorPanel;
+
+ShaderEditorPanel.prototype = {
+ open: function() {
+ let targetPromise;
+
+ // Local debugging needs to make the target remote.
+ if (!this.target.isRemote) {
+ targetPromise = this.target.makeRemote();
+ } else {
+ targetPromise = promise.resolve(this.target);
+ }
+
+ return targetPromise
+ .then(() => {
+ this.panelWin.gTarget = this.target;
+ this.panelWin.gFront = new WebGLFront(this.target.client, this.target.form);
+ return this.panelWin.startupShaderEditor();
+ })
+ .then(() => {
+ this.isReady = true;
+ this.emit("ready");
+ return this;
+ })
+ .then(null, function onError(aReason) {
+ Cu.reportError("ShaderEditorPanel open failed. " +
+ aReason.error + ": " + aReason.message);
+ });
+ },
+
+ // DevToolPanel API
+
+ get target() this._toolbox.target,
+
+ destroy: function() {
+ // Make sure this panel is not already destroyed.
+ if (this._destroyer) {
+ return this._destroyer;
+ }
+
+ return this._destroyer = this.panelWin.shutdownShaderEditor().then(() => {
+ this.emit("destroyed");
+ });
+ }
+};
diff --git a/browser/devtools/shadereditor/shadereditor.js b/browser/devtools/shadereditor/shadereditor.js
new file mode 100644
index 000000000000..11f6ea611870
--- /dev/null
+++ b/browser/devtools/shadereditor/shadereditor.js
@@ -0,0 +1,396 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+
+const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const promise = require("sdk/core/promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+const Editor = require("devtools/sourceeditor/editor");
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+ // When the vertex and fragment sources were shown in the editor.
+ SOURCES_SHOWN: "ShaderEditor:SourcesShown",
+ // When a shader's source was edited and compiled via the editor.
+ SHADER_COMPILED: "ShaderEditor:ShaderCompiled"
+};
+
+const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
+const HIGHLIGHT_COLOR = [1, 0, 0, 1];
+const BLACKBOX_COLOR = [0, 0, 0, 0];
+const TYPING_MAX_DELAY = 500;
+const SHADERS_AUTOGROW_ITEMS = 4;
+const DEFAULT_EDITOR_CONFIG = {
+ mode: Editor.modes.text,
+ lineNumbers: true,
+ showAnnotationRuler: true
+};
+
+/**
+ * The current target and the WebGL Editor front, set by this tool's host.
+ */
+let gTarget, gFront;
+
+/**
+ * Initializes the shader editor controller and views.
+ */
+function startupShaderEditor() {
+ return promise.all([
+ EventsHandler.initialize(),
+ ShadersListView.initialize(),
+ ShadersEditorsView.initialize()
+ ]);
+}
+
+/**
+ * Destroys the shader editor controller and views.
+ */
+function shutdownShaderEditor() {
+ return promise.all([
+ EventsHandler.destroy(),
+ ShadersListView.destroy(),
+ ShadersEditorsView.destroy()
+ ]);
+}
+
+/**
+ * Functions handling target-related lifetime events.
+ */
+let EventsHandler = {
+ /**
+ * Listen for events emitted by the current tab target.
+ */
+ initialize: function() {
+ this._onWillNavigate = this._onWillNavigate.bind(this);
+ this._onProgramLinked = this._onProgramLinked.bind(this);
+ gTarget.on("will-navigate", this._onWillNavigate);
+ gFront.on("program-linked", this._onProgramLinked);
+
+ },
+
+ /**
+ * Remove events emitted by the current tab target.
+ */
+ destroy: function() {
+ gTarget.off("will-navigate", this._onWillNavigate);
+ gFront.off("program-linked", this._onProgramLinked);
+ },
+
+ /**
+ * Called for each location change in the debugged tab.
+ */
+ _onWillNavigate: function() {
+ gFront.setup();
+
+ ShadersListView.empty();
+ ShadersEditorsView.setText({ vs: "", fs: "" });
+ $("#reload-notice").hidden = true;
+ $("#waiting-notice").hidden = false;
+ $("#content").hidden = true;
+ },
+
+ /**
+ * Called every time a program was linked in the debugged tab.
+ */
+ _onProgramLinked: function(programActor) {
+ $("#waiting-notice").hidden = true;
+ $("#reload-notice").hidden = true;
+ $("#content").hidden = false;
+ ShadersListView.addProgram(programActor);
+ }
+};
+
+/**
+ * Functions handling the sources UI.
+ */
+let ShadersListView = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the tool is started.
+ */
+ initialize: function() {
+ this.widget = new SideMenuWidget(this._pane = $("#shaders-pane"), {
+ showArrows: true,
+ showItemCheckboxes: true
+ });
+
+ this._onShaderSelect = this._onShaderSelect.bind(this);
+ this._onShaderCheck = this._onShaderCheck.bind(this);
+ this._onShaderMouseEnter = this._onShaderMouseEnter.bind(this);
+ this._onShaderMouseLeave = this._onShaderMouseLeave.bind(this);
+
+ this.widget.addEventListener("select", this._onShaderSelect, false);
+ this.widget.addEventListener("check", this._onShaderCheck, false);
+ this.widget.addEventListener("mouseenter", this._onShaderMouseEnter, true);
+ this.widget.addEventListener("mouseleave", this._onShaderMouseLeave, true);
+ },
+
+ /**
+ * Destruction function, called when the tool is closed.
+ */
+ destroy: function() {
+ this.widget.removeEventListener("select", this._onShaderSelect, false);
+ this.widget.removeEventListener("check", this._onShaderCheck, false);
+ this.widget.removeEventListener("mouseenter", this._onShaderMouseEnter, true);
+ this.widget.removeEventListener("mouseleave", this._onShaderMouseLeave, true);
+ },
+
+ /**
+ * Adds a program to this programs container.
+ *
+ * @param object programActor
+ * The program actor coming from the active thread.
+ */
+ addProgram: function(programActor) {
+ // Currently, there's no good way of differentiating between programs
+ // in a way that helps humans. It will be a good idea to implement a
+ // standard of allowing debuggees to add some identifiable metadata to their
+ // program sources or instances.
+ let label = L10N.getFormatStr("shadersList.programLabel", this.itemCount);
+
+ // Append a program item to this container.
+ this.push([label, ""], {
+ index: -1, /* specifies on which position should the item be appended */
+ relaxed: true, /* this container should allow dupes & degenerates */
+ attachment: {
+ programActor: programActor,
+ checkboxState: true,
+ checkboxTooltip: L10N.getStr("shadersList.blackboxLabel")
+ }
+ });
+
+ // Make sure there's always a selected item available.
+ if (!this.selectedItem) {
+ this.selectedIndex = 0;
+ }
+ },
+
+ /**
+ * The select listener for the sources container.
+ */
+ _onShaderSelect: function({ detail: sourceItem }) {
+ if (!sourceItem) {
+ return;
+ }
+ // The container is not empty and an actual item was selected.
+ let attachment = sourceItem.attachment;
+
+ function getShaders() {
+ return promise.all([
+ attachment.vs || (attachment.vs = attachment.programActor.getVertexShader()),
+ attachment.fs || (attachment.fs = attachment.programActor.getFragmentShader())
+ ]);
+ }
+ function getSources([vertexShaderActor, fragmentShaderActor]) {
+ return promise.all([
+ vertexShaderActor.getText(),
+ fragmentShaderActor.getText()
+ ]);
+ }
+ function showSources([vertexShaderText, fragmentShaderText]) {
+ ShadersEditorsView.setText({
+ vs: vertexShaderText,
+ fs: fragmentShaderText
+ });
+ }
+
+ getShaders().then(getSources).then(showSources).then(null, Cu.reportError);
+ },
+
+ /**
+ * The check listener for the sources container.
+ */
+ _onShaderCheck: function({ detail: { checked }, target }) {
+ let sourceItem = this.getItemForElement(target);
+ let attachment = sourceItem.attachment;
+ attachment.isBlackBoxed = !checked;
+ attachment.programActor[checked ? "unhighlight" : "highlight"](BLACKBOX_COLOR);
+ },
+
+ /**
+ * The mouseenter listener for the sources container.
+ */
+ _onShaderMouseEnter: function(e) {
+ let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
+ if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
+ sourceItem.attachment.programActor.highlight(HIGHLIGHT_COLOR);
+
+ if (e instanceof Event) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ },
+
+ /**
+ * The mouseleave listener for the sources container.
+ */
+ _onShaderMouseLeave: function(e) {
+ let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
+ if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
+ sourceItem.attachment.programActor.unhighlight();
+
+ if (e instanceof Event) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ }
+});
+
+/**
+ * Functions handling the editors displaying the vertex and fragment shaders.
+ */
+let ShadersEditorsView = {
+ /**
+ * Initialization function, called when the tool is started.
+ */
+ initialize: function() {
+ XPCOMUtils.defineLazyGetter(this, "_editorPromises", () => new Map());
+ this._vsFocused = this._onFocused.bind(this, "vs", "fs");
+ this._fsFocused = this._onFocused.bind(this, "fs", "vs");
+ this._vsChanged = this._onChanged.bind(this, "vs");
+ this._fsChanged = this._onChanged.bind(this, "fs");
+ },
+
+ /**
+ * Destruction function, called when the tool is closed.
+ */
+ destroy: function() {
+ this._toggleListeners("off");
+ },
+
+ /**
+ * Sets the text displayed in the vertex and fragment shader editors.
+ *
+ * @param object sources
+ * An object containing the following properties
+ * - vs: the vertex shader source code
+ * - fs: the fragment shader source code
+ */
+ setText: function(sources) {
+ function setTextAndClearHistory(editor, text) {
+ editor.setText(text);
+ editor.clearHistory();
+ }
+
+ this._toggleListeners("off");
+ this._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs));
+ this._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs));
+ this._toggleListeners("on");
+
+ window.emit(EVENTS.SOURCES_SHOWN, sources);
+ },
+
+ /**
+ * Lazily initializes and returns a promise for an Editor instance.
+ *
+ * @param string type
+ * Specifies for which shader type should an editor be retrieved,
+ * either are "vs" for a vertex, or "fs" for a fragment shader.
+ */
+ _getEditor: function(type) {
+ if ($("#content").hidden) {
+ return promise.reject(null);
+ }
+ if (this._editorPromises.has(type)) {
+ return this._editorPromises.get(type);
+ }
+
+ let deferred = promise.defer();
+ this._editorPromises.set(type, deferred.promise);
+
+ // Initialize the source editor and store the newly created instance
+ // in the ether of a resolved promise's value.
+ let parent = $("#" + type +"-editor");
+ let editor = new Editor(DEFAULT_EDITOR_CONFIG);
+ editor.appendTo(parent).then(() => deferred.resolve(editor));
+
+ return deferred.promise;
+ },
+
+ /**
+ * Toggles all the event listeners for the editors either on or off.
+ *
+ * @param string flag
+ * Either "on" to enable the event listeners, "off" to disable them.
+ */
+ _toggleListeners: function(flag) {
+ ["vs", "fs"].forEach(type => {
+ this._getEditor(type).then(editor => {
+ editor[flag]("focus", this["_" + type + "Focused"]);
+ editor[flag]("change", this["_" + type + "Changed"]);
+ });
+ });
+ },
+
+ /**
+ * The focus listener for a source editor.
+ *
+ * @param string focused
+ * The corresponding shader type for the focused editor (e.g. "vs").
+ * @param string focused
+ * The corresponding shader type for the other editor (e.g. "fs").
+ */
+ _onFocused: function(focused, unfocused) {
+ $("#" + focused + "-editor-label").setAttribute("selected", "");
+ $("#" + unfocused + "-editor-label").removeAttribute("selected");
+ },
+
+ /**
+ * The change listener for a source editor.
+ *
+ * @param string type
+ * The corresponding shader type for the focused editor (e.g. "vs").
+ */
+ _onChanged: function(type) {
+ setNamedTimeout("gl-typed", TYPING_MAX_DELAY, () => this._doCompile(type));
+ },
+
+ /**
+ * Recompiles the source code for the shader being edited.
+ * This function is fired at a certain delay after the user stops typing.
+ *
+ * @param string type
+ * The corresponding shader type for the focused editor (e.g. "vs").
+ */
+ _doCompile: function(type) {
+ Task.spawn(function() {
+ let editor = yield this._getEditor(type);
+ let shaderActor = yield ShadersListView.selectedAttachment[type];
+
+ try {
+ yield shaderActor.compile(editor.getText());
+ window.emit(EVENTS.SHADER_COMPILED, null);
+ // TODO: remove error gutter markers, after bug 919709 lands.
+ } catch (error) {
+ window.emit(EVENTS.SHADER_COMPILED, error);
+ // TODO: add error gutter markers, after bug 919709 lands.
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * Localization convenience methods.
+ */
+let L10N = new ViewHelpers.L10N(STRINGS_URI);
+
+/**
+ * Convenient way of emitting events from the panel window.
+ */
+EventEmitter.decorate(this);
+
+/**
+ * DOM query helper.
+ */
+function $(selector, target = document) target.querySelector(selector);
diff --git a/browser/devtools/shadereditor/shadereditor.xul b/browser/devtools/shadereditor/shadereditor.xul
new file mode 100644
index 000000000000..5fbf52304d18
--- /dev/null
+++ b/browser/devtools/shadereditor/shadereditor.xul
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+ %debuggerDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/devtools/shadereditor/test/browser.ini b/browser/devtools/shadereditor/test/browser.ini
new file mode 100644
index 000000000000..27dd771542bb
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser.ini
@@ -0,0 +1,33 @@
+[DEFAULT]
+support-files =
+ doc_multiple-contexts.html
+ doc_shader-order.html
+ doc_simple-canvas.html
+ head.js
+
+[browser_se_aaa_run_first_leaktest.js]
+[browser_se_editors-contents.js]
+[browser_se_editors-lazy-init.js]
+[browser_se_first-run.js]
+[browser_se_navigation.js]
+[browser_se_programs-blackbox.js]
+[browser_se_programs-cache.js]
+[browser_se_programs-highlight.js]
+[browser_se_programs-list.js]
+[browser_se_shaders-edit-01.js]
+[browser_se_shaders-edit-02.js]
+[browser_se_shaders-edit-03.js]
+[browser_webgl-actor-test-01.js]
+[browser_webgl-actor-test-02.js]
+[browser_webgl-actor-test-03.js]
+[browser_webgl-actor-test-04.js]
+[browser_webgl-actor-test-05.js]
+[browser_webgl-actor-test-06.js]
+[browser_webgl-actor-test-07.js]
+[browser_webgl-actor-test-08.js]
+[browser_webgl-actor-test-09.js]
+[browser_webgl-actor-test-10.js]
+[browser_webgl-actor-test-11.js]
+[browser_webgl-actor-test-12.js]
+[browser_webgl-actor-test-13.js]
+[browser_webgl-actor-test-14.js]
diff --git a/browser/devtools/shadereditor/test/browser_se_aaa_run_first_leaktest.js b/browser/devtools/shadereditor/test/browser_se_aaa_run_first_leaktest.js
new file mode 100644
index 000000000000..4336113d3f84
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_aaa_run_first_leaktest.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the shader editor leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+
+ ok(target, "Should have a target available.");
+ ok(debuggee, "Should have a debuggee available.");
+ ok(panel, "Should have a panel available.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_editors-contents.js b/browser/devtools/shadereditor/test/browser_se_editors-contents.js
new file mode 100644
index 000000000000..593dfa204cc5
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_editors-contents.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the editors contain the correct text when a program
+ * becomes available.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ is(vsEditor.getText().indexOf("gl_Position"), 170,
+ "The vertex shader editor contains the correct text.");
+ is(fsEditor.getText().indexOf("gl_FragColor"), 97,
+ "The fragment shader editor contains the correct text.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_editors-lazy-init.js b/browser/devtools/shadereditor/test/browser_se_editors-lazy-init.js
new file mode 100644
index 000000000000..1b46a2fd9157
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_editors-lazy-init.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if source editors are lazily initialized.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, ShadersEditorsView } = panel.panelWin;
+
+ try {
+ yield ShadersEditorsView._getEditor("vs");
+ ok(false, "The promise for a vertex shader editor should be rejected.");
+ } catch (e) {
+ ok(true, "The vertex shader editors wasn't initialized.");
+ }
+
+ try {
+ yield ShadersEditorsView._getEditor("fs");
+ ok(false, "The promise for a fragment shader editor should be rejected.");
+ } catch (e) {
+ ok(true, "The fragment shader editors wasn't initialized.");
+ }
+
+ reload(target);
+ yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ ok(vsEditor, "A vertex shader editor was initialized.");
+ ok(fsEditor, "A fragment shader editor was initialized.");
+
+ isnot(vsEditor, fsEditor,
+ "The vertex shader editor is distinct from the fragment shader editor.");
+
+ let vsEditor2 = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor2 = yield ShadersEditorsView._getEditor("fs");
+
+ is(vsEditor, vsEditor2,
+ "The vertex shader editor instances are cached.");
+ is(fsEditor, fsEditor2,
+ "The fragment shader editor instances are cached.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_first-run.js b/browser/devtools/shadereditor/test/browser_se_first-run.js
new file mode 100644
index 000000000000..77b3f0ef4301
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_first-run.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the shader editor shows the appropriate UI when opened.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $ } = panel.panelWin;
+
+ is($("#reload-notice").hidden, false,
+ "The 'reload this page' notice should initially be visible.");
+ is($("#waiting-notice").hidden, true,
+ "The 'waiting for a WebGL context' notice should initially be hidden.");
+ is($("#content").hidden, true,
+ "The tool's content should initially be hidden.");
+
+ let navigating = once(target, "will-navigate");
+ let linked = once(gFront, "program-linked");
+ reload(target);
+
+ yield navigating;
+
+ is($("#reload-notice").hidden, true,
+ "The 'reload this page' notice should be hidden when navigating.");
+ is($("#waiting-notice").hidden, false,
+ "The 'waiting for a WebGL context' notice should be visible when navigating.");
+ is($("#content").hidden, true,
+ "The tool's content should still be hidden.");
+
+ yield linked;
+
+ is($("#reload-notice").hidden, true,
+ "The 'reload this page' notice should be hidden after linking.");
+ is($("#waiting-notice").hidden, true,
+ "The 'waiting for a WebGL context' notice should be hidden after linking.");
+ is($("#content").hidden, false,
+ "The tool's content should not be hidden anymore.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_navigation.js b/browser/devtools/shadereditor/test/browser_se_navigation.js
new file mode 100644
index 000000000000..77859ac7f94d
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_navigation.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests target navigations are handled correctly in the UI.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield once(gFront, "program-linked");
+
+ is($("#reload-notice").hidden, true,
+ "The 'reload this page' notice should be hidden after linking.");
+ is($("#waiting-notice").hidden, true,
+ "The 'waiting for a WebGL context' notice should be visible after linking.");
+ is($("#content").hidden, false,
+ "The tool's content should not be hidden anymore.");
+
+ is(ShadersListView.itemCount, 1,
+ "The shaders list contains one entry.");
+ is(ShadersListView.selectedItem, ShadersListView.items[0],
+ "The shaders list has a correct item selected.");
+ is(ShadersListView.selectedIndex, 0,
+ "The shaders list has a correct index selected.");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ is(vsEditor.getText().indexOf("gl_Position"), 170,
+ "The vertex shader editor contains the correct text.");
+ is(fsEditor.getText().indexOf("gl_FragColor"), 97,
+ "The fragment shader editor contains the correct text.");
+
+ let navigating = once(target, "will-navigate");
+ let navigated = once(target, "will-navigate");
+ navigate(target, "about:blank");
+
+ yield navigating;
+
+ is($("#reload-notice").hidden, true,
+ "The 'reload this page' notice should be hidden while navigating.");
+ is($("#waiting-notice").hidden, false,
+ "The 'waiting for a WebGL context' notice should be visible while navigating.");
+ is($("#content").hidden, true,
+ "The tool's content should be hidden now that there's no WebGL content.");
+
+ is(ShadersListView.itemCount, 0,
+ "The shaders list should be empty.");
+ is(ShadersListView.selectedItem, null,
+ "The shaders list has no correct item.");
+ is(ShadersListView.selectedIndex, -1,
+ "The shaders list has a negative index.");
+
+ try {
+ yield ShadersEditorsView._getEditor("vs");
+ ok(false, "The promise for a vertex shader editor should be rejected.");
+ } catch (e) {
+ ok(true, "The vertex shader editors wasn't initialized.");
+ }
+
+ try {
+ yield ShadersEditorsView._getEditor("fs");
+ ok(false, "The promise for a fragment shader editor should be rejected.");
+ } catch (e) {
+ ok(true, "The fragment shader editors wasn't initialized.");
+ }
+
+ yield navigated;
+
+ is($("#reload-notice").hidden, true,
+ "The 'reload this page' notice should still be hidden after navigating.");
+ is($("#waiting-notice").hidden, false,
+ "The 'waiting for a WebGL context' notice should still be visible after navigating.");
+ is($("#content").hidden, true,
+ "The tool's content should be still hidden since there's no WebGL content.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_programs-blackbox.js b/browser/devtools/shadereditor/test/browser_se_programs-blackbox.js
new file mode 100644
index 000000000000..bbcce1903076
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_programs-blackbox.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if blackboxing a program works properly.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ once(panel.panelWin, EVENTS.SHADER_COMPILED).then(() => {
+ ok(false, "No shaders should be publicly compiled during this test.");
+ });
+
+ reload(target);
+ let firstProgramActor = yield once(gFront, "program-linked");
+ let secondProgramActor = yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ vsEditor.once("change", () => {
+ ok(false, "The vertex shader source was unexpectedly changed.");
+ });
+ fsEditor.once("change", () => {
+ ok(false, "The fragment shader source was unexpectedly changed.");
+ });
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN).then(() => {
+ ok(false, "No sources should be changed form this point onward.");
+ });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+
+ ok(!ShadersListView.selectedAttachment.isBlackBoxed,
+ "The first program should not be blackboxed yet.");
+ is(getBlackBoxCheckbox(panel, 0).checked, true,
+ "The first blackbox checkbox should be initially checked.");
+ ok(!ShadersListView.attachments[1].isBlackBoxed,
+ "The second program should not be blackboxed yet.");
+ is(getBlackBoxCheckbox(panel, 1).checked, true,
+ "The second blackbox checkbox should be initially checked.");
+
+ getBlackBoxCheckbox(panel, 0).click();
+
+ ok(ShadersListView.selectedAttachment.isBlackBoxed,
+ "The first program should now be blackboxed.");
+ is(getBlackBoxCheckbox(panel, 0).checked, false,
+ "The first blackbox checkbox should now be unchecked.");
+ ok(!ShadersListView.attachments[1].isBlackBoxed,
+ "The second program should still not be blackboxed.");
+ is(getBlackBoxCheckbox(panel, 1).checked, true,
+ "The second blackbox checkbox should still be checked.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first program was correctly blackboxed.");
+
+ getBlackBoxCheckbox(panel, 1).click();
+
+ ok(ShadersListView.selectedAttachment.isBlackBoxed,
+ "The first program should still be blackboxed.");
+ is(getBlackBoxCheckbox(panel, 0).checked, false,
+ "The first blackbox checkbox should still be unchecked.");
+ ok(ShadersListView.attachments[1].isBlackBoxed,
+ "The second program should now be blackboxed.");
+ is(getBlackBoxCheckbox(panel, 1).checked, false,
+ "The second blackbox checkbox should now be unchecked.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ ok(true, "The second program was correctly blackboxed.");
+
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ ok(true, "Highlighting didn't work while blackboxed (1).");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ ok(true, "Highlighting didn't work while blackboxed (2).");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
+ ok(true, "Highlighting didn't work while blackboxed (3).");
+
+ getBlackBoxCheckbox(panel, 0).click();
+ getBlackBoxCheckbox(panel, 1).click();
+
+ ok(!ShadersListView.selectedAttachment.isBlackBoxed,
+ "The first program should now be unblackboxed.");
+ is(getBlackBoxCheckbox(panel, 0).checked, true,
+ "The first blackbox checkbox should now be rechecked.");
+ ok(!ShadersListView.attachments[1].isBlackBoxed,
+ "The second program should now be unblackboxed.");
+ is(getBlackBoxCheckbox(panel, 1).checked, true,
+ "The second blackbox checkbox should now be rechecked.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were correctly unblackboxed.");
+
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first program was correctly highlighted.");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "The second program was correctly highlighted.");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were correctly unhighlighted.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function getItemLabel(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-label")[aIndex];
+}
+
+function getBlackBoxCheckbox(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-checkbox")[aIndex];
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_programs-cache.js b/browser/devtools/shadereditor/test/browser_se_programs-cache.js
new file mode 100644
index 000000000000..b5e397585577
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_programs-cache.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that program and shader actors are cached in the frontend.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ let programActor = yield once(gFront, "program-linked");
+ let programItem = ShadersListView.selectedItem;
+
+ is(programItem.attachment.programActor, programActor,
+ "The correct program actor is cached for the selected item.");
+
+ is((yield programActor.getVertexShader()),
+ (yield programItem.attachment.vs),
+ "The cached vertex shader promise returns the correct actor.");
+
+ is((yield programActor.getFragmentShader()),
+ (yield programItem.attachment.fs),
+ "The cached fragment shader promise returns the correct actor.");
+
+ is((yield (yield programActor.getVertexShader()).getText()),
+ (yield (yield ShadersEditorsView._getEditor("vs")).getText()),
+ "The cached vertex shader promise returns the correct text.");
+
+ is((yield (yield programActor.getFragmentShader()).getText()),
+ (yield (yield ShadersEditorsView._getEditor("fs")).getText()),
+ "The cached fragment shader promise returns the correct text.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_programs-highlight.js b/browser/devtools/shadereditor/test/browser_se_programs-highlight.js
new file mode 100644
index 000000000000..7d22f4915620
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_programs-highlight.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if highlighting a program works properly.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ once(panel.panelWin, EVENTS.SHADER_COMPILED).then(() => {
+ ok(false, "No shaders should be publicly compiled during this test.");
+ });
+
+ reload(target);
+ let firstProgramActor = yield once(gFront, "program-linked");
+ let secondProgramActor = yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ vsEditor.once("change", () => {
+ ok(false, "The vertex shader source was unexpectedly changed.");
+ });
+ fsEditor.once("change", () => {
+ ok(false, "The fragment shader source was unexpectedly changed.");
+ });
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN).then(() => {
+ ok(false, "No sources should be changed form this point onward.");
+ });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first program was correctly highlighted.");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
+ ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "The second program was correctly highlighted.");
+
+ ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were correctly unhighlighted.");
+
+ ShadersListView._onShaderMouseEnter({ target: getBlackBoxCheckbox(panel, 0) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were left unchanged after hovering a blackbox checkbox.");
+
+ ShadersListView._onShaderMouseLeave({ target: getBlackBoxCheckbox(panel, 0) });
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were left unchanged after unhovering a blackbox checkbox.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function getItemLabel(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-label")[aIndex];
+}
+
+function getBlackBoxCheckbox(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-checkbox")[aIndex];
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_programs-list.js b/browser/devtools/shadereditor/test/browser_se_programs-list.js
new file mode 100644
index 000000000000..d225ae8d0caf
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_programs-list.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the programs list contains an entry after vertex and fragment
+ * shaders are linked.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, EVENTS, L10N, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ is(ShadersListView.itemCount, 0,
+ "The shaders list should initially be empty.");
+ is(ShadersListView.selectedItem, null,
+ "The shaders list has no selected item.");
+ is(ShadersListView.selectedIndex, -1,
+ "The shaders list has a negative index.");
+
+ reload(target);
+
+ let firstProgramActor = yield once(gFront, "program-linked");
+
+ is(ShadersListView.itemCount, 1,
+ "The shaders list contains one entry.");
+ is(ShadersListView.selectedItem, ShadersListView.items[0],
+ "The shaders list has a correct item selected.");
+ is(ShadersListView.selectedIndex, 0,
+ "The shaders list has a correct index selected.");
+
+ let secondProgramActor = yield once(gFront, "program-linked");
+
+ is(ShadersListView.itemCount, 2,
+ "The shaders list contains two entries.");
+ is(ShadersListView.selectedItem, ShadersListView.items[0],
+ "The shaders list has a correct item selected.");
+ is(ShadersListView.selectedIndex, 0,
+ "The shaders list has a correct index selected.");
+
+ is(ShadersListView.labels[0], L10N.getFormatStr("shadersList.programLabel", 0),
+ "The correct first label is shown in the shaders list.");
+ is(ShadersListView.labels[1], L10N.getFormatStr("shadersList.programLabel", 1),
+ "The correct second label is shown in the shaders list.");
+
+ let vertexShader = yield firstProgramActor.getVertexShader();
+ let fragmentShader = yield firstProgramActor.getFragmentShader();
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ is(vertSource, vsEditor.getText(),
+ "The vertex shader editor contains the correct text.");
+ is(fragSource, fsEditor.getText(),
+ "The vertex shader editor contains the correct text.");
+
+ let compiled = once(panel.panelWin, EVENTS.SHADER_COMPILED).then(() => {
+ ok(false, "Selecting a different program shouldn't recompile its shaders.");
+ });
+
+ let shown = once(panel.panelWin, EVENTS.SOURCES_SHOWN).then(() => {
+ ok(true, "The vertex and fragment sources have changed in the editors.");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, ShadersListView.items[1].target);
+ yield shown;
+
+ is(ShadersListView.selectedItem, ShadersListView.items[1],
+ "The shaders list has a correct item selected.");
+ is(ShadersListView.selectedIndex, 1,
+ "The shaders list has a correct index selected.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_shaders-edit-01.js b/browser/devtools/shadereditor/test/browser_se_shaders-edit-01.js
new file mode 100644
index 000000000000..fe8b6125b55d
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-01.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if editing a vertex and a fragment shader works properly.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ is(vsEditor.getText().indexOf("gl_Position"), 170,
+ "The vertex shader editor contains the correct text.");
+ is(fsEditor.getText().indexOf("gl_FragColor"), 97,
+ "The fragment shader editor contains the correct text.");
+
+ is($("#vs-editor-label").hasAttribute("selected"), false,
+ "The vertex shader editor shouldn't be initially selected.");
+ is($("#fs-editor-label").hasAttribute("selected"), false,
+ "The vertex shader editor shouldn't be initially selected.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 191, g: 64, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ vsEditor.focus();
+
+ is($("#vs-editor-label").hasAttribute("selected"), true,
+ "The vertex shader editor should now be selected.");
+ is($("#fs-editor-label").hasAttribute("selected"), false,
+ "The vertex shader editor shouldn't still not be selected.");
+
+ vsEditor.replaceText("2.0", { line: 7, ch: 44 }, { line: 7, ch: 47 });
+ yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(true, "Vertex shader was changed.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ ok(true, "The vertex shader was recompiled successfully.");
+
+ fsEditor.focus();
+
+ is($("#vs-editor-label").hasAttribute("selected"), false,
+ "The vertex shader editor should now be deselected.");
+ is($("#fs-editor-label").hasAttribute("selected"), true,
+ "The vertex shader editor should now be selected.");
+
+ fsEditor.replaceText("0.5", { line: 5, ch: 44 }, { line: 5, ch: 47 });
+ yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(true, "Fragment shader was changed.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ ok(true, "The fragment shader was recompiled successfully.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js b/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
new file mode 100644
index 000000000000..2ccb04e61cc9
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if compile or linkage errors are emitted when a shader source
+ * gets malformed after being edited.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+ let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(error,
+ "The new vertex shader source was compiled with errors.");
+ is(error.compile, "",
+ "The compilation status should be empty.");
+ isnot(error.link, "",
+ "The linkage status should not be empty.");
+ is(error.link.split("ERROR").length - 1, 2,
+ "The linkage status contains two errors.");
+ ok(error.link.contains("ERROR: 0:8: 'constructor'"),
+ "A constructor error is contained in the linkage status.");
+ ok(error.link.contains("ERROR: 0:8: 'assign'"),
+ "An assignment error is contained in the linkage status.");
+
+ fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
+ let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(error,
+ "The new fragment shader source was compiled with errors.");
+ is(error.compile, "",
+ "The compilation status should be empty.");
+ isnot(error.link, "",
+ "The linkage status should not be empty.");
+ is(error.link.split("ERROR").length - 1, 1,
+ "The linkage status contains one error.");
+ ok(error.link.contains("ERROR: 0:6: 'constructor'"),
+ "A constructor error is contained in the linkage status.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ vsEditor.replaceText("vec4", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+ let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+ ok(!error, "The new vertex shader source was compiled successfully.");
+
+ fsEditor.replaceText("vec3", { line: 2, ch: 14 }, { line: 2, ch: 18 });
+ let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+ ok(!error, "The new fragment shader source was compiled successfully.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ yield teardown(panel);
+ finish();
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js b/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
new file mode 100644
index 000000000000..10aed06a83ce
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if editing a vertex and a fragment shader works properly.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ let firstProgramActor = yield once(gFront, "program-linked");
+ let secondProgramActor = yield once(gFront, "program-linked");
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ is(ShadersListView.selectedIndex, 0,
+ "The first program is currently selected.");
+ is(vsEditor.getText().indexOf("1);"), 136,
+ "The vertex shader editor contains the correct initial text (1).");
+ is(fsEditor.getText().indexOf("1);"), 117,
+ "The fragment shader editor contains the correct initial text (1).");
+ is(vsEditor.getText().indexOf("2.);"), -1,
+ "The vertex shader editor contains the correct initial text (2).");
+ is(fsEditor.getText().indexOf(".0);"), -1,
+ "The fragment shader editor contains the correct initial text (2).");
+
+ vsEditor.replaceText("2.", { line: 5, ch: 44 }, { line: 5, ch: 45 });
+ yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ fsEditor.replaceText(".0", { line: 5, ch: 35 }, { line: 5, ch: 37 });
+ yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(true, "Vertex and fragment shaders were changed.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 32, y: 32 }, { r: 255, g: 255, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 255, g: 255, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 32, y: 32 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+
+ ok(true, "The vertex and fragment shaders were recompiled successfully.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, ShadersListView.items[1].target);
+ yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
+
+ is(ShadersListView.selectedIndex, 1,
+ "The second program is currently selected.");
+ is(vsEditor.getText().indexOf("1);"), 136,
+ "The vertex shader editor contains the correct text (1).");
+ is(fsEditor.getText().indexOf("1);"), 117,
+ "The fragment shader editor contains the correct text (1).");
+ is(vsEditor.getText().indexOf("2.);"), -1,
+ "The vertex shader editor contains the correct text (2).");
+ is(fsEditor.getText().indexOf(".0);"), -1,
+ "The fragment shader editor contains the correct text (2).");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, ShadersListView.items[0].target);
+ yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
+
+ is(ShadersListView.selectedIndex, 0,
+ "The first program is currently selected again.");
+ is(vsEditor.getText().indexOf("1);"), -1,
+ "The vertex shader editor contains the correct text (3).");
+ is(fsEditor.getText().indexOf("1);"), -1,
+ "The fragment shader editor contains the correct text (3).");
+ is(vsEditor.getText().indexOf("2.);"), 136,
+ "The vertex shader editor contains the correct text (4).");
+ is(fsEditor.getText().indexOf(".0);"), 116,
+ "The fragment shader editor contains the correct text (4).");
+
+ yield teardown(panel);
+ finish();
+}
+
+function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-01.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-01.js
new file mode 100644
index 000000000000..85ce3f93ac18
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-01.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if a WebGL front can be created for a remote tab target.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+
+ ok(target, "Should have a target available.");
+ ok(debuggee, "Should have a debuggee available.");
+ ok(front, "Should have a protocol front available.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-02.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-02.js
new file mode 100644
index 000000000000..48a7f5e316d9
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-02.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if notifications about WebGL programs being linked are not sent
+ * if the front wasn't set up first.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+
+ once(front, "program-linked").then(() => {
+ ok(false, "A 'program-linked' notification shouldn't have been sent!");
+ });
+
+ yield reload(target);
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-03.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-03.js
new file mode 100644
index 000000000000..8f469ff5722b
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-03.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if notifications about WebGL programs being linked are sent
+ * after a target navigation.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+ let linked = once(front, "program-linked");
+
+ yield front.setup();
+ ok(true, "The front was setup up successfully.");
+
+ yield navigated;
+ ok(true, "Target automatically navigated when the front was set up.");
+
+ yield linked;
+ ok(true, "A 'program-linked' notification was sent after reloading.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-04.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-04.js
new file mode 100644
index 000000000000..50013dd5bbaa
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-04.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if a program actor is sent when WebGL programs are linked,
+ * and that the corresponding vertex and fragment actors can be retrieved.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ ok(programActor,
+ "A program actor was sent along with the 'program-linked' notification.")
+
+ let vertexShader = yield programActor.getVertexShader();
+ ok(programActor,
+ "A vertex shader actor was retrieved from the program actor.");
+
+ let fragmentShader = yield programActor.getFragmentShader();
+ ok(programActor,
+ "A fragment shader actor was retrieved from the program actor.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-05.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-05.js
new file mode 100644
index 000000000000..f9ac191332c2
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-05.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the source contents can be retrieved from the vertex and fragment
+ * shader actors.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ let vertSource = yield vertexShader.getText();
+ ok(vertSource.contains("gl_Position"),
+ "The correct vertex shader source was retrieved.");
+
+ let fragSource = yield fragmentShader.getText();
+ ok(fragSource.contains("gl_FragColor"),
+ "The correct fragment shader source was retrieved.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-06.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-06.js
new file mode 100644
index 000000000000..c50eb4348f1d
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-06.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the highlight/unhighlight operations on program actors
+ * work as expected.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ yield checkShaderSource("The shader sources are correct before highlighting.");
+ ok(true, "The top left pixel color was correct before highlighting.");
+
+ yield programActor.highlight([0, 0, 1, 1]);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield checkShaderSource("The shader sources are preserved after highlighting.");
+ ok(true, "The top left pixel color is correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ yield checkShaderSource("The shader sources are correct after unhighlighting.");
+ ok(true, "The top left pixel color is correct after unhighlighting.");
+
+ function checkShaderSource(aMessage) {
+ return Task.spawn(function() {
+ let newVertexShader = yield programActor.getVertexShader();
+ let newFragmentShader = yield programActor.getFragmentShader();
+ is(vertexShader, newVertexShader,
+ "The same vertex shader actor was retrieved.");
+ is(fragmentShader, newFragmentShader,
+ "The same fragment shader actor was retrieved.");
+
+ let vertSource = yield newVertexShader.getText();
+ let fragSource = yield newFragmentShader.getText();
+ ok(vertSource.contains("I'm special!") &&
+ fragSource.contains("I'm also special!"), aMessage);
+ });
+ }
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-07.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-07.js
new file mode 100644
index 000000000000..6f14371ed600
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-07.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that vertex and fragment shader sources can be changed.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 191, g: 64, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+ ok(!vertSource.contains("2.0"),
+ "The vertex shader source is correct before changing it.");
+ ok(!fragSource.contains("0.5"),
+ "The fragment shader source is correct before changing it.");
+
+ let newVertSource = vertSource.replace("1.0", "2.0");
+ let status = yield vertexShader.compile(newVertSource);
+ ok(!status,
+ "The new vertex shader source was compiled without errors.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+ ok(vertSource.contains("2.0"),
+ "The vertex shader source is correct after changing it.");
+ ok(!fragSource.contains("0.5"),
+ "The fragment shader source is correct after changing the vertex shader.");
+
+ let newFragSource = fragSource.replace("1.0", "0.5");
+ let status = yield fragmentShader.compile(newFragSource);
+ ok(!status,
+ "The new fragment shader source was compiled without errors.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+ ok(vertSource.contains("2.0"),
+ "The vertex shader source is correct after changing the fragment shader.");
+ ok(fragSource.contains("0.5"),
+ "The fragment shader source is correct after changing it.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-08.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-08.js
new file mode 100644
index 000000000000..77d3250596e1
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-08.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the rendering is updated when a varying variable is
+ * changed in one shader.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ let oldVertSource = yield vertexShader.getText();
+ let newVertSource = oldVertSource.replace("= aVertexColor", "= vec3(0, 0, 1)");
+ let status = yield vertexShader.compile(newVertSource);
+ ok(!status,
+ "The new vertex shader source was compiled without errors.");
+
+ yield waitForFrame(debuggee);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+ ok(vertSource.contains("vFragmentColor = vec3(0, 0, 1);"),
+ "The vertex shader source is correct after changing it.");
+ ok(fragSource.contains("gl_FragColor = vec4(vFragmentColor, 1.0);"),
+ "The fragment shader source is correct after changing the vertex shader.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-09.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-09.js
new file mode 100644
index 000000000000..3aa3ac737e51
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-09.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that errors are properly handled when trying to compile a
+ * defective shader source.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ let oldVertSource = yield vertexShader.getText();
+ let newVertSource = oldVertSource.replace("vec4", "vec3");
+
+ try {
+ yield vertexShader.compile(newVertSource);
+ ok(false, "Vertex shader was compiled with a defective source!");
+ } catch (error) {
+ ok(error,
+ "The new vertex shader source was compiled with errors.");
+ is(error.compile, "",
+ "The compilation status should be empty.");
+ isnot(error.link, "",
+ "The linkage status should not be empty.");
+ is(error.link.split("ERROR").length - 1, 2,
+ "The linkage status contains two errors.");
+ ok(error.link.contains("ERROR: 0:8: 'constructor'"),
+ "A constructor error is contained in the linkage status.");
+ ok(error.link.contains("ERROR: 0:8: 'assign'"),
+ "An assignment error is contained in the linkage status.");
+ }
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The shader was reverted to the old source.");
+
+ let vertSource = yield vertexShader.getText();
+ ok(vertSource.contains("vec4(aVertexPosition, 1.0);"),
+ "The previous correct vertex shader source was preserved.");
+
+ let oldFragSource = yield fragmentShader.getText();
+ let newFragSource = oldFragSource.replace("vec3", "vec4");
+
+ try {
+ yield fragmentShader.compile(newFragSource);
+ ok(false, "Fragment shader was compiled with a defective source!");
+ } catch (error) {
+ ok(error,
+ "The new fragment shader source was compiled with errors.");
+ is(error.compile, "",
+ "The compilation status should be empty.");
+ isnot(error.link, "",
+ "The linkage status should not be empty.");
+ is(error.link.split("ERROR").length - 1, 1,
+ "The linkage status contains one error.");
+ ok(error.link.contains("ERROR: 0:6: 'constructor'"),
+ "A constructor error is contained in the linkage status.");
+ }
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The shader was reverted to the old source.");
+
+ let fragSource = yield fragmentShader.getText();
+ ok(fragSource.contains("vec3 vFragmentColor;"),
+ "The previous correct fragment shader source was preserved.");
+
+ yield programActor.highlight([0, 0, 1, 1]);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ ok(true, "Highlighting worked after setting a defective fragment source.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "Unhighlighting worked after setting a defective vertex source.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-10.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-10.js
new file mode 100644
index 000000000000..cfc6790747ef
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-10.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the WebGL context is correctly instrumented every time the
+ * target navigates.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+
+ let linked = once(front, "program-linked");
+ yield front.setup();
+ yield linked;
+ ok(true, "Canvas was correctly instrumented on the first navigation.");
+
+ let linked = once(front, "program-linked");
+ yield reload(target);
+ yield linked;
+ ok(true, "Canvas was correctly instrumented on the second navigation.");
+
+ let linked = once(front, "program-linked");
+ yield reload(target);
+ yield linked;
+ ok(true, "Canvas was correctly instrumented on the third navigation.");
+
+ let programActor = yield linked;
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ ok(true, "The top left pixel color was correct before highlighting.");
+
+ yield programActor.highlight([0, 0, 1, 1]);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ ok(true, "The top left pixel color is correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ ok(true, "The top left pixel color is correct after unhighlighting.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-11.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-11.js
new file mode 100644
index 000000000000..e973c795f7ff
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-11.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the WebGL context is never instrumented anymore after the
+ * finalize method is called.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
+
+ let linked = once(front, "program-linked");
+ yield front.setup();
+ yield linked;
+ ok(true, "Canvas was correctly instrumented on the first navigation.");
+
+ once(front, "program-linked").then(() => {
+ ok(false, "A 'program-linked' notification shouldn't have been sent!");
+ });
+
+ yield front.finalize();
+ yield reload(target);
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-12.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-12.js
new file mode 100644
index 000000000000..1bc5480e7857
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-12.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the correct vertex and fragment shader sources are retrieved
+ * regardless of the order in which they were compiled and attached.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(SHADER_ORDER_URL);
+ front.setup();
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ let vertSource = yield vertexShader.getText();
+ let fragSource = yield fragmentShader.getText();
+
+ ok(vertSource.contains("I'm a vertex shader!"),
+ "The correct vertex shader text was retrieved.");
+ ok(fragSource.contains("I'm a fragment shader!"),
+ "The correct fragment shader text was retrieved.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-13.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-13.js
new file mode 100644
index 000000000000..eacb809f3ecb
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-13.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if multiple WebGL contexts are correctly handled.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
+ front.setup();
+
+ let firstProgramActor = yield once(front, "program-linked");
+ let secondProgramActor = yield once(front, "program-linked");
+
+ isnot(firstProgramActor, secondProgramActor,
+ "Two distinct program actors were recevide from two separate contexts.");
+
+ let firstVertexShader = yield firstProgramActor.getVertexShader();
+ let firstFragmentShader = yield firstProgramActor.getFragmentShader();
+ let secondVertexShader = yield secondProgramActor.getVertexShader();
+ let secondFragmentShader = yield secondProgramActor.getFragmentShader();
+
+ isnot(firstVertexShader, secondVertexShader,
+ "The two programs should have distinct vertex shaders.");
+ isnot(firstFragmentShader, secondFragmentShader,
+ "The two programs should have distinct fragment shaders.");
+
+ let firstVertSource = yield firstVertexShader.getText();
+ let firstFragSource = yield firstFragmentShader.getText();
+ let secondVertSource = yield secondVertexShader.getText();
+ let secondFragSource = yield secondFragmentShader.getText();
+
+ is(firstVertSource, secondVertSource,
+ "The vertex shaders should have identical sources.");
+ is(firstFragSource, secondFragSource,
+ "The vertex shaders should have identical sources.");
+
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases are correctly drawn.");
+
+ yield firstProgramActor.highlight([1, 0, 0, 1]);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first canvas was correctly filled after highlighting.");
+
+ yield secondProgramActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "The second canvas was correctly filled after highlighting.");
+
+ yield firstProgramActor.unhighlight();
+ yield secondProgramActor.unhighlight();
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases were correctly filled after unhighlighting.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/browser_webgl-actor-test-14.js b/browser/devtools/shadereditor/test/browser_webgl-actor-test-14.js
new file mode 100644
index 000000000000..fb452ffedb5c
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-14.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the rendering is updated when a uniform variable is
+ * changed in one shader of a page with multiple WebGL contexts.
+ */
+
+function ifWebGLSupported() {
+ let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
+ front.setup();
+
+ let firstProgramActor = yield once(front, "program-linked");
+ let secondProgramActor = yield once(front, "program-linked");
+ let firstFragmentShader = yield firstProgramActor.getFragmentShader();
+ let secondFragmentShader = yield secondProgramActor.getFragmentShader();
+
+ let oldFragSource = yield firstFragmentShader.getText();
+ let newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.25, 0.25, 0.25");
+ let status = yield firstFragmentShader.compile(newFragSource);
+ ok(!status,
+ "The first new fragment shader source was compiled without errors.");
+
+ yield waitForFrame(debuggee);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first fragment shader was changed.");
+
+ let oldFragSource = yield secondFragmentShader.getText();
+ let newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.75, 0.75, 0.75");
+ let status = yield secondFragmentShader.compile(newFragSource);
+ ok(!status,
+ "The second new fragment shader source was compiled without errors.");
+
+ yield waitForFrame(debuggee);
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 191, g: 191, b: 191, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 191, g: 191, b: 191, a: 255 }, true, "#canvas2");
+ ok(true, "The second fragment shader was changed.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/browser/devtools/shadereditor/test/doc_multiple-contexts.html b/browser/devtools/shadereditor/test/doc_multiple-contexts.html
new file mode 100644
index 000000000000..2a07d27069a7
--- /dev/null
+++ b/browser/devtools/shadereditor/test/doc_multiple-contexts.html
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ WebGL editor test page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/devtools/shadereditor/test/doc_shader-order.html b/browser/devtools/shadereditor/test/doc_shader-order.html
new file mode 100644
index 000000000000..faf6ef8e9c57
--- /dev/null
+++ b/browser/devtools/shadereditor/test/doc_shader-order.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ WebGL editor test page
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/devtools/shadereditor/test/doc_simple-canvas.html b/browser/devtools/shadereditor/test/doc_simple-canvas.html
new file mode 100644
index 000000000000..1bc0de017f38
--- /dev/null
+++ b/browser/devtools/shadereditor/test/doc_simple-canvas.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+ WebGL editor test page
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/devtools/shadereditor/test/head.js b/browser/devtools/shadereditor/test/head.js
new file mode 100644
index 000000000000..b3f16dfb0afe
--- /dev/null
+++ b/browser/devtools/shadereditor/test/head.js
@@ -0,0 +1,252 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+// Enable logging for all the tests. Both the debugger server and frontend will
+// be affected by this pref.
+let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+
+let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+
+let { WebGLFront } = devtools.require("devtools/server/actors/webgl");
+let TiltGL = devtools.require("devtools/tilt/tilt-gl");
+let TargetFactory = devtools.TargetFactory;
+let Toolbox = devtools.Toolbox;
+
+const EXAMPLE_URL = "http://example.com/browser/browser/devtools/shadereditor/test/";
+const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
+const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html";
+const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html";
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+let gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled");
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+ Services.prefs.setBoolPref("devtools.shadereditor.enabled", gToolEnabled);
+});
+
+function addTab(aUrl, aWindow) {
+ info("Adding tab: " + aUrl);
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+
+ targetWindow.focus();
+ let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
+ let linkedBrowser = tab.linkedBrowser;
+
+ linkedBrowser.addEventListener("load", function onLoad() {
+ linkedBrowser.removeEventListener("load", onLoad, true);
+ info("Tab added and finished loading: " + aUrl);
+ deferred.resolve(tab);
+ }, true);
+
+ return deferred.promise;
+}
+
+function removeTab(aTab, aWindow) {
+ info("Removing tab.");
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+ let tabContainer = targetBrowser.tabContainer;
+
+ tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+ tabContainer.removeEventListener("TabClose", onClose, false);
+ info("Tab removed and finished closing.");
+ deferred.resolve();
+ }, false);
+
+ targetBrowser.removeTab(aTab);
+ return deferred.promise;
+}
+
+function handleError(aError) {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ finish();
+}
+
+function ifWebGLSupported() {
+ ok(false, "You need to define a 'ifWebGLSupported' function.");
+ finish();
+}
+
+function ifWebGLUnsupported() {
+ todo(false, "Skipping test because WebGL isn't supported.");
+ finish();
+}
+
+function test() {
+ let generator = isWebGLSupported() ? ifWebGLSupported : ifWebGLUnsupported;
+ Task.spawn(generator).then(null, handleError);
+}
+
+function createCanvas() {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+}
+
+function isWebGLSupported() {
+ let supported =
+ !TiltGL.isWebGLForceEnabled() &&
+ TiltGL.isWebGLSupported() &&
+ TiltGL.create3DContext(createCanvas());
+
+ info("Apparently, WebGL is" + (supported ? "" : " not") + " supported.");
+ return supported;
+}
+
+function once(aTarget, aEventName, aUseCapture = false) {
+ info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
+
+ let deferred = promise.defer();
+
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"],
+ ["on", "off"]
+ ]) {
+ if ((add in aTarget) && (remove in aTarget)) {
+ aTarget[add](aEventName, function onEvent(...aArgs) {
+ aTarget[remove](aEventName, onEvent, aUseCapture);
+ deferred.resolve.apply(deferred, aArgs);
+ }, aUseCapture);
+ break;
+ }
+ }
+
+ return deferred.promise;
+}
+
+function waitForFrame(aDebuggee) {
+ let deferred = promise.defer();
+ aDebuggee.mozRequestAnimationFrame(deferred.resolve);
+ return deferred.promise;
+}
+
+function isApprox(aFirst, aSecond, aMargin = 1) {
+ return Math.abs(aFirst - aSecond) <= aMargin;
+}
+
+function isApproxColor(aFirst, aSecond, aMargin) {
+ return isApprox(aFirst.r, aSecond.r, aMargin) &&
+ isApprox(aFirst.g, aSecond.g, aMargin) &&
+ isApprox(aFirst.b, aSecond.b, aMargin) &&
+ isApprox(aFirst.a, aSecond.a, aMargin);
+}
+
+function getPixels(aDebuggee, aSelector = "canvas") {
+ let canvas = aDebuggee.document.querySelector(aSelector);
+ let gl = canvas.getContext("webgl");
+
+ let { width, height } = canvas;
+ let buffer = new aDebuggee.Uint8Array(width * height * 4);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
+
+ info("Retrieved pixels: " + width + "x" + height);
+ return [buffer, width, height];
+}
+
+function getPixel(aDebuggee, aPosition, aSelector = "canvas") {
+ let canvas = aDebuggee.document.querySelector(aSelector);
+ let gl = canvas.getContext("webgl");
+
+ let { width, height } = canvas;
+ let { x, y } = aPosition;
+ let buffer = new aDebuggee.Uint8Array(4);
+ gl.readPixels(x, height - y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
+
+ let pixel = { r: buffer[0], g: buffer[1], b: buffer[2], a: buffer[3] };
+
+ info("Retrieved pixel: " + pixel.toSource() + " at " + aPosition.toSource());
+ return pixel;
+}
+
+function ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag = false, aSelector = "canvas") {
+ let pixel = getPixel(aDebuggee, aPosition, aSelector);
+ if (isApproxColor(pixel, aColor)) {
+ ok(true, "Expected pixel is shown at: " + aPosition.toSource());
+ return promise.resolve(null);
+ }
+ if (aWaitFlag) {
+ return Task.spawn(function() {
+ yield waitForFrame(aDebuggee);
+ yield ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag, aSelector);
+ });
+ }
+ ok(false, "Expected pixel was not already shown at: " + aPosition.toSource());
+ return promise.reject(null);
+}
+
+function navigate(aTarget, aUrl) {
+ let navigated = once(aTarget, "navigate");
+ aTarget.client.activeTab.navigateTo(aUrl);
+ return navigated;
+}
+
+function reload(aTarget) {
+ let navigated = once(aTarget, "navigate");
+ aTarget.client.activeTab.reload();
+ return navigated;
+}
+
+function initBackend(aUrl) {
+ info("Initializing a shader editor front.");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init(() => true);
+ DebuggerServer.addBrowserActors();
+ }
+
+ return Task.spawn(function*() {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+ let debuggee = target.window.wrappedJSObject;
+
+ yield target.makeRemote();
+
+ let front = new WebGLFront(target.client, target.form);
+ return [target, debuggee, front];
+ });
+}
+
+function initShaderEditor(aUrl) {
+ info("Initializing a shader editor pane.");
+
+ return Task.spawn(function*() {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+ let debuggee = target.window.wrappedJSObject;
+
+ yield target.makeRemote();
+
+ Services.prefs.setBoolPref("devtools.shadereditor.enabled", true);
+ let toolbox = yield gDevTools.showToolbox(target, "shadereditor");
+ let panel = toolbox.getCurrentPanel();
+ return [target, debuggee, panel];
+ });
+}
+
+function teardown(aPanel) {
+ info("Destroying the specified shader editor.");
+
+ return promise.all([
+ once(aPanel, "destroyed"),
+ removeTab(aPanel.target.tab)
+ ]);
+}
diff --git a/browser/devtools/shadereditor/test/moz.build b/browser/devtools/shadereditor/test/moz.build
new file mode 100644
index 000000000000..a21913edfc3e
--- /dev/null
+++ b/browser/devtools/shadereditor/test/moz.build
@@ -0,0 +1,6 @@
+# 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']
diff --git a/browser/devtools/shared/telemetry.js b/browser/devtools/shared/telemetry.js
index ef0f957b051e..5410b2976fd9 100644
--- a/browser/devtools/shared/telemetry.js
+++ b/browser/devtools/shared/telemetry.js
@@ -118,6 +118,11 @@ Telemetry.prototype = {
userHistogram: "DEVTOOLS_STYLEEDITOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS"
},
+ shadereditor: {
+ histogram: "DEVTOOLS_SHADEREDITOR_OPENED_BOOLEAN",
+ userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG",
+ timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS"
+ },
jsprofiler: {
histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",
diff --git a/browser/devtools/shared/widgets/SideMenuWidget.jsm b/browser/devtools/shared/widgets/SideMenuWidget.jsm
index 55c68837db70..d134beba0d3f 100644
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -166,7 +166,8 @@ SideMenuWidget.prototype = {
* The element associated with the displayed item.
*/
removeChild: function(aChild) {
- if (aChild.classList.contains("side-menu-widget-item-contents")) {
+ if (aChild.classList.contains("side-menu-widget-item-contents") &&
+ !aChild.classList.contains("side-menu-widget-item")) {
// Remove the item itself, not the contents.
aChild.parentNode.remove();
} else {
diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm
index 68d44ba3d438..50b925fa43a5 100644
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -892,6 +892,40 @@ VariablesView.prototype = {
this._emptyTextNode = null;
},
+ /**
+ * Gets if all values should be aligned together.
+ * @return boolean
+ */
+ get alignedValues() {
+ return this._alignedValues;
+ },
+
+ /**
+ * Sets if all values should be aligned together.
+ * @param boolean aFlag
+ */
+ set alignedValues(aFlag) {
+ this._alignedValues = aFlag;
+ if (aFlag) {
+ this._parent.setAttribute("aligned-values", "");
+ } else {
+ this._parent.removeAttribute("aligned-values");
+ }
+ },
+
+ /**
+ * Sets if action buttons (like delete) should be placed at the beginning or
+ * end of a line.
+ * @param boolean aFlag
+ */
+ set actionsFirst(aFlag) {
+ if (aFlag) {
+ this._parent.setAttribute("actions-first", "");
+ } else {
+ this._parent.removeAttribute("actions-first");
+ }
+ },
+
/**
* Gets the parent node holding this view.
* @return nsIDOMNode
@@ -1726,6 +1760,7 @@ Scope.prototype = {
}
let throbber = this._throbber = this.document.createElement("hbox");
throbber.className = "variables-view-throbber";
+ throbber.setAttribute("optional-visibility", "");
this._title.insertBefore(throbber, this._spacer);
},
@@ -2322,6 +2357,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
valueLabel.setAttribute("crop", "center");
let spacer = this._spacer = document.createElement("spacer");
+ spacer.setAttribute("optional-visibility", "");
spacer.setAttribute("flex", "1");
this._title.appendChild(separatorLabel);
@@ -2333,7 +2369,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
}
// If no value will be displayed, we don't need the separator.
- if (!descriptor.get && !descriptor.set && !descriptor.value) {
+ if (!descriptor.get && !descriptor.set && !("value" in descriptor)) {
separatorLabel.hidden = true;
}
@@ -2384,7 +2420,6 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (ownerView.delete) {
let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
deleteNode.className = "plain variables-view-delete";
- deleteNode.setAttribute("ordinal", 2);
deleteNode.addEventListener("click", this._onDelete.bind(this), false);
this._title.appendChild(deleteNode);
}
@@ -2399,24 +2434,28 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
let nonWritableIcon = this.document.createElement("hbox");
nonWritableIcon.className = "variable-or-property-non-writable-icon";
+ nonWritableIcon.setAttribute("optional-visibility", "");
this._title.appendChild(nonWritableIcon);
}
if (descriptor.value && typeof descriptor.value == "object") {
if (descriptor.value.frozen) {
let frozenLabel = this.document.createElement("label");
frozenLabel.className = "plain variable-or-property-frozen-label";
+ frozenLabel.setAttribute("optional-visibility", "");
frozenLabel.setAttribute("value", "F");
this._title.appendChild(frozenLabel);
}
if (descriptor.value.sealed) {
let sealedLabel = this.document.createElement("label");
sealedLabel.className = "plain variable-or-property-sealed-label";
+ sealedLabel.setAttribute("optional-visibility", "");
sealedLabel.setAttribute("value", "S");
this._title.appendChild(sealedLabel);
}
if (!descriptor.value.extensible) {
let nonExtensibleLabel = this.document.createElement("label");
nonExtensibleLabel.className = "plain variable-or-property-non-extensible-label";
+ nonExtensibleLabel.setAttribute("optional-visibility", "");
nonExtensibleLabel.setAttribute("value", "N");
this._title.appendChild(nonExtensibleLabel);
}
@@ -2557,7 +2596,9 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
let input = this.document.createElement("textbox");
input.className = "plain " + aClassName;
input.setAttribute("value", initialString);
- input.setAttribute("flex", "1");
+ if (!this._variablesView.alignedValues) {
+ input.setAttribute("flex", "1");
+ }
// Replace the specified label with a textbox input element.
aLabel.parentNode.replaceChild(input, aLabel);
diff --git a/browser/devtools/shared/widgets/ViewHelpers.jsm b/browser/devtools/shared/widgets/ViewHelpers.jsm
index 44aa32485d52..facbb2ba820e 100644
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -973,6 +973,18 @@ this.WidgetMethods = {
return "";
},
+ /**
+ * Retrieves the attachment of the selected element.
+ * @return string
+ */
+ get selectedAttachment() {
+ let selectedElement = this._widget.selectedItem;
+ if (selectedElement) {
+ return this._itemsByElement.get(selectedElement).attachment;
+ }
+ return null;
+ },
+
/**
* Selects the element with the entangled item in this container.
* @param Item | function aItem
diff --git a/browser/devtools/shared/widgets/widgets.css b/browser/devtools/shared/widgets/widgets.css
index 5ee65ea9118c..b3d646b688f5 100644
--- a/browser/devtools/shared/widgets/widgets.css
+++ b/browser/devtools/shared/widgets/widgets.css
@@ -57,3 +57,11 @@
.variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
display: none;
}
+
+*:not(:hover) .variables-view-delete {
+ visibility: hidden;
+}
+
+.variables-view-container[aligned-values] .title > [optional-visibility] {
+ display: none;
+}
diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js
index 0435fc0d617f..0ffac735bdd4 100644
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -226,6 +226,7 @@ Editor.prototype = {
this.showContextMenu(doc, ev.screenX, ev.screenY);
}, false);
+ cm.on("focus", () => this.emit("focus"));
cm.on("change", () => this.emit("change"));
cm.on("gutterClick", (cm, line) => this.emit("gutterClick", line));
cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
diff --git a/browser/locales/en-US/chrome/browser/devtools/shadereditor.dtd b/browser/locales/en-US/chrome/browser/devtools/shadereditor.dtd
new file mode 100644
index 000000000000..9773e6ccc778
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/shadereditor.dtd
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/locales/en-US/chrome/browser/devtools/shadereditor.properties b/browser/locales/en-US/chrome/browser/devtools/shadereditor.properties
new file mode 100644
index 000000000000..a9ae526f0ec2
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/shadereditor.properties
@@ -0,0 +1,32 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used inside the Debugger
+# which is available from the Web Developer sub-menu -> 'Debugger'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (ToolboxShaderEditor.label):
+# This string is displayed in the title of the tab when the Shader Editor is
+# displayed inside the developer tools window and in the Developer Tools Menu.
+ToolboxShaderEditor.label=Shader Editor
+
+# LOCALIZATION NOTE (ToolboxShaderEditor.tooltip):
+# This string is displayed in the tooltip of the tab when the Shader Editor is
+# displayed inside the developer tools window.
+ToolboxShaderEditor.tooltip=Live GLSL shader language editor for WebGL
+
+# LOCALIZATION NOTE (shadersList.programLabel):
+# This string is displayed in the programs list of the Shader Editor,
+# identifying a set of linked GLSL shaders.
+shadersList.programLabel=Program %S
+
+# LOCALIZATION NOTE (shadersList.blackboxLabel):
+# This string is displayed in the programs list of the Shader Editor, while
+# the user hovers over the checkbox used to toggle blackboxing of a program's
+# associated fragment shader.
+shadersList.blackboxLabel=Toggle geometry visibility
diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn
index b50f8afd9de7..99ae32d671d0 100644
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -27,6 +27,8 @@
locale/browser/devtools/debugger.properties (%chrome/browser/devtools/debugger.properties)
locale/browser/devtools/netmonitor.dtd (%chrome/browser/devtools/netmonitor.dtd)
locale/browser/devtools/netmonitor.properties (%chrome/browser/devtools/netmonitor.properties)
+ locale/browser/devtools/shadereditor.dtd (%chrome/browser/devtools/shadereditor.dtd)
+ locale/browser/devtools/shadereditor.properties (%chrome/browser/devtools/shadereditor.properties)
locale/browser/devtools/gcli.properties (%chrome/browser/devtools/gcli.properties)
locale/browser/devtools/gclicommands.properties (%chrome/browser/devtools/gclicommands.properties)
locale/browser/devtools/webconsole.properties (%chrome/browser/devtools/webconsole.properties)
diff --git a/browser/themes/linux/devtools/shadereditor.css b/browser/themes/linux/devtools/shadereditor.css
new file mode 100644
index 000000000000..34817e2453fc
--- /dev/null
+++ b/browser/themes/linux/devtools/shadereditor.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+%include ../../shared/devtools/shadereditor.inc.css
diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css
index babde8110650..56691b8a9c6d 100644
--- a/browser/themes/linux/devtools/widgets.css
+++ b/browser/themes/linux/devtools/widgets.css
@@ -565,6 +565,26 @@
text-shadow: 0 0 8px #ccf;
}
+/* Aligned values */
+
+.variables-view-container[aligned-values] .title > .separator {
+ -moz-box-flex: 1;
+}
+
+.variables-view-container[aligned-values] .title > .value {
+ width: 70vw;
+}
+
+.variables-view-container[aligned-values] .title > .element-value-input {
+ width: calc(70vw - 10px);
+}
+
+/* Actions first */
+
+.variables-view-container[actions-first] .variables-view-delete {
+ -moz-box-ordinal-group: 0;
+}
+
/* Variables and properties tooltips */
.variable-or-property > tooltip > label {
@@ -600,10 +620,6 @@
-moz-image-region: rect(0,48px,16px,32px);
}
-*:not(:hover) .variables-view-delete {
- display: none;
-}
-
.variables-view-delete > .toolbarbutton-text {
display: none;
}
@@ -667,3 +683,5 @@
.arrow[invisible] {
visibility: hidden;
}
+
+%include ../../shared/devtools/app-manager/manifest-editor.inc.css
diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn
index c2cd48249e3a..9742088b4cc3 100644
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -127,7 +127,7 @@ browser.jar:
skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/controls.png (../shared/devtools/controls.png)
- skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
+* skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
skin/classic/browser/devtools/commandline-icon.png (devtools/commandline-icon.png)
skin/classic/browser/devtools/command-paintflashing.png (devtools/command-paintflashing.png)
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
@@ -172,6 +172,7 @@ browser.jar:
skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png (devtools/breadcrumbs/rtl-start-selected.png)
skin/classic/browser/devtools/splitview.css (devtools/splitview.css)
skin/classic/browser/devtools/styleeditor.css (devtools/styleeditor.css)
+* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
skin/classic/browser/devtools/debugger.css (devtools/debugger.css)
* skin/classic/browser/devtools/profiler.css (devtools/profiler.css)
skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css)
diff --git a/browser/themes/osx/devtools/shadereditor.css b/browser/themes/osx/devtools/shadereditor.css
new file mode 100644
index 000000000000..9fb10e31eaa0
--- /dev/null
+++ b/browser/themes/osx/devtools/shadereditor.css
@@ -0,0 +1,6 @@
+/* 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/. */
+
+%include ../shared.inc
+%include ../../shared/devtools/shadereditor.inc.css
diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css
index ca11a9d6ec6e..745296aaaee8 100644
--- a/browser/themes/osx/devtools/widgets.css
+++ b/browser/themes/osx/devtools/widgets.css
@@ -565,6 +565,26 @@
text-shadow: 0 0 8px #ccf;
}
+/* Aligned values */
+
+.variables-view-container[aligned-values] .title > .separator {
+ -moz-box-flex: 1;
+}
+
+.variables-view-container[aligned-values] .title > .value {
+ width: 70vw;
+}
+
+.variables-view-container[aligned-values] .title > .element-value-input {
+ width: calc(70vw - 10px);
+}
+
+/* Actions first */
+
+.variables-view-container[actions-first] .variables-view-delete {
+ -moz-box-ordinal-group: 0;
+}
+
/* Variables and properties tooltips */
.variable-or-property > tooltip > label {
@@ -600,10 +620,6 @@
-moz-image-region: rect(0,48px,16px,32px);
}
-*:not(:hover) .variables-view-delete {
- display: none;
-}
-
.variables-view-delete > .toolbarbutton-text {
display: none;
}
@@ -663,3 +679,5 @@
.arrow[invisible] {
visibility: hidden;
}
+
+%include ../../shared/devtools/app-manager/manifest-editor.inc.css
diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn
index e77de98a285c..0c43f78da8ac 100644
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -219,7 +219,7 @@ browser.jar:
skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/controls.png (../shared/devtools/controls.png)
- skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
+* skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
skin/classic/browser/devtools/commandline-icon.png (devtools/commandline-icon.png)
skin/classic/browser/devtools/command-paintflashing.png (devtools/command-paintflashing.png)
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
@@ -264,6 +264,7 @@ browser.jar:
skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png (devtools/breadcrumbs/rtl-start-selected.png)
skin/classic/browser/devtools/splitview.css (devtools/splitview.css)
skin/classic/browser/devtools/styleeditor.css (devtools/styleeditor.css)
+* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
* skin/classic/browser/devtools/debugger.css (devtools/debugger.css)
* skin/classic/browser/devtools/profiler.css (devtools/profiler.css)
skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css)
diff --git a/browser/themes/shared/devtools/app-manager/images/remove.svg b/browser/themes/shared/devtools/app-manager/images/remove.svg
index afb4b1ebc4f4..3d36871c92bd 100644
--- a/browser/themes/shared/devtools/app-manager/images/remove.svg
+++ b/browser/themes/shared/devtools/app-manager/images/remove.svg
@@ -6,5 +6,5 @@
diff --git a/browser/themes/shared/devtools/app-manager/manifest-editor.inc.css b/browser/themes/shared/devtools/app-manager/manifest-editor.inc.css
new file mode 100644
index 000000000000..dc016a730cb2
--- /dev/null
+++ b/browser/themes/shared/devtools/app-manager/manifest-editor.inc.css
@@ -0,0 +1,67 @@
+/* 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/. */
+
+/* Manifest Editor overrides */
+
+.variables-view-container.manifest-editor {
+ background-color: #F5F5F5;
+ padding: 20px 13px;
+}
+
+.manifest-editor .variable-or-property:focus > .title {
+ background-color: #EDEDED;
+ color: #000;
+ border-radius: 4px;
+}
+
+.manifest-editor .variables-view-property > .title > .name {
+ color: #27406A;
+}
+
+.manifest-editor .variable-or-property > .title > label {
+ font-family: monospace;
+}
+
+.manifest-editor .variable-or-property > .title > .token-string {
+ color: #54BC6A;
+ font-weight: bold;
+}
+
+.manifest-editor .variable-or-property > .title > .token-boolean,
+.manifest-editor .variable-or-property > .title > .token-number {
+ color: #009BD4;
+ font-weight: bold;
+}
+
+.manifest-editor .variable-or-property > .title > .token-undefined {
+ color: #bbb;
+}
+
+.manifest-editor .variable-or-property > .title > .token-null {
+ color: #999;
+}
+
+.manifest-editor .variable-or-property > .title > .token-other {
+ color: #333;
+}
+
+.manifest-editor .variables-view-variable {
+ border-bottom: none;
+}
+
+.manifest-editor .variables-view-delete,
+.manifest-editor .variables-view-delete:hover,
+.manifest-editor .variables-view-delete:active {
+ list-style-image: none;
+ -moz-image-region: initial;
+}
+
+.manifest-editor .variables-view-delete::before {
+ width: 12px;
+ height: 12px;
+ content: "";
+ display: inline-block;
+ background-image: url("app-manager/remove.svg");
+ background-size: 12px auto;
+}
diff --git a/browser/themes/shared/devtools/app-manager/projects.css b/browser/themes/shared/devtools/app-manager/projects.css
index 4edb66dc7175..e31813494e82 100644
--- a/browser/themes/shared/devtools/app-manager/projects.css
+++ b/browser/themes/shared/devtools/app-manager/projects.css
@@ -110,6 +110,7 @@ strong {
right: 5px;
bottom: 5px;
visibility: hidden;
+ opacity: 0.5;
}
.project-item:hover .button-remove {
diff --git a/browser/themes/shared/devtools/shadereditor.inc.css b/browser/themes/shared/devtools/shadereditor.inc.css
new file mode 100644
index 000000000000..94c607a41f83
--- /dev/null
+++ b/browser/themes/shared/devtools/shadereditor.inc.css
@@ -0,0 +1,107 @@
+/* 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/. */
+
+#body {
+ background: url(background-noise-toolbar.png), hsl(208,11%,27%);
+}
+
+#content {
+ background: #fff;
+}
+
+/* Reload and waiting notices */
+
+.notice-container {
+ background: transparent;
+ margin-top: -50vh;
+ color: #fff;
+}
+
+#reload-notice {
+ font-size: 120%;
+}
+
+#waiting-notice {
+ font-size: 110%;
+}
+
+#waiting-notice::before {
+ display: inline-block;
+ content: "";
+ background: url("chrome://global/skin/icons/loading_16.png") center no-repeat;
+ width: 16px;
+ height: 16px;
+ -moz-margin-end: 6px;
+}
+
+#requests-menu-reload-notice-button {
+ min-height: 2em;
+}
+
+/* Shaders pane */
+
+#shaders-pane {
+ min-width: 150px;
+}
+
+#shaders-pane + .devtools-side-splitter {
+ -moz-border-start-color: transparent;
+}
+
+.side-menu-widget-item-checkbox {
+ -moz-appearance: none;
+ -moz-margin-end: -6px;
+ padding: 0;
+ opacity: 0;
+ transition: opacity .15s ease-out 0s;
+}
+
+/* Only show the checkbox when the source is hovered over, is selected, or if it
+ * is not checked. */
+.side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
+.side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
+.side-menu-widget-item-checkbox:not([checked]) {
+ opacity: 1;
+ transition: opacity .15s ease-out 0s;
+}
+
+.side-menu-widget-item-checkbox > .checkbox-check {
+ -moz-appearance: none;
+ background: none;
+ background-image: url("chrome://browser/skin/devtools/itemToggle.png");
+ background-repeat: no-repeat;
+ background-clip: content-box;
+ background-size: 32px 16px;
+ background-position: -16px 0;
+ width: 16px;
+ height: 16px;
+ border: 0;
+}
+
+.side-menu-widget-item-checkbox[checked] > .checkbox-check {
+ background-position: 0 0;
+}
+
+.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
+ color: #888;
+}
+
+/* Shader source editors */
+
+#editors-splitter {
+ -moz-border-start-color: rgb(61,69,76);
+}
+
+.editor-label {
+ background: url(background-noise-toolbar.png), hsl(208,11%,27%);
+ border-top: 1px solid #222426;
+ padding: 1px 12px;
+ color: #fff;
+}
+
+.editor-label[selected] {
+ background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left;
+ box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15),
+ inset 0 -1px 0 hsla(210,40%,83%,.05);
+}
diff --git a/browser/themes/windows/devtools/shadereditor.css b/browser/themes/windows/devtools/shadereditor.css
new file mode 100644
index 000000000000..34817e2453fc
--- /dev/null
+++ b/browser/themes/windows/devtools/shadereditor.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+%include ../../shared/devtools/shadereditor.inc.css
diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css
index bb264d368838..b6b4ea321c79 100644
--- a/browser/themes/windows/devtools/widgets.css
+++ b/browser/themes/windows/devtools/widgets.css
@@ -568,6 +568,26 @@
text-shadow: 0 0 8px #ccf;
}
+/* Aligned values */
+
+.variables-view-container[aligned-values] .title > .separator {
+ -moz-box-flex: 1;
+}
+
+.variables-view-container[aligned-values] .title > .value {
+ width: 70vw;
+}
+
+.variables-view-container[aligned-values] .title > .element-value-input {
+ width: calc(70vw - 10px);
+}
+
+/* Actions first */
+
+.variables-view-container[actions-first] .variables-view-delete {
+ -moz-box-ordinal-group: 0;
+}
+
/* Variables and properties tooltips */
.variable-or-property > tooltip > label {
@@ -603,10 +623,6 @@
-moz-image-region: rect(0,48px,16px,32px);
}
-*:not(:hover) .variables-view-delete {
- display: none;
-}
-
.variables-view-delete > .toolbarbutton-text {
display: none;
}
@@ -670,3 +686,5 @@
.arrow[invisible] {
visibility: hidden;
}
+
+%include ../../shared/devtools/app-manager/manifest-editor.inc.css
diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn
index 33a810f62e28..b927873dfd2c 100644
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -154,7 +154,7 @@ browser.jar:
skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/browser/devtools/controls.png (../shared/devtools/controls.png)
- skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
+* skin/classic/browser/devtools/widgets.css (devtools/widgets.css)
skin/classic/browser/devtools/commandline-icon.png (devtools/commandline-icon.png)
skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
@@ -199,6 +199,7 @@ browser.jar:
skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png (devtools/breadcrumbs/rtl-start-selected.png)
skin/classic/browser/devtools/splitview.css (devtools/splitview.css)
skin/classic/browser/devtools/styleeditor.css (devtools/styleeditor.css)
+* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
skin/classic/browser/devtools/debugger.css (devtools/debugger.css)
* skin/classic/browser/devtools/profiler.css (devtools/profiler.css)
skin/classic/browser/devtools/netmonitor.css (devtools/netmonitor.css)
@@ -430,7 +431,7 @@ browser.jar:
skin/classic/aero/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
skin/classic/aero/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
skin/classic/aero/browser/devtools/controls.png (../shared/devtools/controls.png)
- skin/classic/aero/browser/devtools/widgets.css (devtools/widgets.css)
+* skin/classic/aero/browser/devtools/widgets.css (devtools/widgets.css)
skin/classic/aero/browser/devtools/commandline-icon.png (devtools/commandline-icon.png)
skin/classic/aero/browser/devtools/command-paintflashing.png (devtools/command-paintflashing.png)
skin/classic/aero/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
@@ -475,6 +476,7 @@ browser.jar:
skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-selected.png (devtools/breadcrumbs/rtl-start-selected.png)
skin/classic/aero/browser/devtools/splitview.css (devtools/splitview.css)
skin/classic/aero/browser/devtools/styleeditor.css (devtools/styleeditor.css)
+* skin/classic/aero/browser/devtools/shadereditor.css (devtools/shadereditor.css)
skin/classic/aero/browser/devtools/debugger.css (devtools/debugger.css)
* skin/classic/aero/browser/devtools/profiler.css (devtools/profiler.css)
skin/classic/aero/browser/devtools/netmonitor.css (devtools/netmonitor.css)
diff --git a/mobile/android/base/Tabs.java b/mobile/android/base/Tabs.java
index 27bc7750435c..ea90206433b5 100644
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -71,10 +71,12 @@ public class Tabs implements GeckoEventListener {
private final Runnable mPersistTabsRunnable = new Runnable() {
@Override
public void run() {
- boolean syncIsSetup = SyncAccounts.syncAccountsExist(getAppContext());
- if (syncIsSetup) {
- TabsAccessor.persistLocalTabs(getContentResolver(), getTabsInOrder());
- }
+ try {
+ boolean syncIsSetup = SyncAccounts.syncAccountsExist(getAppContext());
+ if (syncIsSetup) {
+ TabsAccessor.persistLocalTabs(getContentResolver(), getTabsInOrder());
+ }
+ } catch (SecurityException se) {} // will fail without android.permission.GET_ACCOUNTS
}
};
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index 87891ea9e405..ca97446cf101 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3931,6 +3931,10 @@
"kind": "boolean",
"description": "How many times has the devtool's Style Editor been opened?"
},
+ "DEVTOOLS_SHADEREDITOR_OPENED_BOOLEAN": {
+ "kind": "boolean",
+ "description": "How many times has the devtool's Shader Editor been opened?"
+ },
"DEVTOOLS_JSPROFILER_OPENED_BOOLEAN": {
"kind": "boolean",
"description": "How many times has the devtool's JS Profiler been opened?"
@@ -4003,6 +4007,10 @@
"kind": "flag",
"description": "How many users have opened the devtool's Style Editor?"
},
+ "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG": {
+ "kind": "flag",
+ "description": "How many users have opened the devtool's Shader Editor?"
+ },
"DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG": {
"kind": "flag",
"description": "How many users have opened the devtool's JS Profiler?"
@@ -4103,6 +4111,12 @@
"n_buckets": 100,
"description": "How long has the style editor been active (seconds)"
},
+ "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS": {
+ "kind": "exponential",
+ "high": "10000000",
+ "n_buckets": 100,
+ "description": "How long has the Shader Editor been active (seconds)"
+ },
"DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS": {
"kind": "exponential",
"high": "10000000",
diff --git a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
index ee2ff75935c1..d594390be527 100644
--- a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
+++ b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
@@ -2,13 +2,6 @@
* 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/. */
-/**
- * WARNING: BackgroundPageThumbs.jsm is currently excluded from release builds.
- * If you use it, you must also exclude your caller when RELEASE_BUILD is
- * defined, as described here:
- * https://wiki.mozilla.org/Platform/Channel-specific_build_defines
- */
-
const EXPORTED_SYMBOLS = [
"BackgroundPageThumbs",
];
@@ -40,11 +33,6 @@ const BackgroundPageThumbs = {
*
* The page is loaded anonymously, and plug-ins are disabled.
*
- * WARNING: BackgroundPageThumbs.jsm is currently excluded from release
- * builds. If you use it, you must also exclude your caller when
- * RELEASE_BUILD is defined, as described here:
- * https://wiki.mozilla.org/Platform/Channel-specific_build_defines
- *
* @param url The URL to capture.
* @param options An optional object that configures the capture. Its
* properties are the following, and all are optional:
@@ -86,11 +74,6 @@ const BackgroundPageThumbs = {
* Asynchronously captures a thumbnail of the given URL if one does not
* already exist. Otherwise does nothing.
*
- * WARNING: BackgroundPageThumbs.jsm is currently excluded from release
- * builds. If you use it, you must also exclude your caller when
- * RELEASE_BUILD is defined, as described here:
- * https://wiki.mozilla.org/Platform/Channel-specific_build_defines
- *
* @param url The URL to capture.
* @param options An optional object that configures the capture. See
* capture() for description.
diff --git a/toolkit/components/thumbnails/jar.mn b/toolkit/components/thumbnails/jar.mn
index c608329393ec..68d728095b07 100644
--- a/toolkit/components/thumbnails/jar.mn
+++ b/toolkit/components/thumbnails/jar.mn
@@ -3,6 +3,4 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
-#ifndef RELEASE_BUILD
+ content/global/backgroundPageThumbsContent.js (content/backgroundPageThumbsContent.js)
-#endif
diff --git a/toolkit/components/thumbnails/moz.build b/toolkit/components/thumbnails/moz.build
index 39b6d5aafc32..7593729dea85 100644
--- a/toolkit/components/thumbnails/moz.build
+++ b/toolkit/components/thumbnails/moz.build
@@ -12,9 +12,7 @@ EXTRA_COMPONENTS += [
]
EXTRA_JS_MODULES += [
+ 'BackgroundPageThumbs.jsm',
'PageThumbs.jsm',
'PageThumbsWorker.js',
]
-
-if not CONFIG['RELEASE_BUILD']:
- EXTRA_JS_MODULES += ['BackgroundPageThumbs.jsm']
diff --git a/toolkit/components/thumbnails/test/Makefile.in b/toolkit/components/thumbnails/test/Makefile.in
index f8546bab2b7d..d0b50f500fed 100644
--- a/toolkit/components/thumbnails/test/Makefile.in
+++ b/toolkit/components/thumbnails/test/Makefile.in
@@ -2,18 +2,9 @@
# 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/.
-ifndef RELEASE_BUILD
-MOCHITEST_BROWSER_FILES += \
- browser_thumbnails_background.js \
- browser_thumbnails_update.js \
- thumbnails_background.sjs \
- thumbnails_update.sjs \
- $(NULL)
-
ifdef MOZ_CRASHREPORTER
MOCHITEST_BROWSER_FILES += \
browser_thumbnails_background_crash.js \
thumbnails_crash_content_helper.js \
$(NULL)
endif
-endif
diff --git a/toolkit/components/thumbnails/test/browser.ini b/toolkit/components/thumbnails/test/browser.ini
index 6a6ab875d804..d00e90245f75 100644
--- a/toolkit/components/thumbnails/test/browser.ini
+++ b/toolkit/components/thumbnails/test/browser.ini
@@ -5,7 +5,10 @@ support-files =
background_red_scroll.html
head.js
privacy_cache_control.sjs
+ thumbnails_background.sjs
+ thumbnails_update.sjs
+[browser_thumbnails_background.js]
[browser_thumbnails_bug726727.js]
[browser_thumbnails_bug727765.js]
[browser_thumbnails_bug818225.js]
@@ -15,3 +18,4 @@ support-files =
[browser_thumbnails_redirect.js]
[browser_thumbnails_storage.js]
[browser_thumbnails_storage_migrate3.js]
+[browser_thumbnails_update.js]
diff --git a/toolkit/devtools/server/actors/webgl.js b/toolkit/devtools/server/actors/webgl.js
new file mode 100644
index 000000000000..c762ec8f17f8
--- /dev/null
+++ b/toolkit/devtools/server/actors/webgl.js
@@ -0,0 +1,851 @@
+/* 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 {Cc, Ci, Cu, Cr} = require("chrome");
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const events = require("sdk/event/core");
+const protocol = require("devtools/server/protocol");
+
+const { on, once, off, emit } = events;
+const { method, Arg, Option, RetVal } = protocol;
+
+const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
+const HIGHLIGHT_FRAG_SHADER = [
+ "precision lowp float;",
+ "void main() {",
+ "gl_FragColor.rgba = vec4(%color);",
+ "}"
+].join("\n");
+
+exports.register = function(handle) {
+ handle.addTabActor(WebGLActor, "webglActor");
+}
+
+exports.unregister = function(handle) {
+ handle.removeTabActor(WebGLActor);
+}
+
+/**
+ * A WebGL Shader contributing to building a WebGL Program.
+ * You can either retrieve, or compile the source of a shader, which will
+ * automatically inflict the necessary changes to the WebGL state.
+ */
+let ShaderActor = protocol.ActorClass({
+ typeName: "gl-shader",
+ initialize: function(conn, id) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ },
+
+ /**
+ * Gets the source code for this shader.
+ */
+ getText: method(function() {
+ return this.text;
+ }, {
+ response: { text: RetVal("string") }
+ }),
+
+ /**
+ * Sets and compiles new source code for this shader.
+ */
+ compile: method(function(text) {
+ // Get the shader and corresponding program to change via the WebGL proxy.
+ let { context, shader, program, observer: { proxy } } = this;
+
+ // Get the new shader source to inject.
+ let oldText = this.text;
+ let newText = text;
+
+ // Overwrite the shader's source.
+ let error = proxy.call("compileShader", context, program, shader, this.text = newText);
+
+ // If something went wrong, revert to the previous shader.
+ if (error.compile || error.link) {
+ proxy.call("compileShader", context, program, shader, this.text = oldText);
+ return error;
+ }
+ return undefined;
+ }, {
+ request: { text: Arg(0, "string") },
+ response: { error: RetVal("nullable:json") }
+ })
+});
+
+/**
+ * The corresponding Front object for the ShaderActor.
+ */
+let ShaderFront = protocol.FrontClass(ShaderActor, {
+ initialize: function(client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+/**
+ * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
+ * of two shaders: a vertex shader and a fragment shader.
+ */
+let ProgramActor = protocol.ActorClass({
+ typeName: "gl-program",
+ initialize: function(conn, id) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this._shaderActorsCache = { vertex: null, fragment: null };
+ },
+
+ /**
+ * Gets the vertex shader linked to this program. This method guarantees
+ * a single actor instance per shader.
+ */
+ getVertexShader: method(function() {
+ return this._getShaderActor("vertex");
+ }, {
+ response: { shader: RetVal("gl-shader") }
+ }),
+
+ /**
+ * Gets the fragment shader linked to this program. This method guarantees
+ * a single actor instance per shader.
+ */
+ getFragmentShader: method(function() {
+ return this._getShaderActor("fragment");
+ }, {
+ response: { shader: RetVal("gl-shader") }
+ }),
+
+ /**
+ * Replaces this program's fragment shader with an temporary
+ * easy-to-distinguish alternative. See HIGHLIGHT_FRAG_SHADER.
+ */
+ highlight: method(function(color) {
+ let shaderActor = this._getShaderActor("fragment");
+ let oldText = shaderActor.text;
+ let newText = HIGHLIGHT_FRAG_SHADER.replace("%color", color)
+ shaderActor.compile(newText);
+ shaderActor.text = oldText;
+ }, {
+ request: { color: Arg(0, "array:string") },
+ oneway: true
+ }),
+
+ /**
+ * Reverts this program's fragment shader to the latest user-defined source.
+ */
+ unhighlight: method(function() {
+ let shaderActor = this._getShaderActor("fragment");
+ shaderActor.compile(shaderActor.text);
+ }, {
+ oneway: true
+ }),
+
+ /**
+ * Returns a cached ShaderActor instance based on the required shader type.
+ *
+ * @param string type
+ * Either "vertex" or "fragment".
+ * @return ShaderActor
+ * The respective shader actor instance.
+ */
+ _getShaderActor: function(type) {
+ if (this._shaderActorsCache[type]) {
+ return this._shaderActorsCache[type];
+ }
+
+ let shaderActor = new ShaderActor(this.conn);
+ shaderActor.context = this.context;
+ shaderActor.observer = this.observer;
+ shaderActor.program = this.program;
+ shaderActor.shader = this.shadersData[type].ref;
+ shaderActor.text = this.shadersData[type].text;
+
+ return this._shaderActorsCache[type] = shaderActor;
+ }
+});
+
+/**
+ * The corresponding Front object for the ProgramActor.
+ */
+let ProgramFront = protocol.FrontClass(ProgramActor, {
+ initialize: function(client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+/**
+ * The WebGL Actor handles simple interaction with a WebGL context via a few
+ * high-level methods. After instantiating this actor, you'll need to set it
+ * up by calling setup().
+ */
+let WebGLActor = exports.WebGLActor = protocol.ActorClass({
+ typeName: "webgl",
+ initialize: function(conn, tabActor) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this.tabActor = tabActor;
+ this._onGlobalCreated = this._onGlobalCreated.bind(this);
+ this._onProgramLinked = this._onProgramLinked.bind(this);
+ },
+ destroy: function(conn) {
+ protocol.Actor.prototype.destroy.call(this, conn);
+ this.finalize();
+ },
+
+ /**
+ * Starts waiting for the current tab actor's document global to be
+ * created, in order to instrument the Canvas context and become
+ * aware of everything the content does WebGL-wise.
+ *
+ * See ContentObserver and WebGLInstrumenter for more details.
+ */
+ setup: method(function() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ this._contentObserver = new ContentObserver(this.tabActor);
+ this._webglObserver = new WebGLObserver();
+ on(this._contentObserver, "global-created", this._onGlobalCreated);
+ on(this._webglObserver, "program-linked", this._onProgramLinked);
+
+ this.tabActor.window.location.reload();
+ }, {
+ oneway: true
+ }),
+
+ /**
+ * Stops listening for document global changes and puts this actor
+ * to hibernation. This method is called automatically just before the
+ * actor is destroyed.
+ */
+ finalize: method(function() {
+ if (!this._initialized) {
+ return;
+ }
+ this._initialized = false;
+ this._contentObserver.stopListening();
+ off(this._contentObserver, "global-created", this._onGlobalCreated);
+ off(this._webglObserver, "program-linked", this._onProgramLinked);
+ }, {
+ oneway: true
+ }),
+
+ /**
+ * Events emitted by this actor. The "program-linked" event is fired
+ * every time a WebGL program was linked with its respective two shaders.
+ */
+ events: {
+ "program-linked": {
+ type: "programLinked",
+ program: Arg(0, "gl-program")
+ }
+ },
+
+ /**
+ * Invoked whenever the current tab actor's document global is created.
+ */
+ _onGlobalCreated: function(window) {
+ WebGLInstrumenter.handle(window, this._webglObserver);
+ },
+
+ /**
+ * Invoked whenever the current WebGL context links a program.
+ */
+ _onProgramLinked: function(gl, program, shaders) {
+ let observer = this._webglObserver;
+ let shadersData = { vertex: null, fragment: null };
+
+ for (let shader of shaders) {
+ let text = observer.cache.call("getShaderInfo", shader);
+ let data = { ref: shader, text: text };
+
+ // Make sure the shader data object always contains the vertex shader
+ // first, and the fragment shader second. There are no guarantees that
+ // the compilation order of shaders in the debuggee is always the same.
+ if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == gl.VERTEX_SHADER) {
+ shadersData.vertex = data;
+ } else {
+ shadersData.fragment = data;
+ }
+ }
+
+ let programActor = new ProgramActor(this.conn);
+ programActor.context = gl;
+ programActor.observer = observer;
+ programActor.program = program;
+ programActor.shadersData = shadersData;
+
+ events.emit(this, "program-linked", programActor);
+ }
+});
+
+/**
+ * The corresponding Front object for the WebGLActor.
+ */
+let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
+ initialize: function(client, { webglActor }) {
+ protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
+ client.addActorPool(this);
+ this.manage(this);
+ }
+});
+
+/**
+ * Handles adding an observer for the creation of content document globals,
+ * event sent immediately after a web content document window has been set up,
+ * but before any script code has been executed. This will allow us to
+ * instrument the HTMLCanvasElement with the appropriate inspection methods.
+ */
+function ContentObserver(tabActor) {
+ this._contentWindow = tabActor.browser.contentWindow;
+ this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this);
+ this.startListening();
+}
+
+ContentObserver.prototype = {
+ /**
+ * Starts listening for the required observer messages.
+ */
+ startListening: function() {
+ Services.obs.addObserver(
+ this._onContentGlobalCreated, "content-document-global-created", false);
+ },
+
+ /**
+ * Stops listening for the required observer messages.
+ */
+ stopListening: function() {
+ Services.obs.removeObserver(
+ this._onContentGlobalCreated, "content-document-global-created", false);
+ },
+
+ /**
+ * Fired immediately after a web content document window has been set up.
+ */
+ _onContentGlobalCreated: function(subject, topic, data) {
+ if (subject == this._contentWindow) {
+ emit(this, "global-created", subject);
+ }
+ }
+};
+
+/**
+ * Instruments a HTMLCanvasElement with the appropriate inspection methods.
+ */
+let WebGLInstrumenter = {
+ /**
+ * Overrides the getContext method in the HTMLCanvasElement prototype.
+ *
+ * @param nsIDOMWindow window
+ * The window to perform the instrumentation in.
+ * @param WebGLObserver observer
+ * The observer watching function calls in the context.
+ */
+ handle: function(window, observer) {
+ let self = this;
+
+ let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
+ let canvasPrototype = canvasElem.prototype;
+ let originalGetContext = canvasPrototype.getContext;
+
+ /**
+ * Returns a drawing context on the canvas, or null if the context ID is
+ * not supported. This override creates an observer for the targeted context
+ * type and instruments specific functions in the targeted context instance.
+ */
+ canvasPrototype.getContext = function(name, options) {
+ // Make sure a context was able to be created.
+ let context = originalGetContext.call(this, name, options);
+ if (!context) {
+ return context;
+ }
+ // Make sure a WebGL (not a 2D) context will be instrumented.
+ if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
+ return context;
+ }
+
+ // Link our observer to the new WebGL context methods.
+ for (let { timing, callback, functions } of self._methods) {
+ for (let func of functions) {
+ self._instrument(observer, context, func, timing, callback);
+ }
+ }
+
+ // Return the decorated context back to the content consumer, which
+ // will continue using it normally.
+ return context;
+ };
+ },
+
+ /**
+ * Overrides a specific method in a HTMLCanvasElement context.
+ *
+ * @param WebGLObserver observer
+ * The observer watching function calls in the context.
+ * @param WebGLRenderingContext context
+ * The targeted context instance.
+ * @param string funcName
+ * The function to override.
+ * @param string timing [optional]
+ * When to issue the callback in relation to the actual context
+ * function call. Availalble values are "before" and "after" (default).
+ * @param string callbackName [optional]
+ * A custom callback function name in the observer. If unspecified,
+ * it will default to the name of the function to override.
+ */
+ _instrument: function(observer, context, funcName, timing, callbackName) {
+ let originalFunc = context[funcName];
+
+ context[funcName] = function() {
+ let glArgs = Array.slice(arguments);
+ let glResult, glBreak;
+
+ if (timing == "before" && !observer.suppressHandlers) {
+ glBreak = observer.call(callbackName || funcName, context, glArgs);
+ if (glBreak) return undefined;
+ }
+
+ glResult = originalFunc.apply(this, glArgs);
+
+ if (timing == "after" && !observer.suppressHandlers) {
+ glBreak = observer.call(callbackName || funcName, context, glArgs, glResult);
+ if (glBreak) return undefined;
+ }
+
+ return glResult;
+ };
+ },
+
+ /**
+ * Override mappings for WebGL methods.
+ */
+ _methods: [{
+ timing: "after",
+ functions: [
+ "linkProgram", "getAttribLocation", "getUniformLocation"
+ ]
+ }, {
+ timing: "before",
+ callback: "toggleVertexAttribArray",
+ functions: [
+ "enableVertexAttribArray", "disableVertexAttribArray"
+ ]
+ }, {
+ timing: "before",
+ callback: "attribute_",
+ functions: [
+ "vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
+ "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
+ "vertexAttribPointer"
+ ]
+ }, {
+ timing: "before",
+ callback: "uniform_",
+ functions: [
+ "uniform1i", "uniform2i", "uniform3i", "uniform4i",
+ "uniform1f", "uniform2f", "uniform3f", "uniform4f",
+ "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
+ "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
+ "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
+ ]
+ }]
+ // TODO: It'd be a good idea to handle other functions as well:
+ // - getActiveUniform
+ // - getUniform
+ // - getActiveAttrib
+ // - getVertexAttrib
+};
+
+/**
+ * An observer that captures a WebGL context's method calls.
+ */
+function WebGLObserver() {
+ this.cache = new WebGLCache(this);
+ this.proxy = new WebGLProxy(this);
+}
+
+WebGLObserver.prototype = {
+ /**
+ * Set this flag to true to stop observing any context function calls.
+ */
+ suppressHandlers: false,
+
+ /**
+ * Called immediately *after* 'linkProgram' is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ * @param void glResult
+ * The returned value of the original function call.
+ */
+ linkProgram: function(gl, glArgs, glResult) {
+ let program = glArgs[0];
+ let shaders = gl.getAttachedShaders(program);
+
+ for (let shader of shaders) {
+ let source = gl.getShaderSource(shader);
+ this.cache.call("addShaderInfo", shader, source);
+ }
+
+ emit(this, "program-linked", gl, program, shaders);
+ },
+
+ /**
+ * Called immediately *after* 'getAttribLocation' is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ * @param GLint glResult
+ * The returned value of the original function call.
+ */
+ getAttribLocation: function(gl, glArgs, glResult) {
+ let [program, name] = glArgs;
+ this.cache.call("addAttribute", program, name, glResult);
+ },
+
+ /**
+ * Called immediately *after* 'getUniformLocation' is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ * @param WebGLUniformLocation glResult
+ * The returned value of the original function call.
+ */
+ getUniformLocation: function(gl, glArgs, glResult) {
+ let [program, name] = glArgs;
+ this.cache.call("addUniform", program, name, glResult);
+ },
+
+ /**
+ * Called immediately *before* 'enableVertexAttribArray' or
+ * 'disableVertexAttribArray'is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ */
+ toggleVertexAttribArray: function(gl, glArgs) {
+ glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
+ return glArgs[0] < 0;
+ },
+
+ /**
+ * Called immediately *before* 'attribute_' is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ */
+ attribute_: function(gl, glArgs) {
+ glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
+ return glArgs[0] < 0;
+ },
+
+ /**
+ * Called immediately *before* 'uniform_' is requested in the context.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context initiating this call.
+ * @param array glArgs
+ * Overridable arguments with which the function is called.
+ */
+ uniform_: function(gl, glArgs) {
+ glArgs[0] = this.cache.call("getCurrentUniformLocation", glArgs[0]);
+ return !glArgs[0];
+ },
+
+ /**
+ * Executes a function in this object.
+ * This method makes sure that any handlers in the context observer are
+ * suppressed, hence stopping observing any context function calls.
+ *
+ * @param string funcName
+ * The function to call.
+ */
+ call: function(funcName, ...args) {
+ let prevState = this.suppressHandlers;
+
+ this.suppressHandlers = true;
+ let result = this[funcName].apply(this, args);
+ this.suppressHandlers = prevState;
+
+ return result;
+ }
+};
+
+/**
+ * A cache storing WebGL state, like shaders, attributes or uniforms.
+ *
+ * @param WebGLObserver observer
+ * The observer for the target context.
+ */
+function WebGLCache(observer) {
+ this._observer = observer;
+
+ this._shaders = new Map();
+ this._attributes = [];
+ this._uniforms = [];
+ this._attributesBridge = new Map();
+ this._uniformsBridge = new Map();
+}
+
+WebGLCache.prototype = {
+ /**
+ * Adds shader information to the cache.
+ *
+ * @param WebGLShader shader
+ * The shader for which the source is to be cached. If the shader
+ * was already cached, nothing happens.
+ * @param string text
+ * The current shader text.
+ */
+ _addShaderInfo: function(shader, text) {
+ if (!this._shaders.has(shader)) {
+ this._shaders.set(shader, text);
+ }
+ },
+
+ /**
+ * Gets shader information from the cache.
+ *
+ * @param WebGLShader shader
+ * The shader for which the source was cached.
+ * @return object | null
+ * The original shader source, or null if there's a cache miss.
+ */
+ _getShaderInfo: function(shader) {
+ return this._shaders.get(shader);
+ },
+
+ /**
+ * Adds an attribute to the cache.
+ *
+ * @param WebGLProgram program
+ * The program for which the attribute is bound. If the attribute
+ * was already cached, nothing happens.
+ * @param string name
+ * The attribute name.
+ * @param GLint value
+ * The attribute value.
+ */
+ _addAttribute: function(program, name, value) {
+ let isCached = this._attributes.some(e => e.program == program && e.name == name);
+ if (isCached || value < 0) {
+ return;
+ }
+ let attributeInfo = {
+ program: program,
+ name: name,
+ value: value
+ };
+ this._attributes.push(attributeInfo);
+ this._attributesBridge.set(value, attributeInfo);
+ },
+
+ /**
+ * Adds a uniform to the cache.
+ *
+ * @param WebGLProgram program
+ * The program for which the uniform is bound. If the uniform
+ * was already cached, nothing happens.
+ * @param string name
+ * The uniform name.
+ * @param WebGLUniformLocation value
+ * The uniform value.
+ */
+ _addUniform: function(program, name, value) {
+ let isCached = this._uniforms.some(e => e.program == program && e.name == name);
+ if (isCached || !value) {
+ return;
+ }
+ let uniformInfo = {
+ program: program,
+ name: name,
+ value: value
+ };
+ this._uniforms.push(uniformInfo);
+ this._uniformsBridge.set(new XPCNativeWrapper(value), uniformInfo);
+ },
+
+ /**
+ * Gets all the cached attributes for a specific program.
+ *
+ * @param WebGLProgram program
+ * The program for which the attributes are bound.
+ * @return array
+ * A list containing information about all the attributes.
+ */
+ _getAttributesForProgram: function(program) {
+ return this._attributes.filter(e => e.program == program);
+ },
+
+ /**
+ * Gets all the cached uniforms for a specific program.
+ *
+ * @param WebGLProgram program
+ * The program for which the uniforms are bound.
+ * @return array
+ * A list containing information about all the uniforms.
+ */
+ _getUniformsForProgram: function(program) {
+ return this._uniforms.filter(e => e.program == program);
+ },
+
+ /**
+ * Updates the attribute locations for a specific program.
+ * This is necessary, for example, when the shader is relinked and all the
+ * attribute locations become obsolete.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context owning the program.
+ * @param WebGLProgram program
+ * The program for which the attributes need updating.
+ */
+ _updateAttributesForProgram: function(gl, program) {
+ let dirty = this._attributes.filter(e => e.program == program);
+ dirty.forEach(e => e.value = gl.getAttribLocation(program, e.name));
+ },
+
+ /**
+ * Updates the uniform locations for a specific program.
+ * This is necessary, for example, when the shader is relinked and all the
+ * uniform locations become obsolete.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context owning the program.
+ * @param WebGLProgram program
+ * The program for which the uniforms need updating.
+ */
+ _updateUniformsForProgram: function(gl, program) {
+ let dirty = this._uniforms.filter(e => e.program == program);
+ dirty.forEach(e => e.value = gl.getUniformLocation(program, e.name));
+ },
+
+ /**
+ * Gets the actual attribute location in a specific program.
+ * When relinked, all the attribute locations become obsolete and are updated
+ * in the cache. This method returns the (current) real attribute location.
+ *
+ * @param GLint initialValue
+ * The initial attribute value.
+ * @return GLint
+ * The current attribute value, or the initial value if it's already
+ * up to date with its corresponding program.
+ */
+ _getCurrentAttributeLocation: function(initialValue) {
+ let currentInfo = this._attributesBridge.get(initialValue);
+ return currentInfo ? currentInfo.value : initialValue;
+ },
+
+ /**
+ * Gets the actual uniform location in a specific program.
+ * When relinked, all the uniform locations become obsolete and are updated
+ * in the cache. This method returns the (current) real uniform location.
+ *
+ * @param WebGLUniformLocation initialValue
+ * The initial uniform value.
+ * @return WebGLUniformLocation
+ * The current uniform value, or the initial value if it's already
+ * up to date with its corresponding program.
+ */
+ _getCurrentUniformLocation: function(initialValue) {
+ let currentInfo = this._uniformsBridge.get(initialValue);
+ return currentInfo ? currentInfo.value : initialValue;
+ },
+
+ /**
+ * Executes a function in this object.
+ * This method makes sure that any handlers in the context observer are
+ * suppressed, hence stopping observing any context function calls.
+ *
+ * @param string funcName
+ * The function to call.
+ * @return any
+ * The called function result.
+ */
+ call: function(funcName, ...aArgs) {
+ let prevState = this._observer.suppressHandlers;
+
+ this._observer.suppressHandlers = true;
+ let result = this["_" + funcName].apply(this, aArgs);
+ this._observer.suppressHandlers = prevState;
+
+ return result;
+ }
+};
+
+/**
+ * A mechanism for injecting or qureying state into/from a WebGL context.
+ *
+ * @param WebGLObserver observer
+ * The observer for the target context.
+ */
+function WebGLProxy(observer) {
+ this._observer = observer;
+}
+
+WebGLProxy.prototype = {
+ get cache() this._observer.cache,
+
+ /**
+ * Changes a shader's source code and relinks the respective program.
+ *
+ * @param WebGLRenderingContext gl
+ * The WebGL context owning the program.
+ * @param WebGLProgram program
+ * The program who's linked shader is to be modified.
+ * @param WebGLShader shader
+ * The shader to be modified.
+ * @param string text
+ * The new shader source code.
+ * @return string
+ * The shader's compilation and linking status.
+ */
+ _compileShader: function(gl, program, shader, text) {
+ gl.shaderSource(shader, text);
+ gl.compileShader(shader);
+ gl.linkProgram(program);
+
+ let error = { compile: "", link: "" };
+
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ error.compile = gl.getShaderInfoLog(shader);
+ }
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+ error.link = gl.getShaderInfoLog(shader);
+ }
+
+ this.cache.call("updateAttributesForProgram", gl, program);
+ this.cache.call("updateUniformsForProgram", gl, program);
+
+ return error;
+ },
+
+ /**
+ * Executes a function in this object.
+ * This method makes sure that any handlers in the context observer are
+ * suppressed, hence stopping observing any context function calls.
+ *
+ * @param string funcName
+ * The function to call.
+ * @return any
+ * The called function result.
+ */
+ call: function(funcName, ...aArgs) {
+ let prevState = this._observer.suppressHandlers;
+
+ this._observer.suppressHandlers = true;
+ let result = this["_" + funcName].apply(this, aArgs);
+ this._observer.suppressHandlers = prevState;
+
+ return result;
+ }
+};
diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js
index 4e209977846a..93905e18636c 100644
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -366,6 +366,7 @@ var DebuggerServer = {
this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
this.registerModule("devtools/server/actors/inspector");
+ this.registerModule("devtools/server/actors/webgl");
this.registerModule("devtools/server/actors/tracer");
this.registerModule("devtools/server/actors/device");
},
@@ -384,6 +385,7 @@ var DebuggerServer = {
this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
this.registerModule("devtools/server/actors/inspector");
+ this.registerModule("devtools/server/actors/webgl");
}
if (!("ContentAppActor" in DebuggerServer)) {
this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm
index 59f6011d63d3..ee3edf105c3f 100644
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -102,6 +102,7 @@ const KEY_APP_SYSTEM_LOCAL = "app-system-local";
const KEY_APP_SYSTEM_SHARE = "app-system-share";
const KEY_APP_SYSTEM_USER = "app-system-user";
+const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
const XPI_PERMISSION = "install";
const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
@@ -1952,6 +1953,7 @@ var XPIProvider = {
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
+ Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
aOldPlatformVersion);
@@ -3322,10 +3324,6 @@ var XPIProvider = {
aOldPlatformVersion) {
LOG("checkForChanges");
- // Import the website installation permissions if the application has changed
- if (aAppChanged !== false)
- this.importPermissions();
-
// Keep track of whether and why we need to open and update the database at
// startup time.
let updateReasons = [];
@@ -3521,6 +3519,7 @@ var XPIProvider = {
if (aUri.schemeIs("chrome") || aUri.schemeIs("file"))
return true;
+ this.importPermissions();
let permission = Services.perms.testPermission(aUri, XPI_PERMISSION);
if (permission == Ci.nsIPermissionManager.DENY_ACTION)
@@ -3849,15 +3848,24 @@ var XPIProvider = {
* @see nsIObserver
*/
observe: function XPI_observe(aSubject, aTopic, aData) {
- switch (aData) {
- case PREF_EM_MIN_COMPAT_APP_VERSION:
- case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
- this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
- null);
- this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
- null);
- this.updateAddonAppDisabledStates();
- break;
+ if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
+ if (!aData || aData == XPI_PERMISSION) {
+ this.importPermissions();
+ }
+ return;
+ }
+
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case PREF_EM_MIN_COMPAT_APP_VERSION:
+ case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
+ this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
+ null);
+ this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
+ null);
+ this.updateAddonAppDisabledStates();
+ break;
+ }
}
},
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug578467.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug578467.js
deleted file mode 100644
index 16817f1c7160..000000000000
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug578467.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests that xpinstall.[whitelist|blacklist].add preferences are emptied when
-// converted into permissions on startup with new profile
-
-const PREF_XPI_WHITELIST_PERMISSIONS = "xpinstall.whitelist.add";
-const PREF_XPI_BLACKLIST_PERMISSIONS = "xpinstall.blacklist.add";
-
-function run_test() {
- createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
-
- // Create own preferences to test
- Services.prefs.setCharPref("xpinstall.whitelist.add.EMPTY", "");
- Services.prefs.setCharPref("xpinstall.whitelist.add.TEST", "whitelist.example.com");
- Services.prefs.setCharPref("xpinstall.blacklist.add.EMPTY", "");
- Services.prefs.setCharPref("xpinstall.blacklist.add.TEST", "blacklist.example.com");
-
- // Get list of preferences to check
- var whitelistPreferences = Services.prefs.getChildList(PREF_XPI_WHITELIST_PERMISSIONS, {});
- var blacklistPreferences = Services.prefs.getChildList(PREF_XPI_BLACKLIST_PERMISSIONS, {});
- var preferences = whitelistPreferences.concat(blacklistPreferences);
-
- startupManager();
-
- // Check preferences were emptied
- preferences.forEach(function(aPreference) {
- try {
- do_check_eq(Services.prefs.getCharPref(aPreference), "");
- }
- catch (e) {
- // Successfully emptied
- }
- });
-}
-
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js
new file mode 100644
index 000000000000..c7c31dfd764c
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that xpinstall.[whitelist|blacklist].add preferences are emptied when
+// converted into permissions.
+
+const PREF_XPI_WHITELIST_PERMISSIONS = "xpinstall.whitelist.add";
+const PREF_XPI_BLACKLIST_PERMISSIONS = "xpinstall.blacklist.add";
+
+function do_check_permission_prefs(preferences) {
+ // Check preferences were emptied
+ for (let pref of preferences) {
+ try {
+ do_check_eq(Services.prefs.getCharPref(pref), "");
+ }
+ catch (e) {
+ // Successfully emptied
+ }
+ }
+}
+
+function clear_imported_preferences_cache() {
+ let scope = Components.utils.import("resource://gre/modules/PermissionsUtils.jsm", {});
+ scope.gImportedPrefBranches.clear();
+}
+
+function run_test() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+
+ // Create own preferences to test
+ Services.prefs.setCharPref("xpinstall.whitelist.add.EMPTY", "");
+ Services.prefs.setCharPref("xpinstall.whitelist.add.TEST", "whitelist.example.com");
+ Services.prefs.setCharPref("xpinstall.blacklist.add.EMPTY", "");
+ Services.prefs.setCharPref("xpinstall.blacklist.add.TEST", "blacklist.example.com");
+
+ // Get list of preferences to check
+ var whitelistPreferences = Services.prefs.getChildList(PREF_XPI_WHITELIST_PERMISSIONS, {});
+ var blacklistPreferences = Services.prefs.getChildList(PREF_XPI_BLACKLIST_PERMISSIONS, {});
+ var preferences = whitelistPreferences.concat(blacklistPreferences);
+
+ startupManager();
+
+ // Permissions are imported lazily - act as thought we're checking an install,
+ // to trigger on-deman importing of the permissions.
+ let url = Services.io.newURI("http://example.com/file.xpi", null, null);
+ AddonManager.isInstallAllowed("application/x-xpinstall", url);
+ do_check_permission_prefs(preferences);
+
+
+ // Import can also be triggerred by an observer notification by any other area
+ // of code, such as a permissions management UI.
+
+ // First, request to flush all permissions
+ clear_imported_preferences_cache();
+ Services.prefs.setCharPref("xpinstall.whitelist.add.TEST2", "whitelist2.example.com");
+ Services.obs.notifyObservers(null, "flush-pending-permissions", "install");
+ do_check_permission_prefs(preferences);
+
+ // Then, request to flush just install permissions
+ clear_imported_preferences_cache();
+ Services.prefs.setCharPref("xpinstall.whitelist.add.TEST3", "whitelist3.example.com");
+ Services.obs.notifyObservers(null, "flush-pending-permissions", "");
+ do_check_permission_prefs(preferences);
+
+ // And a request to flush some other permissions sholdn't flush install permissions
+ clear_imported_preferences_cache();
+ Services.prefs.setCharPref("xpinstall.whitelist.add.TEST4", "whitelist4.example.com");
+ Services.obs.notifyObservers(null, "flush-pending-permissions", "lolcats");
+ do_check_eq(Services.prefs.getCharPref("xpinstall.whitelist.add.TEST4"), "whitelist4.example.com");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
index 7571de2fbed3..541da136a1d9 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -122,7 +122,6 @@ fail-if = os == "android"
[test_bug569138.js]
[test_bug570173.js]
[test_bug576735.js]
-[test_bug578467.js]
[test_bug587088.js]
[test_bug594058.js]
[test_bug595081.js]
@@ -205,6 +204,7 @@ skip-if = os == "android"
[test_migrate_max_version.js]
[test_onPropertyChanged_appDisabled.js]
[test_permissions.js]
+[test_permissions_prefs.js]
[test_plugins.js]
[test_pluginchange.js]
[test_pluginBlocklistCtp.js]