diff --git a/b2g/chrome/content/devtools.js b/b2g/chrome/content/devtools.js index b0e06096b1c5..84590f7dc017 100644 --- a/b2g/chrome/content/devtools.js +++ b/b2g/chrome/content/devtools.js @@ -58,7 +58,7 @@ let developerHUD = { return; if (!DebuggerServer.initialized) { - RemoteDebugger.start(); + RemoteDebugger.initServer(); } // We instantiate a local debugger connection so that watchers can use our diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js index d69e7cf8acf6..203a8ee9b996 100644 --- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -351,7 +351,7 @@ let AdbController = { // Check if we have a remote debugging session going on. If so, we won't // disable adb even if the screen is locked. - let isDebugging = RemoteDebugger.isDebugging; + let isDebugging = USBRemoteDebugger.isDebugging; if (this.DEBUG) { this.debug("isDebugging=" + isDebugging); } @@ -432,45 +432,78 @@ SettingsListener.observe("lockscreen.enabled", false, AdbController.setLockscreenEnabled.bind(AdbController)); #endif -// Keep the old setting to not break people that won't have updated -// gaia and gecko. -SettingsListener.observe('devtools.debugger.remote-enabled', false, function(value) { - Services.prefs.setBoolPref('devtools.debugger.remote-enabled', value); - // This preference is consulted during startup - Services.prefs.savePrefFile(null); - try { - value ? RemoteDebugger.start() : RemoteDebugger.stop(); - } catch(e) { - dump("Error while initializing devtools: " + e + "\n" + e.stack + "\n"); - } +(function() { + // Track these separately here so we can determine the correct value for the + // pref "devtools.debugger.remote-enabled", which is true when either mode of + // using DevTools is enabled. + let devtoolsUSB = false; + let devtoolsWiFi = false; + + // Keep the old setting to not break people that won't have updated + // gaia and gecko. + SettingsListener.observe('devtools.debugger.remote-enabled', false, + function(value) { + devtoolsUSB = value; + Services.prefs.setBoolPref('devtools.debugger.remote-enabled', + devtoolsUSB || devtoolsWiFi); + // This preference is consulted during startup + Services.prefs.savePrefFile(null); + try { + value ? USBRemoteDebugger.start() : USBRemoteDebugger.stop(); + } catch(e) { + dump("Error while initializing USB devtools: " + + e + "\n" + e.stack + "\n"); + } #ifdef MOZ_WIDGET_GONK - AdbController.setRemoteDebuggerState(value); + AdbController.setRemoteDebuggerState(value); #endif -}); + }); -SettingsListener.observe('debugger.remote-mode', false, function(value) { - if (['disabled', 'adb-only', 'adb-devtools'].indexOf(value) == -1) { - dump('Illegal value for debugger.remote-mode: ' + value + '\n'); - return; - } + SettingsListener.observe('debugger.remote-mode', false, function(value) { + if (['disabled', 'adb-only', 'adb-devtools'].indexOf(value) == -1) { + dump('Illegal value for debugger.remote-mode: ' + value + '\n'); + return; + } - Services.prefs.setBoolPref('devtools.debugger.remote-enabled', - value == 'adb-devtools'); - // This preference is consulted during startup - Services.prefs.savePrefFile(null); + devtoolsUSB = value == 'adb-devtools'; + Services.prefs.setBoolPref('devtools.debugger.remote-enabled', + devtoolsUSB || devtoolsWiFi); + // This preference is consulted during startup + Services.prefs.savePrefFile(null); - try { - (value == 'adb-devtools') ? RemoteDebugger.start() - : RemoteDebugger.stop(); - } catch(e) { - dump("Error while initializing devtools: " + e + "\n" + e.stack + "\n"); - } + try { + (value == 'adb-devtools') ? USBRemoteDebugger.start() + : USBRemoteDebugger.stop(); + } catch(e) { + dump("Error while initializing USB devtools: " + + e + "\n" + e.stack + "\n"); + } #ifdef MOZ_WIDGET_GONK - AdbController.setRemoteDebuggerState(value != 'disabled'); + AdbController.setRemoteDebuggerState(value != 'disabled'); #endif -}); + }); + + SettingsListener.observe('devtools.remote.wifi.enabled', false, + function(value) { + devtoolsWiFi = value; + Services.prefs.setBoolPref('devtools.debugger.remote-enabled', + devtoolsUSB || devtoolsWiFi); + // Allow remote debugging on non-local interfaces when WiFi debug is enabled + // TODO: Bug 1034411: Lock down to WiFi interface, instead of all interfaces + Services.prefs.setBoolPref('devtools.debugger.force-local', !value); + // This preference is consulted during startup + Services.prefs.savePrefFile(null); + + try { + value ? WiFiRemoteDebugger.start() : WiFiRemoteDebugger.stop(); + } catch(e) { + dump("Error while initializing WiFi devtools: " + + e + "\n" + e.stack + "\n"); + } + }); +})(); // =================== Device Storage ==================== SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) { @@ -657,6 +690,9 @@ let settingsToObserve = { defaultValue: false }, 'devtools.eventlooplag.threshold': 100, + 'devtools.remote.wifi.visible': { + resetToPref: true + }, 'dom.mozApps.use_reviewer_certs': false, 'layers.draw-borders': false, 'layers.draw-tile-borders': false, diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 66e208b6dc72..ede5abf9c2ad 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -56,6 +56,16 @@ XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() { return DebuggerServer; }); +XPCOMUtils.defineLazyGetter(this, 'devtools', function() { + const { devtools } = + Cu.import('resource://gre/modules/devtools/Loader.jsm', {}); + return devtools; +}); + +XPCOMUtils.defineLazyGetter(this, 'discovery', function() { + return devtools.require('devtools/toolkit/discovery/discovery'); +}); + XPCOMUtils.defineLazyGetter(this, "ppmm", function() { return Cc["@mozilla.org/parentprocessmessagemanager;1"] .getService(Ci.nsIMessageListenerManager); @@ -809,7 +819,6 @@ let IndexedDBPromptHelper = { let RemoteDebugger = { _promptDone: false, _promptAnswer: false, - _running: false, prompt: function debugger_prompt() { this._promptDone = false; @@ -830,8 +839,69 @@ let RemoteDebugger = { this._promptDone = true; }, + initServer: function() { + if (DebuggerServer.initialized) { + return; + } + + // Ask for remote connections. + DebuggerServer.init(this.prompt.bind(this)); + + // /!\ Be careful when adding a new actor, especially global actors. + // Any new global actor will be exposed and returned by the root actor. + + // Add Firefox-specific actors, but prevent tab actors to be loaded in + // the parent process, unless we enable certified apps debugging. + let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); + DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges); + + /** + * Construct a root actor appropriate for use in a server running in B2G. + * The returned root actor respects the factories registered with + * DebuggerServer.addGlobalActor only if certified apps debugging is on, + * otherwise we used an explicit limited list of global actors + * + * * @param connection DebuggerServerConnection + * The conection to the client. + */ + DebuggerServer.createRootActor = function createRootActor(connection) + { + let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); + let parameters = { + // We do not expose browser tab actors yet, + // but we still have to define tabList.getList(), + // otherwise, client won't be able to fetch global actors + // from listTabs request! + tabList: { + getList: function() { + return promise.resolve([]); + } + }, + // Use an explicit global actor list to prevent exposing + // unexpected actors + globalActorFactories: restrictPrivileges ? { + webappsActor: DebuggerServer.globalActorFactories.webappsActor, + deviceActor: DebuggerServer.globalActorFactories.deviceActor, + } : DebuggerServer.globalActorFactories + }; + let { RootActor } = devtools.require("devtools/server/actors/root"); + let root = new RootActor(connection, parameters); + root.applicationType = "operating-system"; + return root; + }; + +#ifdef MOZ_WIDGET_GONK + DebuggerServer.on("connectionchange", function() { + AdbController.updateState(); + }); +#endif + } +}; + +let USBRemoteDebugger = { + get isDebugging() { - if (!this._running) { + if (!this._listener) { return false; } @@ -839,99 +909,78 @@ let RemoteDebugger = { Object.keys(DebuggerServer._connections).length > 0; }, - // Start the debugger server. - start: function debugger_start() { - if (this._running) { + start: function() { + if (this._listener) { return; } - if (!DebuggerServer.initialized) { - // Ask for remote connections. - DebuggerServer.init(this.prompt.bind(this)); + RemoteDebugger.initServer(); - // /!\ Be careful when adding a new actor, especially global actors. - // Any new global actor will be exposed and returned by the root actor. + let portOrPath = + Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || + "/data/local/debugger-socket"; - // Add Firefox-specific actors, but prevent tab actors to be loaded in - // the parent process, unless we enable certified apps debugging. - let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); - DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges); - - /** - * Construct a root actor appropriate for use in a server running in B2G. - * The returned root actor respects the factories registered with - * DebuggerServer.addGlobalActor only if certified apps debugging is on, - * otherwise we used an explicit limited list of global actors - * - * * @param connection DebuggerServerConnection - * The conection to the client. - */ - DebuggerServer.createRootActor = function createRootActor(connection) - { - let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); - let parameters = { - // We do not expose browser tab actors yet, - // but we still have to define tabList.getList(), - // otherwise, client won't be able to fetch global actors - // from listTabs request! - tabList: { - getList: function() { - return promise.resolve([]); - } - }, - // Use an explicit global actor list to prevent exposing - // unexpected actors - globalActorFactories: restrictPrivileges ? { - webappsActor: DebuggerServer.globalActorFactories.webappsActor, - deviceActor: DebuggerServer.globalActorFactories.deviceActor, - } : DebuggerServer.globalActorFactories - }; - let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; - let { RootActor } = devtools.require("devtools/server/actors/root"); - let root = new RootActor(connection, parameters); - root.applicationType = "operating-system"; - return root; - }; - -#ifdef MOZ_WIDGET_GONK - DebuggerServer.on("connectionchange", function() { - AdbController.updateState(); - }); -#endif - } - - let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || - "/data/local/debugger-socket"; try { - DebuggerServer.openListener(path); + debug("Starting USB debugger on " + portOrPath); + this._listener = DebuggerServer.openListener(portOrPath); // Temporary event, until bug 942756 lands and offers a way to know // when the server is up and running. Services.obs.notifyObservers(null, 'debugger-server-started', null); - this._running = true; } catch (e) { - dump('Unable to start debugger server: ' + e + '\n'); + debug('Unable to start USB debugger server: ' + e); } }, - stop: function debugger_stop() { - if (!this._running) { - return; - } - - if (!DebuggerServer.initialized) { - // Can this really happen if we are running? - this._running = false; + stop: function() { + if (!this._listener) { return; } try { - DebuggerServer.closeAllListeners(); + this._listener.close(); + this._listener = null; } catch (e) { - dump('Unable to stop debugger server: ' + e + '\n'); + debug('Unable to stop USB debugger server: ' + e); } - this._running = false; } -} + +}; + +let WiFiRemoteDebugger = { + + start: function() { + if (this._listener) { + return; + } + + RemoteDebugger.initServer(); + + try { + debug("Starting WiFi debugger"); + this._listener = DebuggerServer.openListener(-1); + let port = this._listener.port; + debug("Started WiFi debugger on " + port); + discovery.addService("devtools", { port: port }); + } catch (e) { + debug('Unable to start WiFi debugger server: ' + e); + } + }, + + stop: function() { + if (!this._listener) { + return; + } + + try { + discovery.removeService("devtools"); + this._listener.close(); + this._listener = null; + } catch (e) { + debug('Unable to stop WiFi debugger server: ' + e); + } + } + +}; let KeyboardHelper = { handleEvent: function keyboard_handleEvent(detail) { diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 32e8eb903882..958ecfb344bd 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 00e538345741..c5d32039fa51 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 66666cfabbbb..454cc018d963 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 32e8eb903882..958ecfb344bd 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 4f2e555d5409..4a98091de00e 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index c5fdb1fb624c..d916f232fdff 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "18f160082057a1c10754458f9e4944220099ab67", + "revision": "eff18c4265f0d8e49e5a2f2d7c9fdb01c87bd42e", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 1985316964c7..1240a3fe403f 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index aa292dc3624d..b4ad2bfc3c0b 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index bacebdf62a7d..c412dc23cc68 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index d48874a7a50c..9a6d81a71c29 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/browser/devtools/canvasdebugger/canvasdebugger.js b/browser/devtools/canvasdebugger/canvasdebugger.js index b8131542f06f..cac1d2e7e820 100644 --- a/browser/devtools/canvasdebugger/canvasdebugger.js +++ b/browser/devtools/canvasdebugger/canvasdebugger.js @@ -207,8 +207,8 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, { let thumbnail = document.createElementNS(HTML_NS, "canvas"); thumbnail.className = "snapshot-item-thumbnail"; - thumbnail.width = CanvasFront.THUMBNAIL_HEIGHT; - thumbnail.height = CanvasFront.THUMBNAIL_HEIGHT; + thumbnail.width = CanvasFront.THUMBNAIL_SIZE; + thumbnail.height = CanvasFront.THUMBNAIL_SIZE; let title = document.createElement("label"); title.className = "plain snapshot-item-title"; @@ -712,14 +712,16 @@ let CallsListView = Heritage.extend(WidgetMethods, { * A single "snapshot-image" instance received from the backend. */ showScreenshot: function(screenshot) { - let { index, width, height, flipped, pixels } = screenshot; + let { index, width, height, scaling, flipped, pixels } = screenshot; let screenshotNode = $("#screenshot-image"); screenshotNode.setAttribute("flipped", flipped); drawBackground("screenshot-rendering", width, height, pixels); let dimensionsNode = $("#screenshot-dimensions"); - dimensionsNode.setAttribute("value", ~~width + " x " + ~~height); + let actualWidth = (width / scaling) | 0; + let actualHeight = (height / scaling) | 0; + dimensionsNode.setAttribute("value", actualWidth + " x " + actualHeight); window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED); }, @@ -754,8 +756,8 @@ let CallsListView = Heritage.extend(WidgetMethods, { let thumbnailNode = document.createElementNS(HTML_NS, "canvas"); thumbnailNode.setAttribute("flipped", flipped); - thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_HEIGHT, width); - thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_HEIGHT, height); + thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width); + thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height); drawImage(thumbnailNode, width, height, pixels, { centered: true }); thumbnailNode.className = "filmstrip-thumbnail"; diff --git a/browser/devtools/canvasdebugger/test/browser.ini b/browser/devtools/canvasdebugger/test/browser.ini index d1fe81961b1a..2430397707b2 100644 --- a/browser/devtools/canvasdebugger/test/browser.ini +++ b/browser/devtools/canvasdebugger/test/browser.ini @@ -5,6 +5,7 @@ support-files = doc_simple-canvas-bitmasks.html doc_simple-canvas-deep-stack.html doc_simple-canvas-transparent.html + doc_webgl-bindings.html doc_webgl-enum.html head.js @@ -17,6 +18,7 @@ support-files = [browser_canvas-actor-test-07.js] [browser_canvas-actor-test-08.js] [browser_canvas-actor-test-09.js] +[browser_canvas-actor-test-10.js] [browser_canvas-frontend-call-highlight.js] [browser_canvas-frontend-call-list.js] [browser_canvas-frontend-call-search.js] diff --git a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js index ab31f11255a8..618a3ab2eaa4 100644 --- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js +++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-08.js @@ -18,9 +18,7 @@ function ifTestingSupported() { ok(true, "Target automatically navigated when the front was set up."); let snapshotActor = yield front.recordAnimationFrame(); - let animationOverview = yield snapshotActor.getOverview(); - let functionCalls = animationOverview.calls; is(functionCalls[0].name, "clearRect", diff --git a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js index 229f3618bc83..8a8e0883409f 100644 --- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js +++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-09.js @@ -26,6 +26,11 @@ function ifTestingSupported() { is(functionCalls[0].argsPreview, "DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT", "The bits passed into `gl.clear` have been cast to their enum values."); + is(functionCalls[1].name, "bindTexture", + "The function's name is correct."); + is(functionCalls[1].argsPreview, "TEXTURE_2D, null", + "The bits passed into `gl.bindTexture` have been cast to their enum values."); + yield removeTab(target.tab); finish(); } diff --git a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js new file mode 100644 index 000000000000..bf29eb65960f --- /dev/null +++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the correct framebuffer, renderbuffer and textures are re-bound + * after generating screenshots using the actor. + */ + +function ifTestingSupported() { + let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_BINDINGS_URL); + + let navigated = once(target, "navigate"); + + yield front.setup({ reload: true }); + ok(true, "The front was setup up successfully."); + + yield navigated; + ok(true, "Target automatically navigated when the front was set up."); + + let snapshotActor = yield front.recordAnimationFrame(); + let animationOverview = yield snapshotActor.getOverview(); + let functionCalls = animationOverview.calls; + + let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]); + is(firstScreenshot.index, -1, + "The first screenshot didn't encounter any draw call."); + is(firstScreenshot.scaling, 0.25, + "The first screenshot has the correct scaling."); + is(firstScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, + "The first screenshot has the correct width."); + is(firstScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, + "The first screenshot has the correct height."); + is(firstScreenshot.flipped, true, + "The first screenshot has the correct 'flipped' flag."); + is(firstScreenshot.pixels.length, 0, + "The first screenshot should be empty."); + + let gl = debuggee.gl; + is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer, + "The debuggee's gl context framebuffer wasn't changed."); + is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer, + "The debuggee's gl context renderbuffer wasn't changed."); + is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture, + "The debuggee's gl context texture binding wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[0], 128, + "The debuggee's gl context viewport's left coord. wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[1], 256, + "The debuggee's gl context viewport's left coord. wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[2], 384, + "The debuggee's gl context viewport's left coord. wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[3], 512, + "The debuggee's gl context viewport's left coord. wasn't changed."); + + let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]); + is(secondScreenshot.index, 1, + "The second screenshot has the correct index."); + is(secondScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, + "The second screenshot has the correct width."); + is(secondScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, + "The second screenshot has the correct height."); + is(secondScreenshot.scaling, 0.25, + "The second screenshot has the correct scaling."); + is(secondScreenshot.flipped, true, + "The second screenshot has the correct 'flipped' flag."); + is(secondScreenshot.pixels.length, Math.pow(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, 2), + "The second screenshot should not be empty."); + is(new Uint8Array(secondScreenshot.pixels.buffer)[0], 0, + "The second screenshot has the correct red component."); + is(new Uint8Array(secondScreenshot.pixels.buffer)[1], 0, + "The second screenshot has the correct green component."); + is(new Uint8Array(secondScreenshot.pixels.buffer)[2], 255, + "The second screenshot has the correct blue component."); + is(new Uint8Array(secondScreenshot.pixels.buffer)[3], 255, + "The second screenshot has the correct alpha component."); + + let gl = debuggee.gl; + is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer, + "The debuggee's gl context framebuffer still wasn't changed."); + is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer, + "The debuggee's gl context renderbuffer still wasn't changed."); + is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture, + "The debuggee's gl context texture binding still wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[0], 128, + "The debuggee's gl context viewport's left coord. still wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[1], 256, + "The debuggee's gl context viewport's left coord. still wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[2], 384, + "The debuggee's gl context viewport's left coord. still wasn't changed."); + is(gl.getParameter(gl.VIEWPORT)[3], 512, + "The debuggee's gl context viewport's left coord. still wasn't changed."); + + yield removeTab(target.tab); + finish(); +} diff --git a/browser/devtools/canvasdebugger/test/doc_webgl-bindings.html b/browser/devtools/canvasdebugger/test/doc_webgl-bindings.html new file mode 100644 index 000000000000..eb1405359f65 --- /dev/null +++ b/browser/devtools/canvasdebugger/test/doc_webgl-bindings.html @@ -0,0 +1,61 @@ + + + + + + + WebGL editor test page + + + + + + + + + diff --git a/browser/devtools/canvasdebugger/test/doc_webgl-enum.html b/browser/devtools/canvasdebugger/test/doc_webgl-enum.html index 3c639363d508..f7f4d6d1e433 100644 --- a/browser/devtools/canvasdebugger/test/doc_webgl-enum.html +++ b/browser/devtools/canvasdebugger/test/doc_webgl-enum.html @@ -25,6 +25,7 @@ function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + gl.bindTexture(gl.TEXTURE_2D, null); window.requestAnimationFrame(drawScene); } diff --git a/browser/devtools/canvasdebugger/test/head.js b/browser/devtools/canvasdebugger/test/head.js index e922c31daac5..88e2aa6abc55 100644 --- a/browser/devtools/canvasdebugger/test/head.js +++ b/browser/devtools/canvasdebugger/test/head.js @@ -30,6 +30,7 @@ const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html"; const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html"; const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html"; const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html"; +const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html"; // All tests are asynchronous. waitForExplicitFinish(); diff --git a/content/base/public/nsIMessageManager.idl b/content/base/public/nsIMessageManager.idl index 4afa64e33820..fb72bc876cd9 100644 --- a/content/base/public/nsIMessageManager.idl +++ b/content/base/public/nsIMessageManager.idl @@ -125,27 +125,29 @@ interface nsIPrincipal; * * Parent process Child processes * ---------------- ----------------- - * global PPMM + * global (GPPMM) * | - * +<----> child PPMM + * +-->parent in-process PIPMM<-->child in-process CIPPMM * | - * +-->parent PMM1<------------------>child process CMM1 + * +-->parent (PPMM1)<------------------>child (CPMM1) * | - * +-->parent PMM2<------------------>child process PMM2 + * +-->parent (PPMM2)<------------------>child (CPMM2) * ... * - * For example: the parent-process PMM1 sends messages directly to - * only the child-process CMM1. + * Note, PIPMM and CIPPMM both run in the parent process. * - * For example: CMM1 sends messages directly to PMM1. The global PPMM + * For example: the parent-process PPMM1 sends messages to the + * child-process CPMM1. + * + * For example: CPMM1 sends messages directly to PPMM1. The global GPPMM * will also notify their message listeners when the message arrives. * - * For example: messages sent through the global PPMM will be - * dispatched to the listeners of the same-process, "child PPMM". - * They will also be broadcast to PPM1, PPM2, etc. + * For example: messages sent through the global GPPMM will be + * dispatched to the listeners of the same-process, CIPPMM, CPMM1, + * CPMM2, etc. * * ***** PERFORMANCE AND SECURITY WARNING ***** - * Messages broadcast through the global PPMM can result in messages + * Messages broadcast through the GPPMM can result in messages * being dispatched across many OS processes, and to many processes * with different permissions. Great care should be taken when * broadcasting. diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 97fd1ea97089..202edb1d3ba4 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -22,6 +22,7 @@ #include "mozilla/Telemetry.h" #include "mozilla/unused.h" #include "mozilla/VisualEventTracer.h" +#include "URIUtils.h" #ifdef MOZ_LOGGING // so we can get logging even in release builds (but only for some things) @@ -4558,16 +4559,24 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI, // if this is a Strict-Transport-Security host and the cert // is bad, don't allow overrides (STS Spec section 7.3). - nsCOMPtr sss = - do_GetService(NS_SSSERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - uint32_t flags = - mInPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; - + uint32_t type = nsISiteSecurityService::HEADER_HSTS; + uint32_t flags = mInPrivateBrowsing + ? nsISocketProvider::NO_PERMANENT_STORAGE + : 0; bool isStsHost = false; - rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, - aURI, flags, &isStsHost); - NS_ENSURE_SUCCESS(rv, rv); + if (XRE_GetProcessType() == GeckoProcessType_Default) { + nsCOMPtr sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(type, aURI, flags, &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + mozilla::ipc::URIParams uri; + SerializeURI(aURI, uri); + cc->SendIsSecureURI(type, uri, flags, &isStsHost); + } uint32_t bucketId; if (isStsHost) { diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 939a0db982d8..47a3156159f0 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -884,7 +884,7 @@ WebappsApplicationMgmt.prototype = { } break; case "Webapps:Uninstall:Return:OK": - Services.DOMRequest.fireSuccess(req, msg.origin); + Services.DOMRequest.fireSuccess(req, msg.manifestURL); break; case "Webapps:Uninstall:Return:KO": Services.DOMRequest.fireError(req, "NOT_INSTALLED"); diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index a242eba6466a..2e6c76feba37 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -2080,14 +2080,11 @@ this.DOMApplicationRegistry = { return false; } - // Disallow multiple hosted apps installations from the same origin for now. - // We will remove this code after multiple apps per origin are supported (bug 778277). - // This will also disallow reinstalls from the same origin for now. + // Disallow reinstalls from the same manifest url for now. for (let id in this.webapps) { - if (this.webapps[id].origin == app.origin && - !this.webapps[id].packageHash && + if (this.webapps[id].manifestURL == app.manifestURL && this._isLaunchable(this.webapps[id])) { - sendError("MULTIPLE_APPS_PER_ORIGIN_FORBIDDEN"); + sendError("REINSTALL_FORBIDDEN"); return false; } } diff --git a/dom/apps/tests/mochitest.ini b/dom/apps/tests/mochitest.ini index 47baedde67f7..8b67d343d118 100644 --- a/dom/apps/tests/mochitest.ini +++ b/dom/apps/tests/mochitest.ini @@ -19,6 +19,7 @@ support-files = [test_app_update.html] [test_bug_795164.html] +[test_install_multiple_apps_origin.html] [test_install_receipts.html] [test_marketplace_pkg_install.html] skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806 diff --git a/dom/apps/tests/test_install_multiple_apps_origin.html b/dom/apps/tests/test_install_multiple_apps_origin.html new file mode 100644 index 000000000000..4aef6d1a7edb --- /dev/null +++ b/dom/apps/tests/test_install_multiple_apps_origin.html @@ -0,0 +1,112 @@ + + + + + Test for Bug {778277} + + + + + + +Mozilla Bug {778277} +

+ +
+
+
+ + diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 1a0fd0facdbd..3dfe0bf6d73c 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -98,6 +98,7 @@ #include "nsIPresShell.h" #include "nsIRemoteBlob.h" #include "nsIScriptError.h" +#include "nsISiteSecurityService.h" #include "nsIStyleSheet.h" #include "nsISupportsPrimitives.h" #include "nsIURIFixup.h" @@ -3239,6 +3240,23 @@ ContentParent::RecvGetSystemMemory(const uint64_t& aGetterId) return true; } +bool +ContentParent::RecvIsSecureURI(const uint32_t& type, + const URIParams& uri, + const uint32_t& flags, + bool* isSecureURI) +{ + nsCOMPtr sss(do_GetService(NS_SSSERVICE_CONTRACTID)); + if (!sss) { + return false; + } + nsCOMPtr ourURI = DeserializeURI(uri); + if (!ourURI) { + return false; + } + nsresult rv = sss->IsSecureURI(type, ourURI, flags, isSecureURI); + return NS_SUCCEEDED(rv); +} bool ContentParent::RecvLoadURIExternal(const URIParams& uri) diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index f2546c1b8445..f29895120cf7 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -422,6 +422,9 @@ private: virtual bool RecvGetRandomValues(const uint32_t& length, InfallibleTArray* randomValues) MOZ_OVERRIDE; + virtual bool RecvIsSecureURI(const uint32_t& type, const URIParams& uri, + const uint32_t& flags, bool* isSecureURI); + virtual bool DeallocPHalParent(PHalParent*) MOZ_OVERRIDE; virtual bool DeallocPIndexedDBParent(PIndexedDBParent* aActor) MOZ_OVERRIDE; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 3f6a86529a4e..e3581bec1a34 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -478,6 +478,9 @@ parent: async GetSystemMemory(uint64_t getterId); + sync IsSecureURI(uint32_t type, URIParams uri, uint32_t flags) + returns (bool isSecureURI); + PHal(); PIndexedDB(); diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp index 59534d31735b..90ba37803d68 100644 --- a/dom/system/gonk/NetworkUtils.cpp +++ b/dom/system/gonk/NetworkUtils.cpp @@ -97,6 +97,8 @@ typedef Tuple3 QueueData; #define GET_CURRENT_CALLBACK (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].c) #define GET_CURRENT_COMMAND (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a->mData) +#define CNT_OF_ARRAY(a) (sizeof(a) / sizeof(a[0])) + static NetworkUtils* gNetworkUtils; static nsTArray gCommandQueue; static CurrentCommand gCurrentCommand; @@ -1087,54 +1089,64 @@ NetworkUtils::~NetworkUtils() void NetworkUtils::ExecuteCommand(NetworkParams aOptions) { - bool ret = true; + typedef bool (NetworkUtils::*CommandHandler)(NetworkParams&); - if (aOptions.mCmd.EqualsLiteral("removeNetworkRoute")) { - removeNetworkRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setDNS")) { - setDNS(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setDefaultRouteAndDNS")) { - setDefaultRouteAndDNS(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("removeDefaultRoute")) { - removeDefaultRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("addHostRoute")) { - addHostRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("removeHostRoute")) { - removeHostRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("removeHostRoutes")) { - removeHostRoutes(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("addSecondaryRoute")) { - addSecondaryRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("removeSecondaryRoute")) { - removeSecondaryRoute(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("getNetworkInterfaceStats")) { - getNetworkInterfaceStats(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setNetworkInterfaceAlarm")) { - setNetworkInterfaceAlarm(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("enableNetworkInterfaceAlarm")) { - enableNetworkInterfaceAlarm(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("disableNetworkInterfaceAlarm")) { - disableNetworkInterfaceAlarm(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setWifiOperationMode")) { - setWifiOperationMode(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setDhcpServer")) { - setDhcpServer(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setWifiTethering")) { - setWifiTethering(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("setUSBTethering")) { - setUSBTethering(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("enableUsbRndis")) { - enableUsbRndis(aOptions); - } else if (aOptions.mCmd.EqualsLiteral("updateUpStream")) { - updateUpStream(aOptions); - } else { - WARN("unknon message"); + const static struct { + const char* mCommandName; + CommandHandler mCommandHandler; + } COMMAND_HANDLER_TABLE[] = { + + // For command 'testCommand', BUILD_ENTRY(testCommand) will generate + // {"testCommand", NetworkUtils::testCommand} + #define BUILD_ENTRY(c) {#c, &NetworkUtils::c} + + BUILD_ENTRY(removeNetworkRoute), + BUILD_ENTRY(setDNS), + BUILD_ENTRY(setDefaultRouteAndDNS), + BUILD_ENTRY(removeDefaultRoute), + BUILD_ENTRY(addHostRoute), + BUILD_ENTRY(removeHostRoute), + BUILD_ENTRY(removeHostRoutes), + BUILD_ENTRY(addSecondaryRoute), + BUILD_ENTRY(removeSecondaryRoute), + BUILD_ENTRY(getNetworkInterfaceStats), + BUILD_ENTRY(setNetworkInterfaceAlarm), + BUILD_ENTRY(enableNetworkInterfaceAlarm), + BUILD_ENTRY(disableNetworkInterfaceAlarm), + BUILD_ENTRY(setWifiOperationMode), + BUILD_ENTRY(setDhcpServer), + BUILD_ENTRY(setWifiTethering), + BUILD_ENTRY(setUSBTethering), + BUILD_ENTRY(enableUsbRndis), + BUILD_ENTRY(updateUpStream), + + #undef BUILD_ENTRY + }; + + // Loop until we find the command name which matches aOptions.mCmd. + CommandHandler handler = nullptr; + for (size_t i = 0; i < CNT_OF_ARRAY(COMMAND_HANDLER_TABLE); i++) { + if (aOptions.mCmd.EqualsASCII(COMMAND_HANDLER_TABLE[i].mCommandName)) { + handler = COMMAND_HANDLER_TABLE[i].mCommandHandler; + break; + } + } + + if (!handler) { + // Command not found in COMMAND_HANDLER_TABLE. + WARN("unknown message: %s", NS_ConvertUTF16toUTF8(aOptions.mCmd).get()); return; } + // Command matches! Dispatch to the handler. + (this->*handler)(aOptions); + if (!aOptions.mIsAsync) { + // The requested command is synchronous, which implies the actual result + // from netd is not important to the client. So, just notify the + // registered callback. NetworkResultOptions result; - result.mRet = ret; + result.mRet = true; postMessage(aOptions, result); } } diff --git a/dom/telephony/gonk/TelephonyService.js b/dom/telephony/gonk/TelephonyService.js index 99ff96b69832..635195b499d2 100644 --- a/dom/telephony/gonk/TelephonyService.js +++ b/dom/telephony/gonk/TelephonyService.js @@ -730,10 +730,11 @@ TelephonyService.prototype = { serviceId: aClientId, emergency: aCall.isEmergency, duration: duration, - direction: aCall.isOutgoing ? "outgoing" : "incoming" + direction: aCall.isOutgoing ? "outgoing" : "incoming", + hangUpLocal: aCall.hangUpLocal }; - if(this._cdmaCallWaitingNumber != null) { + if (this._cdmaCallWaitingNumber != null) { data.secondNumber = this._cdmaCallWaitingNumber; this._cdmaCallWaitingNumber = null; } diff --git a/mobile/android/base/db/SearchHistoryProvider.java b/mobile/android/base/db/SearchHistoryProvider.java index 06ecd40389a3..05d31fefd6aa 100644 --- a/mobile/android/base/db/SearchHistoryProvider.java +++ b/mobile/android/base/db/SearchHistoryProvider.java @@ -4,6 +4,7 @@ package org.mozilla.gecko.db; +import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.db.BrowserContract.SearchHistory; import android.content.ContentUris; @@ -110,10 +111,11 @@ public class SearchHistoryProvider extends SharedBrowserDatabaseProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - String groupBy = null; - String having = null; + final String groupBy = null; + final String having = null; + final String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); final Cursor cursor = getReadableDatabase(uri).query(SearchHistory.TABLE_NAME, projection, - selection, selectionArgs, groupBy, having, sortOrder); + selection, selectionArgs, groupBy, having, sortOrder, limit); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } diff --git a/mobile/android/base/tests/testSearchHistoryProvider.java b/mobile/android/base/tests/testSearchHistoryProvider.java index c1d487706ba6..e0ae0d871c4c 100644 --- a/mobile/android/base/tests/testSearchHistoryProvider.java +++ b/mobile/android/base/tests/testSearchHistoryProvider.java @@ -13,11 +13,13 @@ import org.mozilla.gecko.db.SearchHistoryProvider; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; +import android.net.Uri; public class testSearchHistoryProvider extends ContentProviderTest { // Translations of "United Kingdom" in several different languages - private static final String[] testStrings = {"An Ríocht Aontaithe", // Irish + private static final String[] testStrings = { + "An Ríocht Aontaithe", // Irish "Angli", // Albanian "Britanniarum Regnum", // Latin "Britio", // Esperanto @@ -95,6 +97,7 @@ public class testSearchHistoryProvider extends ContentProviderTest { mTests.add(new TestInsert()); mTests.add(new TestUnicodeQuery()); mTests.add(new TestTimestamp()); + mTests.add(new TestLimit()); mTests.add(new TestDelete()); mTests.add(new TestIncrement()); } @@ -111,6 +114,80 @@ public class testSearchHistoryProvider extends ContentProviderTest { } } + /** + * Verify that we can pass a LIMIT clause using a query parameter. + */ + private class TestLimit extends TestCase { + @Override + public void test() throws Exception { + ContentValues cv; + for (int i = 0; i < testStrings.length; i++) { + cv = new ContentValues(); + cv.put(SearchHistory.QUERY, testStrings[i]); + mProvider.insert(SearchHistory.CONTENT_URI, cv); + } + + final int limit = 5; + + // Test 1: Handle proper input. + + Uri uri = SearchHistory.CONTENT_URI + .buildUpon() + .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit)) + .build(); + + Cursor c = mProvider.query(uri, null, null, null, null); + try { + mAsserter.is(c.getCount(), limit, + String.format("Should have %d results", limit)); + } finally { + c.close(); + } + + // Test 2: Empty input yields all results. + + uri = SearchHistory.CONTENT_URI + .buildUpon() + .appendQueryParameter(BrowserContract.PARAM_LIMIT, "") + .build(); + + c = mProvider.query(uri, null, null, null, null); + try { + mAsserter.is(c.getCount(), testStrings.length, "Should have all results"); + } finally { + c.close(); + } + + // Test 3: Illegal params. + + String[] illegalParams = new String[] {"a", "-1"}; + boolean success = true; + + for (String param : illegalParams) { + success = true; + + uri = SearchHistory.CONTENT_URI + .buildUpon() + .appendQueryParameter(BrowserContract.PARAM_LIMIT, param) + .build(); + + try { + c = mProvider.query(uri, null, null, null, null); + success = false; + } catch(IllegalArgumentException e) { + // noop. + } finally { + if (c != null) { + c.close(); + } + } + + mAsserter.ok(success, "LIMIT", param + " should have been an invalid argument"); + } + + } + } + /** * Verify that we can insert values into the DB, including unicode. */ diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index cdc665afe1d2..2dfa1b41239f 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -654,6 +654,10 @@ pref("devtools.dump.emit", false); pref("devtools.discovery.log", false); // Disable scanning for DevTools devices via WiFi pref("devtools.remote.wifi.scan", false); +// Hide UI options for controlling device visibility over WiFi +// N.B.: This does not set whether the device can be discovered via WiFi, only +// whether the UI control to make such a choice is shown to the user +pref("devtools.remote.wifi.visible", false); // view source pref("view_source.syntax_highlight", true); diff --git a/security/manager/boot/src/nsSiteSecurityService.cpp b/security/manager/boot/src/nsSiteSecurityService.cpp index 3cab048ec8f6..30cc0de889bd 100644 --- a/security/manager/boot/src/nsSiteSecurityService.cpp +++ b/security/manager/boot/src/nsSiteSecurityService.cpp @@ -20,6 +20,7 @@ #include "mozilla/Preferences.h" #include "mozilla/LinkedList.h" #include "nsSecurityHeaderParser.h" +#include "nsXULAppAPI.h" // A note about the preload list: // When a site specifically disables sts by sending a header with @@ -87,6 +88,11 @@ NS_IMPL_ISUPPORTS(nsSiteSecurityService, nsresult nsSiteSecurityService::Init() { + // Child processes are not allowed direct access to this. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + MOZ_CRASH("Child process: no direct access to nsSiteSecurityService"); + } + nsresult rv; mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); diff --git a/toolkit/devtools/server/actors/call-watcher.js b/toolkit/devtools/server/actors/call-watcher.js index e9c902400f28..ab743bcf9d73 100644 --- a/toolkit/devtools/server/actors/call-watcher.js +++ b/toolkit/devtools/server/actors/call-watcher.js @@ -210,9 +210,12 @@ let FunctionCallActor = protocol.ActorClass({ // XXX: All of this sucks. Make this smarter, so that the frontend // can inspect each argument, be it object or primitive. Bug 978960. let serializeArgs = () => args.map((arg, i) => { - if (typeof arg == "undefined") { + if (arg === undefined) { return "undefined"; } + if (arg === null) { + return "null"; + } if (typeof arg == "function") { return "Function"; } @@ -631,7 +634,7 @@ CallWatcherFront.ENUM_METHODS[CallWatcherFront.CANVAS_WEBGL_CONTEXT] = { stencilOpSeparate: [0, 1, 2, 3], texImage2D: (args) => args.length > 6 ? [0, 2, 6, 7] : [0, 2, 3, 4], texParameterf: [0, 1], - texParameteri: [0, 1], + texParameteri: [0, 1, 2], texSubImage2D: (args) => args.length === 9 ? [0, 6, 7] : [0, 4, 5], vertexAttribPointer: [2] }; @@ -643,7 +646,7 @@ CallWatcherFront.ENUM_METHODS[CallWatcherFront.CANVAS_WEBGL_CONTEXT] = { * For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed * argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT". */ -var gEnumRegex = /^[A-Z_]+$/; +var gEnumRegex = /^[A-Z][A-Z0-9_]+$/; var gEnumsLookupTable = {}; // These values are returned from errors, or empty values, diff --git a/toolkit/devtools/server/actors/canvas.js b/toolkit/devtools/server/actors/canvas.js index 391e4f413a01..df9ac1aabd35 100644 --- a/toolkit/devtools/server/actors/canvas.js +++ b/toolkit/devtools/server/actors/canvas.js @@ -80,6 +80,7 @@ protocol.types.addDictType("snapshot-image", { index: "number", width: "number", height: "number", + scaling: "number", flipped: "boolean", pixels: "uint32-array" }); @@ -117,7 +118,7 @@ let FrameSnapshotActor = protocol.ActorClass({ protocol.Actor.prototype.initialize.call(this, conn); this._contentCanvas = canvas; this._functionCalls = calls; - this._lastDrawCallScreenshot = screenshot; + this._animationFrameEndScreenshot = screenshot; }, /** @@ -127,7 +128,7 @@ let FrameSnapshotActor = protocol.ActorClass({ return { calls: this._functionCalls, thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e), - screenshot: this._lastDrawCallScreenshot + screenshot: this._animationFrameEndScreenshot }; }, { response: { overview: RetVal("snapshot-overview") } @@ -148,7 +149,7 @@ let FrameSnapshotActor = protocol.ActorClass({ // To get a screenshot, replay all the steps necessary to render the frame, // by invoking the context calls up to and including the specified one. // This will be done in a custom framebuffer in case of a WebGL context. - let { replayContext, lastDrawCallIndex } = ContextUtils.replayAnimationFrame({ + let replayData = ContextUtils.replayAnimationFrame({ contextType: global, canvas: canvas, calls: calls, @@ -156,24 +157,25 @@ let FrameSnapshotActor = protocol.ActorClass({ last: index }); - // To keep things fast, generate an image that's relatively small. - let dimensions = Math.min(CanvasFront.SCREENSHOT_HEIGHT_MAX, canvas.height); + let { replayContext, replayContextScaling, lastDrawCallIndex, doCleanup } = replayData; + let [left, top, width, height] = replayData.replayViewport; let screenshot; // Depending on the canvas' context, generating a screenshot is done - // in different ways. In case of the WebGL context, we also need to reset - // the framebuffer binding to the default value. + // in different ways. if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - screenshot = ContextUtils.getPixelsForWebGL(replayContext); - replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, null); + screenshot = ContextUtils.getPixelsForWebGL(replayContext, left, top, width, height); screenshot.flipped = true; - } - // In case of 2D contexts, no additional special treatment is necessary. - else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { - screenshot = ContextUtils.getPixelsFor2D(replayContext); + } else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { + screenshot = ContextUtils.getPixelsFor2D(replayContext, left, top, width, height); screenshot.flipped = false; } + // In case of the WebGL context, we also need to reset the framebuffer + // binding to the original value, after generating the screenshot. + doCleanup(); + + screenshot.scaling = replayContextScaling; screenshot.index = lastDrawCallIndex; return screenshot; }, { @@ -188,17 +190,17 @@ let FrameSnapshotActor = protocol.ActorClass({ let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, { initialize: function(client, form) { protocol.Front.prototype.initialize.call(this, client, form); - this._lastDrawCallScreenshot = null; + this._animationFrameEndScreenshot = null; this._cachedScreenshots = new WeakMap(); }, /** - * This implementation caches the last draw call screenshot to optimize + * This implementation caches the animation frame end screenshot to optimize * frontend requests to `generateScreenshotFor`. */ getOverview: custom(function() { return this._getOverview().then(data => { - this._lastDrawCallScreenshot = data.screenshot; + this._animationFrameEndScreenshot = data.screenshot; return data; }); }, { @@ -211,7 +213,7 @@ let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, { */ generateScreenshotFor: custom(function(functionCall) { if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name)) { - return promise.resolve(this._lastDrawCallScreenshot); + return promise.resolve(this._animationFrameEndScreenshot); } let cachedScreenshot = this._cachedScreenshots.get(functionCall); if (cachedScreenshot) { @@ -370,12 +372,13 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({ let index = this._lastDrawCallIndex; let width = this._lastContentCanvasWidth; let height = this._lastContentCanvasHeight; - let flipped = this._lastThumbnailFlipped; + let flipped = !!this._lastThumbnailFlipped; // undefined -> false let pixels = ContextUtils.getPixelStorage()["32bit"]; - let lastDrawCallScreenshot = { + let animationFrameEndScreenshot = { index: index, width: width, height: height, + scaling: 1, flipped: flipped, pixels: pixels.subarray(0, width * height) }; @@ -385,7 +388,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({ let frameSnapshot = new FrameSnapshotActor(this.conn, { canvas: this._lastDrawCallCanvas, calls: functionCalls, - screenshot: lastDrawCallScreenshot + screenshot: animationFrameEndScreenshot }); this._currentAnimationFrameSnapshot.resolve(frameSnapshot); @@ -408,7 +411,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({ let h = this._lastContentCanvasHeight = contentCanvas.height; // To keep things fast, generate images of small and fixed dimensions. - let dimensions = CanvasFront.THUMBNAIL_HEIGHT; + let dimensions = CanvasFront.THUMBNAIL_SIZE; let thumbnail; // Create a thumbnail on every draw call on the canvas context, to augment @@ -529,7 +532,7 @@ let ContextUtils = { */ resizePixels: function(srcPixels, srcWidth, srcHeight, dstHeight) { let screenshotRatio = dstHeight / srcHeight; - let dstWidth = Math.floor(srcWidth * screenshotRatio); + let dstWidth = (srcWidth * screenshotRatio) | 0; // Use a plain array instead of a Uint32Array to make serializing faster. let dstPixels = new Array(dstWidth * dstHeight); @@ -540,8 +543,8 @@ let ContextUtils = { for (let dstX = 0; dstX < dstWidth; dstX++) { for (let dstY = 0; dstY < dstHeight; dstY++) { - let srcX = Math.floor(dstX / screenshotRatio); - let srcY = Math.floor(dstY / screenshotRatio); + let srcX = (dstX / screenshotRatio) | 0; + let srcY = (dstY / screenshotRatio) | 0; let cPos = srcX + srcWidth * srcY; let dPos = dstX + dstWidth * dstY; let color = dstPixels[dPos] = srcPixels[cPos]; @@ -566,8 +569,11 @@ let ContextUtils = { * the respective canvas, and the rendering will be performed into it. * This is necessary because some state (like shaders, textures etc.) can't * be shared between two different WebGL contexts. - * Hopefully, once SharedResources are a thing this won't be necessary: - * http://www.khronos.org/webgl/wiki/SharedResouces + * - Hopefully, once SharedResources are a thing this won't be necessary: + * http://www.khronos.org/webgl/wiki/SharedResouces + * - Alternatively, we could pursue the idea of using the same context + * for multiple canvases, instead of trying to share resources: + * https://www.khronos.org/webgl/public-mailing-list/archives/1210/msg00058.html * * In case of a 2D context, a new canvas is created, since there's no * intrinsic state that can't be easily duplicated. @@ -583,33 +589,59 @@ let ContextUtils = { * @param number last * The last (inclusive) function call to end at. * @return object - * The context on which the specified calls were invoked and the - * last registered draw call's index. + * The context on which the specified calls were invoked, the + * last registered draw call's index and a cleanup function, which + * needs to be called whenever any potential followup work is finished. */ replayAnimationFrame: function({ contextType, canvas, calls, first, last }) { let w = canvas.width; let h = canvas.height; - let replayCanvas; let replayContext; + let replayContextScaling; + let customViewport; let customFramebuffer; let lastDrawCallIndex = -1; + let doCleanup = () => {}; // In case of WebGL contexts, rendering will be done offscreen, in a - // custom framebuffer, but on the provided canvas context. + // custom framebuffer, but using the same provided context. This is + // necessary because it's very memory-unfriendly to rebuild all the + // required GL state (like recompiling shaders, setting global flags, etc.) + // in an entirely new canvas. However, special care is needed to not + // permanently affect the existing GL state in the process. if (contextType == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { - replayCanvas = canvas; - replayContext = this.getWebGLContext(replayCanvas); - customFramebuffer = this.createBoundFramebuffer(replayContext, w, h); + // To keep things fast, replay the context calls on a framebuffer + // of smaller dimensions than the actual canvas (maximum 256x256 pixels). + let scaling = Math.min(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, h) / h; + replayContextScaling = scaling; + w = (w * scaling) | 0; + h = (h * scaling) | 0; + + // Fetch the same WebGL context and bind a new framebuffer. + let gl = replayContext = this.getWebGLContext(canvas); + let { newFramebuffer, oldFramebuffer } = this.createBoundFramebuffer(gl, w, h); + customFramebuffer = newFramebuffer; + + // Set the viewport to match the new framebuffer's dimensions. + let { newViewport, oldViewport } = this.setCustomViewport(gl, w, h); + customViewport = newViewport; + + // Revert the framebuffer and viewport to the original values. + doCleanup = () => { + gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer); + gl.viewport.apply(gl, oldViewport); + }; } // In case of 2D contexts, draw everything on a separate canvas context. else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) { let contentDocument = canvas.ownerDocument; - replayCanvas = contentDocument.createElement("canvas"); + let replayCanvas = contentDocument.createElement("canvas"); replayCanvas.width = w; replayCanvas.height = h; replayContext = replayCanvas.getContext("2d"); - replayContext.clearRect(0, 0, w, h); + replayContextScaling = 1; + customViewport = [0, 0, w, h]; } // Replay all the context calls up to and including the specified one. @@ -620,23 +652,33 @@ let ContextUtils = { // to the default value, since we want to perform the rendering offscreen. if (name == "bindFramebuffer" && args[1] == null) { replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer); - } else { - if (type == CallWatcherFront.METHOD_FUNCTION) { - replayContext[name].apply(replayContext, args); - } else if (type == CallWatcherFront.SETTER_FUNCTION) { - replayContext[name] = args; - } else { - // Ignore getter calls. - } - if (CanvasFront.DRAW_CALLS.has(name)) { - lastDrawCallIndex = i; + continue; + } + // Also prevent WebGL context calls that try to change the viewport + // while our custom framebuffer is bound. + if (name == "viewport") { + let framebufferBinding = replayContext.getParameter(replayContext.FRAMEBUFFER_BINDING); + if (framebufferBinding == customFramebuffer) { + replayContext.viewport.apply(replayContext, customViewport); + continue; } } + if (type == CallWatcherFront.METHOD_FUNCTION) { + replayContext[name].apply(replayContext, args); + } else if (type == CallWatcherFront.SETTER_FUNCTION) { + replayContext[name] = args; + } + if (CanvasFront.DRAW_CALLS.has(name)) { + lastDrawCallIndex = i; + } } return { replayContext: replayContext, - lastDrawCallIndex: lastDrawCallIndex + replayContextScaling: replayContextScaling, + replayViewport: customViewport, + lastDrawCallIndex: lastDrawCallIndex, + doCleanup: doCleanup }; }, @@ -691,16 +733,21 @@ let ContextUtils = { * The generated framebuffer object. */ createBoundFramebuffer: function(gl, width, height) { - let framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + let oldFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + let oldRenderbufferBinding = gl.getParameter(gl.RENDERBUFFER_BINDING); + let oldTextureBinding = gl.getParameter(gl.TEXTURE_BINDING_2D); - // Use a texture as the color rendebuffer attachment, since consumenrs of + let newFramebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, newFramebuffer); + + // Use a texture as the color renderbuffer attachment, since consumers of // this function will most likely want to read the rendered pixels back. let colorBuffer = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, colorBuffer); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.generateMipmap(gl.TEXTURE_2D); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); let depthBuffer = gl.createRenderbuffer(); @@ -710,10 +757,24 @@ let ContextUtils = { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); - gl.bindTexture(gl.TEXTURE_2D, null); - gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, oldTextureBinding); + gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbufferBinding); - return framebuffer; + return { oldFramebuffer, newFramebuffer }; + }, + + /** + * Sets the viewport of the drawing buffer for a WebGL context. + * @param WebGLRenderingContext gl + * @param number width + * @param number height + */ + setCustomViewport: function(gl, width, height) { + let oldViewport = XPCNativeWrapper.unwrap(gl.getParameter(gl.VIEWPORT)); + let newViewport = [0, 0, width, height]; + gl.viewport.apply(gl, newViewport); + + return { oldViewport, newViewport }; } }; @@ -734,8 +795,8 @@ CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS); CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS); CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS); CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS); -CanvasFront.THUMBNAIL_HEIGHT = 50; // px -CanvasFront.SCREENSHOT_HEIGHT_MAX = 256; // px +CanvasFront.THUMBNAIL_SIZE = 50; // px +CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT = 256; // px CanvasFront.INVALID_SNAPSHOT_IMAGE = { index: -1, width: 0,