From ed29f29e5023a6e0ec81791b9b1dc88172b9ef82 Mon Sep 17 00:00:00 2001 From: Mantaroh Yoshinaga Date: Tue, 24 Mar 2015 21:11:00 -0400 Subject: [PATCH 01/42] Bug 1120108 - Show the current date on the Datepicker when value including 'T'. r=wesj --HG-- extra : rebase_source : eb37a58ec5002e15c1318bba067cb1ee2b95602f --- mobile/android/base/prompts/PromptInput.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/prompts/PromptInput.java b/mobile/android/base/prompts/PromptInput.java index a7a1cf326fff..1b2532a258be 100644 --- a/mobile/android/base/prompts/PromptInput.java +++ b/mobile/android/base/prompts/PromptInput.java @@ -216,7 +216,7 @@ public class PromptInput { input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE)); mView = (View)input; } else if (mType.equals("datetime-local") || mType.equals("datetime")) { - DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue, + DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," "), DateTimePicker.PickersState.DATETIME); input.toggleCalendar(true); mView = (View)input; @@ -255,7 +255,7 @@ public class PromptInput { } else if (mType.equals("week")) { return formatDateString("yyyy-'W'ww",calendar); } else if (mType.equals("datetime-local")) { - return formatDateString("yyyy-MM-dd HH:mm",calendar); + return formatDateString("yyyy-MM-dd'T'HH:mm",calendar); } else if (mType.equals("datetime")) { calendar.set(GregorianCalendar.ZONE_OFFSET,0); calendar.setTimeInMillis(dp.getTimeInMillis()); From 994c1415bd4c21773b0165f4b892028f221c0573 Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Thu, 30 Apr 2015 12:50:30 -0700 Subject: [PATCH 02/42] Bug 1138079 - Fix focus issue that sometimes affects browser-chrome test runs. r=enndeakin --HG-- extra : rebase_source : 75c86975abf138491dd1478302e8403a22532055 --- testing/mochitest/browser-harness.xul | 37 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/testing/mochitest/browser-harness.xul b/testing/mochitest/browser-harness.xul index 071dac42f330..388cb5810e7c 100644 --- a/testing/mochitest/browser-harness.xul +++ b/testing/mochitest/browser-harness.xul @@ -248,9 +248,40 @@ var testWin = windowMediator.getMostRecentWindow(winType); setStatus("Running..."); - testWin.focus(); - var Tester = new testWin.Tester(links, gDumper, testsFinished); - Tester.start(); + + // It's possible that the test harness window is not yet focused when this + // function runs (in which case testWin is already focused, and focusing it + // will be a no-op, and then the test harness window will steal focus later, + // which will mess up tests). So wait for the test harness window to be + // focused before trying to focus testWin. + waitForFocus(() => { + // Focus the test window and start tests. + waitForFocus(() => { + var Tester = new testWin.Tester(links, gDumper, testsFinished); + Tester.start(); + }, testWin); + }, window); + } + + function executeSoon(callback) { + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); + } + + function waitForFocus(callback, win) { + // If "win" is already focused, just call the callback. + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + if (fm.focusedWindow == win) { + executeSoon(callback); + return; + } + + // Otherwise focus it, and wait for the focus event. + win.addEventListener("focus", function listener() { + win.removeEventListener("focus", listener, true); + executeSoon(callback); + }, true); + win.focus(); } function sum(a, b) { From 2f38e0cc6ffad0bc7015fc513646334d889564bd Mon Sep 17 00:00:00 2001 From: Mantaroh Yoshinaga Date: Thu, 30 Apr 2015 19:29:00 -0400 Subject: [PATCH 03/42] Bug 1147770 - Modify datapicker to support literal 'T' and 'Z' when choosing datetime. r=wesj --HG-- extra : rebase_source : 2e13e4489b5c6b7dae4374740f417608afdd2055 --- mobile/android/base/prompts/PromptInput.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/prompts/PromptInput.java b/mobile/android/base/prompts/PromptInput.java index 1b2532a258be..1fc710cf4bec 100644 --- a/mobile/android/base/prompts/PromptInput.java +++ b/mobile/android/base/prompts/PromptInput.java @@ -216,7 +216,7 @@ public class PromptInput { input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE)); mView = (View)input; } else if (mType.equals("datetime-local") || mType.equals("datetime")) { - DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," "), + DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," ").replace("Z", ""), DateTimePicker.PickersState.DATETIME); input.toggleCalendar(true); mView = (View)input; @@ -259,7 +259,7 @@ public class PromptInput { } else if (mType.equals("datetime")) { calendar.set(GregorianCalendar.ZONE_OFFSET,0); calendar.setTimeInMillis(dp.getTimeInMillis()); - return formatDateString("yyyy-MM-dd HH:mm",calendar); + return formatDateString("yyyy-MM-dd'T'HH:mm'Z'",calendar); } else if (mType.equals("month")) { return formatDateString("yyyy-MM",calendar); } From b89ff22f42c6aa99e2e4ef9a4befa252f0f00fed Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Wed, 6 May 2015 08:20:00 -0400 Subject: [PATCH 04/42] Bug 1159731 - Move all Addon actor subclasses to a dedicated file. r=ejpbruel --HG-- extra : rebase_source : c4e122e686f3dc950af5819f585bc677cdc78506 --- toolkit/devtools/server/actors/addon.js | 335 +++++++++++++++++++ toolkit/devtools/server/actors/script.js | 29 ++ toolkit/devtools/server/actors/webbrowser.js | 267 +-------------- toolkit/devtools/server/actors/webconsole.js | 86 ----- toolkit/devtools/server/moz.build | 1 + 5 files changed, 368 insertions(+), 350 deletions(-) create mode 100644 toolkit/devtools/server/actors/addon.js diff --git a/toolkit/devtools/server/actors/addon.js b/toolkit/devtools/server/actors/addon.js new file mode 100644 index 000000000000..e97755888094 --- /dev/null +++ b/toolkit/devtools/server/actors/addon.js @@ -0,0 +1,335 @@ +/* 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"; + +let { Ci, Cu } = require("chrome"); +let Services = require("Services"); +let { ActorPool } = require("devtools/server/actors/common"); +let { TabSources } = require("./utils/TabSources"); +let makeDebugger = require("./utils/make-debugger"); +let { ConsoleAPIListener } = require("devtools/toolkit/webconsole/utils"); +let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); +let { dbg_assert, update } = DevToolsUtils; + +loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true); +loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true); +loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id"); +loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true); + +loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); + +function BrowserAddonActor(aConnection, aAddon) { + this.conn = aConnection; + this._addon = aAddon; + this._contextPool = new ActorPool(this.conn); + this.conn.addActorPool(this._contextPool); + this._threadActor = null; + this._global = null; + + this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this); + + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: this._findDebuggees.bind(this), + shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee + }); + + AddonManager.addAddonListener(this); +} +exports.BrowserAddonActor = BrowserAddonActor; + +BrowserAddonActor.prototype = { + actorPrefix: "addon", + + get exited() { + return !this._addon; + }, + + get id() { + return this._addon.id; + }, + + get url() { + return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined; + }, + + get attached() { + return this._threadActor; + }, + + get global() { + return this._global; + }, + + get sources() { + if (!this._sources) { + dbg_assert(this.threadActor, "threadActor should exist when creating sources."); + this._sources = new TabSources(this._threadActor, this._allowSource); + } + return this._sources; + }, + + + form: function BAA_form() { + dbg_assert(this.actorID, "addon should have an actorID."); + if (!this._consoleActor) { + this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this); + this._contextPool.addActor(this._consoleActor); + } + + return { + actor: this.actorID, + id: this.id, + name: this._addon.name, + url: this.url, + debuggable: this._addon.isDebuggable, + consoleActor: this._consoleActor.actorID, + + traits: { + highlightable: false, + networkMonitor: false, + }, + }; + }, + + disconnect: function BAA_disconnect() { + this.conn.removeActorPool(this._contextPool); + this._contextPool = null; + this._consoleActor = null; + this._addon = null; + this._global = null; + AddonManager.removeAddonListener(this); + }, + + setOptions: function BAA_setOptions(aOptions) { + if ("global" in aOptions) { + this._global = aOptions.global; + } + }, + + onDisabled: function BAA_onDisabled(aAddon) { + if (aAddon != this._addon) { + return; + } + + this._global = null; + }, + + onUninstalled: function BAA_onUninstalled(aAddon) { + if (aAddon != this._addon) { + return; + } + + if (this.attached) { + this.onDetach(); + this.conn.send({ from: this.actorID, type: "tabDetached" }); + } + + this.disconnect(); + }, + + onAttach: function BAA_onAttach() { + if (this.exited) { + return { type: "exited" }; + } + + if (!this.attached) { + this._threadActor = new AddonThreadActor(this.conn, this); + this._contextPool.addActor(this._threadActor); + } + + return { type: "tabAttached", threadActor: this._threadActor.actorID }; + }, + + onDetach: function BAA_onDetach() { + if (!this.attached) { + return { error: "wrongState" }; + } + + this._contextPool.removeActor(this._threadActor); + + this._threadActor = null; + this._sources = null; + + return { type: "detached" }; + }, + + preNest: function() { + let e = Services.wm.getEnumerator(null); + while (e.hasMoreElements()) { + let win = e.getNext(); + let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + windowUtils.suppressEventHandling(true); + windowUtils.suspendTimeouts(); + } + }, + + postNest: function() { + let e = Services.wm.getEnumerator(null); + while (e.hasMoreElements()) { + let win = e.getNext(); + let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + windowUtils.resumeTimeouts(); + windowUtils.suppressEventHandling(false); + } + }, + + /** + * Return true if the given global is associated with this addon and should be + * added as a debuggee, false otherwise. + */ + _shouldAddNewGlobalAsDebuggee: function (aGlobal) { + const global = unwrapDebuggerObjectGlobal(aGlobal); + try { + // This will fail for non-Sandbox objects, hence the try-catch block. + let metadata = Cu.getSandboxMetadata(global); + if (metadata) { + return metadata.addonID === this.id; + } + } catch (e) {} + + if (global instanceof Ci.nsIDOMWindow) { + let id = {}; + if (mapURIToAddonID(global.document.documentURIObject, id)) { + return id.value === this.id; + } + return false; + } + + // Check the global for a __URI__ property and then try to map that to an + // add-on + let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__"); + if (uridescriptor && "value" in uridescriptor && uridescriptor.value) { + let uri; + try { + uri = Services.io.newURI(uridescriptor.value, null, null); + } + catch (e) { + DevToolsUtils.reportException( + "BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee", + new Error("Invalid URI: " + uridescriptor.value) + ); + return false; + } + + let id = {}; + if (mapURIToAddonID(uri, id)) { + return id.value === this.id; + } + } + + return false; + }, + + /** + * Override the eligibility check for scripts and sources to make + * sure every script and source with a URL is stored when debugging + * add-ons. + */ + _allowSource: function(aSource) { + // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it. + if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") { + return false; + } + + return true; + }, + + /** + * Yield the current set of globals associated with this addon that should be + * added as debuggees. + */ + _findDebuggees: function (dbg) { + return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee); + } +}; + +BrowserAddonActor.prototype.requestTypes = { + "attach": BrowserAddonActor.prototype.onAttach, + "detach": BrowserAddonActor.prototype.onDetach +}; + +/** + * The AddonConsoleActor implements capabilities needed for the add-on web + * console feature. + * + * @constructor + * @param object aAddon + * The add-on that this console watches. + * @param object aConnection + * The connection to the client, DebuggerServerConnection. + * @param object aParentActor + * The parent BrowserAddonActor actor. + */ +function AddonConsoleActor(aAddon, aConnection, aParentActor) +{ + this.addon = aAddon; + WebConsoleActor.call(this, aConnection, aParentActor); +} + +AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype); + +update(AddonConsoleActor.prototype, { + constructor: AddonConsoleActor, + + actorPrefix: "addonConsole", + + /** + * The add-on that this console watches. + */ + addon: null, + + /** + * The main add-on JS global + */ + get window() { + return this.parentActor.global; + }, + + /** + * Destroy the current AddonConsoleActor instance. + */ + disconnect: function ACA_disconnect() + { + WebConsoleActor.prototype.disconnect.call(this); + this.addon = null; + }, + + /** + * Handler for the "startListeners" request. + * + * @param object aRequest + * The JSON request object received from the Web Console client. + * @return object + * The response object which holds the startedListeners array. + */ + onStartListeners: function ACA_onStartListeners(aRequest) + { + let startedListeners = []; + + while (aRequest.listeners.length > 0) { + let listener = aRequest.listeners.shift(); + switch (listener) { + case "ConsoleAPI": + if (!this.consoleAPIListener) { + this.consoleAPIListener = + new ConsoleAPIListener(null, this, "addon/" + this.addon.id); + this.consoleAPIListener.init(); + } + startedListeners.push(listener); + break; + } + } + return { + startedListeners: startedListeners, + nativeConsoleAPI: true, + traits: this.traits, + }; + }, +}); + +AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes); +AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners; diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 4bfa07772bd3..7fee3215f3c0 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -5303,3 +5303,32 @@ function setBreakpointAtEntryPoints(actor, entryPoints) { } } } + +/** + * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has + * become a dead object, return |undefined|. + * + * @param Debugger.Object wrappedGlobal + * The |Debugger.Object| which wraps a global. + * + * @returns {Object|undefined} + * Returns the unwrapped global object or |undefined| if unwrapping + * failed. + */ +exports.unwrapDebuggerObjectGlobal = wrappedGlobal => { + try { + // Because of bug 991399 we sometimes get nuked window references here. We + // just bail out in that case. + // + // Note that addon sandboxes have a DOMWindow as their prototype. So make + // sure that we can touch the prototype too (whatever it is), in case _it_ + // is it a nuked window reference. We force stringification to make sure + // that any dead object proxies make themselves known. + let global = wrappedGlobal.unsafeDereference(); + Object.getPrototypeOf(global) + ""; + return global; + } + catch (e) { + return undefined; + } +}; diff --git a/toolkit/devtools/server/actors/webbrowser.js b/toolkit/devtools/server/actors/webbrowser.js index 310b8df32796..0b5aa030c388 100644 --- a/toolkit/devtools/server/actors/webbrowser.js +++ b/toolkit/devtools/server/actors/webbrowser.js @@ -13,15 +13,15 @@ let { ActorPool, createExtraActors, appendExtraActors } = require("devtools/serv let { DebuggerServer } = require("devtools/server/main"); let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); let { dbg_assert } = DevToolsUtils; -let { TabSources, isHiddenSource } = require("./utils/TabSources"); +let { TabSources } = require("./utils/TabSources"); let makeDebugger = require("./utils/make-debugger"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true); -loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true); loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true); -loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id"); +loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true); +loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true); loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); // Assumptions on events module: @@ -111,35 +111,6 @@ function sendShutdownEvent() { exports.sendShutdownEvent = sendShutdownEvent; -/** - * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has - * become a dead object, return |undefined|. - * - * @param Debugger.Object wrappedGlobal - * The |Debugger.Object| which wraps a global. - * - * @returns {Object|undefined} - * Returns the unwrapped global object or |undefined| if unwrapping - * failed. - */ -const unwrapDebuggerObjectGlobal = wrappedGlobal => { - try { - // Because of bug 991399 we sometimes get nuked window references here. We - // just bail out in that case. - // - // Note that addon sandboxes have a DOMWindow as their prototype. So make - // sure that we can touch the prototype too (whatever it is), in case _it_ - // is it a nuked window reference. We force stringification to make sure - // that any dead object proxies make themselves known. - let global = wrappedGlobal.unsafeDereference(); - Object.getPrototypeOf(global) + ""; - return global; - } - catch (e) { - return undefined; - } -}; - /** * Construct a root actor appropriate for use in a server running in a * browser. The returned root actor: @@ -2005,238 +1976,6 @@ BrowserAddonList.prototype.onUninstalled = function (aAddon) { exports.BrowserAddonList = BrowserAddonList; -function BrowserAddonActor(aConnection, aAddon) { - this.conn = aConnection; - this._addon = aAddon; - this._contextPool = new ActorPool(this.conn); - this.conn.addActorPool(this._contextPool); - this._threadActor = null; - this._global = null; - - this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this); - - this.makeDebugger = makeDebugger.bind(null, { - findDebuggees: this._findDebuggees.bind(this), - shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee - }); - - AddonManager.addAddonListener(this); -} - -BrowserAddonActor.prototype = { - actorPrefix: "addon", - - get exited() { - return !this._addon; - }, - - get id() { - return this._addon.id; - }, - - get url() { - return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined; - }, - - get attached() { - return this._threadActor; - }, - - get global() { - return this._global; - }, - - get sources() { - if (!this._sources) { - dbg_assert(this.threadActor, "threadActor should exist when creating sources."); - this._sources = new TabSources(this._threadActor, this._allowSource); - } - return this._sources; - }, - - - form: function BAA_form() { - dbg_assert(this.actorID, "addon should have an actorID."); - if (!this._consoleActor) { - let {AddonConsoleActor} = require("devtools/server/actors/webconsole"); - this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this); - this._contextPool.addActor(this._consoleActor); - } - - return { - actor: this.actorID, - id: this.id, - name: this._addon.name, - url: this.url, - debuggable: this._addon.isDebuggable, - consoleActor: this._consoleActor.actorID, - - traits: { - highlightable: false, - networkMonitor: false, - }, - }; - }, - - disconnect: function BAA_disconnect() { - this.conn.removeActorPool(this._contextPool); - this._contextPool = null; - this._consoleActor = null; - this._addon = null; - this._global = null; - AddonManager.removeAddonListener(this); - }, - - setOptions: function BAA_setOptions(aOptions) { - if ("global" in aOptions) { - this._global = aOptions.global; - } - }, - - onDisabled: function BAA_onDisabled(aAddon) { - if (aAddon != this._addon) { - return; - } - - this._global = null; - }, - - onUninstalled: function BAA_onUninstalled(aAddon) { - if (aAddon != this._addon) { - return; - } - - if (this.attached) { - this.onDetach(); - this.conn.send({ from: this.actorID, type: "tabDetached" }); - } - - this.disconnect(); - }, - - onAttach: function BAA_onAttach() { - if (this.exited) { - return { type: "exited" }; - } - - if (!this.attached) { - this._threadActor = new AddonThreadActor(this.conn, this); - this._contextPool.addActor(this._threadActor); - } - - return { type: "tabAttached", threadActor: this._threadActor.actorID }; - }, - - onDetach: function BAA_onDetach() { - if (!this.attached) { - return { error: "wrongState" }; - } - - this._contextPool.removeActor(this._threadActor); - - this._threadActor = null; - this._sources = null; - - return { type: "detached" }; - }, - - preNest: function() { - let e = Services.wm.getEnumerator(null); - while (e.hasMoreElements()) { - let win = e.getNext(); - let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - windowUtils.suppressEventHandling(true); - windowUtils.suspendTimeouts(); - } - }, - - postNest: function() { - let e = Services.wm.getEnumerator(null); - while (e.hasMoreElements()) { - let win = e.getNext(); - let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - windowUtils.resumeTimeouts(); - windowUtils.suppressEventHandling(false); - } - }, - - /** - * Return true if the given global is associated with this addon and should be - * added as a debuggee, false otherwise. - */ - _shouldAddNewGlobalAsDebuggee: function (aGlobal) { - const global = unwrapDebuggerObjectGlobal(aGlobal); - try { - // This will fail for non-Sandbox objects, hence the try-catch block. - let metadata = Cu.getSandboxMetadata(global); - if (metadata) { - return metadata.addonID === this.id; - } - } catch (e) {} - - if (global instanceof Ci.nsIDOMWindow) { - let id = {}; - if (mapURIToAddonID(global.document.documentURIObject, id)) { - return id.value === this.id; - } - return false; - } - - // Check the global for a __URI__ property and then try to map that to an - // add-on - let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__"); - if (uridescriptor && "value" in uridescriptor && uridescriptor.value) { - let uri; - try { - uri = Services.io.newURI(uridescriptor.value, null, null); - } - catch (e) { - DevToolsUtils.reportException( - "BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee", - new Error("Invalid URI: " + uridescriptor.value) - ); - return false; - } - - let id = {}; - if (mapURIToAddonID(uri, id)) { - return id.value === this.id; - } - } - - return false; - }, - - /** - * Override the eligibility check for scripts and sources to make - * sure every script and source with a URL is stored when debugging - * add-ons. - */ - _allowSource: function(aSource) { - // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it. - if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") { - return false; - } - - return true; - }, - - /** - * Yield the current set of globals associated with this addon that should be - * added as debuggees. - */ - _findDebuggees: function (dbg) { - return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee); - } -}; - -BrowserAddonActor.prototype.requestTypes = { - "attach": BrowserAddonActor.prototype.onAttach, - "detach": BrowserAddonActor.prototype.onDetach -}; - /** * The DebuggerProgressListener object is an nsIWebProgressListener which * handles onStateChange events for the inspected browser. If the user tries to diff --git a/toolkit/devtools/server/actors/webconsole.js b/toolkit/devtools/server/actors/webconsole.js index c16345006de9..e6a91bed707c 100644 --- a/toolkit/devtools/server/actors/webconsole.js +++ b/toolkit/devtools/server/actors/webconsole.js @@ -9,7 +9,6 @@ const { Cc, Ci, Cu } = require("chrome"); const { DebuggerServer, ActorPool } = require("devtools/server/main"); const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script"); -const { update } = require("devtools/toolkit/DevToolsUtils"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -1553,91 +1552,6 @@ WebConsoleActor.prototype.requestTypes = exports.WebConsoleActor = WebConsoleActor; - -/** - * The AddonConsoleActor implements capabilities needed for the add-on web - * console feature. - * - * @constructor - * @param object aAddon - * The add-on that this console watches. - * @param object aConnection - * The connection to the client, DebuggerServerConnection. - * @param object aParentActor - * The parent BrowserAddonActor actor. - */ -function AddonConsoleActor(aAddon, aConnection, aParentActor) -{ - this.addon = aAddon; - WebConsoleActor.call(this, aConnection, aParentActor); -} - -AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype); - -update(AddonConsoleActor.prototype, { - constructor: AddonConsoleActor, - - actorPrefix: "addonConsole", - - /** - * The add-on that this console watches. - */ - addon: null, - - /** - * The main add-on JS global - */ - get window() { - return this.parentActor.global; - }, - - /** - * Destroy the current AddonConsoleActor instance. - */ - disconnect: function ACA_disconnect() - { - WebConsoleActor.prototype.disconnect.call(this); - this.addon = null; - }, - - /** - * Handler for the "startListeners" request. - * - * @param object aRequest - * The JSON request object received from the Web Console client. - * @return object - * The response object which holds the startedListeners array. - */ - onStartListeners: function ACA_onStartListeners(aRequest) - { - let startedListeners = []; - - while (aRequest.listeners.length > 0) { - let listener = aRequest.listeners.shift(); - switch (listener) { - case "ConsoleAPI": - if (!this.consoleAPIListener) { - this.consoleAPIListener = - new ConsoleAPIListener(null, this, "addon/" + this.addon.id); - this.consoleAPIListener.init(); - } - startedListeners.push(listener); - break; - } - } - return { - startedListeners: startedListeners, - nativeConsoleAPI: true, - traits: this.traits, - }; - }, -}); - -AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes); -AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners; - -exports.AddonConsoleActor = AddonConsoleActor; - /** * Creates an actor for a network event. * diff --git a/toolkit/devtools/server/moz.build b/toolkit/devtools/server/moz.build index 470a12469d31..36abfcd74330 100644 --- a/toolkit/devtools/server/moz.build +++ b/toolkit/devtools/server/moz.build @@ -34,6 +34,7 @@ EXTRA_JS_MODULES.devtools.server += [ EXTRA_JS_MODULES.devtools.server.actors += [ 'actors/actor-registry.js', + 'actors/addon.js', 'actors/animation.js', 'actors/call-watcher.js', 'actors/canvas.js', From 2266bc08f6d7c604e572380979d6065c0f0fafde Mon Sep 17 00:00:00 2001 From: Richard Marti Date: Thu, 7 May 2015 10:37:39 +0200 Subject: [PATCH 05/42] Bug 1160734 - about:downloads: use the new in-content style-sheet. r=mak CLOSED TREE --HG-- rename : browser/themes/windows/downloads/contentAreaDownloadsView.css => browser/themes/shared/downloads/contentAreaDownloadsView.css extra : rebase_source : 715676562fbf0ec8c98f25d149940221bebc9688 --- .../downloads/contentAreaDownloadsView.css | 11 ---------- browser/themes/linux/jar.mn | 2 +- browser/themes/osx/jar.mn | 2 +- .../downloads/contentAreaDownloadsView.css | 16 ++++++++++---- .../downloads/contentAreaDownloadsView.css | 22 ------------------- browser/themes/windows/jar.mn | 2 +- 6 files changed, 15 insertions(+), 40 deletions(-) delete mode 100644 browser/themes/linux/downloads/contentAreaDownloadsView.css rename browser/themes/{osx => shared}/downloads/contentAreaDownloadsView.css (61%) delete mode 100644 browser/themes/windows/downloads/contentAreaDownloadsView.css diff --git a/browser/themes/linux/downloads/contentAreaDownloadsView.css b/browser/themes/linux/downloads/contentAreaDownloadsView.css deleted file mode 100644 index 56917d7151d2..000000000000 --- a/browser/themes/linux/downloads/contentAreaDownloadsView.css +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -@import url("chrome://global/skin/inContentUI.css"); - -#downloadsListEmptyDescription { - margin: 1em; - text-align: center; - color: GrayText; -} diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index 051692eb97ef..9de7cefaacd9 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -134,7 +134,7 @@ browser.jar: skin/classic/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) - skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) + skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css) skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png) diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 9b7d1a67d4c6..4dd8dd010b05 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -215,7 +215,7 @@ browser.jar: skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/buttons@2x.png (downloads/buttons@2x.png) - skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) + skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css) skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png) skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) diff --git a/browser/themes/osx/downloads/contentAreaDownloadsView.css b/browser/themes/shared/downloads/contentAreaDownloadsView.css similarity index 61% rename from browser/themes/osx/downloads/contentAreaDownloadsView.css rename to browser/themes/shared/downloads/contentAreaDownloadsView.css index ece99eacd395..73e42fba0534 100644 --- a/browser/themes/osx/downloads/contentAreaDownloadsView.css +++ b/browser/themes/shared/downloads/contentAreaDownloadsView.css @@ -2,17 +2,25 @@ * 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/. */ -@import url("chrome://global/skin/inContentUI.css"); +@import url("chrome://global/skin/in-content/common.css"); -.downloadButton { - box-shadow: none; +#contentAreaDownloadsView { + padding: 18px; } +#downloadsRichListBox:not(:-moz-focusring) { + border-color: transparent; +} + +.downloadButton:not([disabled="true"]):hover, .downloadButton:not([disabled="true"]):hover:active, .downloadButton:not([disabled]):hover:active { background: transparent; border: none; - box-shadow: none; +} + +.downloadButton > .button-box { + padding-bottom: 0; } #downloadsListEmptyDescription { diff --git a/browser/themes/windows/downloads/contentAreaDownloadsView.css b/browser/themes/windows/downloads/contentAreaDownloadsView.css deleted file mode 100644 index ece99eacd395..000000000000 --- a/browser/themes/windows/downloads/contentAreaDownloadsView.css +++ /dev/null @@ -1,22 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -@import url("chrome://global/skin/inContentUI.css"); - -.downloadButton { - box-shadow: none; -} - -.downloadButton:not([disabled="true"]):hover:active, -.downloadButton:not([disabled]):hover:active { - background: transparent; - border: none; - box-shadow: none; -} - -#downloadsListEmptyDescription { - margin: 1em; - text-align: center; - color: GrayText; -} diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 8a7186e6e13e..b4918f78f720 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -173,7 +173,7 @@ browser.jar: skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/buttons-XP.png (downloads/buttons-XP.png) - skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) + skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css) skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png) From dbd00abc36e56d390bf635b7eebd5d92367587e8 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Thu, 30 Apr 2015 11:55:36 -0700 Subject: [PATCH 06/42] =?UTF-8?q?Bug=201148021:=20Warn=20users=20when=20in?= =?UTF-8?q?stalling=20unsigned=20add-ons.=20r=3Dd=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --HG-- extra : rebase_source : 8df53efe0b20f14614f01231bceae85753cb9f47 --- browser/base/content/browser-addons.js | 31 +++++- .../content/test/general/browser_bug553455.js | 104 ++++++++++++++++++ .../en-US/chrome/browser/browser.properties | 11 +- 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js index bc9ba6c7580b..6bf3682462fb 100644 --- a/browser/base/content/browser-addons.js +++ b/browser/base/content/browser-addons.js @@ -164,6 +164,9 @@ const gXPInstallObserver = { this._removeProgressNotification(browser); break; } case "addon-install-confirmation": { + let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING); + let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length; + options.eventCallback = (aEvent) => { switch (aEvent) { case "removed": @@ -179,10 +182,21 @@ const gXPInstallObserver = { addonList.firstChild.remove(); for (let install of installInfo.installs) { + let container = document.createElement("hbox"); + let name = document.createElement("label"); name.setAttribute("value", install.addon.name); name.setAttribute("class", "addon-install-confirmation-name"); - addonList.appendChild(name); + container.appendChild(name); + + if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { + let unsigned = document.createElement("label"); + unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned")); + unsigned.setAttribute("class", "addon-install-confirmation-unsigned"); + container.appendChild(unsigned); + } + + addonList.appendChild(container); } this.acceptInstallation = () => { @@ -201,7 +215,20 @@ const gXPInstallObserver = { options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "find-and-install-add-ons"; - messageString = gNavigatorBundle.getString("addonConfirmInstall.message"); + if (unsigned.length == installInfo.installs.length) { + // None of the add-ons are verified + messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message"); + } + else if (unsigned.length == 0) { + // All add-ons are verified or don't need to be verified + messageString = gNavigatorBundle.getString("addonConfirmInstall.message"); + } + else { + // Some of the add-ons are unverified, the list of names will indicate + // which + messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message"); + } + messageString = PluralForm.get(installInfo.installs.length, messageString); messageString = messageString.replace("#1", brandShortName); messageString = messageString.replace("#2", installInfo.installs.length); diff --git a/browser/base/content/test/general/browser_bug553455.js b/browser/base/content/test/general/browser_bug553455.js index ccdf128e0683..4c9008259f70 100644 --- a/browser/base/content/test/general/browser_bug553455.js +++ b/browser/base/content/test/general/browser_bug553455.js @@ -455,6 +455,110 @@ function test_multiple() { gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); }, +function test_someunverified() { + // This test is only relevant if using the new doorhanger UI and allowing + // unsigned add-ons + if (!Preferences.get("xpinstall.customConfirmationUI", false) || + Preferences.get("xpinstall.signatures.required", true)) { + runNextTest(); + return; + } + + // Wait for the progress notification + wait_for_progress_notification(function(aPanel) { + // Wait for the install confirmation dialog + wait_for_install_dialog(function() { + let notification = document.getElementById("addon-install-confirmation-notification"); + let message = notification.getAttribute("label"); + is(message, "Caution: This site would like to install 2 add-ons in " + gApp + ", some of which are unverified. Proceed at your own risk.", + "Should see the right message"); + + let container = document.getElementById("addon-install-confirmation-content"); + is(container.childNodes.length, 2, "Should be two items listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + is(container.childNodes[0].lastChild.getAttribute("class"), + "addon-install-confirmation-unsigned", "Should have the unverified marker"); + is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on"); + is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker"); + + // Wait for the complete notification + wait_for_notification("addon-install-complete", function(aPanel) { + AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org", + "theme-xpi@tests.mozilla.org"], function([a, t]) { + a.uninstall(); + // Installing a new theme tries to switch to it, switch back to the + // default theme. + t.userDisabled = true; + t.uninstall(); + + Services.perms.remove("example.com", "install"); + wait_for_notification_close(runNextTest); + gBrowser.removeTab(gBrowser.selectedTab); + }); + }); + + accept_install_dialog(); + }); + }); + + var pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + var triggers = encodeURIComponent(JSON.stringify({ + "Extension XPI": "restartless.xpi", + "Theme XPI": "theme.xpi" + })); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); +}, + +function test_allunverified() { + // This test is only relevant if using the new doorhanger UI and allowing + // unsigned add-ons + if (!Preferences.get("xpinstall.customConfirmationUI", false) || + Preferences.get("xpinstall.signatures.required", true)) { + runNextTest(); + return; + } + + // Wait for the progress notification + wait_for_progress_notification(function(aPanel) { + // Wait for the install confirmation dialog + wait_for_install_dialog(function() { + let notification = document.getElementById("addon-install-confirmation-notification"); + let message = notification.getAttribute("label"); + is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk."); + + let container = document.getElementById("addon-install-confirmation-content"); + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker"); + + // Wait for the complete notification + wait_for_notification("addon-install-complete", function(aPanel) { + AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) { + aAddon.uninstall(); + + Services.perms.remove("example.com", "install"); + wait_for_notification_close(runNextTest); + gBrowser.removeTab(gBrowser.selectedTab); + }); + }); + + accept_install_dialog(); + }); + }); + + var pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + var triggers = encodeURIComponent(JSON.stringify({ + "Extension XPI": "restartless.xpi" + })); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); +}, + function test_url() { // Wait for the progress notification wait_for_progress_notification(function(aPanel) { diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index f177b7087890..f974182fd02b 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -37,17 +37,26 @@ xpinstallDisabledButton.accesskey=n addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons… addonDownloadVerifying=Verifying +addonInstall.unsigned=(Unverified) addonInstall.cancelButton.label=Cancel addonInstall.cancelButton.accesskey=C addonInstall.acceptButton.label=Install addonInstall.acceptButton.accesskey=I -# LOCALIZATION NOTE (addonConfirmInstallMessage): +# LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned): # Semicolon-separated list of plural forms. See: # http://developer.mozilla.org/en/docs/Localization_and_Plurals # #1 is brandShortName # #2 is the number of add-ons being installed addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1: +addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk. + +# LOCALIZATION NOTE (addonConfirmInstallSomeUnsigned.message): +# Semicolon-separated list of plural forms. See: +# http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is brandShortName +# #2 is the total number of add-ons being installed (at least 2) +addonConfirmInstallSomeUnsigned.message=;Caution: This site would like to install #2 add-ons in #1, some of which are unverified. Proceed at your own risk. addonwatch.slow=%1$S might be making %2$S run slowly addonwatch.disable.label=Disable %S From e6f3be565251aaf091dc02a55a9c8399c8f6d1d5 Mon Sep 17 00:00:00 2001 From: Giorgos Logiotatidis Date: Thu, 7 May 2015 09:42:59 -0700 Subject: [PATCH 07/42] Bug 1155579 - Allow multiple countries per snippet. r=margaret Giorgos Logiotatidis # Parent b3b825c069146b057ff797f5e7a4a1b502d4ccf8 --- mobile/android/components/Snippets.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/android/components/Snippets.js b/mobile/android/components/Snippets.js index 499de354f7d2..a7a0edcebf60 100644 --- a/mobile/android/components/Snippets.js +++ b/mobile/android/components/Snippets.js @@ -173,7 +173,7 @@ var gMessageIds = []; * - text (string): Text to show as banner message * - url (string): URL to open when banner is clicked * - icon (data URI): Icon to appear in banner - * - target_geo (string): Country code for where this message should be shown (e.g. "US") + * - countries (list of strings): Country codes for where this message should be shown (e.g. ["US", "GR"]) */ function updateBanner(messages) { // Remove the current messages, if there are any. @@ -194,9 +194,10 @@ function updateBanner(messages) { messages.forEach(function(message) { // Don't add this message to the banner if it's not supposed to be shown in this country. - if ("target_geo" in message && message.target_geo != gCountryCode) { + if ("countries" in message && message.countries.indexOf(gCountryCode) === -1) { return; } + let id = Home.banner.add({ text: message.text, icon: message.icon, From e974683386622a22a22a415401e8db5a2b8d67e7 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 6 May 2015 11:55:20 +0200 Subject: [PATCH 08/42] Bug 1161928 - Move epoch handling from ContentRestore.jsm to content-sessionStore.js r=billm --- .../sessionstore/ContentRestore.jsm | 30 ++++--------------- .../content/content-sessionStore.js | 25 +++++++++------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/browser/components/sessionstore/ContentRestore.jsm b/browser/components/sessionstore/ContentRestore.jsm index f2cf9cc0a710..75b7b330739a 100644 --- a/browser/components/sessionstore/ContentRestore.jsm +++ b/browser/components/sessionstore/ContentRestore.jsm @@ -36,9 +36,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils", * In a typical restore, content-sessionStore.js will call the following based * on messages and events it receives: * - * restoreHistory(epoch, tabData, callbacks) + * restoreHistory(tabData, loadArguments, callbacks) * Restores the tab's history and session cookies. - * restoreTabContent(finishCallback) + * restoreTabContent(loadArguments, finishCallback) * Starts loading the data for the current page to restore. * restoreDocument() * Restore form and scroll data. @@ -54,11 +54,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils", * At any time, SessionStore.jsm can cancel the ongoing restore by sending a * reset message, which causes resetRestore to be called. At that point it's * legal to begin another restore. - * - * The epoch that is passed into restoreHistory is merely a token. All messages - * sent back to SessionStore.jsm include the epoch. This way, SessionStore.jsm - * can discard messages that relate to restores that it has canceled (by - * starting a new restore, say). */ function ContentRestore(chromeGlobal) { let internal = new ContentRestoreInternal(chromeGlobal); @@ -67,8 +62,7 @@ function ContentRestore(chromeGlobal) { let EXPORTED_METHODS = ["restoreHistory", "restoreTabContent", "restoreDocument", - "resetRestore", - "getRestoreEpoch", + "resetRestore" ]; for (let method of EXPORTED_METHODS) { @@ -84,9 +78,6 @@ function ContentRestoreInternal(chromeGlobal) { // The following fields are only valid during certain phases of the restore // process. - // The epoch that was passed into restoreHistory. Removed in restoreDocument. - this._epoch = 0; - // The tabData for the restore. Set in restoreHistory and removed in // restoreTabContent. this._tabData = null; @@ -123,9 +114,8 @@ ContentRestoreInternal.prototype = { * non-zero) is passed through to all the callbacks. If a load in the tab * is started while it is pending, the appropriate callbacks are called. */ - restoreHistory(epoch, tabData, loadArguments, callbacks) { + restoreHistory(tabData, loadArguments, callbacks) { this._tabData = tabData; - this._epoch = epoch; // In case about:blank isn't done yet. let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation); @@ -290,8 +280,6 @@ ContentRestoreInternal.prototype = { * called when the "load" event fires for the restoring tab. */ restoreDocument: function () { - this._epoch = 0; - if (!this._restoringDocument) { return; } @@ -327,15 +315,7 @@ ContentRestoreInternal.prototype = { this._progressListener.uninstall(); } this._progressListener = null; - }, - - /** - * If a restore is ongoing, this function returns the value of |epoch| that - * was passed to restoreHistory. If no restore is ongoing, it returns 0. - */ - getRestoreEpoch: function () { - return this._epoch; - }, + } }; /* diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index e5f0a16befde..a5625d2b42fb 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -40,6 +40,9 @@ Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this); XPCOMUtils.defineLazyGetter(this, 'gContentRestore', () => { return new ContentRestore(this) }); +// The current epoch. +let gCurrentEpoch = 0; + /** * Returns a lazy function that will evaluate the given * function |fn| only once and cache its return value. @@ -88,14 +91,8 @@ let EventListener = { return; } - // If we're in the process of restoring, this load may signal - // the end of the restoration. - let epoch = gContentRestore.getRestoreEpoch(); - if (!epoch) { - return; - } - - // Restore the form data and scroll position. + // Restore the form data and scroll position. If we're not currently + // restoring a tab state then this call will simply be a noop. gContentRestore.restoreDocument(); } }; @@ -122,6 +119,14 @@ let MessageListener = { return; } + // A fresh tab always starts with epoch=0. The parent has the ability to + // override that to signal a new era in this tab's life. This enables it + // to ignore async messages that were already sent but not yet received + // and would otherwise confuse the internal tab state. + if (data.epoch && data.epoch != gCurrentEpoch) { + gCurrentEpoch = data.epoch; + } + switch (name) { case "SessionStore:restoreHistory": this.restoreHistory(data); @@ -139,7 +144,7 @@ let MessageListener = { }, restoreHistory({epoch, tabData, loadArguments}) { - gContentRestore.restoreHistory(epoch, tabData, loadArguments, { + gContentRestore.restoreHistory(tabData, loadArguments, { onReload() { // Inform SessionStore.jsm about the reload. It will send // restoreTabContent in response. @@ -172,7 +177,7 @@ let MessageListener = { }, restoreTabContent({loadArguments}) { - let epoch = gContentRestore.getRestoreEpoch(); + let epoch = gCurrentEpoch; // We need to pass the value of didStartLoad back to SessionStore.jsm. let didStartLoad = gContentRestore.restoreTabContent(loadArguments, () => { From 21847d3e4e560a737e5720c4139b9f74f9992497 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 6 May 2015 12:03:24 +0200 Subject: [PATCH 09/42] Bug 1161928 - Add assertions to ensure tab restoration methods are used correctly r=billm --- .../components/sessionstore/SessionStore.jsm | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 508293d1f4fb..0f232edfbbba 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -113,6 +113,7 @@ Cu.import("resource://gre/modules/osfile.jsm", this); Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this); Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/debug.js", this); XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup", "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup"); @@ -1613,8 +1614,10 @@ let SessionStoreInternal = { // restore the selected tab and lazily restore the rest. We'll make no // efforts at this time to be smart and restore all of the tabs that had // been in a restored state at the time of the crash. - let tab = aWindow.gBrowser.getTabForBrowser(aBrowser); - this._resetLocalTabRestoringState(tab); + if (aBrowser.__SS_restoreState) { + let tab = aWindow.gBrowser.getTabForBrowser(aBrowser); + this._resetLocalTabRestoringState(tab); + } }, // Clean up data that has been closed a long time ago. @@ -2728,6 +2731,9 @@ let SessionStoreInternal = { // Restores the given tab state for a given tab. restoreTab(tab, tabData, options = {}) { + NS_ASSERT(!tab.linkedBrowser.__SS_restoreState, + "must reset tab before calling restoreTab()"); + let restoreImmediately = options.restoreImmediately; let loadArguments = options.loadArguments; let browser = tab.linkedBrowser; @@ -3590,6 +3596,9 @@ let SessionStoreInternal = { * The tab that will be "reset" */ _resetLocalTabRestoringState: function (aTab) { + NS_ASSERT(aTab.linkedBrowser.__SS_restoreState, + "given tab is not restoring"); + let window = aTab.ownerDocument.defaultView; let browser = aTab.linkedBrowser; @@ -3615,10 +3624,11 @@ let SessionStoreInternal = { }, _resetTabRestoringState: function (tab) { + NS_ASSERT(tab.linkedBrowser.__SS_restoreState, + "given tab is not restoring"); + let browser = tab.linkedBrowser; - if (browser.__SS_restoreState) { - browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {}); - } + browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {}); this._resetLocalTabRestoringState(tab); }, From 0fadc88345aa2d1cd172a87c035b1dc16a9fe6d9 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 6 May 2015 15:06:29 +0200 Subject: [PATCH 10/42] Bug 1161928 - Require an epoch (managed in the parent) included in every message sent by the frame script to get rid of TabState.flush() calls in restoreTab() r=billm --- .../components/sessionstore/SessionStore.jsm | 173 ++++++++++-------- .../content/content-sessionStore.js | 3 +- 2 files changed, 101 insertions(+), 75 deletions(-) diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 0f232edfbbba..7656c0699718 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -88,6 +88,16 @@ const NOTAB_MESSAGES = new Set([ "SessionStore:update", ]); +// The list of messages we accept without an "epoch" parameter. +// See getCurrentEpoch() and friends to find out what an "epoch" is. +const NOEPOCH_MESSAGES = new Set([ + // For a description see above. + "SessionStore:setupSyncHandler", + + // For a description see above. + "SessionStore:crashedTabRevived", +]); + // The list of messages we want to receive even during the short period after a // frame has been removed from the DOM and before its frame script has finished // unloading. @@ -330,10 +340,7 @@ let SessionStoreInternal = { // windows yet to be restored _restoreCount: -1, - // This number gets incremented each time we start to restore a tab. - _nextRestoreEpoch: 1, - - // For each element being restored, records the current epoch. + // For each element, records the current epoch. _browserEpochs: new WeakMap(), // Any browsers that fires the oop-browser-crashed event gets stored in @@ -616,6 +623,19 @@ let SessionStoreInternal = { `from a browser that has no tab`); } + let data = aMessage.data || {}; + let hasEpoch = data.hasOwnProperty("epoch"); + + // Most messages sent by frame scripts require to pass an epoch. + if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) { + throw new Error(`received message '${aMessage.name}' without an epoch`); + } + + // Ignore messages from previous epochs. + if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) { + return; + } + switch (aMessage.name) { case "SessionStore:setupSyncHandler": TabState.setSyncHandler(browser, aMessage.objects.handler); @@ -681,77 +701,69 @@ let SessionStoreInternal = { } break; case "SessionStore:restoreHistoryComplete": - if (this.isCurrentEpoch(browser, aMessage.data.epoch)) { - // Notify the tabbrowser that the tab chrome has been restored. - let tabData = browser.__SS_data; + // Notify the tabbrowser that the tab chrome has been restored. + let tabData = browser.__SS_data; - // wall-paper fix for bug 439675: make sure that the URL to be loaded - // is always visible in the address bar - let activePageData = tabData.entries[tabData.index - 1] || null; - let uri = activePageData ? activePageData.url || null : null; - browser.userTypedValue = uri; + // wall-paper fix for bug 439675: make sure that the URL to be loaded + // is always visible in the address bar + let activePageData = tabData.entries[tabData.index - 1] || null; + let uri = activePageData ? activePageData.url || null : null; + browser.userTypedValue = uri; - // If the page has a title, set it. - if (activePageData) { - if (activePageData.title) { - tab.label = activePageData.title; - tab.crop = "end"; - } else if (activePageData.url != "about:blank") { - tab.label = activePageData.url; - tab.crop = "center"; - } + // If the page has a title, set it. + if (activePageData) { + if (activePageData.title) { + tab.label = activePageData.title; + tab.crop = "end"; + } else if (activePageData.url != "about:blank") { + tab.label = activePageData.url; + tab.crop = "center"; } - - // Restore the tab icon. - if ("image" in tabData) { - win.gBrowser.setIcon(tab, tabData.image); - } - - let event = win.document.createEvent("Events"); - event.initEvent("SSTabRestoring", true, false); - tab.dispatchEvent(event); } + + // Restore the tab icon. + if ("image" in tabData) { + win.gBrowser.setIcon(tab, tabData.image); + } + + let event = win.document.createEvent("Events"); + event.initEvent("SSTabRestoring", true, false); + tab.dispatchEvent(event); break; case "SessionStore:restoreTabContentStarted": - if (this.isCurrentEpoch(browser, aMessage.data.epoch)) { - if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) { - // If a load not initiated by sessionstore was started in a - // previously pending tab. Mark the tab as no longer pending. - this.markTabAsRestoring(tab); - } else { - // If the user was typing into the URL bar when we crashed, but hadn't hit - // enter yet, then we just need to write that value to the URL bar without - // loading anything. This must happen after the load, since it will clear - // userTypedValue. - let tabData = browser.__SS_data; - if (tabData.userTypedValue && !tabData.userTypedClear) { - browser.userTypedValue = tabData.userTypedValue; - win.URLBarSetURI(); - } + if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) { + // If a load not initiated by sessionstore was started in a + // previously pending tab. Mark the tab as no longer pending. + this.markTabAsRestoring(tab); + } else { + // If the user was typing into the URL bar when we crashed, but hadn't hit + // enter yet, then we just need to write that value to the URL bar without + // loading anything. This must happen after the load, since it will clear + // userTypedValue. + let tabData = browser.__SS_data; + if (tabData.userTypedValue && !tabData.userTypedClear) { + browser.userTypedValue = tabData.userTypedValue; + win.URLBarSetURI(); } } break; case "SessionStore:restoreTabContentComplete": - if (this.isCurrentEpoch(browser, aMessage.data.epoch)) { - // This callback is used exclusively by tests that want to - // monitor the progress of network loads. - if (gDebuggingEnabled) { - Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null); - } - - delete browser.__SS_data; - - SessionStoreInternal._resetLocalTabRestoringState(tab); - SessionStoreInternal.restoreNextTab(); - - this._sendTabRestoredNotification(tab); + // This callback is used exclusively by tests that want to + // monitor the progress of network loads. + if (gDebuggingEnabled) { + Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null); } + + delete browser.__SS_data; + + SessionStoreInternal._resetLocalTabRestoringState(tab); + SessionStoreInternal.restoreNextTab(); + + this._sendTabRestoredNotification(tab); break; case "SessionStore:reloadPendingTab": - if (this.isCurrentEpoch(browser, aMessage.data.epoch)) { - if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) { - this.restoreTabContent(tab); - } + if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) { + this.restoreTabContent(tab); } break; case "SessionStore:crashedTabRevived": @@ -2794,11 +2806,6 @@ let SessionStoreInternal = { // Tab is now open. delete tabData.closedAt; - // Flush all data from the content script synchronously. This is done so - // that all async messages that are still on their way to chrome will - // be ignored and don't override any tab data set when restoring. - TabState.flush(browser); - // Ensure the index is in bounds. let activeIndex = (tabData.index || tabData.entries.length) - 1; activeIndex = Math.min(activeIndex, tabData.entries.length - 1); @@ -2816,11 +2823,10 @@ let SessionStoreInternal = { } tabbrowser.updateBrowserRemotenessByURL(browser, uri); - // Start a new epoch and include the epoch in the restoreHistory - // message. If a message is received that relates to a previous epoch, we - // discard it. - let epoch = this._nextRestoreEpoch++; - this._browserEpochs.set(browser.permanentKey, epoch); + // Start a new epoch to discard all frame script messages relating to a + // previous epoch. All async messages that are still on their way to chrome + // will be ignored and don't override any tab data set when restoring. + let epoch = this.startNextEpoch(browser); // keep the data around to prevent dataloss in case // a tab gets closed before it's been properly restored @@ -3607,7 +3613,6 @@ let SessionStoreInternal = { // The browser is no longer in any sort of restoring state. delete browser.__SS_restoreState; - this._browserEpochs.delete(browser.permanentKey); aTab.removeAttribute("pending"); browser.removeAttribute("pending"); @@ -3632,6 +3637,26 @@ let SessionStoreInternal = { this._resetLocalTabRestoringState(tab); }, + /** + * Each fresh tab starts out with epoch=0. This function can be used to + * start a next epoch by incrementing the current value. It will enables us + * to ignore stale messages sent from previous epochs. The function returns + * the new epoch ID for the given |browser|. + */ + startNextEpoch(browser) { + let next = this.getCurrentEpoch(browser) + 1; + this._browserEpochs.set(browser.permanentKey, next); + return next; + }, + + /** + * Returns the current epoch for the given . If we haven't assigned + * a new epoch this will default to zero for new tabs. + */ + getCurrentEpoch(browser) { + return this._browserEpochs.get(browser.permanentKey) || 0; + }, + /** * Each time a element is restored, we increment its "epoch". To * check if a message from content-sessionStore.js is out of date, we can @@ -3640,7 +3665,7 @@ let SessionStoreInternal = { * with respect to |browser|. */ isCurrentEpoch: function (browser, epoch) { - return (this._browserEpochs.get(browser.permanentKey) || 0) == epoch; + return this.getCurrentEpoch(browser) == epoch; }, }; diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index a5625d2b42fb..671c486adb9e 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -703,7 +703,8 @@ let MessageQueue = { // Send all data to the parent process. sendMessage("SessionStore:update", { id: this._id, data, telemetry, - isFinal: options.isFinal || false + isFinal: options.isFinal || false, + epoch: gCurrentEpoch }); // Increase our unique message ID. From c0a4cb4b07a10cf2b29d19bf0acef62794a3d95f Mon Sep 17 00:00:00 2001 From: Wes Kocher Date: Thu, 7 May 2015 10:13:59 -0700 Subject: [PATCH 11/42] Backed out changeset 7ec5c7a4830f (bug 896139) for adding a test that times out --- browser/devtools/debugger/test/browser.ini | 4 --- .../debugger/test/browser_dbg_bug-896139.js | 33 ------------------- .../devtools/debugger/test/code_bug-896139.js | 8 ----- .../debugger/test/doc_bug-896139.html | 18 ---------- 4 files changed, 63 deletions(-) delete mode 100644 browser/devtools/debugger/test/browser_dbg_bug-896139.js delete mode 100644 browser/devtools/debugger/test/code_bug-896139.js delete mode 100644 browser/devtools/debugger/test/doc_bug-896139.html diff --git a/browser/devtools/debugger/test/browser.ini b/browser/devtools/debugger/test/browser.ini index 0f1662156dc7..4c61fbdaa438 100644 --- a/browser/devtools/debugger/test/browser.ini +++ b/browser/devtools/debugger/test/browser.ini @@ -16,7 +16,6 @@ support-files = code_blackboxing_two.js code_breakpoints-break-on-last-line-of-script-on-reload.js code_breakpoints-other-tabs.js - code_bug-896139.js code_frame-script.js code_function-search-01.js code_function-search-02.js @@ -48,7 +47,6 @@ support-files = doc_breakpoints-break-on-last-line-of-script-on-reload.html doc_breakpoints-other-tabs.html doc_breakpoints-reload.html - doc_bug-896139.html doc_closures.html doc_closure-optimized-out.html doc_cmd-break.html @@ -166,8 +164,6 @@ skip-if = e10s && debug skip-if = e10s && debug [browser_dbg_breakpoints-reload.js] skip-if = e10s && debug -[browser_dbg_bug-896139.js] -skip-if = e10s && debug [browser_dbg_chrome-create.js] skip-if = e10s && debug [browser_dbg_chrome-debugging.js] diff --git a/browser/devtools/debugger/test/browser_dbg_bug-896139.js b/browser/devtools/debugger/test/browser_dbg_bug-896139.js deleted file mode 100644 index bb21158a86e6..000000000000 --- a/browser/devtools/debugger/test/browser_dbg_bug-896139.js +++ /dev/null @@ -1,33 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/** - * Bug 896139 - Breakpoints not triggering when reloading script. - */ - -const TAB_URL = "doc_bug-896139.html"; -const SCRIPT_URL = "code_bug-896139.js"; - -function test() { - Task.spawn(function* () { - function testBreakpoint() { - let promise = waitForDebuggerEvents(panel, win.EVENTS.FETCHED_SCOPES); - callInTab(tab, "f"); - return promise.then(() => doResume(panel)); - } - - let [tab,, panel] = yield initDebugger(EXAMPLE_URL + TAB_URL); - let win = panel.panelWin; - yield waitForSourceShown(panel, SCRIPT_URL); - yield panel.addBreakpoint({ - actor: getSourceActor(win.DebuggerView.Sources, EXAMPLE_URL + SCRIPT_URL), - line: 3 - }); - - yield testBreakpoint(); - yield reloadActiveTab(panel, win.EVENTS.SOURCE_SHOWN); - yield testBreakpoint(); - - yield closeDebuggerAndFinish(panel); - }); -} diff --git a/browser/devtools/debugger/test/code_bug-896139.js b/browser/devtools/debugger/test/code_bug-896139.js deleted file mode 100644 index afa4159571b3..000000000000 --- a/browser/devtools/debugger/test/code_bug-896139.js +++ /dev/null @@ -1,8 +0,0 @@ - - -function f() { - var a = 1; - var b = 2; - var c = 3; -} diff --git a/browser/devtools/debugger/test/doc_bug-896139.html b/browser/devtools/debugger/test/doc_bug-896139.html deleted file mode 100644 index 166ad604f619..000000000000 --- a/browser/devtools/debugger/test/doc_bug-896139.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - From 33c3c7354c4fa61e1979733b4079d3f6ca34c3c1 Mon Sep 17 00:00:00 2001 From: Josios Date: Thu, 7 May 2015 10:34:38 -0700 Subject: [PATCH 12/42] Bug 1161640 - |mar_read_entire_file| can leak file descriptor (command line build utility). Close descriptor when fseeko fails. r=rstrong --- modules/libmar/verify/mar_verify.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/libmar/verify/mar_verify.c b/modules/libmar/verify/mar_verify.c index 165a802189f0..07e4354ce1ab 100644 --- a/modules/libmar/verify/mar_verify.c +++ b/modules/libmar/verify/mar_verify.c @@ -51,9 +51,10 @@ mar_read_entire_file(const char * filePath, uint32_t maxSize, } } } - fclose(f); } + fclose(f); + return result; } From d7907499a52128586bf96042b2ce0f384a0354ff Mon Sep 17 00:00:00 2001 From: Josios Date: Thu, 7 May 2015 10:34:51 -0700 Subject: [PATCH 13/42] Bug 1161666 - |extract_signature| leaks memory if |sigIndex| > 0 (command line build utility). Free allocated memory while skipping signatures. r=rstrong --- modules/libmar/sign/mar_sign.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/libmar/sign/mar_sign.c b/modules/libmar/sign/mar_sign.c index 6f21a31977e5..2a08abfa0671 100644 --- a/modules/libmar/sign/mar_sign.c +++ b/modules/libmar/sign/mar_sign.c @@ -533,6 +533,9 @@ extract_signature(const char *src, uint32_t sigIndex, const char * dest) /* Skip to the correct signature */ for (i = 0; i <= sigIndex; i++) { + /* Avoid leaking while skipping signatures */ + free(extractedSignature); + /* skip past the signature algorithm ID */ if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n"); From 7221302f38f34fc4b9032cddf0a96c9dc71c5b21 Mon Sep 17 00:00:00 2001 From: Robert Strong Date: Thu, 7 May 2015 10:35:00 -0700 Subject: [PATCH 14/42] Bug 1161661 - Provide progress and state feedback via the stub installer taskbar icon. r=bbondy --- browser/installer/windows/nsis/installer.nsi | 5 +- browser/installer/windows/nsis/stub.nsi | 59 +++- .../installer/windows/nsis/uninstaller.nsi | 4 +- .../mozapps/installer/windows/nsis/common.nsh | 267 +++++++++++++----- 4 files changed, 248 insertions(+), 87 deletions(-) diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi index a240bd0aa4ee..ff40bb876f70 100755 --- a/browser/installer/windows/nsis/installer.nsi +++ b/browser/installer/windows/nsis/installer.nsi @@ -254,10 +254,7 @@ Section "-InstallStartCleanup" ${CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates" ${RemoveDeprecatedFiles} - - StrCpy $R2 "false" - StrCpy $R3 "false" - ${RemovePrecompleteEntries} "$R2" "$R3" + ${RemovePrecompleteEntries} "false" ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js" Delete "$INSTDIR\defaults\pref\channel-prefs.js" diff --git a/browser/installer/windows/nsis/stub.nsi b/browser/installer/windows/nsis/stub.nsi index 9b001c029c11..c4f7caf2a71f 100644 --- a/browser/installer/windows/nsis/stub.nsi +++ b/browser/installer/windows/nsis/stub.nsi @@ -71,6 +71,8 @@ Var CanSetAsDefault Var InstallCounterStep Var InstallStepSize Var InstallTotalSteps +Var ProgressCompleted +Var ProgressTotal Var TmpVal Var ExitCode @@ -258,6 +260,7 @@ Var ControlRightPX !insertmacro IsUserAdmin !insertmacro RemovePrecompleteEntries !insertmacro SetBrandNameVars +!insertmacro ITBL3Create !insertmacro UnloadUAC VIAddVersionKey "FileDescription" "${BrandShortName} Stub Installer" @@ -1293,6 +1296,9 @@ Function createInstall StrCpy $InstallTotalSteps ${InstallCleanTotalSteps} ${EndIf} + ${ITBL3Create} + ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}" + ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS} LockWindow off @@ -1315,6 +1321,21 @@ Function StartDownload ${EndIf} FunctionEnd +Function SetProgressBars + SendMessage $Progressbar ${PBM_SETPOS} $ProgressCompleted 0 + ${ITBL3SetProgressValue} "$ProgressCompleted" "$ProgressTotal" +FunctionEnd + +Function RemoveFileProgressCallback + IntOp $InstallCounterStep $InstallCounterStep + 2 + System::Int64Op $ProgressCompleted + $InstallStepSize + Pop $ProgressCompleted + Call SetProgressBars + System::Int64Op $ProgressCompleted + $InstallStepSize + Pop $ProgressCompleted + Call SetProgressBars +FunctionEnd + Function OnDownload InetBgDL::GetStats # $0 = HTTP status code, 0=Completed @@ -1333,6 +1354,7 @@ Function OnDownload ${NSD_AddStyle} $Progressbar ${PBS_MARQUEE} SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \ $ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N + ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}" ${EndIf} InetBgDL::Get /RESET /END StrCpy $DownloadSizeBytes "" @@ -1390,8 +1412,9 @@ Function OnDownload SendMessage $Progressbar ${PBM_SETMARQUEE} 0 0 ; start=1|stop=0 interval(ms)=+N ${RemoveStyle} $Progressbar ${PBS_MARQUEE} System::Int64Op $HalfOfDownload + $DownloadSizeBytes - Pop $R9 - SendMessage $Progressbar ${PBM_SETRANGE32} 0 $R9 + Pop $ProgressTotal + StrCpy $ProgressCompleted 0 + SendMessage $Progressbar ${PBM_SETRANGE32} $ProgressCompleted $ProgressTotal ${EndIf} ; Don't update the status until after the download starts @@ -1448,12 +1471,13 @@ Function OnDownload LockWindow on ; Update the progress bars first in the UI change so they take affect ; before other UI changes. - SendMessage $Progressbar ${PBM_SETPOS} $DownloadSizeBytes 0 + StrCpy $ProgressCompleted "$DownloadSizeBytes" + Call SetProgressBars System::Int64Op $InstallStepSize * ${InstallProgressFirstStep} Pop $R9 - SendMessage $Progressbar ${PBM_SETSTEP} $R9 0 - SendMessage $Progressbar ${PBM_STEPIT} 0 0 - SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0 + System::Int64Op $ProgressCompleted + $R9 + Pop $ProgressCompleted + Call SetProgressBars ShowWindow $LabelDownloading ${SW_HIDE} ShowWindow $LabelInstalling ${SW_SHOW} ShowWindow $LabelBlurb2 ${SW_HIDE} @@ -1540,7 +1564,8 @@ Function OnDownload WriteIniStr "$0" "TASKBAR" "Migrated" "true" ${EndIf} - ${RemovePrecompleteEntries} $Progressbar $InstallCounterStep + GetFunctionAddress $0 RemoveFileProgressCallback + ${RemovePrecompleteEntries} $0 ; Delete the install.log and let the full installer create it. When the ; installer closes it we can detect that it has completed. @@ -1569,7 +1594,8 @@ Function OnDownload LockWindow off ${EndIf} StrCpy $DownloadedBytes "$3" - SendMessage $Progressbar ${PBM_SETPOS} $3 0 + StrCpy $ProgressCompleted "$DownloadedBytes" + Call SetProgressBars ${EndIf} ${EndIf} FunctionEnd @@ -1608,7 +1634,9 @@ Function CheckInstall Return ${EndIf} - SendMessage $Progressbar ${PBM_STEPIT} 0 0 + System::Int64Op $ProgressCompleted + $InstallStepSize + Pop $ProgressCompleted + Call SetProgressBars ${If} ${FileExists} "$INSTDIR\install.log" Delete "$INSTDIR\install.tmp" @@ -1632,7 +1660,6 @@ Function CheckInstall Pop $EndInstallPhaseTickCount System::Int64Op $InstallStepSize * ${InstallProgressFinishStep} Pop $InstallStepSize - SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0 ${NSD_CreateTimer} FinishInstall ${InstallIntervalMS} ${EndUnless} ${EndIf} @@ -1647,14 +1674,16 @@ Function FinishInstall ${EndIf} ${If} $InstallTotalSteps != $InstallCounterStep - SendMessage $Progressbar ${PBM_STEPIT} 0 0 + System::Int64Op $ProgressCompleted + $InstallStepSize + Pop $ProgressCompleted + Call SetProgressBars Return ${EndIf} ${NSD_KillTimer} FinishInstall - SendMessage $Progressbar ${PBM_GETRANGE} 0 0 $R9 - SendMessage $Progressbar ${PBM_SETPOS} $R9 0 + StrCpy $ProgressCompleted "$ProgressTotal" + Call SetProgressBars ${If} "$CheckboxSetAsDefault" == "1" ${GetParameters} $0 @@ -1935,6 +1964,10 @@ FunctionEnd Function DisplayDownloadError ${NSD_KillTimer} DisplayDownloadError + ; To better display the error state on the taskbar set the progress completed + ; value to the total value. + ${ITBL3SetProgressValue} "100" "100" + ${ITBL3SetProgressState} "${TBPF_ERROR}" MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK +1 StrCpy $OpenedDownloadPage "1" ; Already initialized to 0 diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi index 0e32726570d2..88938a445d16 100755 --- a/browser/installer/windows/nsis/uninstaller.nsi +++ b/browser/installer/windows/nsis/uninstaller.nsi @@ -397,9 +397,7 @@ Section "Uninstall" ${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll" ${EndIf} - StrCpy $R2 "false" - StrCpy $R3 "false" - ${un.RemovePrecompleteEntries} "$R2" "$R3" + ${un.RemovePrecompleteEntries} "false" ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js" Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js" diff --git a/toolkit/mozapps/installer/windows/nsis/common.nsh b/toolkit/mozapps/installer/windows/nsis/common.nsh index 9179f2523a57..e04e89755ac0 100755 --- a/toolkit/mozapps/installer/windows/nsis/common.nsh +++ b/toolkit/mozapps/installer/windows/nsis/common.nsh @@ -1641,8 +1641,8 @@ !macroend -!define RegisterDLL `!insertmacro RegisterDLL` -!define UnregisterDLL `!insertmacro UnregisterDLL` +!define RegisterDLL "!insertmacro RegisterDLL" +!define UnregisterDLL "!insertmacro UnregisterDLL" ################################################################################ @@ -4332,24 +4332,21 @@ !macroend /** - * Parses the precomplete file to remove an installation's files and directories. + * Parses the precomplete file to remove an installation's files and + * directories. * - * @param _PROGRESSBAR - * The progress bar to update using PBM_STEPIT. Can also be "false" if - * updating a progressbar isn't needed. - * @param _INSTALL_STEP_COUNTER - * The install step counter to increment. The variable specified in - * this parameter is also updated. Can also be "false" if a counter - * isn't needed. - * $R2 = false if all files were deleted or moved to the tobedeleted directory. + * @param _CALLBACK + * The function address of a callback function for progress or "false" + * if there is no callback function. + * + * $R3 = false if all files were deleted or moved to the tobedeleted directory. * true if file(s) could not be moved to the tobedeleted directory. - * $R3 = Path to temporary precomplete file. - * $R4 = File handle for the temporary precomplete file. - * $R5 = String returned from FileRead. - * $R6 = First seven characters of the string returned from FileRead. - * $R7 = Temporary file path used to rename files that are in use. - * $R8 = _PROGRESSBAR - * $R9 = _INSTALL_STEP_COUNTER + * $R4 = Path to temporary precomplete file. + * $R5 = File handle for the temporary precomplete file. + * $R6 = String returned from FileRead. + * $R7 = First seven characters of the string returned from FileRead. + * $R8 = Temporary file path used to rename files that are in use. + * $R9 = _CALLBACK */ !macro RemovePrecompleteEntries @@ -4368,95 +4365,89 @@ Function ${_MOZFUNC_UN}RemovePrecompleteEntries Exch $R9 - Exch 1 - Exch $R8 + Push $R8 Push $R7 Push $R6 Push $R5 Push $R4 Push $R3 - Push $R2 ${If} ${FileExists} "$INSTDIR\precomplete" - StrCpy $R2 "false" + StrCpy $R3 "false" RmDir /r "$INSTDIR\${TO_BE_DELETED}" CreateDirectory "$INSTDIR\${TO_BE_DELETED}" - GetTempFileName $R3 "$INSTDIR\${TO_BE_DELETED}" - Delete "$R3" - Rename "$INSTDIR\precomplete" "$R3" + GetTempFileName $R4 "$INSTDIR\${TO_BE_DELETED}" + Delete "$R4" + Rename "$INSTDIR\precomplete" "$R4" ClearErrors ; Rename and then remove files - FileOpen $R4 "$R3" r + FileOpen $R5 "$R4" r ${Do} - FileRead $R4 $R5 + FileRead $R5 $R6 ${If} ${Errors} ${Break} ${EndIf} - ${${_MOZFUNC_UN}TrimNewLines} "$R5" $R5 + ${${_MOZFUNC_UN}TrimNewLines} "$R6" $R6 ; Replace all occurrences of "/" with "\". - ${${_MOZFUNC_UN}WordReplace} "$R5" "/" "\" "+" $R5 + ${${_MOZFUNC_UN}WordReplace} "$R6" "/" "\" "+" $R6 ; Copy the first 7 chars - StrCpy $R6 "$R5" 7 - ${If} "$R6" == "remove " + StrCpy $R7 "$R6" 7 + ${If} "$R7" == "remove " ; Copy the string starting after the 8th char - StrCpy $R5 "$R5" "" 8 + StrCpy $R6 "$R6" "" 8 ; Copy all but the last char to remove the double quote. - StrCpy $R5 "$R5" -1 - ${If} ${FileExists} "$INSTDIR\$R5" + StrCpy $R6 "$R6" -1 + ${If} ${FileExists} "$INSTDIR\$R6" ${Unless} "$R9" == "false" - IntOp $R9 $R9 + 2 - ${EndUnless} - ${Unless} "$R8" == "false" - SendMessage $R8 ${PBM_STEPIT} 0 0 - SendMessage $R8 ${PBM_STEPIT} 0 0 + Call $R9 ${EndUnless} ClearErrors - Delete "$INSTDIR\$R5" + Delete "$INSTDIR\$R6" ${If} ${Errors} - GetTempFileName $R7 "$INSTDIR\${TO_BE_DELETED}" - Delete "$R7" + GetTempFileName $R8 "$INSTDIR\${TO_BE_DELETED}" + Delete "$R8" ClearErrors - Rename "$INSTDIR\$R5" "$R7" + Rename "$INSTDIR\$R6" "$R8" ${Unless} ${Errors} - Delete /REBOOTOK "$R7" + Delete /REBOOTOK "$R8" ClearErrors ${EndUnless} !ifdef __UNINSTALL__ ${If} ${Errors} - Delete /REBOOTOK "$INSTDIR\$R5" - StrCpy $R2 "true" + Delete /REBOOTOK "$INSTDIR\$R6" + StrCpy $R3 "true" ClearErrors ${EndIf} !endif ${EndIf} ${EndIf} - ${ElseIf} "$R6" == "rmdir $\"" + ${ElseIf} "$R7" == "rmdir $\"" ; Copy the string starting after the 7th char. - StrCpy $R5 "$R5" "" 7 + StrCpy $R6 "$R6" "" 7 ; Copy all but the last two chars to remove the slash and the double quote. - StrCpy $R5 "$R5" -2 - ${If} ${FileExists} "$INSTDIR\$R5" + StrCpy $R6 "$R6" -2 + ${If} ${FileExists} "$INSTDIR\$R6" ; Ignore directory removal errors - RmDir "$INSTDIR\$R5" + RmDir "$INSTDIR\$R6" ClearErrors ${EndIf} ${EndIf} ${Loop} - FileClose $R4 + FileClose $R5 ; Delete the temporary precomplete file - Delete /REBOOTOK "$R3" + Delete /REBOOTOK "$R4" RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}" ${If} ${RebootFlag} - ${AndIf} "$R2" == "false" + ${AndIf} "$R3" == "false" ; Clear the reboot flag if all files were deleted or moved to the ; tobedeleted directory. SetRebootFlag false @@ -4465,14 +4456,12 @@ ClearErrors - Pop $R2 Pop $R3 Pop $R4 Pop $R5 Pop $R6 Pop $R7 - Exch $R8 - Exch 1 + Pop $R8 Exch $R9 FunctionEnd @@ -4480,24 +4469,19 @@ !endif !macroend -!macro RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER +!macro RemovePrecompleteEntriesCall _CALLBACK !verbose push - Push "${_PROGRESSBAR}" - Push "${_INSTALL_STEP_COUNTER}" + Push "${_CALLBACK}" !verbose ${_MOZFUNC_VERBOSE} Call RemovePrecompleteEntries - Pop ${_INSTALL_STEP_COUNTER} !verbose pop !macroend -!macro un.RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER +!macro un.RemovePrecompleteEntriesCall _CALLBACK !verbose push !verbose ${_MOZFUNC_VERBOSE} - Push "${_PROGRESSBAR}" - Push "${_INSTALL_STEP_COUNTER}" + Push "${_CALLBACK}" Call un.RemovePrecompleteEntries - Pop ${_INSTALL_STEP_COUNTER} - Pop $0 !verbose pop !macroend @@ -7328,6 +7312,155 @@ !endif !macroend +################################################################################ +# Helpers for taskbar progress + +!ifndef CLSCTX_INPROC_SERVER + !define CLSCTX_INPROC_SERVER 1 +!endif + +!define CLSID_ITaskbarList {56fdf344-fd6d-11d0-958a-006097c9a090} +!define IID_ITaskbarList3 {ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf} +!define ITaskbarList3->SetProgressValue $ITaskbarList3->9 +!define ITaskbarList3->SetProgressState $ITaskbarList3->10 + +/** + * Creates a single uninitialized object of the ITaskbarList class with a + * reference to the ITaskbarList3 interface. This object can be used to set + * progress and state on the installer's taskbar icon using the helper macros + * in this section. + */ +!macro ITBL3Create + + !ifndef ${_MOZFUNC_UN}ITBL3Create + Var ITaskbarList3 + + !verbose push + !verbose ${_MOZFUNC_VERBOSE} + !define ${_MOZFUNC_UN}ITBL3Create "!insertmacro ${_MOZFUNC_UN}ITBL3CreateCall" + + Function ${_MOZFUNC_UN}ITBL3Create + ; Setting to 0 allows the helper macros to detect when the object was not + ; created. + StrCpy $ITaskbarList3 0 + ; Don't create when running silently. + ${Unless} ${Silent} + ; This is only supported on Win 7 and above. + ${If} ${AtLeastWin7} + System::Call "ole32::CoCreateInstance(g '${CLSID_ITaskbarList}', \ + i 0, \ + i ${CLSCTX_INPROC_SERVER}, \ + g '${IID_ITaskbarList3}', \ + *i .s)" + Pop $ITaskbarList3 + ${EndIf} + ${EndUnless} + FunctionEnd + + !verbose pop + !endif +!macroend + +!macro ITBL3CreateCall + !verbose push + !verbose ${_MOZFUNC_VERBOSE} + Call ITBL3Create + !verbose pop +!macroend + +!macro un.ITBL3CreateCall _PATH_TO_IMAGE + !verbose push + !verbose ${_MOZFUNC_VERBOSE} + Call un.ITBL3Create + !verbose pop +!macroend + +!macro un.ITBL3Create + !ifndef un.ITBL3Create + !verbose push + !verbose ${_MOZFUNC_VERBOSE} + !undef _MOZFUNC_UN + !define _MOZFUNC_UN "un." + + !insertmacro ITBL3Create + + !undef _MOZFUNC_UN + !define _MOZFUNC_UN + !verbose pop + !endif +!macroend + +/** + * Sets the percentage completed on the taskbar process icon progress indicator. + * + * @param _COMPLETED + * The proportion of the operation that has been completed in relation + * to _TOTAL. + * @param _TOTAL + * The value _COMPLETED will have when the operation has completed. + * + * $R8 = _COMPLETED + * $R9 = _TOTAL + */ +!macro ITBL3SetProgressValueCall _COMPLETED _TOTAL + Push ${_COMPLETED} + Push ${_TOTAL} + ${CallArtificialFunction} ITBL3SetProgressValue_ +!macroend + +!define ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall" +!define un.ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall" + +!macro ITBL3SetProgressValue_ + Exch $R9 + Exch 1 + Exch $R8 + ${If} ${AtLeastWin7} + ${AndIf} $ITaskbarList3 <> 0 + System::Call "${ITaskbarList3->SetProgressValue}(i$HWNDPARENT, l$R8, l$R9)" + ${EndIf} + Exch $R8 + Exch 1 + Exch $R9 +!macroend + +; Normal state / no progress bar +!define TBPF_NOPROGRESS 0x00000000 +; Marquee style progress bar +!define TBPF_INDETERMINATE 0x00000001 +; Standard progress bar +!define TBPF_NORMAL 0x00000002 +; Red taskbar button to indicate an error occurred +!define TBPF_ERROR 0x00000004 +; Yellow taskbar button to indicate user attention (input) is required to +; resume progress +!define TBPF_PAUSED 0x00000008 + +/** + * Sets the state on the taskbar process icon progress indicator. + * + * @param _STATE + * The state to set on the taskbar icon progress indicator. Only one of + * the states defined above should be specified. + * + * $R9 = _STATE + */ +!macro ITBL3SetProgressStateCall _STATE + Push ${_STATE} + ${CallArtificialFunction} ITBL3SetProgressState_ +!macroend + +!define ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall" +!define un.ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall" + +!macro ITBL3SetProgressState_ + Exch $R9 + ${If} ${AtLeastWin7} + ${AndIf} $ITaskbarList3 <> 0 + System::Call "${ITaskbarList3->SetProgressState}(i$HWNDPARENT, i$R9)" + ${EndIf} + Exch $R9 +!macroend ################################################################################ # Helpers for the new user interface @@ -7430,7 +7563,7 @@ Exch $0 Pop ${HANDLE} !macroend -!define SetStretchedTransparentImage `!insertmacro __SetStretchedTransparentImage` +!define SetStretchedTransparentImage "!insertmacro __SetStretchedTransparentImage" /** * Removes a single style from a control. From 603b72ce41a0c0eab80401b6ed00a948dca266e4 Mon Sep 17 00:00:00 2001 From: Robert Strong Date: Thu, 7 May 2015 10:35:09 -0700 Subject: [PATCH 15/42] Bug 1162331 - refreshUpdateStatus incorrectly checks if the service should be used. r=spohl --- toolkit/mozapps/update/nsUpdateService.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js index 3f579ab57787..22ecfd720ee5 100644 --- a/toolkit/mozapps/update/nsUpdateService.js +++ b/toolkit/mozapps/update/nsUpdateService.js @@ -497,7 +497,7 @@ function hasUpdateMutex() { XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() { let useService = false; - if (shouldUseService() && isServiceInstalled()) { + if (shouldUseService()) { // No need to perform directory write checks, the maintenance service will // be able to write to all directories. LOG("gCanApplyUpdates - bypass the write checks because we'll use the service"); @@ -648,8 +648,7 @@ function getCanStageUpdates() { return false; } - if (AppConstants.platform == "win" && isServiceInstalled() && - shouldUseService()) { + if (AppConstants.platform == "win" && shouldUseService()) { // No need to perform directory write checks, the maintenance service will // be able to write to all directories. LOG("getCanStageUpdates - able to stage updates using the service"); @@ -1025,13 +1024,12 @@ function releaseSDCardMountLock() { /** * Determines if the service should be used to attempt an update - * or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED - * is true and we have Firefox. + * or not. * * @return true if the service should be used for updates. */ function shouldUseService() { - if (AppConstants.MOZ_MAINTENANCE_SERVICE) { + if (AppConstants.MOZ_MAINTENANCE_SERVICE && isServiceInstalled()) { return getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false); } @@ -4214,7 +4212,7 @@ Downloader.prototype = { if (maxProgress != this._patch.size) { LOG("Downloader:onProgress - maxProgress: " + maxProgress + - " is not equal to expectd patch size: " + this._patch.size); + " is not equal to expected patch size: " + this._patch.size); // It's important that we use a different code than // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference // between a hash error and a wrong download error. From f72a8005f9550491f22dc225b32553941576a79d Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Fri, 24 Apr 2015 21:38:09 +0300 Subject: [PATCH 16/42] Bug 862341 Part 1: Move the network request storage from the console frontend to the console client so the netmonitor can reuse it. r=vporof --- browser/devtools/webconsole/test/head.js | 1 - browser/devtools/webconsole/webconsole.js | 140 ++++++---------------- toolkit/devtools/webconsole/client.js | 129 +++++++++++++++++++- 3 files changed, 162 insertions(+), 108 deletions(-) diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js index f1d02cceefce..cbd027a1f1be 100644 --- a/browser/devtools/webconsole/test/head.js +++ b/browser/devtools/webconsole/test/head.js @@ -15,7 +15,6 @@ let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils"); let {Messages} = require("devtools/webconsole/console-output"); const asyncStorage = require("devtools/toolkit/shared/async-storage"); -// promise._reportErrors = true; // please never leave me. //Services.prefs.setBoolPref("devtools.debugger.log", true); let gPendingOutputTest = 0; diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index 904f54bcf42c..49428606df4d 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -205,7 +205,6 @@ function WebConsoleFrame(aWebConsoleOwner) this._outputQueue = []; this._itemDestroyQueue = []; this._pruneCategoriesQueue = {}; - this._networkRequests = {}; this.filterPrefs = {}; this.output = new ConsoleOutput(this); @@ -253,14 +252,6 @@ WebConsoleFrame.prototype = { */ _initDefer: null, - /** - * Holds the network requests currently displayed by the Web Console. Each key - * represents the connection ID and the value is network request information. - * @private - * @type object - */ - _networkRequests: null, - /** * Last time when we displayed any message in the output. * @@ -1528,19 +1519,14 @@ WebConsoleFrame.prototype = { /** * Log network event. * - * @param object aActor - * The network event actor to log. + * @param object networkInfo + * The network request information to log. * @return nsIDOMElement|null * The message element to display in the Web Console output. */ - logNetEvent: function WCF_logNetEvent(aActor) + logNetEvent: function(networkInfo) { - let actorId = aActor.actor; - let networkInfo = this._networkRequests[actorId]; - if (!networkInfo) { - return null; - } - + let actorId = networkInfo.actor; let request = networkInfo.request; let clipboardText = request.method + " " + request.url; let severity = SEVERITY_LOG; @@ -1798,84 +1784,29 @@ WebConsoleFrame.prototype = { /** * Handle the network events coming from the remote Web Console. * - * @param object aActor - * The NetworkEventActor grip. + * @param object networkInfo + * The network request information. */ - handleNetworkEvent: function WCF_handleNetworkEvent(aActor) + handleNetworkEvent: function(networkInfo) { - let networkInfo = { - node: null, - actor: aActor.actor, - discardRequestBody: true, - discardResponseBody: true, - startedDateTime: aActor.startedDateTime, - request: { - url: aActor.url, - method: aActor.method, - }, - isXHR: aActor.isXHR, - response: {}, - timings: {}, - updates: [], // track the list of network event updates - private: aActor.private, - }; - - this._networkRequests[aActor.actor] = networkInfo; - this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor]); + this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [networkInfo]); }, /** * Handle network event updates coming from the server. * - * @param string aActorId - * The network event actor ID. - * @param string aType - * Update type. - * @param object aPacket + * @param object networkInfo + * The network request information. + * @param object packet * Update details. */ - handleNetworkEventUpdate: - function WCF_handleNetworkEventUpdate(aActorId, aType, aPacket) + handleNetworkEventUpdate: function(networkInfo, packet) { - let networkInfo = this._networkRequests[aActorId]; - if (!networkInfo) { - return; - } - - networkInfo.updates.push(aType); - - switch (aType) { - case "requestHeaders": - networkInfo.request.headersSize = aPacket.headersSize; - break; - case "requestPostData": - networkInfo.discardRequestBody = aPacket.discardRequestBody; - networkInfo.request.bodySize = aPacket.dataSize; - break; - case "responseStart": - networkInfo.response.httpVersion = aPacket.response.httpVersion; - networkInfo.response.status = aPacket.response.status; - networkInfo.response.statusText = aPacket.response.statusText; - networkInfo.response.headersSize = aPacket.response.headersSize; - networkInfo.discardResponseBody = aPacket.response.discardResponseBody; - break; - case "responseContent": - networkInfo.response.content = { - mimeType: aPacket.mimeType, - }; - networkInfo.response.bodySize = aPacket.contentSize; - networkInfo.discardResponseBody = aPacket.discardResponseBody; - break; - case "eventTimings": - networkInfo.totalTime = aPacket.totalTime; - break; - } - - if (networkInfo.node && this._updateNetMessage(aActorId)) { + if (networkInfo.node && this._updateNetMessage(packet.from)) { this.emit("new-messages", new Set([{ update: true, node: networkInfo.node, - response: aPacket, + response: packet, }])); } @@ -1900,7 +1831,7 @@ WebConsoleFrame.prototype = { */ _updateNetMessage: function WCF__updateNetMessage(aActorId) { - let networkInfo = this._networkRequests[aActorId]; + let networkInfo = this.webConsoleClient.getNetworkRequest(aActorId); if (!networkInfo || !networkInfo.node) { return; } @@ -2446,8 +2377,8 @@ WebConsoleFrame.prototype = { else if (typeof methodOrNode != "function") { connectionId = methodOrNode._connectionId; } - if (connectionId && connectionId in this._networkRequests) { - delete this._networkRequests[connectionId]; + if (connectionId && this.webConsoleClient.hasNetworkRequest(connectionId)) { + this.webConsoleClient.removeNetworkRequest(connectionId); this._releaseObject(connectionId); } } @@ -2524,7 +2455,7 @@ WebConsoleFrame.prototype = { } else if (aNode._connectionId && aNode.category == CATEGORY_NETWORK) { - delete this._networkRequests[aNode._connectionId]; + this.webConsoleClient.removeNetworkRequest(aNode._connectionId); this._releaseObject(aNode._connectionId); } else if (aNode.classList.contains("inlined-variables-view")) { @@ -3018,7 +2949,7 @@ WebConsoleFrame.prototype = { this._itemDestroyQueue.forEach(this._destroyItem, this); this._itemDestroyQueue = []; this._pruneCategoriesQueue = {}; - this._networkRequests = {}; + this.webConsoleClient.clearNetworkRequests(); if (this._outputTimerInitialized) { this._outputTimerInitialized = false; @@ -3972,7 +3903,7 @@ JSTerm.prototype = { hud.groupDepth = 0; hud._outputQueue.forEach(hud._destroyItem, hud); hud._outputQueue = []; - hud._networkRequests = {}; + this.webConsoleClient.clearNetworkRequests(); hud._repeatNodes = {}; if (aClearStorage) { @@ -5114,8 +5045,6 @@ WebConsoleConnectionProxy.prototype = { client.addListener("logMessage", this._onLogMessage); client.addListener("pageError", this._onPageError); client.addListener("consoleAPICall", this._onConsoleAPICall); - client.addListener("networkEvent", this._onNetworkEvent); - client.addListener("networkEventUpdate", this._onNetworkEventUpdate); client.addListener("fileActivity", this._onFileActivity); client.addListener("reflowActivity", this._onReflowActivity); client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited); @@ -5180,6 +5109,8 @@ WebConsoleConnectionProxy.prototype = { this.webConsoleClient = aWebConsoleClient; this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI; + this.webConsoleClient.on("networkEvent", this._onNetworkEvent); + this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate); let msgs = ["PageError", "ConsoleAPI"]; this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages); @@ -5275,15 +5206,15 @@ WebConsoleConnectionProxy.prototype = { * the UI for displaying. * * @private - * @param string aType + * @param string type * Message type. - * @param object aPacket - * The message received from the server. + * @param object networkInfo + * The network request information. */ - _onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket) + _onNetworkEvent: function(type, networkInfo) { - if (this.owner && aPacket.from == this._consoleActor) { - this.owner.handleNetworkEvent(aPacket.eventActor); + if (this.owner) { + this.owner.handleNetworkEvent(networkInfo); } }, @@ -5292,16 +5223,17 @@ WebConsoleConnectionProxy.prototype = { * the UI for displaying. * * @private - * @param string aType + * @param string type * Message type. - * @param object aPacket + * @param object packet * The message received from the server. + * @param object networkInfo + * The network request information. */ - _onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket) + _onNetworkEventUpdate: function(type, { packet, networkInfo }) { if (this.owner) { - this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType, - aPacket); + this.owner.handleNetworkEventUpdate(networkInfo, packet); } }, @@ -5401,11 +5333,11 @@ WebConsoleConnectionProxy.prototype = { this.client.removeListener("logMessage", this._onLogMessage); this.client.removeListener("pageError", this._onPageError); this.client.removeListener("consoleAPICall", this._onConsoleAPICall); - this.client.removeListener("networkEvent", this._onNetworkEvent); - this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate); this.client.removeListener("fileActivity", this._onFileActivity); this.client.removeListener("reflowActivity", this._onReflowActivity); this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited); + this.webConsoleClient.off("networkEvent", this._onNetworkEvent); + this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); this.target.off("will-navigate", this._onTabNavigated); this.target.off("navigate", this._onTabNavigated); diff --git a/toolkit/devtools/webconsole/client.js b/toolkit/devtools/webconsole/client.js index 4e1a0720f67c..f6221cd7747f 100644 --- a/toolkit/devtools/webconsole/client.js +++ b/toolkit/devtools/webconsole/client.js @@ -8,6 +8,7 @@ const {Cc, Ci, Cu} = require("chrome"); const DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); +const EventEmitter = require("devtools/toolkit/event-emitter"); loader.lazyImporter(this, "LongStringClient", "resource://gre/modules/devtools/dbg-client.jsm"); @@ -28,11 +29,17 @@ function WebConsoleClient(aDebuggerClient, aResponse) this._longStrings = {}; this.traits = aResponse.traits || {}; this.events = []; + this._networkRequests = new Map(); this.pendingEvaluationResults = new Map(); this.onEvaluationResult = this.onEvaluationResult.bind(this); + this.onNetworkEvent = this._onNetworkEvent.bind(this); + this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); this._client.addListener("evaluationResult", this.onEvaluationResult); + this._client.addListener("networkEvent", this.onNetworkEvent); + this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate); + EventEmitter.decorate(this); } exports.WebConsoleClient = WebConsoleClient; @@ -41,24 +48,132 @@ WebConsoleClient.prototype = { _longStrings: null, traits: null, + /** + * Holds the network requests currently displayed by the Web Console. Each key + * represents the connection ID and the value is network request information. + * @private + * @type object + */ + _networkRequests: null, + + getNetworkRequest(actorId) { + return this._networkRequests.get(actorId); + }, + + hasNetworkRequest(actorId) { + return this._networkRequests.has(actorId); + }, + + removeNetworkRequest(actorId) { + this._networkRequests.delete(actorId); + }, + get actor() { return this._actor; }, + /** + * The "networkEvent" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onNetworkEvent: function (type, packet) + { + if (packet.from == this._actor) { + let actor = packet.eventActor; + let networkInfo = { + node: null, + actor: actor.actor, + discardRequestBody: true, + discardResponseBody: true, + startedDateTime: actor.startedDateTime, + request: { + url: actor.url, + method: actor.method, + }, + isXHR: actor.isXHR, + response: {}, + timings: {}, + updates: [], // track the list of network event updates + private: actor.private, + }; + this._networkRequests.set(actor.actor, networkInfo); + + this.emit("networkEvent", networkInfo); + } + }, + + /** + * The "networkEventUpdate" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onNetworkEventUpdate: function (type, packet) + { + let networkInfo = this.getNetworkRequest(packet.from); + if (!networkInfo) { + return; + } + + networkInfo.updates.push(packet.updateType); + + switch (packet.updateType) { + case "requestHeaders": + networkInfo.request.headersSize = packet.headersSize; + break; + case "requestPostData": + networkInfo.discardRequestBody = packet.discardRequestBody; + networkInfo.request.bodySize = packet.dataSize; + break; + case "responseStart": + networkInfo.response.httpVersion = packet.response.httpVersion; + networkInfo.response.status = packet.response.status; + networkInfo.response.statusText = packet.response.statusText; + networkInfo.response.headersSize = packet.response.headersSize; + networkInfo.discardResponseBody = packet.response.discardResponseBody; + break; + case "responseContent": + networkInfo.response.content = { + mimeType: packet.mimeType, + }; + networkInfo.response.bodySize = packet.contentSize; + networkInfo.discardResponseBody = packet.discardResponseBody; + break; + case "eventTimings": + networkInfo.totalTime = packet.totalTime; + break; + } + + this.emit("networkEventUpdate", { + packet: packet, + networkInfo + }); + }, + /** * Retrieve the cached messages from the server. * * @see this.CACHED_MESSAGES - * @param array aTypes + * @param array types * The array of message types you want from the server. See * this.CACHED_MESSAGES for known types. * @param function aOnResponse * The function invoked when the response is received. */ - getCachedMessages: function WCC_getCachedMessages(aTypes, aOnResponse) + getCachedMessages: function WCC_getCachedMessages(types, aOnResponse) { let packet = { to: this._actor, type: "getCachedMessages", - messageTypes: aTypes, + messageTypes: types, }; this._client.request(packet, aOnResponse); }, @@ -473,10 +588,18 @@ WebConsoleClient.prototype = { detach: function WCC_detach(aOnResponse) { this._client.removeListener("evaluationResult", this.onEvaluationResult); + this._client.removeListener("networkEvent", this.onNetworkEvent); + this._client.removeListener("networkEventUpdate", this.onNetworkEventUpdate); this.stopListeners(null, aOnResponse); this._longStrings = null; this._client = null; this.pendingEvaluationResults.clear(); this.pendingEvaluationResults = null; + this.clearNetworkRequests(); + this._networkRequests = null; }, + + clearNetworkRequests: function () { + this._networkRequests.clear(); + } }; From 968c7537955ee86e96943d321424b21e8d286448 Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Mon, 27 Apr 2015 19:59:35 +0300 Subject: [PATCH 17/42] Bug 862341 Part 2: Display cached network requests in the web console. r=vporof --- browser/devtools/netmonitor/netmonitor-controller.js | 1 - browser/devtools/webconsole/webconsole.js | 11 +++++++++-- toolkit/devtools/server/actors/webconsole.js | 3 +-- toolkit/devtools/webconsole/client.js | 6 ++++++ toolkit/devtools/webconsole/utils.js | 7 ------- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js index 291361e09a8b..d27c7a99637c 100644 --- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -440,7 +440,6 @@ function TargetEventsHandler() { TargetEventsHandler.prototype = { get target() NetMonitorController._target, - get webConsoleClient() NetMonitorController.webConsoleClient, /** * Listen for events emitted by the current tab target. diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index 49428606df4d..a7aad564bd56 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -1213,6 +1213,9 @@ WebConsoleFrame.prototype = { this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]); break; + case "NetworkEvent": + this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aMessage]); + break; } }, this); }, @@ -1546,7 +1549,8 @@ WebConsoleFrame.prototype = { let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity, methodNode, null, null, - clipboardText); + clipboardText, null, + networkInfo.timeStamp); if (networkInfo.private) { messageNode.setAttribute("private", true); } @@ -5140,7 +5144,10 @@ WebConsoleConnectionProxy.prototype = { Cu.reportError("Web Console getCachedMessages error: invalid state."); } - this.owner.displayCachedMessages(aResponse.messages); + let messages = aResponse.messages.concat(...this.webConsoleClient.getNetworkEvents()); + messages.sort((a, b) => a.timeStamp - b.timeStamp); + + this.owner.displayCachedMessages(messages); if (!this._hasNativeConsoleAPI) { this.owner.logWarningAboutReplacedAPI(); diff --git a/toolkit/devtools/server/actors/webconsole.js b/toolkit/devtools/server/actors/webconsole.js index e6a91bed707c..3356cb8bc899 100644 --- a/toolkit/devtools/server/actors/webconsole.js +++ b/toolkit/devtools/server/actors/webconsole.js @@ -734,8 +734,6 @@ WebConsoleActor.prototype = } } - messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; }); - return { from: this.actorID, messages: messages, @@ -1606,6 +1604,7 @@ NetworkEventActor.prototype = return { actor: this.actorID, startedDateTime: this._startedDateTime, + timeStamp: Date.parse(this._startedDateTime), url: this._request.url, method: this._request.method, isXHR: this._isXHR, diff --git a/toolkit/devtools/webconsole/client.js b/toolkit/devtools/webconsole/client.js index f6221cd7747f..8f868655077e 100644 --- a/toolkit/devtools/webconsole/client.js +++ b/toolkit/devtools/webconsole/client.js @@ -68,6 +68,10 @@ WebConsoleClient.prototype = { this._networkRequests.delete(actorId); }, + getNetworkEvents() { + return this._networkRequests.values(); + }, + get actor() { return this._actor; }, /** @@ -85,6 +89,8 @@ WebConsoleClient.prototype = { if (packet.from == this._actor) { let actor = packet.eventActor; let networkInfo = { + _type: "NetworkEvent", + timeStamp: actor.timeStamp, node: null, actor: actor.actor, discardRequestBody: true, diff --git a/toolkit/devtools/webconsole/utils.js b/toolkit/devtools/webconsole/utils.js index 3dbd397a39e5..cfec471f00c8 100644 --- a/toolkit/devtools/webconsole/utils.js +++ b/toolkit/devtools/webconsole/utils.js @@ -1496,13 +1496,6 @@ ConsoleAPIListener.prototype = messages = messages.filter((m) => m.consoleID == this.consoleID); } - // ConsoleAPIStorage gives up messages sorted, but we ask for different - // blocks of events and we must sort them again in order to show them in the - // proper order. - messages = messages.sort(function(a, b) { - return a.timeStamp - b.timeStamp; - }); - if (aIncludePrivate) { return messages; } From 42164a66ced066e3899f03e29e5b0b5d41c4289e Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Tue, 28 Apr 2015 20:20:20 +0300 Subject: [PATCH 18/42] Bug 862341 Part 3: Display cached network requests in the network panel. r=vporof --- .../netmonitor/netmonitor-controller.js | 88 +++++++++++-------- toolkit/devtools/webconsole/client.js | 7 ++ 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js index d27c7a99637c..fd41ba18cd49 100644 --- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -527,8 +527,28 @@ NetworkEventsHandler.prototype = { */ connect: function() { dumpn("NetworkEventsHandler is connecting..."); - this.client.addListener("networkEvent", this._onNetworkEvent); - this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate); + this.webConsoleClient.on("networkEvent", this._onNetworkEvent); + this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate); + this._displayCachedEvents(); + }, + + /** + * Display any network events already in the cache. + */ + _displayCachedEvents: function() { + for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) { + // First add the request to the timeline. + this._onNetworkEvent("networkEvent", cachedEvent); + // Then replay any updates already received. + for (let update of cachedEvent.updates) { + this._onNetworkEventUpdate("networkEventUpdate", { + packet: { + updateType: update + }, + networkInfo: cachedEvent + }); + } + } }, /** @@ -539,25 +559,21 @@ NetworkEventsHandler.prototype = { return; } dumpn("NetworkEventsHandler is disconnecting..."); - this.client.removeListener("networkEvent", this._onNetworkEvent); - this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate); + this.webConsoleClient.off("networkEvent", this._onNetworkEvent); + this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); }, /** * The "networkEvent" message type handler. * - * @param string aType + * @param string type * Message type. - * @param object aPacket - * The message received from the server. + * @param object networkInfo + * The network request information. */ - _onNetworkEvent: function(aType, aPacket) { - if (aPacket.from != this.webConsoleClient.actor) { - // Skip events from different console actors. - return; - } + _onNetworkEvent: function(type, networkInfo) { + let { actor, startedDateTime, request: { method, url }, isXHR, fromCache } = networkInfo; - let { actor, startedDateTime, method, url, isXHR, fromCache } = aPacket.eventActor; NetMonitorView.RequestsMenu.addRequest( actor, startedDateTime, method, url, isXHR, fromCache ); @@ -567,19 +583,17 @@ NetworkEventsHandler.prototype = { /** * The "networkEventUpdate" message type handler. * - * @param string aType + * @param string type * Message type. - * @param object aPacket + * @param object packet * The message received from the server. + * @param object networkInfo + * The network request information. */ - _onNetworkEventUpdate: function(aType, aPacket) { - let actor = aPacket.from; - if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) { - // Skip events from unknown actors. - return; - } + _onNetworkEventUpdate: function(type, { packet, networkInfo }) { + let actor = networkInfo.actor; - switch (aPacket.updateType) { + switch (packet.updateType) { case "requestHeaders": this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders); window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor); @@ -593,8 +607,8 @@ NetworkEventsHandler.prototype = { window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor); break; case "securityInfo": - NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { - securityState: aPacket.state, + NetMonitorView.RequestsMenu.updateRequest(actor, { + securityState: networkInfo.securityInfo, }); this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo); window.emit(EVENTS.UPDATING_SECURITY_INFO, actor); @@ -608,28 +622,28 @@ NetworkEventsHandler.prototype = { window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor); break; case "responseStart": - NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { - httpVersion: aPacket.response.httpVersion, - remoteAddress: aPacket.response.remoteAddress, - remotePort: aPacket.response.remotePort, - status: aPacket.response.status, - statusText: aPacket.response.statusText, - headersSize: aPacket.response.headersSize + NetMonitorView.RequestsMenu.updateRequest(actor, { + httpVersion: networkInfo.response.httpVersion, + remoteAddress: networkInfo.response.remoteAddress, + remotePort: networkInfo.response.remotePort, + status: networkInfo.response.status, + statusText: networkInfo.response.statusText, + headersSize: networkInfo.response.headersSize }); window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor); break; case "responseContent": - NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { - contentSize: aPacket.contentSize, - transferredSize: aPacket.transferredSize, - mimeType: aPacket.mimeType + NetMonitorView.RequestsMenu.updateRequest(actor, { + contentSize: networkInfo.response.bodySize, + transferredSize: networkInfo.response.transferredSize, + mimeType: networkInfo.response.content.mimeType }); this.webConsoleClient.getResponseContent(actor, this._onResponseContent); window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor); break; case "eventTimings": - NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { - totalTime: aPacket.totalTime + NetMonitorView.RequestsMenu.updateRequest(actor, { + totalTime: networkInfo.totalTime }); this.webConsoleClient.getEventTimings(actor, this._onEventTimings); window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor); diff --git a/toolkit/devtools/webconsole/client.js b/toolkit/devtools/webconsole/client.js index 8f868655077e..beb4a9d16bb5 100644 --- a/toolkit/devtools/webconsole/client.js +++ b/toolkit/devtools/webconsole/client.js @@ -105,6 +105,7 @@ WebConsoleClient.prototype = { timings: {}, updates: [], // track the list of network event updates private: actor.private, + fromCache: actor.fromCache }; this._networkRequests.set(actor.actor, networkInfo); @@ -144,6 +145,8 @@ WebConsoleClient.prototype = { networkInfo.response.status = packet.response.status; networkInfo.response.statusText = packet.response.statusText; networkInfo.response.headersSize = packet.response.headersSize; + networkInfo.response.remoteAddress = packet.response.remoteAddress; + networkInfo.response.remotePort = packet.response.remotePort; networkInfo.discardResponseBody = packet.response.discardResponseBody; break; case "responseContent": @@ -151,11 +154,15 @@ WebConsoleClient.prototype = { mimeType: packet.mimeType, }; networkInfo.response.bodySize = packet.contentSize; + networkInfo.response.transferredSize = packet.transferredSize; networkInfo.discardResponseBody = packet.discardResponseBody; break; case "eventTimings": networkInfo.totalTime = packet.totalTime; break; + case "securityInfo": + networkInfo.securityInfo = packet.state; + break; } this.emit("networkEventUpdate", { From 0ad1685cde273d21ce3558cae02130968c4ad0b1 Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Mon, 4 May 2015 13:58:25 +0300 Subject: [PATCH 19/42] Bug 862341 Part 4: Start recording network requests when the toolbox opens. r=vporof --- browser/devtools/framework/target.js | 33 +++++-- browser/devtools/framework/toolbox.js | 5 +- .../netmonitor/netmonitor-controller.js | 97 +++---------------- .../test/browser_net_reload-button.js | 3 - browser/devtools/netmonitor/test/head.js | 5 + 5 files changed, 45 insertions(+), 98 deletions(-) diff --git a/browser/devtools/framework/target.js b/browser/devtools/framework/target.js index 2daf18fc1a50..ee9cb1ac0b12 100644 --- a/browser/devtools/framework/target.js +++ b/browser/devtools/framework/target.js @@ -165,6 +165,7 @@ function TabTarget(tab) { this._handleThreadState = this._handleThreadState.bind(this); this.on("thread-resumed", this._handleThreadState); this.on("thread-paused", this._handleThreadState); + this.activeTab = this.activeConsole = null; // Only real tabs need initialization here. Placeholder objects for remote // targets will be initialized after a makeRemote method call. if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) { @@ -420,6 +421,19 @@ TabTarget.prototype = { } this.activeTab = aTabClient; this.threadActor = aResponse.threadActor; + attachConsole(); + }); + }; + + let attachConsole = () => { + this._client.attachConsole(this._form.consoleActor, + [ "NetworkActivity" ], + (aResponse, aWebConsoleClient) => { + if (!aWebConsoleClient) { + this._remote.reject("Unable to attach to the console"); + return; + } + this.activeConsole = aWebConsoleClient; this._remote.resolve(null); }); }; @@ -439,7 +453,7 @@ TabTarget.prototype = { } else { // AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and // doesn't need to be attached. - this._remote.resolve(null); + attachConsole(); } return this._remote.promise; @@ -593,17 +607,15 @@ TabTarget.prototype = { // We started with a local tab and created the client ourselves, so we // should close it. this._client.close(cleanupAndResolve); - } else { + } else if (this.activeTab) { // The client was handed to us, so we are not responsible for closing // it. We just need to detach from the tab, if already attached. - if (this.activeTab) { - // |detach| may fail if the connection is already dead, so proceed - // cleanup directly after this. - this.activeTab.detach(); - cleanupAndResolve(); - } else { - cleanupAndResolve(); - } + // |detach| may fail if the connection is already dead, so proceed with + // cleanup directly after this. + this.activeTab.detach(); + cleanupAndResolve(); + } else { + cleanupAndResolve(); } } @@ -620,6 +632,7 @@ TabTarget.prototype = { promiseTargets.delete(this._form); } this.activeTab = null; + this.activeConsole = null; this._client = null; this._tab = null; this._form = null; diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js index dab515ac7c8b..81547cac2884 100644 --- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -110,6 +110,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) { this._toolPanels = new Map(); this._telemetry = new Telemetry(); + this._initInspector = null; + this._inspector = null; + this._toolRegistered = this._toolRegistered.bind(this); this._toolUnregistered = this._toolUnregistered.bind(this); this._refreshHostTitle = this._refreshHostTitle.bind(this); @@ -367,7 +370,7 @@ Toolbox.prototype = { this._pingTelemetry(); - let panel = yield this.selectTool(this._defaultToolId); + yield this.selectTool(this._defaultToolId); // Wait until the original tool is selected so that the split // console input will receive focus. diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js index fd41ba18cd49..ad936af9ab73 100644 --- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -196,7 +196,9 @@ let NetMonitorController = { /** * Initiates remote or chrome network monitoring based on the current target, - * wiring event handlers as necessary. + * wiring event handlers as necessary. Since the TabTarget will have already + * started listening to network requests by now, this is largely + * netmonitor-specific initialization. * * @return object * A promise that is resolved when the monitor finishes connecting. @@ -209,15 +211,18 @@ let NetMonitorController = { let deferred = promise.defer(); this._connection = deferred.promise; - let target = this._target; - let { client, form } = target; + this.client = this._target.client; // Some actors like AddonActor or RootActor for chrome debugging - // do not support attach/detach and can be used directly - if (!target.isTabActor) { - this._startChromeMonitoring(client, form.consoleActor, deferred.resolve); - } else { - this._startMonitoringTab(client, form, deferred.resolve); + // aren't actual tabs. + if (this._target.isTabActor) { + this.tabClient = this._target.activeTab; } + this.webConsoleClient = this._target.activeConsole; + this.webConsoleClient.setPreferences(NET_PREFS, () => { + this.TargetEventsHandler.connect(); + this.NetworkEventsHandler.connect(); + deferred.resolve(); + }); yield deferred.promise; window.emit(EVENTS.CONNECTED); @@ -243,82 +248,6 @@ let NetMonitorController = { return !!this.client; }, - /** - * Sets up a monitoring session. - * - * @param DebuggerClient aClient - * The debugger client. - * @param object aTabGrip - * The remote protocol grip of the tab. - * @param function aCallback - * A function to invoke once the client attached to the console client. - */ - _startMonitoringTab: function(aClient, aTabGrip, aCallback) { - if (!aClient) { - Cu.reportError("No client found!"); - return; - } - this.client = aClient; - - aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => { - if (!aTabClient) { - Cu.reportError("No tab client found!"); - return; - } - this.tabClient = aTabClient; - - aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { - if (!aWebConsoleClient) { - Cu.reportError("Couldn't attach to console: " + aResponse.error); - return; - } - this.webConsoleClient = aWebConsoleClient; - this.webConsoleClient.setPreferences(NET_PREFS, () => { - this.TargetEventsHandler.connect(); - this.NetworkEventsHandler.connect(); - - if (aCallback) { - aCallback(); - } - }); - }); - }); - }, - - /** - * Sets up a chrome monitoring session. - * - * @param DebuggerClient aClient - * The debugger client. - * @param object aConsoleActor - * The remote protocol grip of the chrome debugger. - * @param function aCallback - * A function to invoke once the client attached to the console client. - */ - _startChromeMonitoring: function(aClient, aConsoleActor, aCallback) { - if (!aClient) { - Cu.reportError("No client found!"); - return; - } - this.client = aClient; - - aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { - if (!aWebConsoleClient) { - Cu.reportError("Couldn't attach to console: " + aResponse.error); - return; - } - this.webConsoleClient = aWebConsoleClient; - this.webConsoleClient.setPreferences(NET_PREFS, () => { - this.TargetEventsHandler.connect(); - this.NetworkEventsHandler.connect(); - - if (aCallback) { - aCallback(); - } - }); - }); - }, - /** * Gets the activity currently performed by the frontend. * @return number diff --git a/browser/devtools/netmonitor/test/browser_net_reload-button.js b/browser/devtools/netmonitor/test/browser_net_reload-button.js index ab551b124454..bc350d0d3fae 100644 --- a/browser/devtools/netmonitor/test/browser_net_reload-button.js +++ b/browser/devtools/netmonitor/test/browser_net_reload-button.js @@ -15,9 +15,6 @@ function test() { let { RequestsMenu } = NetMonitorView; reqMenu = RequestsMenu; - is(reqMenu.itemCount, 0, - "The request menu should empty before reloading"); - let button = document.querySelector("#requests-menu-reload-notice-button"); button.click(); }) diff --git a/browser/devtools/netmonitor/test/head.js b/browser/devtools/netmonitor/test/head.js index b3271ac1ec96..3ed0add19742 100644 --- a/browser/devtools/netmonitor/test/head.js +++ b/browser/devtools/netmonitor/test/head.js @@ -147,6 +147,11 @@ function initNetMonitor(aUrl, aWindow, aEnableCache) { if(!aEnableCache) { yield toggleCache(target, true); info("Cache disabled when the current and all future toolboxes are open."); + // Remove any requests generated by the reload while toggling the cache to + // avoid interfering with the test. + isnot([...target.activeConsole.getNetworkEvents()].length, 0, + "Request to reconfigure the tab was recorded."); + target.activeConsole.clearNetworkRequests(); } let toolbox = yield gDevTools.showToolbox(target, "netmonitor"); From 5b07b0e703187507096032b8af99e6eee681662a Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Wed, 6 May 2015 09:24:57 +0300 Subject: [PATCH 20/42] Bug 862341 Part 5: Tests. r=vporof --- browser/devtools/webconsole/test/browser.ini | 2 + ...ser_netmonitor_shows_reqs_in_webconsole.js | 71 +++++++++++++++++++ .../test/browser_webconsole_netlogging.js | 3 +- ...ser_webconsole_shows_reqs_in_netmonitor.js | 71 +++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js create mode 100644 browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini index f71d89efbf95..3638cd88888e 100644 --- a/browser/devtools/webconsole/test/browser.ini +++ b/browser/devtools/webconsole/test/browser.ini @@ -385,3 +385,5 @@ skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout) [browser_webconsole_column_numbers.js] [browser_console_open_or_focus.js] [browser_webconsole_bug_922212_console_dirxml.js] +[browser_webconsole_shows_reqs_in_netmonitor.js] +[browser_netmonitor_shows_reqs_in_webconsole.js] diff --git a/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js b/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js new file mode 100644 index 000000000000..029f5a2be309 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js @@ -0,0 +1,71 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const TEST_URI = "data:text/html;charset=utf8,Test that the netmonitor " + + "displays requests that have been recorded in the " + + "web console, even if the netmonitor hadn't opened yet."; + +const TEST_FILE = "test-network-request.html"; +const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" + + TEST_FILE; + +const NET_PREF = "devtools.webconsole.filter.networkinfo"; +Services.prefs.setBoolPref(NET_PREF, true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref(NET_PREF); +}); + +add_task(function* () { + let { tab, browser } = yield loadTab(TEST_URI); + + // Test that the request appears in the console. + let hud = yield openConsole(); + info("Web console is open"); + + yield loadDocument(browser); + info("Document loaded."); + + yield waitForMessages({ + webconsole: hud, + messages: [ + { + name: "network message", + text: TEST_FILE, + category: CATEGORY_NETWORK, + severity: SEVERITY_LOG + } + ] + }); + + // Test that the request appears in the network panel. + let target = TargetFactory.forTab(tab); + let toolbox = yield gDevTools.showToolbox(target, "netmonitor"); + info("Network panel is open."); + + testNetmonitor(toolbox); +}); + + +function loadDocument(browser) { + let deferred = promise.defer(); + + browser.addEventListener("load", function onLoad() { + browser.removeEventListener("load", onLoad, true); + deferred.resolve(); + }, true); + content.location = TEST_PATH; + + return deferred.promise; +} + +function testNetmonitor(toolbox) { + let monitor = toolbox.getCurrentPanel(); + let { RequestsMenu } = monitor.panelWin.NetMonitorView; + is(RequestsMenu.itemCount, 1, "Network request appears in the network panel"); + + let item = RequestsMenu.getItemAtIndex(0); + is(item.attachment.method, "GET", "The attached method is correct."); + is(item.attachment.url, TEST_PATH, "The attached url is correct."); +} diff --git a/browser/devtools/webconsole/test/browser_webconsole_netlogging.js b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js index 7cf95822ef65..c12673af6e96 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_netlogging.js +++ b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js @@ -73,7 +73,8 @@ function testPageLoad() ok(!lastRequest.request.postData.text, "No request body was stored"); ok(lastRequest.discardRequestBody, "Request body was discarded"); ok(!lastRequest.response.content.text, "No response body was stored"); - ok(lastRequest.discardResponseBody, "Response body was discarded"); + ok(lastRequest.discardResponseBody || lastRequest.fromCache, + "Response body was discarded or response came from the cache"); lastRequest = null; requestCallback = null; diff --git a/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js b/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js new file mode 100644 index 000000000000..d6a40da78c7a --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js @@ -0,0 +1,71 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const TEST_URI = "data:text/html;charset=utf8,Test that the web console " + + "displays requests that have been recorded in the " + + "netmonitor, even if the console hadn't opened yet."; + +const TEST_FILE = "test-network-request.html"; +const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" + + TEST_FILE; + +const NET_PREF = "devtools.webconsole.filter.networkinfo"; +Services.prefs.setBoolPref(NET_PREF, true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref(NET_PREF); +}); + +add_task(function* () { + let { tab, browser } = yield loadTab(TEST_URI); + + let target = TargetFactory.forTab(tab); + let toolbox = yield gDevTools.showToolbox(target, "netmonitor"); + info("Network panel is open."); + + yield loadDocument(browser); + info("Document loaded."); + + // Test that the request appears in the network panel. + testNetmonitor(toolbox); + + // Test that the request appears in the console. + let hud = yield openConsole(); + info("Web console is open"); + + yield waitForMessages({ + webconsole: hud, + messages: [ + { + name: "network message", + text: TEST_FILE, + category: CATEGORY_NETWORK, + severity: SEVERITY_LOG + } + ] + }); +}); + + +function loadDocument(browser) { + let deferred = promise.defer(); + + browser.addEventListener("load", function onLoad() { + browser.removeEventListener("load", onLoad, true); + deferred.resolve(); + }, true); + content.location = TEST_PATH; + + return deferred.promise; +} + +function testNetmonitor(toolbox) { + let monitor = toolbox.getCurrentPanel(); + let { RequestsMenu } = monitor.panelWin.NetMonitorView; + is(RequestsMenu.itemCount, 1, "Network request appears in the network panel"); + + let item = RequestsMenu.getItemAtIndex(0); + is(item.attachment.method, "GET", "The attached method is correct."); + is(item.attachment.url, TEST_PATH, "The attached url is correct."); +} From 722316be233d489bab4f7128922f621baf1675ac Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Thu, 7 May 2015 11:08:45 -0700 Subject: [PATCH 21/42] Bug 1162490 - Only set the transparent accentcolor for Dev Edition theme in Windows;r=Gijs It is causing the drop shadow to disappear on osx 10.9. Windows is the only platform that uses a transparent background on the tabs, so it's the only one that neesd the accentcolor --- browser/components/nsBrowserGlue.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 678306e2eb8e..e597ff802c1d 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -733,7 +733,9 @@ BrowserGlue.prototype = { LightweightThemeManager.addBuiltInTheme({ id: "firefox-devedition@mozilla.org", name: themeName, +#ifdef XP_WIN accentcolor: "transparent", +#endif headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png", iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png", author: vendorShortName, From 6e774435348e94fd2fb679cce57af21a543ebd53 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 7 May 2015 20:45:06 +0100 Subject: [PATCH 22/42] Bug 1162646 - Enable eslint rules for Loop: no trailing spaces. r=dmose NPOTB DONTBUILD --- browser/components/loop/.eslintignore | 13 ++++++++++++- browser/components/loop/.eslintrc | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/browser/components/loop/.eslintignore b/browser/components/loop/.eslintignore index 8ac40f1ae3a4..16298ed1319e 100644 --- a/browser/components/loop/.eslintignore +++ b/browser/components/loop/.eslintignore @@ -11,4 +11,15 @@ standalone/content/libs standalone/node_modules # Libs we don't need to check test/shared/vendor - +# These are generated react files that we don't need to check +content/js/contacts.js +content/js/conversation.js +content/js/conversationViews.js +content/js/panel.js +content/js/roomViews.js +content/shared/js/feedbackViews.js +content/shared/js/views.js +standalone/content/js/fxOSMarketplace.js +standalone/content/js/standaloneRoomViews.js +standalone/content/js/webapp.js +ui/ui-showcase.js diff --git a/browser/components/loop/.eslintrc b/browser/components/loop/.eslintrc index 0ccffaa43bf7..7b9ac8ed490f 100644 --- a/browser/components/loop/.eslintrc +++ b/browser/components/loop/.eslintrc @@ -53,7 +53,6 @@ "no-return-assign": 0, // TODO: Remove (use default) "no-shadow": 0, // TODO: Remove (use default) "no-spaced-func": 0, // TODO: Remove (use default) - "no-trailing-spaces": 0, // TODO: Remove (use default) "no-undef": 0, // TODO: Remove (use default) "no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables. "no-unused-expressions": 0, // TODO: Remove (use default) From 49406feb3c391a1c5a6c6b8f4d0d35c41e01aef0 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Thu, 7 May 2015 13:19:46 -0700 Subject: [PATCH 23/42] Bug 1144779 - Do not use mouse coordinates from onMouseUp to set selection end when dragging in Graph;r=vporof Instead use the cursor.x property that was stored during the mousemove event. This helps preserve the selection when a mouseup happens outside of the window. --- browser/devtools/shared/test/browser_graphs-07a.js | 10 ++++++---- browser/devtools/shared/widgets/Graphs.jsm | 12 +++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/browser/devtools/shared/test/browser_graphs-07a.js b/browser/devtools/shared/test/browser_graphs-07a.js index 0bb082d381f7..8aeed71714da 100644 --- a/browser/devtools/shared/test/browser_graphs-07a.js +++ b/browser/devtools/shared/test/browser_graphs-07a.js @@ -201,11 +201,13 @@ function buggyDragStop(graph, x, y = 1) { x /= window.devicePixelRatio; y /= window.devicePixelRatio; - // Only fire a mousemove instead of a mouseup. - // This happens when the mouseup happens outside of the toolbox, - // see Bug 1066504. graph._onMouseMove({ testX: x, testY: y }); - graph._onMouseMove({ testX: x, testY: y, buttons: 0 }); + + // Only fire a mousemove with no buttons instead of a mouseup. + // This happens when the mouseup happens outside of the window. + // Send different coordinates to make sure the selection is preserved, + // see Bugs 1066504 and 1144779. + graph._onMouseMove({ testX: x+1, testY: y+1, buttons: 0 }); } function scroll(graph, wheel, x, y = 1) { diff --git a/browser/devtools/shared/widgets/Graphs.jsm b/browser/devtools/shared/widgets/Graphs.jsm index cc9acb6c2371..da4adab7ac49 100644 --- a/browser/devtools/shared/widgets/Graphs.jsm +++ b/browser/devtools/shared/widgets/Graphs.jsm @@ -986,12 +986,12 @@ AbstractCanvasGraph.prototype = { e.stopPropagation(); } - // If a mouseup happened outside the toolbox and the current operation - // is causing the selection changed, then end it. + // If a mouseup happened outside the window and the current operation + // is causing the selection to change, then end it. if (e.buttons == 0 && (this.hasSelectionInProgress() || resizer.margin != null || dragger.origin != null)) { - return this._onMouseUp(e); + return this._onMouseUp(); } let {mouseX,mouseY} = this._getRelativeEventCoordinates(e); @@ -1093,10 +1093,8 @@ AbstractCanvasGraph.prototype = { /** * Listener for the "mouseup" event on the graph's container. */ - _onMouseUp: function(e) { + _onMouseUp: function() { this._isMouseActive = false; - let {mouseX} = this._getRelativeEventCoordinates(e); - switch (this._canvas.getAttribute("input")) { case "hovering-background": case "hovering-region": @@ -1115,7 +1113,7 @@ AbstractCanvasGraph.prototype = { this.emit("deselecting"); } } else { - this._selection.end = mouseX; + this._selection.end = this._cursor.x; this.emit("selecting"); } break; From 7b4994dfc84f9b0be5abf8ef8e6db256de0c15d4 Mon Sep 17 00:00:00 2001 From: Daniel Veditz Date: Tue, 5 May 2015 20:21:00 +0200 Subject: [PATCH 24/42] Bug 1038072 - signature verification for JAR files unpacked into a directory. r=keeler --HG-- extra : rebase_source : 5728bfc05f8326f5392a787d38bc64ec8dbefe21 extra : source : a02ea85607a2c0989f057053858125fa5046763b --- security/apps/AppSignatureVerification.cpp | 586 +++++++++++++++++- security/manager/ssl/public/nsIX509CertDB.idl | 25 +- .../unit/test_signed_apps/sslcontrol.xpi | Bin 0 -> 6074 bytes .../manager/ssl/tests/unit/test_signed_dir.js | 173 ++++++ security/manager/ssl/tests/unit/xpcshell.ini | 1 + 5 files changed, 774 insertions(+), 11 deletions(-) create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/sslcontrol.xpi create mode 100644 security/manager/ssl/tests/unit/test_signed_dir.js diff --git a/security/apps/AppSignatureVerification.cpp b/security/apps/AppSignatureVerification.cpp index 0c53e23c7850..ec73b129d92d 100644 --- a/security/apps/AppSignatureVerification.cpp +++ b/security/apps/AppSignatureVerification.cpp @@ -20,6 +20,7 @@ #include "nsIFileStreams.h" #include "nsIInputStream.h" #include "nsIStringEnumerator.h" +#include "nsIDirectoryEnumerator.h" #include "nsIZipReader.h" #include "nsNetUtil.h" #include "nsNSSCertificate.h" @@ -98,7 +99,7 @@ ReadStream(const nsCOMPtr& stream, /*out*/ SECItem& buf) return NS_OK; } -// Finds exactly one (signature metadata) entry that matches the given +// Finds exactly one (signature metadata) JAR entry that matches the given // search pattern, and then load it. Fails if there are no matches or if // there is more than one match. If bugDigest is not null then on success // bufDigest will contain the SHA-1 digeset of the entry. @@ -153,27 +154,22 @@ FindAndLoadOneEntry(nsIZipReader * zip, // at once, which would require memory in proportion to the size of the largest // entry. Instead, we require only a small, fixed amount of memory. // +// @param stream an input stream from a JAR entry or file depending on whether +// it is from a signed archive or unpacked into a directory // @param digestFromManifest The digest that we're supposed to check the file's // contents against, from the manifest // @param buf A scratch buffer that we use for doing the I/O, which must have // already been allocated. The size of this buffer is the unit // size of our I/O. nsresult -VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename, - const SECItem & digestFromManifest, SECItem & buf) +VerifyStreamContentDigest(nsIInputStream* stream, + const SECItem& digestFromManifest, SECItem& buf) { MOZ_ASSERT(buf.len > 0); if (digestFromManifest.len != SHA1_LENGTH) return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; nsresult rv; - - nsCOMPtr stream; - rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); - if (NS_FAILED(rv)) { - return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; - } - uint64_t len64; rv = stream->Available(&len64); NS_ENSURE_SUCCESS(rv, rv); @@ -226,6 +222,81 @@ VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename, return NS_OK; } +nsresult +VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename, + const SECItem& digestFromManifest, SECItem& buf) +{ + nsCOMPtr stream; + nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + return VerifyStreamContentDigest(stream, digestFromManifest, buf); +} + +// @oaram aDir directory containing the unpacked signed archive +// @param aFilename path of the target file relative to aDir +// @param digestFromManifest The digest that we're supposed to check the file's +// contents against, from the manifest +// @param buf A scratch buffer that we use for doing the I/O +nsresult +VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename, + const SECItem& digestFromManifest, SECItem& buf) +{ + // Find the file corresponding to the manifest path + nsCOMPtr file; + nsresult rv = aDir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't know how to handle JARs with signed directory entries. + // It's technically possible in the manifest but makes no sense on disk. + // Inside an archive we just ignore them, but here we have to treat it + // as an error because the signed bytes never got unpacked. + int32_t pos = 0; + int32_t slash; + int32_t namelen = aFilename.Length(); + if (namelen == 0 || aFilename[namelen - 1] == '/') { + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + + // Append path segments one by one + do { + slash = aFilename.FindChar('/', pos); + int32_t segend = (slash == kNotFound) ? namelen : slash; + rv = file->Append(Substring(aFilename, pos, (segend - pos))); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + pos = slash + 1; + } while (pos < namelen && slash != kNotFound); + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv) || isDir) { + // We only support signed files, not directory entries + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; + } + + // Open an input stream for that file and verify it. + nsCOMPtr stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1, + nsIFileInputStream::CLOSE_ON_EOF); + if (NS_FAILED(rv) || !stream) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + return VerifyStreamContentDigest(stream, digestFromManifest, buf); +} + // On input, nextLineStart is the start of the current line. On output, // nextLineStart is the start of the next line. nsresult @@ -286,6 +357,7 @@ ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line, #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$" +#define JAR_META_DIR "META-INF" #define JAR_MF_HEADER "Manifest-Version: 1.0" #define JAR_SF_HEADER "Signature-Version: 1.0" @@ -949,3 +1021,497 @@ nsNSSCertificateDB::VerifySignedManifestAsync( aSignatureStream, aCallback)); return task->Dispatch("SignedManifest"); } + + +// +// Signature verification for archives unpacked into a file structure +// + +// Finds the "*.rsa" signature file in the META-INF directory and returns +// the name. It is an error if there are none or more than one .rsa file +nsresult +FindSignatureFilename(nsIFile* aMetaDir, + /*out*/ nsAString& aFilename) +{ + nsCOMPtr entries; + nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries)); + nsCOMPtr files = do_QueryInterface(entries); + if (NS_FAILED(rv) || !files) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + bool found = false; + nsCOMPtr file; + rv = files->GetNextFile(getter_AddRefs(file)); + + while (NS_SUCCEEDED(rv) && file) { + nsAutoString leafname; + rv = file->GetLeafName(leafname); + if (NS_SUCCEEDED(rv)) { + if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) { + if (!found) { + found = true; + aFilename = leafname; + } else { + // second signature file is an error + rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + break; + } + } + rv = files->GetNextFile(getter_AddRefs(file)); + } + } + + if (!found) { + rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + files->Close(); + return rv; +} + +// Loads the signature metadata file that matches the given filename in +// the passed-in Meta-inf directory. If bufDigest is not null then on +// success bufDigest will contain the SHA-1 digest of the entry. +nsresult +LoadOneMetafile(nsIFile* aMetaDir, + const nsAString& aFilename, + /*out*/ SECItem& aBuf, + /*optional, out*/ Digest* aBufDigest) +{ + nsCOMPtr metafile; + nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metafile->Append(aFilename); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = metafile->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + // we can call a missing .rsa file "unsigned" but FindSignatureFilename() + // already found one: missing other metadata files means a broken signature. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + nsCOMPtr stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadStream(stream, aBuf); + stream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + if (aBufDigest) { + rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// Parses MANIFEST.MF and verifies the contents of the unpacked files +// listed in the manifest. +// The filenames of all entries will be returned in aMfItems. aBuf must +// be a pre-allocated scratch buffer that is used for doing I/O. +nsresult +ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir, + /*out*/ nsTHashtable& aMfItems, + ScopedAutoSECItem& aBuf) +{ + nsresult rv; + + const char* nextLineStart = aFilebuf; + + rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER)); + if (NS_FAILED(rv)) { + return rv; + } + + // Skip the rest of the header section, which ends with a blank line. + { + nsAutoCString line; + do { + rv = ReadLine(nextLineStart, line); + if (NS_FAILED(rv)) { + return rv; + } + } while (line.Length() > 0); + + // Manifest containing no file entries is OK, though useless. + if (*nextLineStart == '\0') { + return NS_OK; + } + } + + nsAutoString curItemName; + ScopedAutoSECItem digest; + + for (;;) { + nsAutoCString curLine; + rv = ReadLine(nextLineStart, curLine); + if (NS_FAILED(rv)) { + return rv; + } + + if (curLine.Length() == 0) { + // end of section (blank line or end-of-file) + + if (curItemName.Length() == 0) { + // '...Each section must start with an attribute with the name as + // "Name",...', so every section must have a Name attribute. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (digest.len == 0) { + // We require every entry to have a digest, since we require every + // entry to be signed and we don't allow duplicate entries. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (aMfItems.Contains(curItemName)) { + // Duplicate entry + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Verify that the file's content digest matches the digest from this + // MF section. + rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf); + if (NS_FAILED(rv)) { + return rv; + } + + aMfItems.PutEntry(curItemName); + + if (*nextLineStart == '\0') { + // end-of-file + break; + } + + // reset so we know we haven't encountered either of these for the next + // item yet. + curItemName.Truncate(); + digest.reset(); + + continue; // skip the rest of the loop below + } + + nsAutoCString attrName; + nsAutoCString attrValue; + rv = ParseAttribute(curLine, attrName, attrValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Lines to look for: + + // (1) Digest: + if (attrName.LowerCaseEqualsLiteral("sha1-digest")) { + if (digest.len > 0) { + // multiple SHA1 digests in section + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get())); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + continue; + } + + // (2) Name: associates this manifest section with a file in the jar. + if (attrName.LowerCaseEqualsLiteral("name")) { + if (MOZ_UNLIKELY(curItemName.Length() > 0)) { + // multiple names in section + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (MOZ_UNLIKELY(attrValue.Length() == 0)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + curItemName = NS_ConvertUTF8toUTF16(attrValue); + + continue; + } + + // (3) Magic: the only other must-understand attribute + if (attrName.LowerCaseEqualsLiteral("magic")) { + // We don't understand any magic, so we can't verify an entry that + // requires magic. Since we require every entry to have a valid + // signature, we have no choice but to reject the entry. + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // unrecognized attributes must be ignored + } + + return NS_OK; +} + +// recursively check a directory tree for files not in the list of +// verified files we found in the manifest. For each file we find +// Check it against the files found in the manifest. If the file wasn't +// in the manifest then it's unsigned and we can stop looking. Otherwise +// remove it from the collection so we can check leftovers later. +// +// @param aDir Directory to check +// @param aPath Relative path to that directory (to check against aItems) +// @param aItems All the files found +// @param *Filename signature files that won't be in the manifest +nsresult +CheckDirForUnsignedFiles(nsIFile* aDir, + const nsString& aPath, + /* in/out */ nsTHashtable& aItems, + const nsAString& sigFilename, + const nsAString& sfFilename, + const nsAString& mfFilename) +{ + nsCOMPtr entries; + nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries)); + nsCOMPtr files = do_QueryInterface(entries); + if (NS_FAILED(rv) || !files) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR)); + + while (NS_SUCCEEDED(rv)) { + nsCOMPtr file; + rv = files->GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) { + break; + } + + nsAutoString leafname; + rv = file->GetLeafName(leafname); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString curName(aPath + leafname); + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + // if it's a directory we need to recurse + if (isDir) { + curName.Append(NS_LITERAL_STRING("/")); + rv = CheckDirForUnsignedFiles(file, curName, aItems, + sigFilename, sfFilename, mfFilename); + } else { + // The files that comprise the signature mechanism are not covered by the + // signature. + // + // XXX: This is OK for a single signature, but doesn't work for + // multiple signatures because the metadata for the other signatures + // is not signed either. + if (inMeta && ( leafname == sigFilename || + leafname == sfFilename || + leafname == mfFilename )) { + continue; + } + + // make sure the current file was found in the manifest + nsStringHashKey* item = aItems.GetEntry(curName); + if (!item) { + return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY; + } + + // Remove the item so we can check for leftover items later + aItems.RemoveEntry(curName); + } + } + files->Close(); + return rv; +} + +/* + * Verify the signature of a directory structure as if it were a + * signed JAR file (used for unpacked JARs) + */ +nsresult +VerifySignedDirectory(AppTrustedRoot aTrustedRoot, + nsIFile* aDirectory, + /*out, optional */ nsIX509Cert** aSignerCert) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + if (aSignerCert) { + *aSignerCert = nullptr; + } + + // Make sure there's a META-INF directory + + nsCOMPtr metaDir; + nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR)); + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = metaDir->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + bool isDirectory; + rv = metaDir->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + // Find and load the Signature (RSA) file + + nsAutoString sigFilename; + rv = FindSignatureFilename(metaDir, sigFilename); + if (NS_FAILED(rv)) { + return rv; + } + + ScopedAutoSECItem sigBuffer; + rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; + } + + // Load the signature (SF) file and verify the signature. + // The .sf and .rsa files must have the same name apart from the extension. + + nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3) + + NS_LITERAL_STRING("sf")); + + ScopedAutoSECItem sfBuffer; + Digest sfCalculatedDigest; + rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + sigBuffer.type = siBuffer; + ScopedCERTCertList builtChain; + rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(), + builtChain); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Get the expected manifest hash from the signed .sf file + + ScopedAutoSECItem mfDigest; + rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Load manifest (MF) file and verify signature + + nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf")); + ScopedAutoSECItem manifestBuffer; + Digest mfCalculatedDigest; + rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest); + if (NS_FAILED(rv)) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) { + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; + } + + // Parse manifest and verify signed hash of all listed files + + // Allocate the I/O buffer only once per JAR, instead of once per entry, in + // order to minimize malloc/free calls and in order to avoid fragmenting + // memory. + ScopedAutoSECItem buf(128 * 1024); + + nsTHashtable items; + rv = ParseMFUnpacked(char_ptr_cast(manifestBuffer.data), + aDirectory, items, buf); + if (NS_FAILED(rv)){ + return rv; + } + + // We've checked that everything listed in the manifest exists and is signed + // correctly. Now check on disk for extra (unsigned) files. + // Deletes found entries from items as it goes. + rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items, + sigFilename, sfFilename, mfFilename); + if (NS_FAILED(rv)) { + return rv; + } + + // We verified that every entry that we require to be signed is signed. But, + // were there any missing entries--that is, entries that are mentioned in the + // manifest but missing from the directory tree? (There shouldn't be given + // ParseMFUnpacked() checking them all, but it's a cheap sanity check.) + if (items.Count() != 0) { + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; + } + + // Return the signer's certificate to the reader if they want it. + // XXX: We should return an nsIX509CertList with the whole validated chain. + if (aSignerCert) { + MOZ_ASSERT(CERT_LIST_HEAD(builtChain)); + nsCOMPtr signerCert = + nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert); + NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY); + signerCert.forget(aSignerCert); + } + + return NS_OK; +} + +class VerifySignedDirectoryTask final : public CryptoTask +{ +public: + VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar, + nsIVerifySignedDirectoryCallback* aCallback) + : mTrustedRoot(aTrustedRoot) + , mDirectory(aUnpackedJar) + , mCallback(new nsMainThreadPtrHolder(aCallback)) + { + } + +private: + virtual nsresult CalculateResult() override + { + return VerifySignedDirectory(mTrustedRoot, + mDirectory, + getter_AddRefs(mSignerCert)); + } + + // This class doesn't directly hold NSS resources so there's nothing that + // needs to be released + virtual void ReleaseNSSResources() override { } + + virtual void CallCallback(nsresult rv) override + { + (void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert); + } + + const AppTrustedRoot mTrustedRoot; + const nsCOMPtr mDirectory; + nsMainThreadPtrHandle mCallback; + nsCOMPtr mSignerCert; // out +}; + +NS_IMETHODIMP +nsNSSCertificateDB::VerifySignedDirectoryAsync( + AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar, + nsIVerifySignedDirectoryCallback* aCallback) +{ + NS_ENSURE_ARG_POINTER(aUnpackedJar); + NS_ENSURE_ARG_POINTER(aCallback); + RefPtr task(new VerifySignedDirectoryTask(aTrustedRoot, + aUnpackedJar, + aCallback)); + return task->Dispatch("UnpackedJar"); +} diff --git a/security/manager/ssl/public/nsIX509CertDB.idl b/security/manager/ssl/public/nsIX509CertDB.idl index 9a4e3f53c207..c4cc232c9227 100644 --- a/security/manager/ssl/public/nsIX509CertDB.idl +++ b/security/manager/ssl/public/nsIX509CertDB.idl @@ -28,6 +28,13 @@ interface nsIOpenSignedAppFileCallback : nsISupports in nsIX509Cert aSignerCert); }; +[scriptable, function, uuid(d5f97827-622a-488f-be08-d850432ac8ec)] +interface nsIVerifySignedDirectoryCallback : nsISupports +{ + void verifySignedDirectoryFinished(in nsresult rv, + in nsIX509Cert aSignerCert); +}; + [scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)] interface nsIVerifySignedManifestCallback : nsISupports { @@ -39,7 +46,7 @@ interface nsIVerifySignedManifestCallback : nsISupports * This represents a service to access and manipulate * X.509 certificates stored in a database. */ -[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)] +[scriptable, uuid(6d27211b-7119-4777-9c62-f29310eeb262)] interface nsIX509CertDB : nsISupports { /** @@ -317,6 +324,22 @@ interface nsIX509CertDB : nsISupports { in nsIFile aJarFile, in nsIOpenSignedAppFileCallback callback); + /** + * Verifies the signature on a directory representing an unpacked signed + * JAR file. To be considered valid, there must be exactly one signature + * on the directory structure and that signature must have signed every + * entry. Further, the signature must come from a certificate that + * is trusted for code signing. + * + * On success NS_OK and the trusted certificate that signed the + * unpacked JAR are returned. + * + * On failure, an error code is returned. + */ + void verifySignedDirectoryAsync(in AppTrustedRoot trustedRoot, + in nsIFile aUnpackedDir, + in nsIVerifySignedDirectoryCallback callback); + /** * Given streams containing a signature and a manifest file, verifies * that the signature is valid for the manifest. The signature must diff --git a/security/manager/ssl/tests/unit/test_signed_apps/sslcontrol.xpi b/security/manager/ssl/tests/unit/test_signed_apps/sslcontrol.xpi new file mode 100644 index 0000000000000000000000000000000000000000..c212c70eebb9e42088dd22e556964489667698f0 GIT binary patch literal 6074 zcmb7|1yCJJy2lUh?ivX05S-u;+yfkf+rcFqBoH*XYj8Vw@Zc^9!QI_GXz+uCM|SUf zxx0IB)xMhQshO$ie|Pow`?q{*ig57w0000Pu<4c}V>(AF^?(il=wJf?zrHHJ(v)D6 zSCQeccYJScXJ^6=b~af~_7Ir5TkN&uuCL<7i*vQ(>hbDj?~jj-2R&CovhEW#V~|8l zP@Cx^>^8@)1{Fyq@Xl8QlcMFw6<;My#t7}Y`0PSAp$AP_2SShDtE-++*4bU3w+aUpLH#5;|JUv{Eeetj!R4XZ@u_Yi_7knC>^z%SJHgJl~C zlI+ArCik-FH;^P-yBS0qYA3=Uxk-YzfeE0YCWLRLqWa7+7b|B5j*Vz(Y1>1oZeW;S zT$v40`nm}?Y)_7&W= z6*Tc~e`ykSb#{e`X&xWb+IwwxX70$)iVHcNC4BRxHC}Y}HPpC2OEl%==jvwNW90LA z5rBy(X6p+`J|y1{sI95-XsuduDBmqciybZtE4+F=a%I->=+y;?YExVy(vzh}50u)%R($0X0HPT+ zd+&;(`=NZlB*yHuP?Q;bi-<+FFxs6=sK-FJDh*wRLgfi<4u~$L{(^E`20Lz#cX`YZhf^4b26q*y*yM(%`cR zAM#FX5#ZG2Og@;Vz|eQ>6W!8sfT-&t5RuTBJ$6*WTBtFgk?eEB`7Uyv_%t*LKpO?Z zilO_n#`Y0#*hyEkt_3jUJ)ArhI^P0tHQfLdbUZAQ{tixS$p4_An?lJsE=x&Xt}lac z&g=2Rwq_)v57LT(KR6j=&2~kDnT4hJy`%sL9udyMFiGjlSJ`t1g|d}j-%gchK%VJP zBcU@ae6#!%lGyQ<&!&WZ+>1%6ddd2`%gp^pG0U6tAnp!tE65;W?DGjcmV~V+SKjoJ zi6CeCvP_5*v%0QS31hJla2KmpLpd+OVK!^c;-eB~DV-PbS5+ad*YTKDXK_LmHI$5! z^x#K&pC5p&vA3#`+^y)$t6z2F&1qpj_)Dz$;xo8BnrNaVu{30>FQ)R(j#V#|rP08X zP-e&=6Za_|$YeU%t27X?&F}!llS$P~bB}YKlY`SNVf7-CCf&*tY#mfGzXN$1*cn&0 zfXW0c11_O#U6B0CHQTRZ-M3B0v$Af3mpqr{@NS^4ST0kK1QH zzF$mEyUbDF9up{fi)HolVJXKYtYeDGccMr%Wu>Dtwg1J8(lB&Em@G@UBjSiSf2n{>ju%K18DbiO;+w|bZ3p@?yQy7ZQS2+&f*Qk(fv8c&R;7~VUZ z>WURDA>tATLl=}fI-1GDt};!}NU9lQ4@=;I@f)P~);sY9rjuz} zebz{Zh&w(S3+mLW>MgPT)e8hOq7p@LQ=xMi-6}MX8+VsI`LK&yh8>LfwbN}o2|tHJ z;(%%ra3PHo&D8=MJ0z3i9DElKt!OmMN(yv}LT8opgPE{lw6X5~JI@z91%{(#lkX_? zR>Dnm_gK`MPbC2~6We}nO9&UPo94VFXTY9_N+$~z<6r!2b=z&;>5_0e0B}=pObR$du00Z z3XpRGzk5la;C-DE^+48tB+};_3&g1`fqZHWSrs{BK2$6a+v+Inx_5+kJW?n3WOX-A zQ*LDn$d*fI&?h>Tk9(v1Ft+p2z7cO>vTt$x<694@Vl}HRWrJxc;AU=wWjgE%h%~v* zGz%-w3s%dx!Aw4_*s67aYKHk$(3te zQJ>GF3x?lu8pjS{dwW_$k#8ztVlNW zi)VRG;I#&RkZH($oz9!^x5t1k`x;TDPtFubn{9(~q2UF0Vm?{k6m>sb&OUCIGQ3z& z?!Y3BP7fOJaARc&c+Y7QmVEh&4$`}?pEO2Wv`QI3UuRb19Yie9*fuZx*_44v{lv@eQoU|qeY(pOc`$ zbz$nD)JOtbw8uLp_uge`v}j0;StwWN z`fc2{49t4*CAi%8#v!IPGvG7I2GgJ zh6OIPO_nJzkPAkQ^M<9fsj~5^-S~6ci(t6cJ6*654S-Jo4H47A)HGPKBQXt|A(ANg ztj#7QcX%w`f<Rh_n#jvY1KN7#c@X7$JrnkI(+vx;bbyIu{1Io%@bI9ER!K0rLOTKowxIvn0 zuj`19ONt#2qifYLwvzSQY$~4WpOh^#6d|Z-yb4WbSubUAs!{!>thMB2L0nP^$}VQQ zp=R_^NpDAo4$S5ec;te`bJk9qA!p;SKd7}$Xw;dWe>uZpHTw?Xdn2hjBxQTMjNU+6 zMChCBfMB%f*>v|=YIyHJSkKI$mvf&USYoSS%s==ZcjnW}V_>>@P+$l4Nd4_IZL*Ev zc({U*6;dO5XfPX2qS87Z+0is6@(!tJlPq|9kuLbB1{95o zkE2X^#uTGJ+=91RMd!53$~U(+ocxD2T5#dLk^@?x7CW+*B27PTt{Jd#Sk`hEbWigl zg{+5*yZlcq(tPlAax*RGE|xx-p6m!Sre;)Z6nO5#8pU`B_Cd8dqeCb|b+(}|g1mjR zebU_;&YII{2Zge&0*r4h2a#&Mn!3Jt&-@(9VhnC>Fe^R+VD>tn;P4m5QWoL z=>o+B7tRYcK>?R5?#P$Ym@bCi&YtvX#lSAH!wUD(RVT{wu#w>^EJ36#Nzq|5wm>ps zKGoTm#2?u2^PSTXJZ zJ*-xnnBb7t1thZmgKfOcnQW7sufipJLDAzN_~a5=P+;vBdyJauVq_e)TUhKm1T|TB*#{3dFI6k&TR=H zdZF_^Uplko12Bw@z(GHLmZ_v%R{_TQ&3ksfVSe-I5_>c-T_nj}I+pyUsvY*8Ug;3c zGFeh=C@%ugJR0~}ty&73d-+Re-LcC1bij02md+1Krw}rTJ0|+aWf$B??b7JMoOwU5 z%e$n(77lIxEZ=A`IxV<*i*RO1;Lx*kZO3ndDLpCnMG_O|HA==>*HCR3wP)@kL~KRc zb?rGj>xF0AU;FEgxY&S`SRnl>mDAO|dhDbjyWtYMn~O90df50F*K+dj_@P?Zy-+O9 z?dr*ZUS@|~6>q0Q3?@$ve#hW9Ws?`$sT4Yx-!w6p;n63}UXqggyq{9N8|cRvZ4hi| zqY$SqFIZ#$gdB}3vm9cWv`%^_J=5~j6a03o&ebio zfSg^lG!X!Bmbc!TuI^|682C#V0N@{IEya;h7WrWobw>7)aW+LZ=8>V%NoB-vg6&;K zTB)szCcy7jqILaW@!T#Mu)_iXLv-fjwYFqIhUA94F#ho%y>$0vChW0+aOLn1GJxAJ*0~IY}m~ve`EIaHTJy%Vy zTaDSA!;%z3FP7)FF~YAF(Yh+s^X!Q=ngc&D=bI<;qx*lrf_`zIR2B+U+A(gzL1+#} z*4xJ|TAujiWJZbxij$XjE|f+jWJ#!z5GAxeW1SFvXbKhmFwYi(b#-9p{fMr^*`9gB zJFzi5CBKHzDq3e+PE`)slvLbMgpt=N*Qz}Fs$g4dr4t8X%7InUP)z);Cf1))%R7mj znRX-2lqxUJ^( zo^9KtbI(|y&#D#G?}~=7ig;tuis$Bhl+*uFwF`vPYD*}?LTjX{K?6$PFL7~y!3W9$(MFqE6A&Ncq4HowuX&9OI7DxrNg}>qJ zKKkNljdMsC=SYrtWl6GO)ULFHUk+b?uBaD*`;-t{0Za13&x@4PsByu@)u6g~z96Q` z-ZLy!wLSjU295*u(R;`1d$;D!-GQ8|bdDE4mMhtzXuom$1$84AJN3mA(05O`;{Af! z(ZL1e;KJef*2UVRn-G{k-IC=GtcP3y2{8VD6v{gYGS3=F;jBX$|t15b^~!_i>&a zguD?eQf+VuT!2KX{_}TA!p5tr6s)U_6PI`{ZR2+gdk-?tep)g8!~o+R=P6WGKn}I< z*&E7z{^V6YZU0wa8tY7ap9Zeu$*<^7gJqGeuv)ZdEM=Ww; zx7`6-(-UG7`9&NUKVsm+TFdv>t0`cG3mca9#g9kgPJkXgw+{>DsoII#Qm=WZ;Z@5) zc9P-2pOJajokJ;J^jP18M2f@a(;;_9*MfFW zGDJ0MavBE{gPWjQKFvH7J^1~WaW8%J(nkkYOFE-YQnw6jYCjL0gS`o#trmJI;$scI z9z$C&n_8*e9858CR>krC$4}RxM2?D?ONcsUN8zm{HBRPM580VBcNmrOo6I?8PQAZl9wN zXL)DZZLuH@qS~>Uskg$q`jsNX{a40@jS8n7R6x(E*Zq?8elZ6}?ivW^Xp=Li_`;~B z3WA#;O*tmdH&|v(PW71$@MVc0vg-E>CEE#<$L2_7cU<4~LP$apsYGjMq^p|lb^#yX zO1sAp-d~Z>^J-mz5n!n_MEPqGi3GUD>6AoLj|DKyll@^}@!|eHgZc+b0KX?H{-UQ(`P5$ulz%Av)u!TC zyh--7T@x#R!Gn(=4hznVdR3rpYt z{;T=)pNfBP>0gQf05;%A0``fX|LyAE8~k4b{O=b3ryAfXz~WD8zft^c_+S0`Ga~2Zwop?YO+^3!GhG_!~k{CfKz7eS*m literal 0 HcmV?d00001 diff --git a/security/manager/ssl/tests/unit/test_signed_dir.js b/security/manager/ssl/tests/unit/test_signed_dir.js new file mode 100644 index 000000000000..4c37374edbac --- /dev/null +++ b/security/manager/ssl/tests/unit/test_signed_dir.js @@ -0,0 +1,173 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/ZipUtils.jsm"); + +do_get_profile(); // must be called before getting nsIX509CertDB +const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); + +var gSignedXPI = do_get_file("test_signed_apps/sslcontrol.xpi", false); +var gTarget = FileUtils.getDir("TmpD", ["test_signed_dir"]); +gTarget.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + +// tamper data structure, each element is optional +// { copy: [[path,newname], [path2,newname2], ...], +// delete: [path, path2, ...], +// corrupt: [path, path2, ...] +// } + +function prepare(tamper) { + ZipUtils.extractFiles(gSignedXPI, gTarget); + + // copy files + if (tamper.copy) { + tamper.copy.forEach(i => { + let f = gTarget.clone(); + i[0].split('/').forEach(seg => {f.append(seg);}); + f.copyTo(null, i[1]); + }); + } + + // delete files + if (tamper.delete) { + tamper.delete.forEach(i => { + let f = gTarget.clone(); + i.split('/').forEach(seg => {f.append(seg);}); + f.remove(true); + }); + } + + // corrupt files + if (tamper.corrupt) { + tamper.corrupt.forEach(i => { + let f = gTarget.clone(); + i.split('/').forEach(seg => {f.append(seg);}); + let s = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY); + const str = "Kilroy was here"; + s.write(str, str.length); + s.close(); + }); + } + + return gTarget; +} + + +function check_result(name, expectedRv, dir) { + return function verifySignedDirCallback(rv, aSignerCert) { + equal(rv, expectedRv, name + " rv:"); + equal(aSignerCert != null, Components.isSuccessCode(expectedRv), + "expecting certificate:"); + // cleanup and kick off next test + dir.remove(true); + run_next_test(); + }; +} + +function verifyDirAsync(name, expectedRv, tamper) { + let targetDir = prepare(tamper); + certdb.verifySignedDirectoryAsync( + Ci.nsIX509CertDB.AddonsPublicRoot, targetDir, + check_result(name, expectedRv, targetDir)); +} + + +// +// the tests +// + +add_test(function() { + verifyDirAsync("'valid'", Cr.NS_OK, {} /* no tampering */ ); +}); + +add_test(function() { + verifyDirAsync("'no meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED, + {delete: ["META-INF"]}); +}); + +add_test(function() { + verifyDirAsync("'empty meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED, + {delete: ["META-INF/mozilla.rsa", + "META-INF/mozilla.sf", + "META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'two rsa files'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {copy: [["META-INF/mozilla.rsa","extra.rsa"]]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt rsa file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/mozilla.rsa"]}); +}); + +add_test(function() { + verifyDirAsync("'missing sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {delete: ["META-INF/mozilla.sf"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/mozilla.sf"]}); +}); + +add_test(function() { + verifyDirAsync("'extra .sf file (invalid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["META-INF/mozilla.rsa","extra.sf"]]}); +}); + +add_test(function() { + verifyDirAsync("'extra .sf file (valid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["META-INF/mozilla.sf","extra.sf"]]}); +}); + +add_test(function() { + verifyDirAsync("'missing manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {delete: ["META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, + {corrupt: ["META-INF/manifest.mf"]}); +}); + +add_test(function() { + verifyDirAsync("'missing file'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING, + {delete: ["bootstrap.js"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt file'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, + {corrupt: ["bootstrap.js"]}); +}); + +add_test(function() { + verifyDirAsync("'extra file'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["bootstrap.js","extra"]]}); +}); + +add_test(function() { + verifyDirAsync("'missing file in dir'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING, + {delete: ["content/options.xul"]}); +}); + +add_test(function() { + verifyDirAsync("'corrupt file in dir'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, + {corrupt: ["content/options.xul"]}); +}); + +add_test(function() { + verifyDirAsync("'extra file in dir'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, + {copy: [["content/options.xul","extra"]]}); +}); + +do_register_cleanup(function() { + if (gTarget.exists()) { + gTarget.remove(true); + } +}); + +function run_test() { + run_next_test(); +} \ No newline at end of file diff --git a/security/manager/ssl/tests/unit/xpcshell.ini b/security/manager/ssl/tests/unit/xpcshell.ini index 4f0aedf9a797..2e340c8c210b 100644 --- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -70,6 +70,7 @@ run-sequentially = hardcoded ports [test_cert_version.js] [test_signed_apps.js] [test_signed_apps-marketplace.js] +[test_signed_dir.js] [test_cert_eku-CA_EP.js] [test_cert_eku-CA_EP_NS_OS_SA_TS.js] From b6db17399a07fa82c0a3efc1fddd78f695a51030 Mon Sep 17 00:00:00 2001 From: Daniel Veditz Date: Mon, 4 May 2015 18:00:00 +0200 Subject: [PATCH 25/42] Bug 1038072 - call unpacked jar verification from XPIProvider. r=mossop --HG-- extra : rebase_source : de5a3ea103ca93a6dab98410aa121428a31a7f08 extra : source : 266853fe89654e03923048aa4e50789b411a97fc --- .../mozapps/extensions/internal/XPIProvider.jsm | 14 ++++++++++++-- .../extensions/test/xpcshell/xpcshell-shared.ini | 6 ++++++ .../mozapps/extensions/test/xpcshell/xpcshell.ini | 6 ------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 39254faf4c5e..c63fef91c131 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1357,8 +1357,18 @@ function verifyDirSignedState(aDir, aAddon) { if (!SIGNED_TYPES.has(aAddon.type)) return Promise.resolve(undefined); - // TODO: Get the certificate for an unpacked add-on (bug 1038072) - return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING); + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + + let root = Ci.nsIX509CertDB.AddonsPublicRoot; + if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false)) + root = Ci.nsIX509CertDB.AddonsStageRoot; + + return new Promise(resolve => { + certDB.verifySignedDirectoryAsync(root, aDir, (aRv, aCert) => { + resolve(getSignedStatus(aRv, aCert, aAddon.id)); + }); + }); } /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index a4d6d792c268..13daaa72dc0a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -236,6 +236,12 @@ fail-if = buildapp == "mulet" || os == "android" [test_pref_properties.js] [test_registry.js] [test_safemode.js] +[test_signed_verify.js] +[test_signed_inject.js] +skip-if = true +[test_signed_install.js] +run-sequentially = Uses hardcoded ports in xpi files. +[test_signed_migrate.js] [test_startup.js] # Bug 676992: test consistently fails on Android fail-if = os == "android" diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index a59e9e3b8d2b..56ac519751e7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -22,12 +22,6 @@ skip-if = appname != "firefox" [test_provider_unsafe_access_shutdown.js] [test_provider_unsafe_access_startup.js] [test_shutdown.js] -[test_signed_verify.js] -[test_signed_inject.js] -skip-if = true -[test_signed_install.js] -run-sequentially = Uses hardcoded ports in xpi files. -[test_signed_migrate.js] [test_XPIcancel.js] [test_XPIStates.js] From 83401d4067f53657504c4860a9f6d61130467422 Mon Sep 17 00:00:00 2001 From: Felipe Gomes Date: Thu, 7 May 2015 17:38:56 -0300 Subject: [PATCH 26/42] Bug 1161260 - Make e10s opt-in on Aurora and display popup asking users to try it. r=billm --- browser/app/profile/firefox.js | 2 +- browser/components/nsBrowserGlue.js | 14 ++++++++------ configure.in | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 0238c5cb6fc8..b3e2f916e3a3 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1857,7 +1857,7 @@ pref("browser.polaris.enabled", false); pref("privacy.trackingprotection.ui.enabled", false); #endif -#ifdef E10S_TESTING_ONLY +#ifdef NIGHTLY_BUILD // At the moment, autostart.2 is used, while autostart.1 is unused. // We leave it here set to false to reset users' defaults and allow // us to change everybody to true in the future, when desired. diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index e597ff802c1d..f18df9fa9f17 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -2791,8 +2791,11 @@ let E10SUINotification = { checkStatus: function() { let skipE10sChecks = false; try { - skipE10sChecks = (UpdateChannel.get() != "nightly") || - Services.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y"); + let updateChannel = UpdateChannel.get(); + let channelAuthorized = updateChannel == "nightly" || updateChannel == "aurora"; + + skipE10sChecks = !channelAuthorized || + UpdateServices.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y"); } catch(e) {} if (skipE10sChecks) { @@ -2911,7 +2914,7 @@ let E10SUINotification = { let browser = win.gBrowser.selectedBrowser; - let promptMessage = "Would you like to help us test multiprocess Nightly (e10s)? You can also enable e10s in Nightly preferences. Notable fixes:"; + let promptMessage = "Multi-process is coming soon to Firefox. You can start using it now to get early access to some of the benefits:"; let mainAction = { label: "Enable and Restart", accessKey: "E", @@ -2944,9 +2947,8 @@ let E10SUINotification = { win.PopupNotifications.show(browser, "enable-e10s", promptMessage, null, mainAction, secondaryActions, options); let highlights = [ - "Less crashing!", - "Improved add-on compatibility and DevTools", - "PDF.js, Web Console, Spellchecking, WebRTC now work" + "Improved responsiveness", + "Fewer crashes" ]; let doorhangerExtraContent = win.document.getElementById("enable-e10s-notification") diff --git a/configure.in b/configure.in index 19bd8ecea36b..36739476287d 100644 --- a/configure.in +++ b/configure.in @@ -3519,10 +3519,10 @@ AC_SUBST(NIGHTLY_BUILD) AC_SUBST(RELEASE_BUILD) dnl ======================================================== -dnl Multiprocess Firefox Nightly Testing UI +dnl Multiprocess Firefox Testing UI - Nightly and Aurora dnl To be removed in Bug 1003313 dnl ======================================================== -if test -n "$NIGHTLY_BUILD"; then +if test -z "$RELEASE_BUILD"; then E10S_TESTING_ONLY=1 AC_DEFINE(E10S_TESTING_ONLY) fi From 570a96ef27c2b79b1c689d7770356c7af8cb7eb8 Mon Sep 17 00:00:00 2001 From: Tomas Flek Date: Tue, 5 May 2015 10:09:29 +0200 Subject: [PATCH 27/42] Bug 1127139 - BounceAnimator incorrectly overrides start. r=liuche --HG-- rename : mobile/android/base/animation/BounceAnimator.java => mobile/android/base/animation/BounceAnimatorBuilder.java --- ...imator.java => BounceAnimatorBuilder.java} | 9 ++--- .../android/base/home/HomePagerTabStrip.java | 40 ++++++++++--------- mobile/android/base/moz.build | 2 +- 3 files changed, 26 insertions(+), 25 deletions(-) rename mobile/android/base/animation/{BounceAnimator.java => BounceAnimatorBuilder.java} (89%) diff --git a/mobile/android/base/animation/BounceAnimator.java b/mobile/android/base/animation/BounceAnimatorBuilder.java similarity index 89% rename from mobile/android/base/animation/BounceAnimator.java rename to mobile/android/base/animation/BounceAnimatorBuilder.java index cd8e8f599f89..562e08f7e729 100644 --- a/mobile/android/base/animation/BounceAnimator.java +++ b/mobile/android/base/animation/BounceAnimatorBuilder.java @@ -18,7 +18,7 @@ import com.nineoldandroids.animation.ValueAnimator; * After constructing an instance, animations can be queued up sequentially with the * {@link #queue(Attributes) queue} method. */ -public class BounceAnimator extends ValueAnimator { +public class BounceAnimatorBuilder extends ValueAnimator { public static final class Attributes { public final float value; @@ -34,7 +34,7 @@ public class BounceAnimator extends ValueAnimator { private final String mPropertyName; private final List animatorChain = new LinkedList(); - public BounceAnimator(View view, String property) { + public BounceAnimatorBuilder(View view, String property) { mView = view; mPropertyName = property; } @@ -46,10 +46,9 @@ public class BounceAnimator extends ValueAnimator { animatorChain.add(animator); } - @Override - public void start() { + public AnimatorSet build(){ AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(animatorChain); - animatorSet.start(); + return animatorSet; } } diff --git a/mobile/android/base/home/HomePagerTabStrip.java b/mobile/android/base/home/HomePagerTabStrip.java index 8b885f93c5e4..7edff5a0c072 100644 --- a/mobile/android/base/home/HomePagerTabStrip.java +++ b/mobile/android/base/home/HomePagerTabStrip.java @@ -6,8 +6,8 @@ package org.mozilla.gecko.home; import org.mozilla.gecko.R; -import org.mozilla.gecko.animation.BounceAnimator; -import org.mozilla.gecko.animation.BounceAnimator.Attributes; +import org.mozilla.gecko.animation.BounceAnimatorBuilder; +import org.mozilla.gecko.animation.BounceAnimatorBuilder.Attributes; import org.mozilla.gecko.animation.TransitionsTracker; import android.content.Context; @@ -33,12 +33,11 @@ import com.nineoldandroids.view.ViewHelper; class HomePagerTabStrip extends PagerTabStrip { private static final String LOGTAG = "PagerTabStrip"; - private static final int ANIMATION_DELAY_MS = 250; + private static final int ANIMATION_DELAY_MS = 50; private static final int ALPHA_MS = 10; private static final int BOUNCE1_MS = 350; private static final int BOUNCE2_MS = 200; private static final int BOUNCE3_MS = 100; - private static final int BOUNCE4_MS = 100; private static final int INIT_OFFSET = 100; private final Paint shadowPaint; @@ -103,30 +102,33 @@ class HomePagerTabStrip extends PagerTabStrip { final AnimatorSet alphaAnimatorSet = new AnimatorSet(); alphaAnimatorSet.playTogether(alpha1, alpha2); - alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS); alphaAnimatorSet.setDuration(ALPHA_MS); + alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS); // Bounce animation. final float bounceDistance = getWidth()/100f; // Hack: TextFields still have 0 width here. - final BounceAnimator prevBounceAnimator = new BounceAnimator(prevTextView, "translationX"); - prevBounceAnimator.queue(new Attributes(bounceDistance, BOUNCE1_MS)); - prevBounceAnimator.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS)); - prevBounceAnimator.queue(new Attributes(0, BOUNCE4_MS)); - prevBounceAnimator.setStartDelay(ANIMATION_DELAY_MS); + final BounceAnimatorBuilder prevBounceAnimatorBuilder = new BounceAnimatorBuilder(prevTextView, "translationX"); + prevBounceAnimatorBuilder.queue(new Attributes(bounceDistance, BOUNCE1_MS)); + prevBounceAnimatorBuilder.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS)); + prevBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS)); - final BounceAnimator nextBounceAnimator = new BounceAnimator(nextTextView, "translationX"); - nextBounceAnimator.queue(new Attributes(-bounceDistance, BOUNCE1_MS)); - nextBounceAnimator.queue(new Attributes(bounceDistance/4, BOUNCE2_MS)); - nextBounceAnimator.queue(new Attributes(0, BOUNCE4_MS)); - nextBounceAnimator.setStartDelay(ANIMATION_DELAY_MS); + final BounceAnimatorBuilder nextBounceAnimatorBuilder = new BounceAnimatorBuilder(nextTextView, "translationX"); + nextBounceAnimatorBuilder.queue(new Attributes(-bounceDistance, BOUNCE1_MS)); + nextBounceAnimatorBuilder.queue(new Attributes(bounceDistance/4, BOUNCE2_MS)); + nextBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS)); - TransitionsTracker.track(nextBounceAnimator); + final AnimatorSet bounceAnimatorSet = new AnimatorSet(); + bounceAnimatorSet.playTogether(prevBounceAnimatorBuilder.build(), nextBounceAnimatorBuilder.build()); + + TransitionsTracker.track(nextBounceAnimatorBuilder); + + final AnimatorSet titlesAnimatorSet = new AnimatorSet(); + titlesAnimatorSet.playTogether(alphaAnimatorSet, bounceAnimatorSet); + titlesAnimatorSet.setStartDelay(ANIMATION_DELAY_MS); // Start animations. - alphaAnimatorSet.start(); - prevBounceAnimator.start(); - nextBounceAnimator.start(); + titlesAnimatorSet.start(); } private class PreDrawListener implements ViewTreeObserver.OnPreDrawListener { diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 6e9319abcd67..2fcb2e36b0dc 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -144,7 +144,7 @@ gbjar.sources += [ 'AndroidGamepadManager.java', 'animation/AnimationUtils.java', 'animation/AnimatorProxy.java', - 'animation/BounceAnimator.java', + 'animation/BounceAnimatorBuilder.java', 'animation/HeightChangeAnimation.java', 'animation/PropertyAnimator.java', 'animation/Rotate3DAnimation.java', From 5c04d7525998090dcc566ffe24590895f65cb1d0 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Thu, 7 May 2015 17:53:46 -0400 Subject: [PATCH 28/42] Bug 1162735 - Re-add code that got removed accidentally to fix context menus. r=florian --- browser/components/pocket/Pocket.jsm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/browser/components/pocket/Pocket.jsm b/browser/components/pocket/Pocket.jsm index a06c0b752cc2..adaa1754006f 100644 --- a/browser/components/pocket/Pocket.jsm +++ b/browser/components/pocket/Pocket.jsm @@ -15,6 +15,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm"); let Pocket = { + get site() Services.prefs.getCharPref("browser.pocket.site"), + get listURL() { return "https://" + Pocket.site; }, + /** * Functions related to the Pocket panel UI. */ From 6e4ebfd8c35241f5026bc5cd7a2913fa6269568d Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Thu, 7 May 2015 17:55:21 -0400 Subject: [PATCH 29/42] Bug 1161793 - Wait to run the Pocket popupshowing code until the popupshowing event is dispatched, same for the popupshown code. r=dolske --- .../customizableui/content/panelUI.js | 1 + browser/components/pocket/Pocket.jsm | 14 ++++++++++- browser/components/pocket/main.js | 25 +++++++------------ .../customizableui/panelUIOverlay.inc.css | 5 ++++ 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js index 37191afd4572..b2c00aa45fbb 100644 --- a/browser/components/customizableui/content/panelUI.js +++ b/browser/components/customizableui/content/panelUI.js @@ -332,6 +332,7 @@ const PanelUI = { tempPanel.setAttribute("type", "arrow"); tempPanel.setAttribute("id", "customizationui-widget-panel"); tempPanel.setAttribute("class", "cui-widget-panel"); + tempPanel.setAttribute("viewId", aViewId); if (this._disableAnimations) { tempPanel.setAttribute("animate", "false"); } diff --git a/browser/components/pocket/Pocket.jsm b/browser/components/pocket/Pocket.jsm index adaa1754006f..78074e40ea81 100644 --- a/browser/components/pocket/Pocket.jsm +++ b/browser/components/pocket/Pocket.jsm @@ -23,8 +23,20 @@ let Pocket = { */ onPanelViewShowing(event) { let window = event.target.ownerDocument.defaultView; + window.addEventListener("popupshowing", Pocket.onPocketPanelShowing, true); + window.addEventListener("popupshown", Pocket.onPocketPanelShown, true); + }, + + onPocketPanelShowing(event) { + let window = event.target.ownerDocument.defaultView; + window.removeEventListener("popupshowing", Pocket.onPocketPanelShowing, true); window.pktUI.pocketButtonOnCommand(event); - window.pktUI.pocketPanelDidShow(event) + }, + + onPocketPanelShown(event) { + let window = event.target.ownerDocument.defaultView; + window.removeEventListener("popupshown", Pocket.onPocketPanelShown, true); + window.pktUI.pocketPanelDidShow(event); }, onPanelViewHiding(event) { diff --git a/browser/components/pocket/main.js b/browser/components/pocket/main.js index f209201e7460..389d614762ad 100644 --- a/browser/components/pocket/main.js +++ b/browser/components/pocket/main.js @@ -301,12 +301,10 @@ var pktUI = (function() { } showPanel("chrome://browser/content/pocket/panels/signup.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&fxasignedin=" + fxasignedin + "&variant=" + pktApi.getSignupAB(), { onShow: function() { - resizePanel({ - width: 300, - height: startheight - }); }, onHide: panelDidHide, + width: 300, + height: startheight }); }); } @@ -325,12 +323,6 @@ var pktUI = (function() { showPanel("chrome://browser/content/pocket/panels/saved.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&premiumStatus=" + (pktApi.isPremiumUser() ? '1' : '0'), { onShow: function() { - // Open and resize the panel - resizePanel({ - width: 350, - height: 263 - }); - // Send error message for invalid url if (!isValidURL) { var error = new Error('Only links can be saved'); @@ -369,6 +361,8 @@ var pktUI = (function() { pktApi.addLink(url, options); }, onHide: panelDidHide, + width: 350, + height: 267 }); } @@ -397,6 +391,11 @@ var pktUI = (function() { // do it this hacky way for now currentPanelDidShow = options.onShow; currentPanelDidHide = options.onHide; + + resizePanel({ + width: options.width, + height: options.height + }); } /** @@ -411,15 +410,9 @@ var pktUI = (function() { var iframe = getPanelFrame(); iframe.width = options.width; iframe.height = options.height; - return; // TODO : Animate the change if given options.animate = true getPanel().sizeTo(options.width, options.height); - setTimeout(function(){ - // we set the iframe size directly because it does not automatically stretch vertically - var height = document.getElementById('pocket-panel-container').clientHeight + 'px'; - getPanelFrame().style.height = height; - },1); } /** diff --git a/browser/themes/shared/customizableui/panelUIOverlay.inc.css b/browser/themes/shared/customizableui/panelUIOverlay.inc.css index 95b5926272ff..04afc2284c24 100644 --- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -256,6 +256,11 @@ panelview:not([mainview]) .toolbarbutton-text, padding: 4px 0; } +.cui-widget-panel[viewId="PanelUI-pocketView"] > .panel-arrowcontainer > .panel-arrowcontent { + padding-top: 0; + padding-bottom: 0; +} + .cui-widget-panel.cui-widget-panelWithFooter > .panel-arrowcontainer > .panel-arrowcontent { padding-bottom: 0; } From 8ee20de4400e3643d6831c0cad420fd333a848ce Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Thu, 7 May 2015 09:13:35 -0700 Subject: [PATCH 30/42] Bug 1160279: Recreate nsIFiles in the content process for dragged files. r=smaug --HG-- extra : rebase_source : f90e7cb31486ef8fd29769c86f25f8341b7b97e6 extra : amend_source : ec7283835b92bc45715549420163059e8f45f187 --- dom/ipc/ContentChild.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index a2b43f7aab7c..b63e396fc1ae 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2898,7 +2898,16 @@ ContentChild::RecvInvokeDragSession(nsTArray&& aTransfers, } else if (item.data().type() == IPCDataTransferData::TPBlobChild) { BlobChild* blob = static_cast(item.data().get_PBlobChild()); nsRefPtr fileImpl = blob->GetBlobImpl(); - variant->SetAsISupports(fileImpl); + nsString path; + ErrorResult result; + fileImpl->GetMozFullPathInternal(path, result); + if (result.Failed()) { + variant->SetAsISupports(fileImpl); + } else { + nsCOMPtr file; + NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, getter_AddRefs(file)); + variant->SetAsISupports(file); + } } else { continue; } From d16da0c2cdae0052bf3d8171a689a6c73422ec57 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Thu, 30 Apr 2015 14:51:02 -0700 Subject: [PATCH 31/42] Bug 1129029 - Telemetry probes for reader mode performance. r=Gijs --HG-- extra : rebase_source : 84d342b520b9fdec97c5c2dc406aff9576eab36f --- toolkit/components/reader/ReaderMode.jsm | 34 ++++++++++++++++++-- toolkit/components/telemetry/Histograms.json | 34 ++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/toolkit/components/reader/ReaderMode.jsm b/toolkit/components/reader/ReaderMode.jsm index c4d347a8b6c4..e00d02544687 100644 --- a/toolkit/components/reader/ReaderMode.jsm +++ b/toolkit/components/reader/ReaderMode.jsm @@ -8,6 +8,16 @@ this.EXPORTED_SYMBOLS = ["ReaderMode"]; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +// Constants for telemetry. +const DOWNLOAD_SUCCESS = 0; +const DOWNLOAD_ERROR_XHR = 1; +const DOWNLOAD_ERROR_NO_DOC = 2; + +const PARSE_SUCCESS = 0; +const PARSE_ERROR_TOO_MANY_ELEMENTS = 1; +const PARSE_ERROR_WORKER = 2; +const PARSE_ERROR_NO_ARTICLE = 3; + Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -17,6 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-comm XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"); XPCOMUtils.defineLazyGetter(this, "Readability", function() { let scope = {}; @@ -169,11 +180,17 @@ this.ReaderMode = { */ downloadAndParseDocument: Task.async(function* (url) { let uri = Services.io.newURI(url, null, null); - let doc = yield this._downloadDocument(url); + TelemetryStopwatch.start("READER_MODE_DOWNLOAD_MS"); + let doc = yield this._downloadDocument(url).catch(e => { + TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS"); + throw e; + }); + TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS"); return yield this._readerParse(uri, doc); }), _downloadDocument: function (url) { + let histogram = Services.telemetry.getHistogramById("READER_MODE_DOWNLOAD_RESULT"); return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("GET", url, true); @@ -182,12 +199,14 @@ this.ReaderMode = { xhr.onload = evt => { if (xhr.status !== 200) { reject("Reader mode XHR failed with status: " + xhr.status); + histogram.add(DOWNLOAD_ERROR_XHR); return; } let doc = xhr.responseXML; if (!doc) { reject("Reader mode XHR didn't return a document"); + histogram.add(DOWNLOAD_ERROR_NO_DOC); return; } @@ -205,6 +224,7 @@ this.ReaderMode = { } } resolve(doc); + histogram.add(DOWNLOAD_SUCCESS); } xhr.send(); }); @@ -292,10 +312,12 @@ this.ReaderMode = { * @resolves JS object representing the article, or null if no article is found. */ _readerParse: Task.async(function* (uri, doc) { + let histogram = Services.telemetry.getHistogramById("READER_MODE_PARSE_RESULT"); if (this.parseNodeLimit) { let numTags = doc.getElementsByTagName("*").length; if (numTags > this.parseNodeLimit) { this.log("Aborting parse for " + uri.spec + "; " + numTags + " elements found"); + histogram.add(PARSE_ERROR_TOO_MANY_ELEMENTS); return null; } } @@ -308,19 +330,25 @@ this.ReaderMode = { pathBase: Services.io.newURI(".", null, uri).spec }; + TelemetryStopwatch.start("READER_MODE_SERIALIZE_DOM_MS"); let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"]. createInstance(Ci.nsIDOMSerializer); - let serializedDoc = yield Promise.resolve(serializer.serializeToString(doc)); + let serializedDoc = serializer.serializeToString(doc); + TelemetryStopwatch.finish("READER_MOD_SERIALIZE_DOM_MS"); + TelemetryStopwatch.start("READER_MODE_WORKER_PARSE_MS"); let article = null; try { article = yield ReaderWorker.post("parseDocument", [uriParam, serializedDoc]); } catch (e) { Cu.reportError("Error in ReaderWorker: " + e); + histogram.add(PARSE_ERROR_WORKER); } + TelemetryStopwatch.finish("READER_MODE_WORKER_PARSE_MS"); if (!article) { this.log("Worker did not return an article"); + histogram.add(PARSE_ERROR_NO_ARTICLE); return null; } @@ -331,6 +359,8 @@ this.ReaderMode = { let flags = Ci.nsIDocumentEncoder.OutputSelectionOnly | Ci.nsIDocumentEncoder.OutputAbsoluteLinks; article.title = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils) .convertToPlainText(article.title, flags, 0); + + histogram.add(PARSE_SUCCESS); return article; }), diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 1d3e4ac001fd..8e9c735f5f0b 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7793,5 +7793,39 @@ "expires_in_version": "50", "kind": "count", "description": "Tracking whether a ServiceWorker spawn gets queued due to hitting max workers per domain limit" + }, + "READER_MODE_SERIALIZE_DOM_MS": { + "expires_in_version": "42", + "kind": "exponential", + "high": "5000", + "n_buckets": 15, + "description": "Time (ms) to serialize a DOM to send to the reader worker" + }, + "READER_MODE_WORKER_PARSE_MS": { + "expires_in_version": "42", + "kind": "exponential", + "high": "10000", + "n_buckets": 30, + "description": "Time (ms) for the reader worker to parse a document" + }, + "READER_MODE_DOWNLOAD_MS": { + "expires_in_version": "42", + "kind": "exponential", + "low": 50, + "high": "40000", + "n_buckets": 60, + "description": "Time (ms) to download a document to show in reader mode" + }, + "READER_MODE_PARSE_RESULT" : { + "expires_in_version": "42", + "kind": "enumerated", + "n_values": 5, + "description": "The result of trying to parse a document to show in reader view (0=Success, 1=Error too many elements, 2=Error in worker, 3=Error no article)" + }, + "READER_MODE_DOWNLOAD_RESULT" : { + "expires_in_version": "42", + "kind": "enumerated", + "n_values": 5, + "description": "The result of trying to download a document to show in reader view (0=Success, 1=Error XHR, 2=Error no document)" } } From 78fd08e3f7490e4c58c6cfc836c9f5917d332e5b Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Fri, 8 May 2015 11:01:00 +1000 Subject: [PATCH 32/42] Bug 1160371 - use mock storage in the oauth token test to avoid intermittent orange on b2g. r=zaach --- .../tests/xpcshell/test_oauth_tokens.js | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js index 556d63c9041c..5de59fe94181 100644 --- a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js +++ b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js @@ -19,7 +19,26 @@ function promiseNotification(topic) { }); } -// Just enough mocks so we can avoid hawk etc. +// Just enough mocks so we can avoid hawk and storage. +let MockStorage = function() { + this.data = null; +}; +MockStorage.prototype = Object.freeze({ + set: function (contents) { + this.data = contents; + return Promise.resolve(null); + }, + get: function () { + return Promise.resolve(this.data); + }, + getOAuthTokens() { + return Promise.resolve(null); + }, + setOAuthTokens(contents) { + return Promise.resolve(); + }, +}); + function MockFxAccountsClient() { this._email = "nobody@example.com"; this._verified = false; @@ -43,6 +62,7 @@ function MockFxAccounts(mockGrantClient) { return new FxAccounts({ fxAccountsClient: new MockFxAccountsClient(), getAssertion: () => Promise.resolve("assertion"), + signedInUserStorage: new MockStorage(), _destroyOAuthToken: function(tokenData) { // somewhat sad duplication of _destroyOAuthToken, but hard to avoid. return mockGrantClient.destroyToken(tokenData.token).then( () => { From 64ccac222756ab1b56fde270ce58f9ecb2b23f87 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Fri, 8 May 2015 11:02:11 +1000 Subject: [PATCH 33/42] Bug 1140131 - pre-instantiate some xpcom services used by the debugger to avoid recursive instantiation. r=jlongster --- toolkit/devtools/server/actors/script.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 7fee3215f3c0..bbfe0ddfde18 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -39,6 +39,17 @@ let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", // collections, etc. let OBJECT_PREVIEW_MAX_ITEMS = 10; +// The debugger may be called as XPCOM services are instantiated. The debugger +// itself uses xpcom services to fetch the source code for such services, with +// the end result being that we attempt to recursively create some services, +// which results in the component manager asserting in debug builds +// (ASSERTION: Recursive GetService!). We solve this by pre-creating the +// following services when the debugger is attached. See bug 1140131 for more. +const REQUIRED_SERVICES = [ + "@mozilla.org/addons/integration;1", + "@mozilla.org/uriloader/handler-service;1" +]; + /** * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap * the resulting `value` or `reason` properties in a Debugger.Object instance. @@ -659,6 +670,9 @@ ThreadActor.prototype = { this._state = "attached"; + // now's a good time to instantiate the services we need. + REQUIRED_SERVICES.map(cid => Cc[cid].getService(Ci.nsISupports)); + update(this._options, aRequest.options || {}); this.sources.reconfigure(this._options); this.sources.on('newSource', (name, source) => { From 325c5aa973c01a938d7214b2850b62c2bd6d89d4 Mon Sep 17 00:00:00 2001 From: Phil Ringnalda Date: Thu, 7 May 2015 22:35:49 -0700 Subject: [PATCH 34/42] Back out 1bae19c2d494 (bug 1140131) for xpcshell and dt bustage CLOSED TREE --- toolkit/devtools/server/actors/script.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index bbfe0ddfde18..7fee3215f3c0 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -39,17 +39,6 @@ let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", // collections, etc. let OBJECT_PREVIEW_MAX_ITEMS = 10; -// The debugger may be called as XPCOM services are instantiated. The debugger -// itself uses xpcom services to fetch the source code for such services, with -// the end result being that we attempt to recursively create some services, -// which results in the component manager asserting in debug builds -// (ASSERTION: Recursive GetService!). We solve this by pre-creating the -// following services when the debugger is attached. See bug 1140131 for more. -const REQUIRED_SERVICES = [ - "@mozilla.org/addons/integration;1", - "@mozilla.org/uriloader/handler-service;1" -]; - /** * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap * the resulting `value` or `reason` properties in a Debugger.Object instance. @@ -670,9 +659,6 @@ ThreadActor.prototype = { this._state = "attached"; - // now's a good time to instantiate the services we need. - REQUIRED_SERVICES.map(cid => Cc[cid].getService(Ci.nsISupports)); - update(this._options, aRequest.options || {}); this.sources.reconfigure(this._options); this.sources.on('newSource', (name, source) => { From 3071aa45eff102a674cef4cbab080d4a3aea7812 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Fri, 8 May 2015 12:05:33 +0100 Subject: [PATCH 35/42] Bug 1162917 - update readability from github repo, rs=me --HG-- extra : rebase_source : c3a1681b06f26fcab03a227db29470e88e62fcf9 --- toolkit/components/reader/JSDOMParser.js | 1 + toolkit/components/reader/Readability.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/toolkit/components/reader/JSDOMParser.js b/toolkit/components/reader/JSDOMParser.js index ef99e79aa22b..7d8b225e066f 100644 --- a/toolkit/components/reader/JSDOMParser.js +++ b/toolkit/components/reader/JSDOMParser.js @@ -283,6 +283,7 @@ "meta": true, "param": true, "source": true, + "wbr": true }; var whitespace = [" ", "\t", "\n", "\r"]; diff --git a/toolkit/components/reader/Readability.js b/toolkit/components/reader/Readability.js index 278294d874b5..6dfc314a43fd 100644 --- a/toolkit/components/reader/Readability.js +++ b/toolkit/components/reader/Readability.js @@ -109,12 +109,12 @@ Readability.prototype = { DEFAULT_MAX_PAGES: 5, // Element tags to score by default. - DEFAULT_TAGS_TO_SCORE: ["SECTION", "P", "TD", "PRE"], + DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","), // All of the regular expressions in use within readability. // Defined up here so we don't instantiate them repeatedly in loops. REGEXPS: { - unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i, + unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|related|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i, okMaybeItsACandidate: /and|article|body|column|main|shadow/i, positive: /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i, negative: /hidden|banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, @@ -122,7 +122,7 @@ Readability.prototype = { byline: /byline|author|dateline|writtenby/i, replaceFonts: /<(\/?)font[^>]*>/gi, normalize: /\s{2,}/g, - videos: /https?:\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i, + videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i, nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i, prevLink: /(prev|earl|old|new|<|«)/i, whitespace: /^\s*$/, @@ -739,7 +739,12 @@ Readability.prototype = { candidates.push(ancestor); } - ancestor.readability.contentScore += contentScore / (level === 0 ? 1 : level * 2); + // Node score divider: + // - parent: 1 (no division) + // - grandparent: 2 + // - great grandparent+: ancestor level * 3 + var scoreDivider = level === 0 ? 1 : level === 1 ? 2 : level * 3; + ancestor.readability.contentScore += contentScore / scoreDivider; }); }); From f3a908411e4b4966502ef048c28d290c5d3eee6f Mon Sep 17 00:00:00 2001 From: Mohamed Waleed Date: Mon, 27 Apr 2015 21:24:21 +0200 Subject: [PATCH 36/42] Bug 1062894 - Avoid batching tag changes if number of changed tags is low. r=mak, ttaubert --- .../places/nsINavHistoryService.idl | 2 +- toolkit/components/places/nsNavHistory.cpp | 9 +- .../components/places/nsNavHistoryResult.cpp | 3 +- .../components/places/nsNavHistoryResult.h | 1 - toolkit/components/places/nsTaggingService.js | 111 +++++++++--------- .../bookmarks/test_nsINavBookmarkObserver.js | 8 -- .../test_history_queries_tags_liveUpdate.js | 8 ++ 7 files changed, 74 insertions(+), 68 deletions(-) diff --git a/toolkit/components/places/nsINavHistoryService.idl b/toolkit/components/places/nsINavHistoryService.idl index 484c2ac17ea9..037b3ea1e476 100644 --- a/toolkit/components/places/nsINavHistoryService.idl +++ b/toolkit/components/places/nsINavHistoryService.idl @@ -1415,7 +1415,7 @@ interface nsINavHistoryService : nsISupports /** * @see runInBatchMode of nsINavHistoryService/nsINavBookmarksService */ -[scriptable, uuid(5143f2bb-be0a-4faf-9acb-b0ed3f82952c)] +[scriptable, function, uuid(5a5a9154-95ac-4e3d-90df-558816297407)] interface nsINavHistoryBatchCallback : nsISupports { void runBatched(in nsISupports aUserData); }; diff --git a/toolkit/components/places/nsNavHistory.cpp b/toolkit/components/places/nsNavHistory.cpp index 5773bdeea759..5c9e39cbbf8a 100644 --- a/toolkit/components/places/nsNavHistory.cpp +++ b/toolkit/components/places/nsNavHistory.cpp @@ -4186,10 +4186,13 @@ nsNavHistory::URIToResultNode(nsIURI* aURI, true, tagsFragment); // Should match kGetInfoIndex_* nsCOMPtr stmt = mDB->GetStatement(NS_LITERAL_CSTRING( - "SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, " - "h.last_visit_date, f.url, null, null, null, null, " - ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " + "SELECT h.id, :page_url, COALESCE(b.title, h.title), " + "h.rev_host, h.visit_count, h.last_visit_date, f.url, " + "b.id, b.dateAdded, b.lastModified, b.parent, " + ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, " + "b.guid, b.position, b.type, b.fk " "FROM moz_places h " + "LEFT JOIN moz_bookmarks b ON b.fk = h.id " "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " "WHERE h.url = :page_url ") ); diff --git a/toolkit/components/places/nsNavHistoryResult.cpp b/toolkit/components/places/nsNavHistoryResult.cpp index fb273b32f735..543bc13f4fd4 100644 --- a/toolkit/components/places/nsNavHistoryResult.cpp +++ b/toolkit/components/places/nsNavHistoryResult.cpp @@ -2767,7 +2767,7 @@ nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI) nsCOMArray matches; RecursiveFindURIs(onlyOneEntry, this, spec, &matches); - if (matches.Count() == 0 && mHasSearchTerms && !mRemovingURI) { + if (matches.Count() == 0 && mHasSearchTerms) { // A new tag has been added, it's possible it matches our query. NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node)); @@ -2838,7 +2838,6 @@ nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId, const nsACString& aGUID, const nsACString& aParentGUID) { - mRemovingURI = aURI; if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK && mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) { nsresult rv = Refresh(); diff --git a/toolkit/components/places/nsNavHistoryResult.h b/toolkit/components/places/nsNavHistoryResult.h index 5c1d33206f25..3a63384e0fda 100644 --- a/toolkit/components/places/nsNavHistoryResult.h +++ b/toolkit/components/places/nsNavHistoryResult.h @@ -674,7 +674,6 @@ public: virtual void RecursiveSort(const char* aData, SortComparator aComparator) override; - nsCOMPtr mRemovingURI; nsresult NotifyIfTagsChanged(nsIURI* aURI); uint32_t mBatchChanges; diff --git a/toolkit/components/places/nsTaggingService.js b/toolkit/components/places/nsTaggingService.js index 14dc853b304f..eedbaa69a9d7 100644 --- a/toolkit/components/places/nsTaggingService.js +++ b/toolkit/components/places/nsTaggingService.js @@ -107,30 +107,31 @@ TaggingService.prototype = { * @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not * a valid tag. */ - _convertInputMixedTagsArray: function TS__convertInputMixedTagsArray(aTags, trim=false) - { - return aTags.map(function (val) - { - let tag = { _self: this }; - if (typeof(val) == "number" && this._tagFolders[val]) { + _convertInputMixedTagsArray(aTags, trim=false) { + // Handle sparse array with a .filter. + return aTags.filter(tag => tag !== undefined) + .map(idOrName => { + let tag = {}; + if (typeof(idOrName) == "number" && this._tagFolders[idOrName]) { // This is a tag folder id. - tag.id = val; + tag.id = idOrName; // We can't know the name at this point, since a previous tag could // want to change it. - tag.__defineGetter__("name", function () this._self._tagFolders[this.id]); + tag.__defineGetter__("name", () => this._tagFolders[tag.id]); } - else if (typeof(val) == "string" && val.length > 0 && val.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) { + else if (typeof(idOrName) == "string" && idOrName.length > 0 && + idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) { // This is a tag name. - tag.name = trim ? val.trim() : val; + tag.name = trim ? idOrName.trim() : idOrName; // We can't know the id at this point, since a previous tag could // have created it. - tag.__defineGetter__("id", function () this._self._getItemIdForTag(this.name)); + tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name)); } else { throw Cr.NS_ERROR_INVALID_ARG; } return tag; - }, this); + }); }, // nsITaggingService @@ -143,35 +144,37 @@ TaggingService.prototype = { // This also does some input validation. let tags = this._convertInputMixedTagsArray(aTags, true); - let taggingService = this; - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function (aUserData) - { - tags.forEach(function (tag) - { - if (tag.id == -1) { - // Tag does not exist yet, create it. - this._createTag(tag.name); - } + let taggingFunction = () => { + for (let tag of tags) { + if (tag.id == -1) { + // Tag does not exist yet, create it. + this._createTag(tag.name); + } - if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) { - // The provided URI is not yet tagged, add a tag for it. - // Note that bookmarks under tag containers must have null titles. - PlacesUtils.bookmarks.insertBookmark( - tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null - ); - } + if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) { + // The provided URI is not yet tagged, add a tag for it. + // Note that bookmarks under tag containers must have null titles. + PlacesUtils.bookmarks.insertBookmark( + tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null + ); + } - // Try to preserve user's tag name casing. - // Rename the tag container so the Places view matches the most-recent - // user-typed value. - if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) { - // this._tagFolders is updated by the bookmarks observer. - PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name); - } - }, taggingService); + // Try to preserve user's tag name casing. + // Rename the tag container so the Places view matches the most-recent + // user-typed value. + if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) { + // this._tagFolders is updated by the bookmarks observer. + PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name); + } } - }, null); + }; + + // Use a batch only if creating more than 2 tags. + if (tags.length < 3) { + taggingFunction(); + } else { + PlacesUtils.bookmarks.runInBatchMode(taggingFunction, null); + } }, /** @@ -225,23 +228,25 @@ TaggingService.prototype = { "https://bugzilla.mozilla.org/show_bug.cgi?id=967196"); } - let taggingService = this; - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function (aUserData) - { - tags.forEach(function (tag) - { - if (tag.id != -1) { - // A tag could exist. - let itemId = this._getItemIdForTaggedURI(aURI, tag.name); - if (itemId != -1) { - // There is a tagged item. - PlacesUtils.bookmarks.removeItem(itemId); - } + let untaggingFunction = () => { + for (let tag of tags) { + if (tag.id != -1) { + // A tag could exist. + let itemId = this._getItemIdForTaggedURI(aURI, tag.name); + if (itemId != -1) { + // There is a tagged item. + PlacesUtils.bookmarks.removeItem(itemId); } - }, taggingService); + } } - }, null); + }; + + // Use a batch only if creating more than 2 tags. + if (tags.length < 3) { + untaggingFunction(); + } else { + PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null); + } }, // nsITaggingService diff --git a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js index 1aef89c57ddd..420709f4e75c 100644 --- a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js +++ b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js @@ -143,8 +143,6 @@ add_test(function onItemChanged_tags_bookmark() { const TITLE = "New title"; const TAG = "tag" gBookmarksObserver.expected = [ - { name: "onBeginUpdateBatch", // Tag addition uses a batch. - args: [] }, { name: "onItemAdded", // This is the tag folder. args: [ { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 }, @@ -181,10 +179,6 @@ add_test(function onItemChanged_tags_bookmark() { { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) }, { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) }, ] }, - { name: "onEndUpdateBatch", - args: [] }, - { name: "onBeginUpdateBatch", // Tag removal uses a batch. - args: [] }, { name: "onItemRemoved", // This is the tag. args: [ { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 }, @@ -217,8 +211,6 @@ add_test(function onItemChanged_tags_bookmark() { { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) }, { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) }, ] }, - { name: "onEndUpdateBatch", - args: [] }, ]; PlacesUtils.tagging.tagURI(uri, [TAG]); PlacesUtils.tagging.untagURI(uri, [TAG]); diff --git a/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js b/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js index 084262a63a1e..d537b6802960 100644 --- a/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js +++ b/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js @@ -160,6 +160,10 @@ add_task(function pages_searchterm_is_tag_query() compareArrayToResult([], root); gTestData.forEach(function (data) { let uri = NetUtil.newURI(data.uri); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + data.title); PlacesUtils.tagging.tagURI(uri, ["test-tag"]); compareArrayToResult([data], root); PlacesUtils.tagging.untagURI(uri, ["test-tag"]); @@ -177,6 +181,10 @@ add_task(function visits_searchterm_is_tag_query() compareArrayToResult([], root); gTestData.forEach(function (data) { let uri = NetUtil.newURI(data.uri); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + data.title); PlacesUtils.tagging.tagURI(uri, ["test-tag"]); compareArrayToResult([data], root); PlacesUtils.tagging.untagURI(uri, ["test-tag"]); From 8f2fe9ad4ffe3ca39b4ffbb690d07ac5df5aeec3 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Fri, 8 May 2015 12:43:17 +0200 Subject: [PATCH 37/42] Bug 1062894 - Fix wrong assumption in test-places-events.js. r=mossop --- addon-sdk/source/test/addons/places/lib/places-helper.js | 5 +++++ .../source/test/addons/places/lib/test-places-events.js | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/addon-sdk/source/test/addons/places/lib/places-helper.js b/addon-sdk/source/test/addons/places/lib/places-helper.js index 8107b4eae0b5..fb6cc6313289 100644 --- a/addon-sdk/source/test/addons/places/lib/places-helper.js +++ b/addon-sdk/source/test/addons/places/lib/places-helper.js @@ -143,6 +143,11 @@ function createBookmark (data) { } exports.createBookmark = createBookmark; +function historyBatch () { + hsrv.runInBatchMode(() => {}, null); +} +exports.historyBatch = historyBatch; + function createBookmarkItem (data) { let deferred = defer(); data = data || {}; diff --git a/addon-sdk/source/test/addons/places/lib/test-places-events.js b/addon-sdk/source/test/addons/places/lib/test-places-events.js index 7cdff1812cc1..e31385238ffe 100644 --- a/addon-sdk/source/test/addons/places/lib/test-places-events.js +++ b/addon-sdk/source/test/addons/places/lib/test-places-events.js @@ -29,7 +29,7 @@ const { search } = require('sdk/places/history'); const { invalidResolve, invalidReject, createTree, createBookmark, compareWithHost, addVisits, resetPlaces, createBookmarkItem, - removeVisits + removeVisits, historyBatch } = require('./places-helper'); const { save, MENU, UNSORTED } = require('sdk/places/bookmarks'); const { promisedEmitter } = require('sdk/places/utils'); @@ -236,9 +236,8 @@ exports['test history-start-batch, history-end-batch, history-start-clear'] = fu on(startEvent, 'data', startHandler); on(clearEvent, 'data', clearHandler); - createBookmark().then(() => { - resetPlaces(complete); - }) + historyBatch(); + resetPlaces(complete); }; exports['test history-visit, history-title-changed'] = function (assert, done) { From 8f3d7648d5243b8fb4646851d288efc58cb5f8d5 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Fri, 8 May 2015 12:43:24 +0200 Subject: [PATCH 38/42] Bug 1161882 - "Add a Keyword for this search..." dialog accept button is disabled. r=ttaubert --- browser/components/places/content/editBookmarkOverlay.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index 7b0fd5ee4648..30de8c48f909 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -184,7 +184,10 @@ let gEditItemOverlay = { this._namePicker.readOnly = this.readOnly; } - if (showOrCollapse("locationRow", isURI, "location")) { + // In some cases we want to hide the location field, since it's not + // human-readable, but we still want to initialize it. + showOrCollapse("locationRow", isURI, "location"); + if (isURI) { this._initLocationField(); this._locationField.readOnly = !this._paneInfo.isItem; } From a4a2afd118b428d0653d189e95b86acaaf71c5ea Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Fri, 8 May 2015 12:43:29 +0200 Subject: [PATCH 39/42] Bug 1160864 - The description field is not shown when editing a read-only container child in the Library. r=mano --- .../places/content/editBookmarkOverlay.js | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index 30de8c48f909..7207fd3c65b4 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -82,12 +82,18 @@ let gEditItemOverlay = { // Check if the pane is initialized to show only read-only fields. get readOnly() { - // Bug 1120314 - Folder shortcuts are read-only due to some quirky implementation - // details (the most important being the "smart" semantics of node.title). - return (!this.initialized || - (!this._paneInfo.visibleRows.has("tagsRow") && - (this._paneInfo.isFolderShortcut || - this._paneInfo.isParentReadOnly))); + // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some + // quirky implementation details (the most important being the "smart" + // semantics of node.title that makes hard to edit the right entry). + // This pane is read-only if: + // * the panel is not initialized + // * the node is a folder shortcut + // * the node is not bookmarked + // * the node is child of a read-only container and is not a bookmarked URI + return !this.initialized || + this._paneInfo.isFolderShortcut || + !this._paneInfo.isItem || + (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark); }, // the first field which was edited after this panel was initialized for @@ -189,17 +195,20 @@ let gEditItemOverlay = { showOrCollapse("locationRow", isURI, "location"); if (isURI) { this._initLocationField(); - this._locationField.readOnly = !this._paneInfo.isItem; + this._locationField.readOnly = !this.readOnly; } - if (showOrCollapse("descriptionRow", - this._paneInfo.isItem && !this.readOnly, + // hide the description field for + if (showOrCollapse("descriptionRow", isItem && !this.readOnly, "description")) { this._initDescriptionField(); + this._descriptionField.readOnly = this.readOnly; } - if (showOrCollapse("keywordRow", isBookmark, "keyword")) + if (showOrCollapse("keywordRow", isBookmark, "keyword")) { this._initKeywordField(); + this._keywordField.readOnly = this.readOnly; + } // Collapse the tag selector if the item does not accept tags. if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags")) @@ -358,7 +367,7 @@ let gEditItemOverlay = { // Hide the folders-separator if no folder is annotated as recently-used this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6); - this._folderMenuList.disabled = this._readOnly; + this._folderMenuList.disabled = this.readOnly; }, QueryInterface: @@ -390,7 +399,7 @@ let gEditItemOverlay = { }, onTagsFieldChange() { - if (!this.readOnly) { + if (this._paneInfo.isURI || this._paneInfo.bulkTagging) { this._updateTags().then( anyChanges => { if (anyChanges) From dcb8d107c103a8f8656a4f9d778e14b47ec0a1f8 Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Fri, 8 May 2015 13:13:54 +0100 Subject: [PATCH 40/42] Bug 1148026 - Add a skeleton of the login fill doorhanger. r=MattN --HG-- extra : rebase_source : 83617f2f083b23ca96066f137cc605a5c3623e4f extra : amend_source : da993ee2338b78a4fab692b3fd5a459c39506690 --- browser/base/content/browser.css | 8 + browser/base/content/popup-notifications.inc | 7 + browser/base/content/urlbarBindings.xml | 10 + browser/themes/linux/browser.css | 1 + browser/themes/osx/browser.css | 1 + .../themes/shared/login-doorhanger.inc.css | 19 ++ browser/themes/windows/browser.css | 1 + .../passwordmgr/LoginDoorhangers.jsm | 244 ++++++++++++++++++ .../components/passwordmgr/content/login.xml | 22 ++ toolkit/components/passwordmgr/jar.mn | 1 + toolkit/components/passwordmgr/moz.build | 1 + 11 files changed, 315 insertions(+) create mode 100644 browser/themes/shared/login-doorhanger.inc.css create mode 100644 toolkit/components/passwordmgr/LoginDoorhangers.jsm create mode 100644 toolkit/components/passwordmgr/content/login.xml diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index a9341fc27a17..106cae155a22 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -773,6 +773,14 @@ window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#pri -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification"); } +#password-fill-notification { + -moz-binding: url("chrome://browser/content/urlbarBindings.xml#password-fill-notification"); +} + +.login-fill-item { + -moz-binding: url("chrome://passwordmgr/content/login.xml#login"); +} + .plugin-popupnotification-centeritem { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item"); } diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc index 90a957142fa6..04c281ff7063 100644 --- a/browser/base/content/popup-notifications.inc +++ b/browser/base/content/popup-notifications.inc @@ -62,6 +62,13 @@ + + #ifdef E10S_TESTING_ONLY