diff options
Diffstat (limited to 'devtools/client/shadereditor')
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; +} |