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