summaryrefslogtreecommitdiffstats
path: root/devtools/client/shadereditor
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shadereditor')
-rw-r--r--devtools/client/shadereditor/moz.build10
-rw-r--r--devtools/client/shadereditor/panel.js76
-rw-r--r--devtools/client/shadereditor/shadereditor.js633
-rw-r--r--devtools/client/shadereditor/shadereditor.xul70
-rw-r--r--devtools/client/shadereditor/test/.eslintrc.js6
-rw-r--r--devtools/client/shadereditor/test/browser.ini47
-rw-r--r--devtools/client/shadereditor/test/browser_se_aaa_run_first_leaktest.js17
-rw-r--r--devtools/client/shadereditor/test/browser_se_bfcache.js60
-rw-r--r--devtools/client/shadereditor/test/browser_se_editors-contents.js30
-rw-r--r--devtools/client/shadereditor/test/browser_se_editors-error-gutter.js156
-rw-r--r--devtools/client/shadereditor/test/browser_se_editors-error-tooltip.js56
-rw-r--r--devtools/client/shadereditor/test/browser_se_editors-lazy-init.js34
-rw-r--r--devtools/client/shadereditor/test/browser_se_first-run.js43
-rw-r--r--devtools/client/shadereditor/test/browser_se_navigation.js71
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-blackbox-01.js169
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-blackbox-02.js63
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-cache.js41
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-highlight-01.js93
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-highlight-02.js49
-rw-r--r--devtools/client/shadereditor/test/browser_se_programs-list.js87
-rw-r--r--devtools/client/shadereditor/test/browser_se_shaders-edit-01.js73
-rw-r--r--devtools/client/shadereditor/test/browser_se_shaders-edit-02.js74
-rw-r--r--devtools/client/shadereditor/test/browser_se_shaders-edit-03.js85
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-01.js16
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-02.js21
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-03.js26
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-04.js27
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-05.js27
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-06.js64
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-07.js61
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-08.js37
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-09.js89
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-10.js44
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-11.js25
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-12.js27
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-13.js67
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-14.js46
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-15.js133
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-16.js141
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-17.js46
-rw-r--r--devtools/client/shadereditor/test/browser_webgl-actor-test-18.js31
-rw-r--r--devtools/client/shadereditor/test/doc_blended-geometry.html136
-rw-r--r--devtools/client/shadereditor/test/doc_multiple-contexts.html112
-rw-r--r--devtools/client/shadereditor/test/doc_overlapping-geometry.html120
-rw-r--r--devtools/client/shadereditor/test/doc_shader-order.html83
-rw-r--r--devtools/client/shadereditor/test/doc_simple-canvas.html125
-rw-r--r--devtools/client/shadereditor/test/head.js292
47 files changed, 3839 insertions, 0 deletions
diff --git a/devtools/client/shadereditor/moz.build b/devtools/client/shadereditor/moz.build
new file mode 100644
index 000000000..684fabc22
--- /dev/null
+++ b/devtools/client/shadereditor/moz.build
@@ -0,0 +1,10 @@
+# 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/.
+
+DevToolsModules(
+ 'panel.js'
+)
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
diff --git a/devtools/client/shadereditor/panel.js b/devtools/client/shadereditor/panel.js
new file mode 100644
index 000000000..92fac9646
--- /dev/null
+++ b/devtools/client/shadereditor/panel.js
@@ -0,0 +1,76 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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("promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+const { WebGLFront } = require("devtools/shared/fronts/webgl");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+function ShaderEditorPanel(iframeWindow, toolbox) {
+ this.panelWin = iframeWindow;
+ this._toolbox = toolbox;
+ this._destroyer = null;
+
+ EventEmitter.decorate(this);
+}
+
+exports.ShaderEditorPanel = ShaderEditorPanel;
+
+ShaderEditorPanel.prototype = {
+ /**
+ * Open is effectively an asynchronous constructor.
+ *
+ * @return object
+ * A promise that is resolved when the Shader Editor completes opening.
+ */
+ 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.gToolbox = this._toolbox;
+ 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) {
+ DevToolsUtils.reportException("ShaderEditorPanel.prototype.open", aReason);
+ });
+ },
+
+ // DevToolPanel API
+
+ get target() {
+ return 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(() => {
+ // Destroy front to ensure packet handler is removed from client
+ this.panelWin.gFront.destroy();
+ this.emit("destroyed");
+ });
+ }
+};
diff --git a/devtools/client/shadereditor/shadereditor.js b/devtools/client/shadereditor/shadereditor.js
new file mode 100644
index 000000000..6b53302c4
--- /dev/null
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -0,0 +1,633 @@
+/* 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";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
+const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+const promise = require("promise");
+const Services = require("Services");
+const EventEmitter = require("devtools/shared/event-emitter");
+const Tooltip = require("devtools/client/shared/widgets/tooltip/Tooltip");
+const Editor = require("devtools/client/sourceeditor/editor");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const {Heritage, WidgetMethods, setNamedTimeout} =
+ require("devtools/client/shared/widgets/view-helpers");
+const {Task} = require("devtools/shared/task");
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+ // When new programs are received from the server.
+ NEW_PROGRAM: "ShaderEditor:NewProgram",
+ PROGRAMS_ADDED: "ShaderEditor:ProgramsAdded",
+
+ // 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",
+
+ // When the UI is reset from tab navigation
+ UI_RESET: "ShaderEditor:UIReset",
+
+ // When the editor's error markers are all removed
+ EDITOR_ERROR_MARKERS_REMOVED: "ShaderEditor:EditorCleaned"
+};
+XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
+
+const STRINGS_URI = "devtools/client/locales/shadereditor.properties";
+const HIGHLIGHT_TINT = [1, 0, 0.25, 1]; // rgba
+const TYPING_MAX_DELAY = 500; // ms
+const SHADERS_AUTOGROW_ITEMS = 4;
+const GUTTER_ERROR_PANEL_OFFSET_X = 7; // px
+const GUTTER_ERROR_PANEL_DELAY = 100; // ms
+const DEFAULT_EDITOR_CONFIG = {
+ gutters: ["errors"],
+ lineNumbers: true,
+ showAnnotationRuler: true
+};
+
+/**
+ * The current target and the WebGL Editor front, set by this tool's host.
+ */
+var gToolbox, 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.
+ */
+var EventsHandler = {
+ /**
+ * Listen for events emitted by the current tab target.
+ */
+ initialize: function () {
+ this._onHostChanged = this._onHostChanged.bind(this);
+ this._onTabNavigated = this._onTabNavigated.bind(this);
+ this._onProgramLinked = this._onProgramLinked.bind(this);
+ this._onProgramsAdded = this._onProgramsAdded.bind(this);
+ gToolbox.on("host-changed", this._onHostChanged);
+ gTarget.on("will-navigate", this._onTabNavigated);
+ gTarget.on("navigate", this._onTabNavigated);
+ gFront.on("program-linked", this._onProgramLinked);
+ this.reloadButton = $("#requests-menu-reload-notice-button");
+ this.reloadButton.addEventListener("command", this._onReloadCommand);
+ },
+
+ /**
+ * Remove events emitted by the current tab target.
+ */
+ destroy: function () {
+ gToolbox.off("host-changed", this._onHostChanged);
+ gTarget.off("will-navigate", this._onTabNavigated);
+ gTarget.off("navigate", this._onTabNavigated);
+ gFront.off("program-linked", this._onProgramLinked);
+ this.reloadButton.removeEventListener("command", this._onReloadCommand);
+ },
+
+ /**
+ * Handles a command event on reload button
+ */
+ _onReloadCommand() {
+ gFront.setup({ reload: true });
+ },
+
+ /**
+ * Handles a host change event on the parent toolbox.
+ */
+ _onHostChanged: function () {
+ if (gToolbox.hostType == "side") {
+ $("#shaders-pane").removeAttribute("height");
+ }
+ },
+
+ /**
+ * Called for each location change in the debugged tab.
+ */
+ _onTabNavigated: function (event, {isFrameSwitching}) {
+ switch (event) {
+ case "will-navigate": {
+ // Make sure the backend is prepared to handle WebGL contexts.
+ if (!isFrameSwitching) {
+ gFront.setup({ reload: false });
+ }
+
+ // Reset UI.
+ ShadersListView.empty();
+ // When switching to an iframe, ensure displaying the reload button.
+ // As the document has already been loaded without being hooked.
+ if (isFrameSwitching) {
+ $("#reload-notice").hidden = false;
+ $("#waiting-notice").hidden = true;
+ } else {
+ $("#reload-notice").hidden = true;
+ $("#waiting-notice").hidden = false;
+ }
+
+ $("#content").hidden = true;
+ window.emit(EVENTS.UI_RESET);
+
+ break;
+ }
+ case "navigate": {
+ // Manually retrieve the list of program actors known to the server,
+ // because the backend won't emit "program-linked" notifications
+ // in the case of a bfcache navigation (since no new programs are
+ // actually linked).
+ gFront.getPrograms().then(this._onProgramsAdded);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Called every time a program was linked in the debugged tab.
+ */
+ _onProgramLinked: function (programActor) {
+ this._addProgram(programActor);
+ window.emit(EVENTS.NEW_PROGRAM);
+ },
+
+ /**
+ * Callback for the front's getPrograms() method.
+ */
+ _onProgramsAdded: function (programActors) {
+ programActors.forEach(this._addProgram);
+ window.emit(EVENTS.PROGRAMS_ADDED);
+ },
+
+ /**
+ * Adds a program to the shaders list and unhides any modal notices.
+ */
+ _addProgram: function (programActor) {
+ $("#waiting-notice").hidden = true;
+ $("#reload-notice").hidden = true;
+ $("#content").hidden = false;
+ ShadersListView.addProgram(programActor);
+ }
+};
+
+/**
+ * Functions handling the sources UI.
+ */
+var 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._onProgramSelect = this._onProgramSelect.bind(this);
+ this._onProgramCheck = this._onProgramCheck.bind(this);
+ this._onProgramMouseOver = this._onProgramMouseOver.bind(this);
+ this._onProgramMouseOut = this._onProgramMouseOut.bind(this);
+
+ this.widget.addEventListener("select", this._onProgramSelect, false);
+ this.widget.addEventListener("check", this._onProgramCheck, false);
+ this.widget.addEventListener("mouseover", this._onProgramMouseOver, true);
+ this.widget.addEventListener("mouseout", this._onProgramMouseOut, true);
+ },
+
+ /**
+ * Destruction function, called when the tool is closed.
+ */
+ destroy: function () {
+ this.widget.removeEventListener("select", this._onProgramSelect, false);
+ this.widget.removeEventListener("check", this._onProgramCheck, false);
+ this.widget.removeEventListener("mouseover", this._onProgramMouseOver, true);
+ this.widget.removeEventListener("mouseout", this._onProgramMouseOut, true);
+ },
+
+ /**
+ * Adds a program to this programs container.
+ *
+ * @param object programActor
+ * The program actor coming from the active thread.
+ */
+ addProgram: function (programActor) {
+ if (this.hasProgram(programActor)) {
+ return;
+ }
+
+ // 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);
+ let contents = document.createElement("label");
+ contents.className = "plain program-item";
+ contents.setAttribute("value", label);
+ contents.setAttribute("crop", "start");
+ contents.setAttribute("flex", "1");
+
+ // Append a program item to this container.
+ this.push([contents], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: {
+ label: label,
+ programActor: programActor,
+ checkboxState: true,
+ checkboxTooltip: L10N.getStr("shadersList.blackboxLabel")
+ }
+ });
+
+ // Make sure there's always a selected item available.
+ if (!this.selectedItem) {
+ this.selectedIndex = 0;
+ }
+
+ // Prevent this container from growing indefinitely in height when the
+ // toolbox is docked to the side.
+ if (gToolbox.hostType == "side" && this.itemCount == SHADERS_AUTOGROW_ITEMS) {
+ this._pane.setAttribute("height", this._pane.getBoundingClientRect().height);
+ }
+ },
+
+ /**
+ * Returns whether a program was already added to this programs container.
+ *
+ * @param object programActor
+ * The program actor coming from the active thread.
+ * @param boolean
+ * True if the program was added, false otherwise.
+ */
+ hasProgram: function (programActor) {
+ return !!this.attachments.filter(e => e.programActor == programActor).length;
+ },
+
+ /**
+ * The select listener for the programs container.
+ */
+ _onProgramSelect: 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]) {
+ return ShadersEditorsView.setText({
+ vs: vertexShaderText,
+ fs: fragmentShaderText
+ });
+ }
+
+ getShaders()
+ .then(getSources)
+ .then(showSources)
+ .then(null, e => console.error(e));
+ },
+
+ /**
+ * The check listener for the programs container.
+ */
+ _onProgramCheck: function ({ detail: { checked }, target }) {
+ let sourceItem = this.getItemForElement(target);
+ let attachment = sourceItem.attachment;
+ attachment.isBlackBoxed = !checked;
+ attachment.programActor[checked ? "unblackbox" : "blackbox"]();
+ },
+
+ /**
+ * The mouseover listener for the programs container.
+ */
+ _onProgramMouseOver: function (e) {
+ let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
+ if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
+ sourceItem.attachment.programActor.highlight(HIGHLIGHT_TINT);
+
+ if (e instanceof Event) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ },
+
+ /**
+ * The mouseout listener for the programs container.
+ */
+ _onProgramMouseOut: 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.
+ */
+var 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: Task.async(function* () {
+ this._destroyed = true;
+ yield this._toggleListeners("off");
+ for (let p of this._editorPromises.values()) {
+ let editor = yield p;
+ editor.destroy();
+ }
+ }),
+
+ /**
+ * 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
+ * @return object
+ * A promise resolving upon completion of text setting.
+ */
+ setText: function (sources) {
+ let view = this;
+ function setTextAndClearHistory(editor, text) {
+ editor.setText(text);
+ editor.clearHistory();
+ }
+
+ return Task.spawn(function* () {
+ yield view._toggleListeners("off");
+ yield promise.all([
+ view._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs)),
+ view._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs))
+ ]);
+ yield view._toggleListeners("on");
+ }).then(() => 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.
+ * @return object
+ * Returns a promise that resolves to an editor instance
+ */
+ _getEditor: function (type) {
+ 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.config.mode = Editor.modes[type];
+
+ if (this._destroyed) {
+ deferred.resolve(editor);
+ } else {
+ 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.
+ * @return object
+ * A promise resolving upon completion of toggling the listeners.
+ */
+ _toggleListeners: function (flag) {
+ return promise.all(["vs", "fs"].map(type => {
+ return 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));
+
+ // Remove all the gutter markers and line classes from the editor.
+ this._cleanEditor(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());
+ this._onSuccessfulCompilation();
+ } catch (e) {
+ this._onFailedCompilation(type, editor, e);
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Called uppon a successful shader compilation.
+ */
+ _onSuccessfulCompilation: function () {
+ // Signal that the shader was compiled successfully.
+ window.emit(EVENTS.SHADER_COMPILED, null);
+ },
+
+ /**
+ * Called uppon an unsuccessful shader compilation.
+ */
+ _onFailedCompilation: function (type, editor, errors) {
+ let lineCount = editor.lineCount();
+ let currentLine = editor.getCursor().line;
+ let listeners = { mouseover: this._onMarkerMouseOver };
+
+ function matchLinesAndMessages(string) {
+ return {
+ // First number that is not equal to 0.
+ lineMatch: string.match(/\d{2,}|[1-9]/),
+ // The string after all the numbers, semicolons and spaces.
+ textMatch: string.match(/[^\s\d:][^\r\n|]*/)
+ };
+ }
+ function discardInvalidMatches(e) {
+ // Discard empty line and text matches.
+ return e.lineMatch && e.textMatch;
+ }
+ function sanitizeValidMatches(e) {
+ return {
+ // Drivers might yield confusing line numbers under some obscure
+ // circumstances. Don't throw the errors away in those cases,
+ // just display them on the currently edited line.
+ line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
+ // Trim whitespace from the beginning and the end of the message,
+ // and replace all other occurences of double spaces to a single space.
+ text: e.textMatch[0].trim().replace(/\s{2,}/g, " ")
+ };
+ }
+ function sortByLine(first, second) {
+ // Sort all the errors ascending by their corresponding line number.
+ return first.line > second.line ? 1 : -1;
+ }
+ function groupSameLineMessages(accumulator, current) {
+ // Group errors corresponding to the same line number to a single object.
+ let previous = accumulator[accumulator.length - 1];
+ if (!previous || previous.line != current.line) {
+ return [...accumulator, {
+ line: current.line,
+ messages: [current.text]
+ }];
+ } else {
+ previous.messages.push(current.text);
+ return accumulator;
+ }
+ }
+ function displayErrors({ line, messages }) {
+ // Add gutter markers and line classes for every error in the source.
+ editor.addMarker(line, "errors", "error");
+ editor.setMarkerListeners(line, "errors", "error", listeners, messages);
+ editor.addLineClass(line, "error-line");
+ }
+
+ (this._errors[type] = errors.link
+ .split("ERROR")
+ .map(matchLinesAndMessages)
+ .filter(discardInvalidMatches)
+ .map(sanitizeValidMatches)
+ .sort(sortByLine)
+ .reduce(groupSameLineMessages, []))
+ .forEach(displayErrors);
+
+ // Signal that the shader wasn't compiled successfully.
+ window.emit(EVENTS.SHADER_COMPILED, errors);
+ },
+
+ /**
+ * Event listener for the 'mouseover' event on a marker in the editor gutter.
+ */
+ _onMarkerMouseOver: function (line, node, messages) {
+ if (node._markerErrorsTooltip) {
+ return;
+ }
+
+ let tooltip = node._markerErrorsTooltip = new Tooltip(document);
+ tooltip.defaultOffsetX = GUTTER_ERROR_PANEL_OFFSET_X;
+ tooltip.setTextContent({ messages: messages });
+ tooltip.startTogglingOnHover(node, () => true, {
+ toggleDelay: GUTTER_ERROR_PANEL_DELAY
+ });
+ },
+
+ /**
+ * Removes all the gutter markers and line classes from the editor.
+ */
+ _cleanEditor: function (type) {
+ this._getEditor(type).then(editor => {
+ editor.removeAllMarkers("errors");
+ this._errors[type].forEach(e => editor.removeLineClass(e.line));
+ this._errors[type].length = 0;
+ window.emit(EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
+ });
+ },
+
+ _errors: {
+ vs: [],
+ fs: []
+ }
+};
+
+/**
+ * Localization convenience methods.
+ */
+var L10N = new LocalizationHelper(STRINGS_URI);
+
+/**
+ * Convenient way of emitting events from the panel window.
+ */
+EventEmitter.decorate(this);
+
+/**
+ * DOM query helper.
+ */
+var $ = (selector, target = document) => target.querySelector(selector);
diff --git a/devtools/client/shadereditor/shadereditor.xul b/devtools/client/shadereditor/shadereditor.xul
new file mode 100644
index 000000000..dc7f764b7
--- /dev/null
+++ b/devtools/client/shadereditor/shadereditor.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/shadereditor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
+<!DOCTYPE window [
+ <!ENTITY % debuggerDTD SYSTEM "chrome://devtools/locale/shadereditor.dtd">
+ %debuggerDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://devtools/content/shared/theme-switching.js"/>
+
+ <script type="application/javascript" src="shadereditor.js"/>
+
+ <vbox class="theme-body" flex="1">
+ <hbox id="reload-notice"
+ class="notice-container"
+ align="center"
+ pack="center"
+ flex="1">
+ <button id="requests-menu-reload-notice-button"
+ class="devtools-toolbarbutton"
+ standalone="true"
+ label="&shaderEditorUI.reloadNotice1;"/>
+ <label id="requests-menu-reload-notice-label"
+ class="plain"
+ value="&shaderEditorUI.reloadNotice2;"/>
+ </hbox>
+ <hbox id="waiting-notice"
+ class="notice-container devtools-throbber"
+ align="center"
+ pack="center"
+ flex="1"
+ hidden="true">
+ <label id="requests-menu-waiting-notice-label"
+ class="plain"
+ value="&shaderEditorUI.emptyNotice;"/>
+ </hbox>
+
+ <box id="content"
+ class="devtools-responsive-container"
+ flex="1"
+ hidden="true">
+ <vbox id="shaders-pane"/>
+ <splitter class="devtools-side-splitter"/>
+ <box id="shaders-editors" class="devtools-responsive-container" flex="1">
+ <vbox flex="1">
+ <vbox id="vs-editor" flex="1"/>
+ <label id="vs-editor-label"
+ class="plain editor-label"
+ value="&shaderEditorUI.vertexShader;"/>
+ </vbox>
+ <splitter id="editors-splitter" class="devtools-side-splitter"/>
+ <vbox flex="1">
+ <vbox id="fs-editor" flex="1"/>
+ <label id="fs-editor-label"
+ class="plain editor-label"
+ value="&shaderEditorUI.fragmentShader;"/>
+ </vbox>
+ </box>
+ </box>
+ </vbox>
+
+</window>
diff --git a/devtools/client/shadereditor/test/.eslintrc.js b/devtools/client/shadereditor/test/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/client/shadereditor/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/shadereditor/test/browser.ini b/devtools/client/shadereditor/test/browser.ini
new file mode 100644
index 000000000..b26bc3a74
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser.ini
@@ -0,0 +1,47 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ doc_blended-geometry.html
+ doc_multiple-contexts.html
+ doc_overlapping-geometry.html
+ doc_shader-order.html
+ doc_simple-canvas.html
+ head.js
+
+[browser_se_aaa_run_first_leaktest.js]
+[browser_se_bfcache.js]
+skip-if = true # Bug 942473, caused by Bug 940541
+[browser_se_editors-contents.js]
+[browser_se_editors-error-gutter.js]
+[browser_se_editors-error-tooltip.js]
+[browser_se_editors-lazy-init.js]
+[browser_se_first-run.js]
+[browser_se_navigation.js]
+[browser_se_programs-blackbox-01.js]
+[browser_se_programs-blackbox-02.js]
+[browser_se_programs-cache.js]
+[browser_se_programs-highlight-01.js]
+[browser_se_programs-highlight-02.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]
+[browser_webgl-actor-test-15.js]
+[browser_webgl-actor-test-16.js]
+[browser_webgl-actor-test-17.js]
+[browser_webgl-actor-test-18.js]
diff --git a/devtools/client/shadereditor/test/browser_se_aaa_run_first_leaktest.js b/devtools/client/shadereditor/test/browser_se_aaa_run_first_leaktest.js
new file mode 100644
index 000000000..6efe51091
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_aaa_run_first_leaktest.js
@@ -0,0 +1,17 @@
+/* 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, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+
+ ok(target, "Should have a target available.");
+ ok(panel, "Should have a panel available.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_se_bfcache.js b/devtools/client/shadereditor/test/browser_se_bfcache.js
new file mode 100644
index 000000000..2b568e3fe
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_bfcache.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the shader editor works with bfcache.
+ */
+function* ifWebGLSupported() {
+ let { target, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ // Attach frame scripts if in e10s to perform
+ // history navigation via the content
+ loadFrameScripts();
+
+ let reloaded = reload(target);
+ let firstProgram = yield once(gFront, "program-linked");
+ yield reloaded;
+
+ let navigated = navigate(target, MULTIPLE_CONTEXTS_URL);
+ let [secondProgram, thirdProgram] = yield getPrograms(gFront, 2);
+ yield navigated;
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ yield navigateInHistory(target, "back", "will-navigate");
+ yield once(panel.panelWin, EVENTS.PROGRAMS_ADDED);
+ yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
+
+ is($("#content").hidden, false,
+ "The tool's content should not be hidden.");
+ is(ShadersListView.itemCount, 1,
+ "The shaders list contains one entry after navigating back.");
+ is(ShadersListView.selectedIndex, 0,
+ "The shaders list has a correct selection after navigating back.");
+
+ 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 navigateInHistory(target, "forward", "will-navigate");
+ yield once(panel.panelWin, EVENTS.PROGRAMS_ADDED);
+ yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
+
+ is($("#content").hidden, false,
+ "The tool's content should not be hidden.");
+ is(ShadersListView.itemCount, 2,
+ "The shaders list contains two entries after navigating forward.");
+ is(ShadersListView.selectedIndex, 0,
+ "The shaders list has a correct selection after navigating forward.");
+
+ is(vsEditor.getText().indexOf("gl_Position"), 100,
+ "The vertex shader editor contains the correct text.");
+ is(fsEditor.getText().indexOf("gl_FragColor"), 89,
+ "The fragment shader editor contains the correct text.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_se_editors-contents.js b/devtools/client/shadereditor/test/browser_se_editors-contents.js
new file mode 100644
index 000000000..fdf2612ed
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_editors-contents.js
@@ -0,0 +1,30 @@
+/* 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, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, ShadersEditorsView, EVENTS } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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/devtools/client/shadereditor/test/browser_se_editors-error-gutter.js b/devtools/client/shadereditor/test/browser_se_editors-error-gutter.js
new file mode 100644
index 000000000..439d6074f
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_editors-error-gutter.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if error indicators are shown in the editor's gutter and text area
+ * when there's a shader compilation error.
+ */
+
+function* ifWebGLSupported() {
+ let { target, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+ let [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(false, vertError);
+ info("Error marks added in the vertex shader editor.");
+
+ vsEditor.insertText(" ", { line: 1, ch: 0 });
+ yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
+ is(vsEditor.getText(1), " precision lowp float;", "Typed space.");
+ checkHasVertFirstError(false, vertError);
+ checkHasVertSecondError(false, vertError);
+ info("Error marks removed while typing in the vertex shader editor.");
+
+ [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(false, vertError);
+ info("Error marks were re-added after recompiling the vertex shader.");
+
+ fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
+ let [, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(false, vertError);
+ checkHasFragError(true, fragError);
+ info("Error marks added in the fragment shader editor.");
+
+ fsEditor.insertText(" ", { line: 1, ch: 0 });
+ yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
+ is(fsEditor.getText(1), " precision lowp float;", "Typed space.");
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(false, vertError);
+ checkHasFragError(false, fragError);
+ info("Error marks removed while typing in the fragment shader editor.");
+
+ [, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(false, vertError);
+ checkHasFragError(true, fragError);
+ info("Error marks were re-added after recompiling the fragment shader.");
+
+ vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
+ yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
+ checkHasVertFirstError(false, vertError);
+ checkHasVertSecondError(false, vertError);
+ checkHasFragError(true, fragError);
+ info("Error marks removed while typing in the vertex shader editor again.");
+
+ [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ checkHasVertFirstError(true, vertError);
+ checkHasVertSecondError(true, vertError);
+ checkHasFragError(true, fragError);
+ info("Error marks were re-added after recompiling the fragment shader again.");
+
+ yield teardown(panel);
+ finish();
+
+ function checkHasVertFirstError(bool, error) {
+ ok(error, "Vertex shader compiled with errors.");
+ isnot(error.link, "", "The linkage status should not be empty.");
+
+ let line = 7;
+ info("Checking first vertex shader error on line " + line + "...");
+
+ is(vsEditor.hasMarker(line, "errors", "error"), bool,
+ "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+ is(vsEditor.hasLineClass(line, "error-line"), bool,
+ "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+ let parsed = ShadersEditorsView._errors.vs;
+ is(parsed.length >= 1, bool,
+ "There's " + (bool ? ">= 1" : "< 1") + " parsed vertex shader error(s).");
+
+ if (bool) {
+ is(parsed[0].line, line,
+ "The correct line was parsed.");
+ is(parsed[0].messages.length, 2,
+ "There are 2 parsed messages.");
+ ok(parsed[0].messages[0].includes("'constructor' : too many arguments"),
+ "The correct first message was parsed.");
+ ok(parsed[0].messages[1].includes("'assign' : cannot convert from"),
+ "The correct second message was parsed.");
+ }
+ }
+
+ function checkHasVertSecondError(bool, error) {
+ ok(error, "Vertex shader compiled with errors.");
+ isnot(error.link, "", "The linkage status should not be empty.");
+
+ let line = 8;
+ info("Checking second vertex shader error on line " + line + "...");
+
+ is(vsEditor.hasMarker(line, "errors", "error"), bool,
+ "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+ is(vsEditor.hasLineClass(line, "error-line"), bool,
+ "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+ let parsed = ShadersEditorsView._errors.vs;
+ is(parsed.length >= 2, bool,
+ "There's " + (bool ? ">= 2" : "< 2") + " parsed vertex shader error(s).");
+
+ if (bool) {
+ is(parsed[1].line, line,
+ "The correct line was parsed.");
+ is(parsed[1].messages.length, 1,
+ "There is 1 parsed message.");
+ ok(parsed[1].messages[0].includes("'assign' : cannot convert from"),
+ "The correct message was parsed.");
+ }
+ }
+
+ function checkHasFragError(bool, error) {
+ ok(error, "Fragment shader compiled with errors.");
+ isnot(error.link, "", "The linkage status should not be empty.");
+
+ let line = 5;
+ info("Checking first vertex shader error on line " + line + "...");
+
+ is(fsEditor.hasMarker(line, "errors", "error"), bool,
+ "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+ is(fsEditor.hasLineClass(line, "error-line"), bool,
+ "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+ let parsed = ShadersEditorsView._errors.fs;
+ is(parsed.length >= 1, bool,
+ "There's " + (bool ? ">= 2" : "< 1") + " parsed fragment shader error(s).");
+
+ if (bool) {
+ is(parsed[0].line, line,
+ "The correct line was parsed.");
+ is(parsed[0].messages.length, 1,
+ "There is 1 parsed message.");
+ ok(parsed[0].messages[0].includes("'constructor' : too many arguments"),
+ "The correct message was parsed.");
+ }
+ }
+}
diff --git a/devtools/client/shadereditor/test/browser_se_editors-error-tooltip.js b/devtools/client/shadereditor/test/browser_se_editors-error-tooltip.js
new file mode 100644
index 000000000..1ce31bebf
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_editors-error-tooltip.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if error tooltips can be opened from the editor's gutter when there's
+ * a shader compilation error.
+ */
+
+function* ifWebGLSupported() {
+ let { target, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ let vsEditor = yield ShadersEditorsView._getEditor("vs");
+ let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+ vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+ yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ // Synthesizing 'mouseover' events doesn't work, hack around this by
+ // manually calling the event listener with the expected arguments.
+ let editorDocument = vsEditor.container.contentDocument;
+ let marker = editorDocument.querySelector(".error");
+ let parsed = ShadersEditorsView._errors.vs[0].messages;
+ ShadersEditorsView._onMarkerMouseOver(7, marker, parsed);
+
+ let tooltip = marker._markerErrorsTooltip;
+ ok(tooltip, "A tooltip was created successfully.");
+
+ let content = tooltip.content;
+ ok(tooltip.content,
+ "Some tooltip's content was set.");
+ ok(tooltip.content.className.includes("devtools-tooltip-simple-text-container"),
+ "The tooltip's content container was created correctly.");
+
+ let messages = content.childNodes;
+ is(messages.length, 2,
+ "There are two messages displayed in the tooltip.");
+ ok(messages[0].className.includes("devtools-tooltip-simple-text"),
+ "The first message was created correctly.");
+ ok(messages[1].className.includes("devtools-tooltip-simple-text"),
+ "The second message was created correctly.");
+
+ ok(messages[0].textContent.includes("'constructor' : too many arguments"),
+ "The first message contains the correct text.");
+ ok(messages[1].textContent.includes("'assign' : cannot convert"),
+ "The second message contains the correct text.");
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_se_editors-lazy-init.js b/devtools/client/shadereditor/test/browser_se_editors-lazy-init.js
new file mode 100644
index 000000000..b2d9d888e
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_editors-lazy-init.js
@@ -0,0 +1,34 @@
+/* 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, 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");
+
+ 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/devtools/client/shadereditor/test/browser_se_first-run.js b/devtools/client/shadereditor/test/browser_se_first-run.js
new file mode 100644
index 000000000..33239bcbe
--- /dev/null
+++ b/devtools/client/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, 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/devtools/client/shadereditor/test/browser_se_navigation.js b/devtools/client/shadereditor/test/browser_se_navigation.js
new file mode 100644
index 000000000..f1adc3e76
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_navigation.js
@@ -0,0 +1,71 @@
+/* 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, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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 promise.all([navigating, once(panel.panelWin, EVENTS.UI_RESET) ]);
+
+ 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.");
+
+ 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/devtools/client/shadereditor/test/browser_se_programs-blackbox-01.js b/devtools/client/shadereditor/test/browser_se_programs-blackbox-01.js
new file mode 100644
index 000000000..4c8199c22
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-blackbox-01.js
@@ -0,0 +1,169 @@
+/* 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, secondProgramActor]] = yield promise.all([
+ getPrograms(gFront, 2),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { 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(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { 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(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "The second program was correctly blackboxed.");
+
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "Highlighting shouldn't work while blackboxed (1).");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 0) });
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "Highlighting shouldn't work while blackboxed (2).");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
+ ok(true, "Highlighting shouldn'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(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were correctly unblackboxed.");
+
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 0) });
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 64, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 64, a: 255 }, true, "#canvas2");
+ ok(true, "The second program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { 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-contents")[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/devtools/client/shadereditor/test/browser_se_programs-blackbox-02.js b/devtools/client/shadereditor/test/browser_se_programs-blackbox-02.js
new file mode 100644
index 000000000..391a272c8
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-blackbox-02.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if blackboxing a program works properly in tandem with blended
+ * overlapping geometry.
+ */
+
+function* ifWebGLSupported() {
+ let { target, debuggee, panel } = yield initShaderEditor(BLENDED_GEOMETRY_CANVAS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ let [[firstProgramActor, secondProgramActor]] = yield promise.all([
+ getPrograms(gFront, 2),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
+ ok(true, "The canvas was correctly drawn.");
+
+ getBlackBoxCheckbox(panel, 0).click();
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 0, b: 0, a: 127 }, true);
+ ok(true, "The first program was correctly blackboxed.");
+
+ getBlackBoxCheckbox(panel, 0).click();
+ getBlackBoxCheckbox(panel, 1).click();
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ ok(true, "The second program was correctly blackboxed.");
+
+ getBlackBoxCheckbox(panel, 1).click();
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
+ ok(true, "The two programs were correctly unblackboxed.");
+
+ getBlackBoxCheckbox(panel, 0).click();
+ getBlackBoxCheckbox(panel, 1).click();
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ ok(true, "The two programs were correctly blackboxed again.");
+
+ getBlackBoxCheckbox(panel, 0).click();
+ getBlackBoxCheckbox(panel, 1).click();
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
+ ok(true, "The two programs were correctly unblackboxed again.");
+
+ yield teardown(panel);
+ finish();
+}
+
+function getBlackBoxCheckbox(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-checkbox")[aIndex];
+}
diff --git a/devtools/client/shadereditor/test/browser_se_programs-cache.js b/devtools/client/shadereditor/test/browser_se_programs-cache.js
new file mode 100644
index 000000000..5e5708e44
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-cache.js
@@ -0,0 +1,41 @@
+/* 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 { EVENTS, gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ let [[programActor]] = yield promise.all([
+ getPrograms(gFront, 1),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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/devtools/client/shadereditor/test/browser_se_programs-highlight-01.js b/devtools/client/shadereditor/test/browser_se_programs-highlight-01.js
new file mode 100644
index 000000000..863dc2672
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-highlight-01.js
@@ -0,0 +1,93 @@
+/* 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, 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, secondProgramActor]] = yield promise.all([
+ getPrograms(gFront, 2),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 0) });
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 64, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 64, a: 255 }, true, "#canvas2");
+ ok(true, "The second program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two programs were correctly unhighlighted.");
+
+ ShadersListView._onProgramMouseOver({ target: getBlackBoxCheckbox(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { 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._onProgramMouseOut({ target: getBlackBoxCheckbox(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { 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-contents")[aIndex];
+}
+
+function getBlackBoxCheckbox(aPanel, aIndex) {
+ return aPanel.panelWin.document.querySelectorAll(
+ ".side-menu-widget-item-checkbox")[aIndex];
+}
diff --git a/devtools/client/shadereditor/test/browser_se_programs-highlight-02.js b/devtools/client/shadereditor/test/browser_se_programs-highlight-02.js
new file mode 100644
index 000000000..c6cbd796b
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-highlight-02.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if highlighting a program works properly in tandem with blended
+ * overlapping geometry.
+ */
+
+function* ifWebGLSupported() {
+ let { target, debuggee, panel } = yield initShaderEditor(BLENDED_GEOMETRY_CANVAS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ let [[firstProgramActor, secondProgramActor]] = yield promise.all([
+ getPrograms(gFront, 2),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
+ ok(true, "The canvas was correctly drawn.");
+
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 0) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 0, b: 32, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 0, b: 32, a: 127 }, true);
+ ok(true, "The first program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 0) });
+ ShadersListView._onProgramMouseOver({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 255, g: 0, b: 64, a: 255 }, true);
+ ok(true, "The second program was correctly highlighted.");
+
+ ShadersListView._onProgramMouseOut({ target: getItemLabel(panel, 1) });
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
+ 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-contents")[aIndex];
+}
diff --git a/devtools/client/shadereditor/test/browser_se_programs-list.js b/devtools/client/shadereditor/test/browser_se_programs-list.js
new file mode 100644
index 000000000..621880886
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_programs-list.js
@@ -0,0 +1,87 @@
+/* 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, 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, secondProgramActor] = yield promise.all([
+ getPrograms(gFront, 2, (actors) => {
+ // Fired upon each actor addition, we want to check only
+ // after the first actor has been added so we can test state
+ if (actors.length === 1)
+ checkFirstProgram();
+ if (actors.length === 2)
+ checkSecondProgram();
+ }),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]).then(([programs, ]) => programs);
+
+ is(ShadersListView.attachments[0].label, L10N.getFormatStr("shadersList.programLabel", 0),
+ "The correct first label is shown in the shaders list.");
+ is(ShadersListView.attachments[1].label, 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 checkFirstProgram() {
+ 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.");
+ }
+ function checkSecondProgram() {
+ 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.");
+ }
+}
diff --git a/devtools/client/shadereditor/test/browser_se_shaders-edit-01.js b/devtools/client/shadereditor/test/browser_se_shaders-edit-01.js
new file mode 100644
index 000000000..0cb52ac19
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_shaders-edit-01.js
@@ -0,0 +1,73 @@
+/* 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, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, $, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 128, y: 128 }, { r: 191, g: 64, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { 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(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { 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(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
+ yield ensurePixelIs(gFront, { 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();
+}
diff --git a/devtools/client/shadereditor/test/browser_se_shaders-edit-02.js b/devtools/client/shadereditor/test/browser_se_shaders-edit-02.js
new file mode 100644
index 000000000..0bb72c461
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_se_shaders-edit-02.js
@@ -0,0 +1,74 @@
+/* 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, panel } = yield initShaderEditor(SIMPLE_CANVAS_URL);
+ let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+ ]);
+
+ 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 onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(error,
+ "The new vertex shader source was compiled with errors.");
+
+ // The implementation has the choice to defer all compile-time errors to link time.
+ let infoLog = (error.compile != "") ? error.compile : error.link;
+
+ isnot(infoLog, "",
+ "The one of the compile or link info logs should not be empty.");
+ is(infoLog.split("ERROR").length - 1, 2,
+ "The info log status contains two errors.");
+ ok(infoLog.includes("ERROR: 0:8: 'constructor'"),
+ "A constructor error is contained in the info log.");
+ ok(infoLog.includes("ERROR: 0:8: 'assign'"),
+ "An assignment error is contained in the info log.");
+
+
+ fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
+ [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+ ok(error,
+ "The new fragment shader source was compiled with errors.");
+
+ infoLog = (error.compile != "") ? error.compile : error.link;
+
+ isnot(infoLog, "",
+ "The one of the compile or link info logs should not be empty.");
+ is(infoLog.split("ERROR").length - 1, 1,
+ "The info log contains one error.");
+ ok(infoLog.includes("ERROR: 0:6: 'constructor'"),
+ "A constructor error is contained in the info log.");
+
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ vsEditor.replaceText("vec4", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+ [, error] = yield onceSpread(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 });
+ [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
+ ok(!error, "The new fragment shader source was compiled successfully.");
+
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(gFront, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+
+ yield teardown(panel);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_se_shaders-edit-03.js b/devtools/client/shadereditor/test/browser_se_shaders-edit-03.js
new file mode 100644
index 000000000..2c413dd72
--- /dev/null
+++ b/devtools/client/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 would permanently store
+ * their new source on the backend and reshow it in the frontend when required.
+ */
+
+function* ifWebGLSupported() {
+ let { target, panel } = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
+ let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
+
+ reload(target);
+
+ yield promise.all([
+ once(gFront, "program-linked"),
+ once(gFront, "program-linked")
+ ]);
+
+ yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
+
+ 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(gFront, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 32, y: 32 }, { r: 255, g: 255, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 255, g: 255, b: 0, a: 0 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 32, y: 32 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(gFront, { 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();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-01.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-01.js
new file mode 100644
index 000000000..242018a76
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-01.js
@@ -0,0 +1,16 @@
+/* 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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+
+ ok(target, "Should have a target available.");
+ ok(front, "Should have a protocol front available.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-02.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-02.js
new file mode 100644
index 000000000..addec87ca
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-02.js
@@ -0,0 +1,21 @@
+/* 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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+
+ once(front, "program-linked").then(() => {
+ ok(false, "A 'program-linked' notification shouldn't have been sent!");
+ });
+
+ ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
+
+ yield reload(target);
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-03.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-03.js
new file mode 100644
index 000000000..0381973ec
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+
+ let navigated = once(target, "navigate");
+ let linked = once(front, "program-linked");
+
+ yield front.setup({ reload: true });
+ 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/devtools/client/shadereditor/test/browser_webgl-actor-test-04.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-04.js
new file mode 100644
index 000000000..4256a5329
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ 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/devtools/client/shadereditor/test/browser_webgl-actor-test-05.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-05.js
new file mode 100644
index 000000000..96e445e01
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ let vertSource = yield vertexShader.getText();
+ ok(vertSource.includes("gl_Position"),
+ "The correct vertex shader source was retrieved.");
+
+ let fragSource = yield fragmentShader.getText();
+ ok(fragSource.includes("gl_FragColor"),
+ "The correct fragment shader source was retrieved.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-06.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-06.js
new file mode 100644
index 000000000..5cbe88a77
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-06.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the highlight/unhighlight and blackbox/unblackbox operations on
+ * program actors work as expected.
+ */
+
+function* ifWebGLSupported() {
+ let { target, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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 corner pixel colors are correct before highlighting.");
+
+ yield programActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ yield checkShaderSource("The shader sources are preserved after highlighting.");
+ ok(true, "The corner pixel colors are correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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 corner pixel colors are correct after unhighlighting.");
+
+ yield programActor.blackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield checkShaderSource("The shader sources are preserved after blackboxing.");
+ ok(true, "The corner pixel colors are correct after blackboxing.");
+
+ yield programActor.unblackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ yield checkShaderSource("The shader sources are correct after unblackboxing.");
+ ok(true, "The corner pixel colors are correct after unblackboxing.");
+
+ 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.includes("I'm special!") &&
+ fragSource.includes("I'm also special!"), aMessage);
+ });
+ }
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-07.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-07.js
new file mode 100644
index 000000000..a7634de44
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ let programActor = yield once(front, "program-linked");
+ let vertexShader = yield programActor.getVertexShader();
+ let fragmentShader = yield programActor.getFragmentShader();
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 128, y: 128 }, { r: 191, g: 64, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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.includes("2.0"),
+ "The vertex shader source is correct before changing it.");
+ ok(!fragSource.includes("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(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ vertSource = yield vertexShader.getText();
+ fragSource = yield fragmentShader.getText();
+ ok(vertSource.includes("2.0"),
+ "The vertex shader source is correct after changing it.");
+ ok(!fragSource.includes("0.5"),
+ "The fragment shader source is correct after changing the vertex shader.");
+
+ let newFragSource = fragSource.replace("1.0", "0.5");
+ status = yield fragmentShader.compile(newFragSource);
+ ok(!status,
+ "The new fragment shader source was compiled without errors.");
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+
+ vertSource = yield vertexShader.getText();
+ fragSource = yield fragmentShader.getText();
+ ok(vertSource.includes("2.0"),
+ "The vertex shader source is correct after changing the fragment shader.");
+ ok(fragSource.includes("0.5"),
+ "The fragment shader source is correct after changing it.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-08.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-08.js
new file mode 100644
index 000000000..8025bc703
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ 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 front.waitForFrame();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 128, y: 128 }, { r: 0, g: 0, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { 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.includes("vFragmentColor = vec3(0, 0, 1);"),
+ "The vertex shader source is correct after changing it.");
+ ok(fragSource.includes("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/devtools/client/shadereditor/test/browser_webgl-actor-test-09.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-09.js
new file mode 100644
index 000000000..2054140a6
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-09.js
@@ -0,0 +1,89 @@
+/* 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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: true });
+
+ 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.");
+
+ // The implementation has the choice to defer all compile-time errors to link time.
+ let infoLog = (error.compile != "") ? error.compile : error.link;
+
+ isnot(infoLog, "",
+ "The one of the compile or link info logs should not be empty.");
+ is(infoLog.split("ERROR").length - 1, 2,
+ "The info log contains two errors.");
+ ok(infoLog.includes("ERROR: 0:8: 'constructor'"),
+ "A constructor error is contained in the info log.");
+ ok(infoLog.includes("ERROR: 0:8: 'assign'"),
+ "An assignment error is contained in the info log.");
+ }
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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.includes("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.");
+
+ // The implementation has the choice to defer all compile-time errors to link time.
+ let infoLog = (error.compile != "") ? error.compile : error.link;
+
+ isnot(infoLog, "",
+ "The one of the compile or link info logs should not be empty.");
+ is(infoLog.split("ERROR").length - 1, 1,
+ "The info log contains one error.");
+ ok(infoLog.includes("ERROR: 0:6: 'constructor'"),
+ "A constructor error is contained in the info log.");
+ }
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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.includes("vec3 vFragmentColor;"),
+ "The previous correct fragment shader source was preserved.");
+
+ yield programActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "Highlighting worked after setting a defective fragment source.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { 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/devtools/client/shadereditor/test/browser_webgl-actor-test-10.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-10.js
new file mode 100644
index 000000000..87dfe35bf
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+
+ front.setup({ reload: true });
+ yield testHighlighting((yield once(front, "program-linked")));
+ ok(true, "Canvas was correctly instrumented on the first navigation.");
+
+ reload(target);
+ yield testHighlighting((yield once(front, "program-linked")));
+ ok(true, "Canvas was correctly instrumented on the second navigation.");
+
+ reload(target);
+ yield testHighlighting((yield once(front, "program-linked")));
+ ok(true, "Canvas was correctly instrumented on the third navigation.");
+
+ yield removeTab(target.tab);
+ finish();
+
+ function testHighlighting(programActor) {
+ return Task.spawn(function* () {
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct before highlighting.");
+
+ yield programActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after unhighlighting.");
+ });
+ }
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-11.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-11.js
new file mode 100644
index 000000000..28663057e
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SIMPLE_CANVAS_URL);
+
+ let linked = once(front, "program-linked");
+ front.setup({ reload: true });
+ 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/devtools/client/shadereditor/test/browser_webgl-actor-test-12.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-12.js
new file mode 100644
index 000000000..f69d5e403
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(SHADER_ORDER_URL);
+ front.setup({ reload: true });
+
+ 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.includes("I'm a vertex shader!"),
+ "The correct vertex shader text was retrieved.");
+ ok(fragSource.includes("I'm a fragment shader!"),
+ "The correct fragment shader text was retrieved.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-13.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-13.js
new file mode 100644
index 000000000..f4730ba39
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-13.js
@@ -0,0 +1,67 @@
+/* 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, front } = yield initBackend(MULTIPLE_CONTEXTS_URL);
+ front.setup({ reload: true });
+
+ let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
+
+ 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(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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/devtools/client/shadereditor/test/browser_webgl-actor-test-14.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-14.js
new file mode 100644
index 000000000..342bba382
--- /dev/null
+++ b/devtools/client/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, front } = yield initBackend(MULTIPLE_CONTEXTS_URL);
+ front.setup({ reload: true });
+
+ let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
+
+ 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 front.waitForFrame();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The first fragment shader was changed.");
+
+ oldFragSource = yield secondFragmentShader.getText();
+ newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.75, 0.75, 0.75");
+ status = yield secondFragmentShader.compile(newFragSource);
+ ok(!status,
+ "The second new fragment shader source was compiled without errors.");
+
+ yield front.waitForFrame();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 191, g: 191, b: 191, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { 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/devtools/client/shadereditor/test/browser_webgl-actor-test-15.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-15.js
new file mode 100644
index 000000000..0a65dbe0a
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-15.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if program actors are cached when navigating in the bfcache.
+ */
+
+function* ifWebGLSupported() {
+ let { target, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: false });
+
+ // Attach frame scripts if in e10s to perform
+ // history navigation via the content
+ loadFrameScripts();
+
+ reload(target);
+ let firstProgram = yield once(front, "program-linked");
+ yield checkFirstCachedPrograms(firstProgram);
+ yield checkHighlightingInTheFirstPage(firstProgram);
+ ok(true, "The cached programs behave correctly before the navigation.");
+
+ navigate(target, MULTIPLE_CONTEXTS_URL);
+ let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
+ yield checkSecondCachedPrograms(firstProgram, [secondProgram, thirdProgram]);
+ yield checkHighlightingInTheSecondPage(secondProgram, thirdProgram);
+ ok(true, "The cached programs behave correctly after the navigation.");
+
+ once(front, "program-linked").then(() => {
+ ok(false, "Shouldn't have received any more program-linked notifications.");
+ });
+
+ yield navigateInHistory(target, "back");
+ yield checkFirstCachedPrograms(firstProgram);
+ yield checkHighlightingInTheFirstPage(firstProgram);
+ ok(true, "The cached programs behave correctly after navigating back.");
+
+ yield navigateInHistory(target, "forward");
+ yield checkSecondCachedPrograms(firstProgram, [secondProgram, thirdProgram]);
+ yield checkHighlightingInTheSecondPage(secondProgram, thirdProgram);
+ ok(true, "The cached programs behave correctly after navigating forward.");
+
+ yield navigateInHistory(target, "back");
+ yield checkFirstCachedPrograms(firstProgram);
+ yield checkHighlightingInTheFirstPage(firstProgram);
+ ok(true, "The cached programs behave correctly after navigating back again.");
+
+ yield navigateInHistory(target, "forward");
+ yield checkSecondCachedPrograms(firstProgram, [secondProgram, thirdProgram]);
+ yield checkHighlightingInTheSecondPage(secondProgram, thirdProgram);
+ ok(true, "The cached programs behave correctly after navigating forward again.");
+
+ yield removeTab(target.tab);
+ finish();
+
+ function checkFirstCachedPrograms(programActor) {
+ return Task.spawn(function* () {
+ let programs = yield front.getPrograms();
+
+ is(programs.length, 1,
+ "There should be 1 cached program actor.");
+ is(programs[0], programActor,
+ "The cached program actor was the expected one.");
+ });
+ }
+
+ function checkSecondCachedPrograms(oldProgramActor, newProgramActors) {
+ return Task.spawn(function* () {
+ let programs = yield front.getPrograms();
+
+ is(programs.length, 2,
+ "There should be 2 cached program actors after the navigation.");
+ is(programs[0], newProgramActors[0],
+ "The first cached program actor was the expected one after the navigation.");
+ is(programs[1], newProgramActors[1],
+ "The second cached program actor was the expected one after the navigation.");
+
+ isnot(newProgramActors[0], oldProgramActor,
+ "The old program actor is not equal to the new first program actor.");
+ isnot(newProgramActors[1], oldProgramActor,
+ "The old program actor is not equal to the new second program actor.");
+ });
+ }
+
+ function checkHighlightingInTheFirstPage(programActor) {
+ return Task.spawn(function* () {
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct before highlighting.");
+
+ yield programActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after unhighlighting.");
+ });
+ }
+
+ function checkHighlightingInTheSecondPage(firstProgramActor, secondProgramActor) {
+ return Task.spawn(function* () {
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases are correctly drawn before highlighting.");
+
+ yield firstProgramActor.highlight([1, 0, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases were correctly filled after unhighlighting.");
+ });
+ }
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-16.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-16.js
new file mode 100644
index 000000000..e61e73102
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-16.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if program actors are invalidated from the cache when a window is
+ * removed from the bfcache.
+ */
+
+function* ifWebGLSupported() {
+ let { target, front } = yield initBackend(SIMPLE_CANVAS_URL);
+ front.setup({ reload: false });
+
+ // Attach frame scripts if in e10s to perform
+ // history navigation via the content
+ loadFrameScripts();
+
+ // 0. Perform the initial reload.
+
+ reload(target);
+ let firstProgram = yield once(front, "program-linked");
+ let programs = yield front.getPrograms();
+ is(programs.length, 1,
+ "The first program should be returned by a call to getPrograms().");
+ is(programs[0], firstProgram,
+ "The first programs was correctly retrieved from the cache.");
+
+ let allPrograms = yield front._getAllPrograms();
+ is(allPrograms.length, 1,
+ "Should be only one program in cache.");
+
+ // 1. Perform a simple navigation.
+
+ navigate(target, MULTIPLE_CONTEXTS_URL);
+ let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
+ programs = yield front.getPrograms();
+ is(programs.length, 2,
+ "The second and third programs should be returned by a call to getPrograms().");
+ is(programs[0], secondProgram,
+ "The second programs was correctly retrieved from the cache.");
+ is(programs[1], thirdProgram,
+ "The third programs was correctly retrieved from the cache.");
+
+ allPrograms = yield front._getAllPrograms();
+ is(allPrograms.length, 3,
+ "Should be three programs in cache.");
+
+ // 2. Perform a bfcache navigation.
+
+ yield navigateInHistory(target, "back");
+ let globalDestroyed = once(front, "global-created");
+ let globalCreated = once(front, "global-destroyed");
+ let programsLinked = once(front, "program-linked");
+ reload(target);
+
+ yield promise.all([programsLinked, globalDestroyed, globalCreated]);
+ allPrograms = yield front._getAllPrograms();
+ is(allPrograms.length, 3,
+ "Should be 3 programs total in cache.");
+
+ programs = yield front.getPrograms();
+ is(programs.length, 1,
+ "There should be 1 cached program actor now.");
+
+ yield checkHighlightingInTheFirstPage(programs[0]);
+ ok(true, "The cached programs behave correctly after navigating back and reloading.");
+
+ // 3. Perform a bfcache navigation and a page reload.
+
+ yield navigateInHistory(target, "forward");
+
+ globalDestroyed = once(front, "global-created");
+ globalCreated = once(front, "global-destroyed");
+ programsLinked = getPrograms(front, 2);
+
+ reload(target);
+
+ yield promise.all([programsLinked, globalDestroyed, globalCreated]);
+ allPrograms = yield front._getAllPrograms();
+ is(allPrograms.length, 3,
+ "Should be 3 programs total in cache.");
+
+ programs = yield front.getPrograms();
+ is(programs.length, 2,
+ "There should be 2 cached program actors now.");
+
+ yield checkHighlightingInTheSecondPage(programs[0], programs[1]);
+ ok(true, "The cached programs behave correctly after navigating forward and reloading.");
+
+ yield removeTab(target.tab);
+ finish();
+
+ function checkHighlightingInTheFirstPage(programActor) {
+ return Task.spawn(function* () {
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct before highlighting.");
+
+ yield programActor.highlight([0, 1, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after highlighting.");
+
+ yield programActor.unhighlight();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner pixel colors are correct after unhighlighting.");
+ });
+ }
+
+ function checkHighlightingInTheSecondPage(firstProgramActor, secondProgramActor) {
+ return Task.spawn(function* () {
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases are correctly drawn before highlighting.");
+
+ yield firstProgramActor.highlight([1, 0, 0, 1]);
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { 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(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
+ ok(true, "The two canvases were correctly filled after unhighlighting.");
+ });
+ }
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-17.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-17.js
new file mode 100644
index 000000000..92b940d4a
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-17.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the blackbox/unblackbox operations work as expected with
+ * overlapping geometry.
+ */
+
+function* ifWebGLSupported() {
+ let { target, front } = yield initBackend(OVERLAPPING_GEOMETRY_CANVAS_URL);
+ front.setup({ reload: true });
+
+ let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
+
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner vs. center pixel colors are correct before blackboxing.");
+
+ yield firstProgramActor.blackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true);
+ ok(true, "The corner vs. center pixel colors are correct after blackboxing (1).");
+
+ yield firstProgramActor.unblackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner vs. center pixel colors are correct after unblackboxing (1).");
+
+ yield secondProgramActor.blackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 64, y: 64 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner vs. center pixel colors are correct after blackboxing (2).");
+
+ yield secondProgramActor.unblackbox();
+ yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
+ yield ensurePixelIs(front, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
+ ok(true, "The corner vs. center pixel colors are correct after unblackboxing (2).");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/browser_webgl-actor-test-18.js b/devtools/client/shadereditor/test/browser_webgl-actor-test-18.js
new file mode 100644
index 000000000..977b07d86
--- /dev/null
+++ b/devtools/client/shadereditor/test/browser_webgl-actor-test-18.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the `getPixel` actor method works across threads.
+ */
+
+function* ifWebGLSupported() {
+ let { target, front } = yield initBackend(MULTIPLE_CONTEXTS_URL);
+ front.setup({ reload: true });
+
+ yield getPrograms(front, 2);
+
+ // Wait a frame to ensure rendering
+ yield front.waitForFrame();
+
+ let pixel = yield front.getPixel({ selector: "#canvas1", position: { x: 0, y: 0 }});
+ is(pixel.r, 255, "correct `r` value for first canvas.");
+ is(pixel.g, 255, "correct `g` value for first canvas.");
+ is(pixel.b, 0, "correct `b` value for first canvas.");
+ is(pixel.a, 255, "correct `a` value for first canvas.");
+
+ pixel = yield front.getPixel({ selector: "#canvas2", position: { x: 0, y: 0 }});
+ is(pixel.r, 0, "correct `r` value for second canvas.");
+ is(pixel.g, 255, "correct `g` value for second canvas.");
+ is(pixel.b, 255, "correct `b` value for second canvas.");
+ is(pixel.a, 255, "correct `a` value for second canvas.");
+
+ yield removeTab(target.tab);
+ finish();
+}
diff --git a/devtools/client/shadereditor/test/doc_blended-geometry.html b/devtools/client/shadereditor/test/doc_blended-geometry.html
new file mode 100644
index 000000000..75cad6dc7
--- /dev/null
+++ b/devtools/client/shadereditor/test/doc_blended-geometry.html
@@ -0,0 +1,136 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+
+ <script id="shader-vs" type="x-shader/x-vertex">
+ precision lowp float;
+ attribute vec3 aVertexPosition;
+ uniform float uDepth;
+
+ void main(void) {
+ gl_Position = vec4(aVertexPosition, uDepth);
+ }
+ </script>
+
+ <script id="shader-fs-0" type="x-shader/x-fragment">
+ precision lowp float;
+
+ void main(void) {
+ gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
+ }
+ </script>
+
+ <script id="shader-fs-1" type="x-shader/x-fragment">
+ precision lowp float;
+
+ void main(void) {
+ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+ }
+ </script>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="128" height="128"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+ let program = [];
+ let squareVerticesPositionBuffer;
+ let vertexPositionAttribute = [];
+ let depthUniform = [];
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+
+ initProgram(0);
+ initProgram(1);
+ initBuffers();
+ drawScene();
+ }
+
+ function initProgram(i) {
+ let vertexShader = getShader("shader-vs");
+ let fragmentShader = getShader("shader-fs-" + i);
+
+ program[i] = gl.createProgram();
+ gl.attachShader(program[i], vertexShader);
+ gl.attachShader(program[i], fragmentShader);
+ gl.linkProgram(program[i]);
+
+ vertexPositionAttribute[i] = gl.getAttribLocation(program[i], "aVertexPosition");
+ gl.enableVertexAttribArray(vertexPositionAttribute[i]);
+
+ depthUniform[i] = gl.getUniformLocation(program[i], "uDepth");
+ }
+
+ function getShader(id) {
+ let script = document.getElementById(id);
+ let source = script.textContent;
+ let shader;
+
+ if (script.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (script.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ }
+
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ return shader;
+ }
+
+ function initBuffers() {
+ squareVerticesPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0, 0.0,
+ -1.0, 1.0, 0.0,
+ 1.0, -1.0, 0.0,
+ -1.0, -1.0, 0.0
+ ]), gl.STATIC_DRAW);
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ for (let i = 0; i < 2; i++) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.vertexAttribPointer(vertexPositionAttribute[i], 3, gl.FLOAT, false, 0, 0);
+
+ gl.useProgram(program[i]);
+ gl.uniform1f(depthUniform[i], i + 1);
+ blend(i);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ }
+
+ window.requestAnimationFrame(drawScene);
+ }
+
+ function blend(i) {
+ if (i == 0) {
+ gl.disable(gl.BLEND);
+ }
+ else if (i == 1) {
+ gl.enable(gl.BLEND);
+ gl.blendColor(0.5, 0, 0, 0.25);
+ gl.blendEquationSeparate(
+ gl.FUNC_REVERSE_SUBTRACT, gl.FUNC_SUBTRACT);
+ gl.blendFuncSeparate(
+ gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_COLOR,
+ gl.ONE_MINUS_CONSTANT_COLOR, gl.CONSTANT_COLOR);
+ }
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/shadereditor/test/doc_multiple-contexts.html b/devtools/client/shadereditor/test/doc_multiple-contexts.html
new file mode 100644
index 000000000..039ee62d0
--- /dev/null
+++ b/devtools/client/shadereditor/test/doc_multiple-contexts.html
@@ -0,0 +1,112 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+
+ <script id="shader-vs" type="x-shader/x-vertex">
+ precision lowp float;
+ attribute vec3 aVertexPosition;
+
+ void main(void) {
+ gl_Position = vec4(aVertexPosition, 1);
+ }
+ </script>
+
+ <script id="shader-fs" type="x-shader/x-fragment">
+ precision lowp float;
+ uniform vec3 uColor;
+
+ void main(void) {
+ gl_FragColor = vec4(uColor, 1);
+ }
+ </script>
+ </head>
+
+ <body>
+ <canvas id="canvas1" width="128" height="128"></canvas>
+ <canvas id="canvas2" width="128" height="128"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas = [], gl = [];
+ let program = [];
+ let squareVerticesPositionBuffer = [];
+ let vertexPositionAttribute = [];
+ let colorUniform = [];
+
+ window.onload = function() {
+ for (let i = 0; i < 2; i++) {
+ canvas[i] = document.querySelector("#canvas" + (i + 1));
+ gl[i] = canvas[i].getContext("webgl", { preserveDrawingBuffer: true });
+ gl[i].clearColor(0.0, 0.0, 0.0, 1.0);
+
+ initProgram(i);
+ initBuffers(i);
+ drawScene(i);
+ }
+ }
+
+ function initProgram(i) {
+ let vertexShader = getShader(gl[i], "shader-vs");
+ let fragmentShader = getShader(gl[i], "shader-fs");
+
+ program[i] = gl[i].createProgram();
+ gl[i].attachShader(program[i], vertexShader);
+ gl[i].attachShader(program[i], fragmentShader);
+ gl[i].linkProgram(program[i]);
+
+ vertexPositionAttribute[i] = gl[i].getAttribLocation(program[i], "aVertexPosition");
+ gl[i].enableVertexAttribArray(vertexPositionAttribute[i]);
+
+ colorUniform[i] = gl[i].getUniformLocation(program[i], "uColor");
+ }
+
+ function getShader(gl, id) {
+ let script = document.getElementById(id);
+ let source = script.textContent;
+ let shader;
+
+ if (script.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (script.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ }
+
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ return shader;
+ }
+
+ function initBuffers(i) {
+ squareVerticesPositionBuffer[i] = gl[i].createBuffer();
+ gl[i].bindBuffer(gl[i].ARRAY_BUFFER, squareVerticesPositionBuffer[i]);
+ gl[i].bufferData(gl[i].ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0, 0.0,
+ -1.0, 1.0, 0.0,
+ 1.0, -1.0, 0.0,
+ -1.0, -1.0, 0.0
+ ]), gl[i].STATIC_DRAW);
+ }
+
+ function drawScene(i) {
+ gl[i].clear(gl[i].COLOR_BUFFER_BIT);
+
+ gl[i].bindBuffer(gl[i].ARRAY_BUFFER, squareVerticesPositionBuffer[i]);
+ gl[i].vertexAttribPointer(vertexPositionAttribute[i], 3, gl[i].FLOAT, false, 0, 0);
+
+ gl[i].useProgram(program[i]);
+ gl[i].uniform3fv(colorUniform[i], i == 0 ? [1, 1, 0] : [0, 1, 1]);
+ gl[i].drawArrays(gl[i].TRIANGLE_STRIP, 0, 4);
+
+ window.requestAnimationFrame(() => drawScene(i));
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/shadereditor/test/doc_overlapping-geometry.html b/devtools/client/shadereditor/test/doc_overlapping-geometry.html
new file mode 100644
index 000000000..34be8f57a
--- /dev/null
+++ b/devtools/client/shadereditor/test/doc_overlapping-geometry.html
@@ -0,0 +1,120 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+
+ <script id="shader-vs" type="x-shader/x-vertex">
+ precision lowp float;
+ attribute vec3 aVertexPosition;
+ uniform float uDepth;
+
+ void main(void) {
+ gl_Position = vec4(aVertexPosition, uDepth);
+ }
+ </script>
+
+ <script id="shader-fs-0" type="x-shader/x-fragment">
+ precision lowp float;
+
+ void main(void) {
+ gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
+ }
+ </script>
+
+ <script id="shader-fs-1" type="x-shader/x-fragment">
+ precision lowp float;
+
+ void main(void) {
+ gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);
+ }
+ </script>
+ </head>
+
+ <body>
+ <canvas id="canvas" width="128" height="128"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+ let program = [];
+ let squareVerticesPositionBuffer;
+ let vertexPositionAttribute = [];
+ let depthUniform = [];
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+
+ initProgram(0);
+ initProgram(1);
+ initBuffers();
+ drawScene();
+ }
+
+ function initProgram(i) {
+ let vertexShader = getShader("shader-vs");
+ let fragmentShader = getShader("shader-fs-" + i);
+
+ program[i] = gl.createProgram();
+ gl.attachShader(program[i], vertexShader);
+ gl.attachShader(program[i], fragmentShader);
+ gl.linkProgram(program[i]);
+
+ vertexPositionAttribute[i] = gl.getAttribLocation(program[i], "aVertexPosition");
+ gl.enableVertexAttribArray(vertexPositionAttribute[i]);
+
+ depthUniform[i] = gl.getUniformLocation(program[i], "uDepth");
+ }
+
+ function getShader(id) {
+ let script = document.getElementById(id);
+ let source = script.textContent;
+ let shader;
+
+ if (script.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (script.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ }
+
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ return shader;
+ }
+
+ function initBuffers() {
+ squareVerticesPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0, 0.0,
+ -1.0, 1.0, 0.0,
+ 1.0, -1.0, 0.0,
+ -1.0, -1.0, 0.0
+ ]), gl.STATIC_DRAW);
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ for (let i = 0; i < 2; i++) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.vertexAttribPointer(vertexPositionAttribute[i], 3, gl.FLOAT, false, 0, 0);
+
+ gl.useProgram(program[i]);
+ gl.uniform1f(depthUniform[i], i + 1);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ }
+
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/shadereditor/test/doc_shader-order.html b/devtools/client/shadereditor/test/doc_shader-order.html
new file mode 100644
index 000000000..a7cec53aa
--- /dev/null
+++ b/devtools/client/shadereditor/test/doc_shader-order.html
@@ -0,0 +1,83 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+
+ <script id="shader-vs" type="x-shader/x-vertex">
+ precision lowp float;
+
+ void main(void) {
+ gl_Position = vec4(0, 0, 0, 1); // I'm a vertex shader!
+ }
+ </script>
+
+ <script id="shader-fs" type="x-shader/x-fragment">
+ precision lowp float;
+ varying vec3 vFragmentColor;
+
+ void main(void) {
+ gl_FragColor = vec4(1, 0, 0, 1); // I'm a fragment shader!
+ }
+ </script>
+ </head>
+
+ <body>
+ <canvas width="512" height="512"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+
+ let shaderProgram = gl.createProgram();
+ let vertexShader, fragmentShader;
+
+ // Compile and attach the shaders in a random order. The test will
+ // ensure that the correct vertex and fragment source is retrieved
+ // regardless of this crazyness.
+ if (Math.random() > 0.5) {
+ vertexShader = getShader(gl, "shader-vs");
+ fragmentShader = getShader(gl, "shader-fs");
+ } else {
+ fragmentShader = getShader(gl, "shader-fs");
+ vertexShader = getShader(gl, "shader-vs");
+ }
+ if (Math.random() > 0.5) {
+ gl.attachShader(shaderProgram, vertexShader);
+ gl.attachShader(shaderProgram, fragmentShader);
+ } else {
+ gl.attachShader(shaderProgram, fragmentShader);
+ gl.attachShader(shaderProgram, vertexShader);
+ }
+
+ gl.linkProgram(shaderProgram);
+ }
+
+ function getShader(gl, id) {
+ let script = document.getElementById(id);
+ let source = script.textContent;
+ let shader;
+
+ if (script.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (script.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ }
+
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ return shader;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/shadereditor/test/doc_simple-canvas.html b/devtools/client/shadereditor/test/doc_simple-canvas.html
new file mode 100644
index 000000000..2a709ad8e
--- /dev/null
+++ b/devtools/client/shadereditor/test/doc_simple-canvas.html
@@ -0,0 +1,125 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>WebGL editor test page</title>
+
+ <script id="shader-vs" type="x-shader/x-vertex">
+ precision lowp float;
+ attribute vec3 aVertexPosition;
+ attribute vec3 aVertexColor;
+ varying vec3 vFragmentColor;
+
+ void main(void) {
+ gl_Position = vec4(aVertexPosition, 1.0);
+ vFragmentColor = aVertexColor; // I'm special!
+ }
+ </script>
+
+ <script id="shader-fs" type="x-shader/x-fragment">
+ precision lowp float;
+ varying vec3 vFragmentColor;
+
+ void main(void) {
+ gl_FragColor = vec4(vFragmentColor, 1.0); // I'm also special!
+ }
+ </script>
+ </head>
+
+ <body>
+ <canvas width="512" height="512"></canvas>
+
+ <script type="text/javascript;version=1.8">
+ "use strict";
+
+ let canvas, gl;
+ let program;
+ let squareVerticesPositionBuffer;
+ let squareVerticesColorBuffer;
+ let vertexPositionAttribute;
+ let vertexColorAttribute;
+
+ window.onload = function() {
+ canvas = document.querySelector("canvas");
+ gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+
+ initProgram();
+ initBuffers();
+ drawScene();
+ }
+
+ function initProgram() {
+ let vertexShader = getShader(gl, "shader-vs");
+ let fragmentShader = getShader(gl, "shader-fs");
+
+ program = gl.createProgram();
+ gl.attachShader(program, vertexShader);
+ gl.attachShader(program, fragmentShader);
+ gl.linkProgram(program);
+
+ vertexPositionAttribute = gl.getAttribLocation(program, "aVertexPosition");
+ gl.enableVertexAttribArray(vertexPositionAttribute);
+
+ vertexColorAttribute = gl.getAttribLocation(program, "aVertexColor");
+ gl.enableVertexAttribArray(vertexColorAttribute);
+ }
+
+ function getShader(gl, id) {
+ let script = document.getElementById(id);
+ let source = script.textContent;
+ let shader;
+
+ if (script.type == "x-shader/x-fragment") {
+ shader = gl.createShader(gl.FRAGMENT_SHADER);
+ } else if (script.type == "x-shader/x-vertex") {
+ shader = gl.createShader(gl.VERTEX_SHADER);
+ }
+
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ return shader;
+ }
+
+ function initBuffers() {
+ squareVerticesPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0, 0.0,
+ -1.0, 1.0, 0.0,
+ 1.0, -1.0, 0.0,
+ -1.0, -1.0, 0.0
+ ]), gl.STATIC_DRAW);
+
+ squareVerticesColorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesColorBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0, 1.0, 1.0,
+ 1.0, 0.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0, 1.0
+ ]), gl.STATIC_DRAW);
+ }
+
+ function drawScene() {
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
+ gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesColorBuffer);
+ gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
+
+ gl.useProgram(program);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+ window.requestAnimationFrame(drawScene);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/shadereditor/test/head.js b/devtools/client/shadereditor/test/head.js
new file mode 100644
index 000000000..754a0605d
--- /dev/null
+++ b/devtools/client/shadereditor/test/head.js
@@ -0,0 +1,292 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { Task } = require("devtools/shared/task");
+
+var Services = require("Services");
+var promise = require("promise");
+var { gDevTools } = require("devtools/client/framework/devtools");
+var { DebuggerClient } = require("devtools/shared/client/main");
+var { DebuggerServer } = require("devtools/server/main");
+var { WebGLFront } = require("devtools/shared/fronts/webgl");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
+var { TargetFactory } = require("devtools/client/framework/target");
+var { Toolbox } = require("devtools/client/framework/toolbox");
+var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
+var mm = null;
+
+const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/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";
+const OVERLAPPING_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_overlapping-geometry.html";
+const BLENDED_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_blended-geometry.html";
+
+var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+// To enable logging for try runs, just set the pref to true.
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+var gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled");
+
+flags.testing = true;
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+ flags.testing = false;
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+ Services.prefs.setBoolPref("devtools.shadereditor.enabled", gToolEnabled);
+
+ // These tests use a lot of memory due to GL contexts, so force a GC to help
+ // fragmentation.
+ info("Forcing GC after shadereditor test.");
+ Cu.forceGC();
+});
+
+/**
+ * Call manually in tests that use frame script utils after initializing
+ * the shader editor. Must be called after initializing so we can detect
+ * whether or not `content` is a CPOW or not. Call after init but before navigating
+ * to different pages, as bfcache and thus shader caching gets really strange if
+ * frame script attached in the middle of the test.
+ */
+function loadFrameScripts() {
+ if (Cu.isCrossProcessWrapper(content)) {
+ mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+ }
+}
+
+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;
+
+ BrowserTestUtils.browserLoaded(linkedBrowser).then(function () {
+ info("Tab added and finished loading: " + aUrl);
+ deferred.resolve(tab);
+ });
+
+ 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(document) ? ifWebGLSupported : ifWebGLUnsupported;
+ Task.spawn(generator).then(null, handleError);
+}
+
+function createCanvas() {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+}
+
+function once(aTarget, aEventName, aUseCapture = false) {
+ info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
+
+ let deferred = promise.defer();
+
+ for (let [add, remove] of [
+ ["on", "off"], // Use event emitter before DOM events for consistency
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"]
+ ]) {
+ if ((add in aTarget) && (remove in aTarget)) {
+ aTarget[add](aEventName, function onEvent(...aArgs) {
+ aTarget[remove](aEventName, onEvent, aUseCapture);
+ deferred.resolve(...aArgs);
+ }, aUseCapture);
+ break;
+ }
+ }
+
+ return deferred.promise;
+}
+
+// Hack around `once`, as that only resolves to a single (first) argument
+// and discards the rest. `onceSpread` is similar, except resolves to an
+// array of all of the arguments in the handler. These should be consolidated
+// into the same function, but many tests will need to be changed.
+function onceSpread(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, (...args) => deferred.resolve(args));
+ return deferred.promise;
+}
+
+function observe(aNotificationName, aOwnsWeak = false) {
+ info("Waiting for observer notification: '" + aNotificationName + ".");
+
+ let deferred = promise.defer();
+
+ Services.obs.addObserver(function onNotification(...aArgs) {
+ Services.obs.removeObserver(onNotification, aNotificationName);
+ deferred.resolve.apply(deferred, aArgs);
+ }, aNotificationName, aOwnsWeak);
+
+ 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 ensurePixelIs(aFront, aPosition, aColor, aWaitFlag = false, aSelector = "canvas") {
+ return Task.spawn(function* () {
+ let pixel = yield aFront.getPixel({ selector: aSelector, position: aPosition });
+ if (isApproxColor(pixel, aColor)) {
+ ok(true, "Expected pixel is shown at: " + aPosition.toSource());
+ return;
+ }
+
+ if (aWaitFlag) {
+ yield aFront.waitForFrame();
+ return ensurePixelIs(aFront, aPosition, aColor, aWaitFlag, aSelector);
+ }
+
+ ok(false, "Expected pixel was not already shown at: " + aPosition.toSource());
+ throw new Error("Expected pixel was not already shown at: " + aPosition.toSource());
+ });
+}
+
+function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
+ if (Cu.isCrossProcessWrapper(content)) {
+ if (!mm) {
+ throw new Error("`loadFrameScripts()` must be called before attempting to navigate in e10s.");
+ }
+ mm.sendAsyncMessage("devtools:test:history", { direction: aDirection });
+ }
+ else {
+ executeSoon(() => content.history[aDirection]());
+ }
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
+ executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function reload(aTarget, aWaitForTargetEvent = "navigate") {
+ executeSoon(() => aTarget.activeTab.reload());
+ return once(aTarget, aWaitForTargetEvent);
+}
+
+function initBackend(aUrl) {
+ info("Initializing a shader editor front.");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+
+ let front = new WebGLFront(target.client, target.form);
+ return { target, front };
+ });
+}
+
+function initShaderEditor(aUrl) {
+ info("Initializing a shader editor pane.");
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+
+ Services.prefs.setBoolPref("devtools.shadereditor.enabled", true);
+ let toolbox = yield gDevTools.showToolbox(target, "shadereditor");
+ let panel = toolbox.getCurrentPanel();
+ return { target, panel };
+ });
+}
+
+function teardown(aPanel) {
+ info("Destroying the specified shader editor.");
+
+ return promise.all([
+ once(aPanel, "destroyed"),
+ removeTab(aPanel.target.tab)
+ ]);
+}
+
+// Due to `program-linked` events firing synchronously, we cannot
+// just yield/chain them together, as then we miss all actors after the
+// first event since they're fired consecutively. This allows us to capture
+// all actors and returns an array containing them.
+//
+// Takes a `front` object that is an event emitter, the number of
+// programs that should be listened to and waited on, and an optional
+// `onAdd` function that calls with the entire actors array on program link
+function getPrograms(front, count, onAdd) {
+ let actors = [];
+ let deferred = promise.defer();
+ front.on("program-linked", function onLink(actor) {
+ if (actors.length !== count) {
+ actors.push(actor);
+ if (typeof onAdd === "function") onAdd(actors);
+ }
+ if (actors.length === count) {
+ front.off("program-linked", onLink);
+ deferred.resolve(actors);
+ }
+ });
+ return deferred.promise;
+}