diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index eed3491c810..ad04f544d3d 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 2e2d2282515..551ff94eb23 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 3d66b8d27a4..b45c2f396fc 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index baca4017103..87dcf02876a 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index ba3b8d939cd..7902ffd5fa6 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index dae84b6e1ad..129a6667ec2 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 3d66b8d27a4..b45c2f396fc 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 1ba136bc46c..939c06284f3 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 7022a0108d2..5cbd47dfa9a 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "8a1e4ae522c121c5cacd39b20a5386ec9055db82", + "git_revision": "0b166043ef2a1f235a4d7d4f40a51b625784195a", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "155bda717bccdcab21d76d66aeebe400d887fb39", + "revision": "8fc4e30c525ad4801000e23bb739fe794c397cd6", "repo_path": "integration/gaia-central" } diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 3e8e49ce84f..af974261d6a 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 0f0de3f087e..c3eeb46c4ea 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 6c199cf8f3e..c7e158c5fe8 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1665,8 +1665,6 @@ pref("prompts.tab_modal.enabled", true); // Whether the Panorama should animate going in/out of tabs pref("browser.panorama.animate_zoom", true); -// Defines the url to be used for new tabs. -pref("browser.newtab.url", "about:newtab"); // Activates preloading of the new tab url. pref("browser.newtab.preload", true); @@ -1733,7 +1731,7 @@ pref("shumway.swf.whitelist", "http://www.areweflashyet.com/*.swf"); pref("image.mem.max_decoded_image_kb", 256000); pref("loop.enabled", true); -pref("loop.textChat.enabled", false); +pref("loop.textChat.enabled", true); pref("loop.server", "https://loop.services.mozilla.com/v0"); pref("loop.seenToS", "unseen"); pref("loop.showPartnerLogo", true); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 7b41240169b..958e73ce2a9 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -54,6 +54,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Pocket", "resource:///modules/Pocket.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NewTabURL", + "resource:///modules/NewTabURL.jsm"); // Can't use XPCOMUtils for these because the scripts try to define the variables // on window, and so the defineProperty inside defineLazyGetter fails. @@ -4091,6 +4093,10 @@ var XULBrowserWindow = { return true; }, + shouldAddToSessionHistory: function(aDocShell, aURI) { + return aURI.spec != NewTabURL.get(); + }, + onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { @@ -6958,6 +6964,10 @@ var gIdentityHandler = { host = this.getEffectiveHost(); } catch (e) { // Some URIs might have no hosts. + } + + if (!host) { + // Fallback for special protocols. host = this._lastUri.specIgnoringRef; } diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 46b15111f1e..da35fddd4e8 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -1241,13 +1241,18 @@ #include tab-shape.inc.svg - + #ifndef XP_MACOSX - + #else #endif +#ifdef XP_WIN + + + +#endif diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 7b12cee18e5..c1fef6a25c2 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -56,6 +56,9 @@ addEventListener("DOMFormHasPassword", function(event) { LoginManagerContent.onDOMFormHasPassword(event, content); InsecurePasswordUtils.checkForInsecurePasswords(event.target); }); +addEventListener("DOMInputPasswordAdded", function(event) { + LoginManagerContent.onDOMInputPasswordAdded(event, content); +}); addEventListener("pageshow", function(event) { LoginManagerContent.onPageShow(event, content); }); diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index ad37a0f78f1..5a478d2f165 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1609,7 +1609,7 @@ // and the URL is "about:newtab". We do not support preloading for // custom newtab URLs. return Services.prefs.getBoolPref("browser.newtab.preload") && - !Services.prefs.prefHasUserValue("browser.newtab.url"); + !NewTabURL.overridden; ]]> @@ -3487,16 +3487,18 @@ let b = tab.linkedBrowser; - if (!b.isRemoteBrowser) { - // non-remote browsers are not the problem. - return true; - } - if (!b._alive) { // The browser binding has been removed. Dump a bunch of // diagnostic information to the browser console. - err("The tabbrowser-remote-browser binding has been removed " + - "from the tab being switched to."); + let utils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); + let results = utils.getBindingURLs(b); + let urls = []; + + for (let i = 0; i < results.length; ++i) { + urls.push(results.queryElementAt(i, Ci.nsIURI).spec); + } + err("The browser has the following bindings:"); + err(urls); err("MozBinding is currently: " + window.getComputedStyle(b).MozBinding); if (!b.parentNode) { @@ -6161,8 +6163,6 @@ - - true diff --git a/browser/base/content/test/general/browser_bug763468_perwindowpb.js b/browser/base/content/test/general/browser_bug763468_perwindowpb.js index bdd6943d96a..1ddc35d44bb 100644 --- a/browser/base/content/test/general/browser_bug763468_perwindowpb.js +++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.js @@ -8,7 +8,6 @@ function test() { waitForExplicitFinish(); let windowsToClose = []; let newTab; - let newTabPrefName = "browser.newtab.url"; let newTabURL; let mode; @@ -19,7 +18,7 @@ function test() { newTabURL = "about:privatebrowsing"; } else { mode = "normal"; - newTabURL = Services.prefs.getCharPref(newTabPrefName) || "about:blank"; + newTabURL = NewTabURL.get(); } is(aWindow.gBrowser.currentURI.spec, newTabURL, diff --git a/browser/base/content/test/general/browser_bug767836_perwindowpb.js b/browser/base/content/test/general/browser_bug767836_perwindowpb.js index 131a4aae62f..ee27a8dcf2e 100644 --- a/browser/base/content/test/general/browser_bug767836_perwindowpb.js +++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.js @@ -5,7 +5,6 @@ function test() { //initialization waitForExplicitFinish(); - let newTabPrefName = "browser.newtab.url"; let newTabURL; let testURL = "http://example.com/"; let mode; @@ -17,24 +16,24 @@ function test() { newTabURL = "about:privatebrowsing"; } else { mode = "normal"; - newTabURL = Services.prefs.getCharPref(newTabPrefName) || "about:blank"; + newTabURL = NewTabURL.get(); } // Check the new tab opened while in normal/private mode is(aWindow.gBrowser.selectedBrowser.currentURI.spec, newTabURL, "URL of NewTab should be " + newTabURL + " in " + mode + " mode"); // Set the custom newtab url - Services.prefs.setCharPref(newTabPrefName, testURL); - ok(Services.prefs.prefHasUserValue(newTabPrefName), "Custom newtab url is set"); + NewTabURL.override(testURL); + is(NewTabURL.get(), testURL, "Custom newtab url is set"); // Open a newtab after setting the custom newtab url openNewTab(aWindow, function () { is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL, "URL of NewTab should be the custom url"); - // clear the custom url preference - Services.prefs.clearUserPref(newTabPrefName); - ok(!Services.prefs.prefHasUserValue(newTabPrefName), "No custom newtab url is set"); + // Clear the custom url. + NewTabURL.reset(); + is(NewTabURL.get(), "about:newtab", "No custom newtab url is set"); aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab); aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab); @@ -51,7 +50,7 @@ function test() { } // check whether any custom new tab url has been configured - ok(!Services.prefs.prefHasUserValue(newTabPrefName), "No custom newtab url is set"); + ok(!NewTabURL.overridden, "No custom newtab url is set"); // test normal mode testOnWindow(false, function(aWindow) { diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index 32d26b32f9c..519101caaaf 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -330,7 +330,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. url = action.params.url; } else if (action.type == "searchengine") { let engine = Services.search.getEngineByName(action.params.engineName); - let submission = engine.getSubmission(action.params.searchQuery); + let query = action.params.searchSuggestion || + action.params.searchQuery; + let submission = engine.getSubmission(query); url = submission.uri.spec; postData = submission.postData; @@ -790,12 +792,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. try { action.params = JSON.parse(params); - if (action.params.input) { - action.params.input = decodeURIComponent(action.params.input); - } - if (action.params.searchQuery) { - action.params.searchQuery = decodeURIComponent(action.params.searchQuery); - } } catch (e) { // If this failed, we assume that params is not a JSON object, and // is instead just a flat string. This will happen when @@ -804,6 +800,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. action.params = { url: params, } + return action; + } + + for (let key of [ + "input", + "searchQuery", + "searchSuggestion", + ]) { + if (action.params[key]) { + action.params[key] = decodeURIComponent(action.params[key]); + } } return action; @@ -1577,7 +1584,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. } case "searchengine": { let engine = Services.search.getEngineByName(action.params.engineName); - let submission = engine.getSubmission(action.params.searchQuery); + let query = action.params.searchSuggestion || + action.params.searchQuery; + let submission = engine.getSubmission(query); url = submission.uri.spec; options.postData = submission.postData; break; diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index 493d2e5adb6..48fb6d39258 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -9,32 +9,16 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); Components.utils.import("resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () { - const PREF = "browser.newtab.url"; +XPCOMUtils.defineLazyModuleGetter(this, "NewTabURL", + "resource:///modules/NewTabURL.jsm"); - function getNewTabPageURL() { - if (PrivateBrowsingUtils.isWindowPrivate(window) && - !PrivateBrowsingUtils.permanentPrivateBrowsing && - !Services.prefs.prefHasUserValue(PREF)) { - return "about:privatebrowsing"; - } - - let url = Services.prefs.getComplexValue(PREF, Ci.nsISupportsString).data; - return url || "about:blank"; +this.__defineGetter__("BROWSER_NEW_TAB_URL", () => { + if (PrivateBrowsingUtils.isWindowPrivate(window) && + !PrivateBrowsingUtils.permanentPrivateBrowsing && + !NewTabURL.overridden) { + return "about:privatebrowsing"; } - - function update() { - BROWSER_NEW_TAB_URL = getNewTabPageURL(); - } - - Services.prefs.addObserver(PREF, update, false); - - addEventListener("unload", function onUnload() { - removeEventListener("unload", onUnload); - Services.prefs.removeObserver(PREF, update); - }); - - return getNewTabPageURL(); + return NewTabURL.get(); }); var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; diff --git a/browser/components/controlcenter/content/panel.inc.xul b/browser/components/controlcenter/content/panel.inc.xul index f3d662ba78c..d91bac14b3e 100644 --- a/browser/components/controlcenter/content/panel.inc.xul +++ b/browser/components/controlcenter/content/panel.inc.xul @@ -69,6 +69,9 @@ value="&identity.connectionInternal;"/> + + &identity.connectionVerified; @@ -76,8 +79,6 @@ class="identity-popup-text"/> - diff --git a/browser/components/loop/content/js/otconfig.js b/browser/components/loop/content/js/otconfig.js index 7f9098f3cfc..e8b33ebcc14 100644 --- a/browser/components/loop/content/js/otconfig.js +++ b/browser/components/loop/content/js/otconfig.js @@ -7,4 +7,3 @@ window.OTProperties = { }; window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/"; window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js"; -window.OTProperties.cssURL = window.OTProperties.assetURL + "css/ot.css"; diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js index 640947e89ab..9d34d2bc91b 100644 --- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -344,6 +344,11 @@ loop.OTSdkDriver = (function() { _onSessionConnectionCompleted: function(error) { if (error) { console.error("Failed to complete connection", error); + // We log this here before the connection failure to ensure the metrics + // event gets to the server before the leave action occurs. Otherwise + // the server won't log the metrics event because the user is no longer + // in the room. + this._notifyMetricsEvent("sdk.exception." + error.code); this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ reason: FAILURE_DETAILS.COULD_NOT_CONNECT })); @@ -399,6 +404,7 @@ loop.OTSdkDriver = (function() { this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now()); + this._notifyMetricsEvent("Session." + event.reason); this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ reason: reason })); @@ -485,9 +491,14 @@ loop.OTSdkDriver = (function() { case "Session.streamDestroyed": this._metrics.recvStreams--; break; + case "Session.networkDisconnected": + case "Session.forceDisconnected": + break; default: - console.error("Unexpected event name", eventName); - return; + if (eventName.indexOf("sdk.exception") === -1) { + console.error("Unexpected event name", eventName); + return; + } } if (!state) { state = this._getConnectionState(); @@ -910,6 +921,8 @@ loop.OTSdkDriver = (function() { this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA })); + } else { + this._notifyMetricsEvent("sdk.exception." + event.code); } }, diff --git a/browser/components/loop/jar.mn b/browser/components/loop/jar.mn index 218b5fa33d9..3f254734715 100644 --- a/browser/components/loop/jar.mn +++ b/browser/components/loop/jar.mn @@ -118,18 +118,4 @@ browser.jar: # Partner SDK assets content/browser/loop/libs/sdk.js (content/shared/libs/sdk.js) - content/browser/loop/sdk-content/css/ot.css (content/shared/libs/sdk-content/css/ot.css) content/browser/loop/sdk-content/js/dynamic_config.min.js (content/shared/libs/sdk-content/js/dynamic_config.min.js) - content/browser/loop/sdk-content/images/rtc/access-denied-chrome.png (content/shared/libs/sdk-content/images/rtc/access-denied-chrome.png) - content/browser/loop/sdk-content/images/rtc/access-denied-copy-firefox.png (content/shared/libs/sdk-content/images/rtc/access-denied-copy-firefox.png) - content/browser/loop/sdk-content/images/rtc/access-denied-firefox.png (content/shared/libs/sdk-content/images/rtc/access-denied-firefox.png) - content/browser/loop/sdk-content/images/rtc/access-predenied-chrome.png (content/shared/libs/sdk-content/images/rtc/access-predenied-chrome.png) - content/browser/loop/sdk-content/images/rtc/access-prompt-chrome.png (content/shared/libs/sdk-content/images/rtc/access-prompt-chrome.png) - content/browser/loop/sdk-content/images/rtc/audioonly-publisher.png (content/shared/libs/sdk-content/images/rtc/audioonly-publisher.png) - content/browser/loop/sdk-content/images/rtc/audioonly-subscriber.png (content/shared/libs/sdk-content/images/rtc/audioonly-subscriber.png) - content/browser/loop/sdk-content/images/rtc/buttons.png (content/shared/libs/sdk-content/images/rtc/buttons.png) - content/browser/loop/sdk-content/images/rtc/loader.gif (content/shared/libs/sdk-content/images/rtc/loader.gif) - content/browser/loop/sdk-content/images/rtc/mic-off.png (content/shared/libs/sdk-content/images/rtc/mic-off.png) - content/browser/loop/sdk-content/images/rtc/mic-on.png (content/shared/libs/sdk-content/images/rtc/mic-on.png) - content/browser/loop/sdk-content/images/rtc/speaker-off.png (content/shared/libs/sdk-content/images/rtc/speaker-off.png) - content/browser/loop/sdk-content/images/rtc/speaker-on.png (content/shared/libs/sdk-content/images/rtc/speaker-on.png) diff --git a/browser/components/loop/standalone/content/index.html b/browser/components/loop/standalone/content/index.html index adfdcf80e28..29c32d35674 100644 --- a/browser/components/loop/standalone/content/index.html +++ b/browser/components/loop/standalone/content/index.html @@ -109,7 +109,6 @@ }; window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/"; window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js"; - window.OTProperties.cssURL = window.OTProperties.assetURL + "css/ot.css"; diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js index bb9d1a3c21e..07b96fcbe66 100644 --- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -67,6 +67,7 @@ describe("loop.OTSdkDriver", function () { window.OT = { ExceptionCodes: { + CONNECT_FAILED: 1006, UNABLE_TO_PUBLISH: 1500 } }; @@ -388,12 +389,31 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(session.publish); }); + it("should notify metrics", function() { + session.connect.callsArgWith(2, { + title: "Fake", + code: OT.ExceptionCodes.CONNECT_FAILED + }); + + driver.connectSession(sessionData); + + sinon.assert.calledTwice(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + event: "sdk.exception." + OT.ExceptionCodes.CONNECT_FAILED, + state: "starting", + connections: 0, + sendStreams: 0, + recvStreams: 0 + })); + }); + it("should dispatch connectionFailure if connecting failed", function() { session.connect.callsArgWith(2, new Error("Failure")); driver.connectSession(sessionData); - sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledTwice(dispatcher.dispatch); sinon.assert.calledWithMatch(dispatcher.dispatch, sinon.match.hasOwn("name", "connectionFailure")); sinon.assert.calledWithMatch(dispatcher.dispatch, @@ -723,13 +743,29 @@ describe("loop.OTSdkDriver", function () { }); describe("sessionDisconnected", function() { + it("should notify metrics", function() { + session.trigger("sessionDisconnected", { + reason: "networkDisconnected" + }); + + sinon.assert.calledTwice(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + event: "Session.networkDisconnected", + state: "starting", + connections: 0, + sendStreams: 0, + recvStreams: 0 + })); + }); + it("should dispatch a connectionFailure action if the session was disconnected", function() { session.trigger("sessionDisconnected", { reason: "networkDisconnected" }); - sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledTwice(dispatcher.dispatch); sinon.assert.calledWithMatch(dispatcher.dispatch, sinon.match.hasOwn("name", "connectionFailure")); sinon.assert.calledWithMatch(dispatcher.dispatch, @@ -742,7 +778,7 @@ describe("loop.OTSdkDriver", function () { reason: "forceDisconnected" }); - sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledTwice(dispatcher.dispatch); sinon.assert.calledWithMatch(dispatcher.dispatch, sinon.match.hasOwn("name", "connectionFailure")); sinon.assert.calledWithMatch(dispatcher.dispatch, @@ -1368,6 +1404,24 @@ describe("loop.OTSdkDriver", function () { }); describe("exception", function() { + it("should notify metrics", function() { + sdk.trigger("exception", { + code: OT.ExceptionCodes.CONNECT_FAILED, + message: "Fake", + title: "Connect Failed" + }); + + sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + event: "sdk.exception." + OT.ExceptionCodes.CONNECT_FAILED, + state: "starting", + connections: 0, + sendStreams: 0, + recvStreams: 0 + })); + }); + describe("Unable to publish (GetUserMedia)", function() { it("should destroy the publisher", function() { sdk.trigger("exception", { @@ -1378,6 +1432,25 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(publisher.destroy); }); + // XXX We should remove this when we stop being unable to publish as a + // workaround for knowing if the user has video as well as audio devices + // installed (bug 1138851). + it("should not notify metrics", function() { + sdk.trigger("exception", { + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: "GetUserMedia" + }); + + sinon.assert.neverCalledWith(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH, + state: "starting", + connections: 0, + sendStreams: 0, + recvStreams: 0 + })); + }); + it("should dispatch a ConnectionFailure action", function() { sdk.trigger("exception", { code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, diff --git a/browser/components/loop/ui/index.html b/browser/components/loop/ui/index.html index ee647666d6c..64902db97d2 100644 --- a/browser/components/loop/ui/index.html +++ b/browser/components/loop/ui/index.html @@ -35,7 +35,6 @@ }; window.OTProperties.assetURL = window.OTProperties.cdnURL + 'sdk-content/'; window.OTProperties.configURL = window.OTProperties.assetURL + 'js/dynamic_config.min.js'; - window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css'; diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js index a1c22fb706b..bb2c93df35d 100644 --- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js @@ -28,14 +28,14 @@ add_task(function* () { }); add_task(function* () { - Services.prefs.setCharPref("browser.newtab.url", "about:blank"); + NewTabURL.override("about:blank"); registerCleanupFunction(() => { - Services.prefs.clearUserPref("browser.newtab.url"); + NewTabURL.reset(); }); let win = yield openNewPrivateWindow(); checkUrlbarFocus(win); win.close(); - Services.prefs.clearUserPref("browser.newtab.url"); + NewTabURL.reset(); }); diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js index d8f1010282e..5c0f583c329 100644 --- a/browser/devtools/inspector/inspector-panel.js +++ b/browser/devtools/inspector/inspector-panel.js @@ -686,6 +686,19 @@ InspectorPanel.prototype = { let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner"); let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter"); let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview"); + let expandAll = this.panelDoc.getElementById("node-menu-expand"); + let collapse = this.panelDoc.getElementById("node-menu-collapse"); + + expandAll.setAttribute("disabled", "true"); + collapse.setAttribute("disabled", "true"); + + let markUpContainer = this.markup.importNode(this.selection.nodeFront, false); + if (this.selection.isNode() && markUpContainer.hasChildren) { + if (markUpContainer.expanded) { + collapse.removeAttribute("disabled"); + } + expandAll.removeAttribute("disabled"); + } this._target.actorHasMethod("domnode", "scrollIntoView").then(value => { scrollIntoView.hidden = !value; @@ -1121,6 +1134,14 @@ InspectorPanel.prototype = { } }, + expandNode: function() { + this.markup.expandAll(this.selection.nodeFront); + }, + + collapseNode: function() { + this.markup.collapseNode(this.selection.nodeFront); + }, + /** * This method is here for the benefit of the node-menu-link-follow menu item * in the inspector contextual-menu. It's behavior depends on which node was diff --git a/browser/devtools/inspector/inspector.xul b/browser/devtools/inspector/inspector.xul index b939033e49d..d4bb848c912 100644 --- a/browser/devtools/inspector/inspector.xul +++ b/browser/devtools/inspector/inspector.xul @@ -55,6 +55,12 @@ + + - - + +
&addonUnsigned.message; &addonUnsigned.learnMore;
diff --git a/mobile/android/chrome/content/aboutApps.js b/mobile/android/chrome/content/aboutApps.js index defe88bdaa1..f2daaff6739 100644 --- a/mobile/android/chrome/content/aboutApps.js +++ b/mobile/android/chrome/content/aboutApps.js @@ -6,6 +6,8 @@ * * ***** END LICENSE BLOCK ***** */ +/*globals gChromeWin */ + let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm") @@ -18,14 +20,14 @@ const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png"; let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutApps.properties"); -XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() +XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() { window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow) - .QueryInterface(Ci.nsIDOMChromeWindow)); + .QueryInterface(Ci.nsIDOMChromeWindow)}); document.addEventListener("DOMContentLoaded", onLoad, false); diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index f12b40f5ae0..f1f1141b2a8 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -603,6 +603,13 @@ var BrowserApp = { InitLater(() => WebcompatReporter.init()); } + InitLater(function () { + // title == 0 and url == 1. See: + // https://mxr.mozilla.org/mozilla-central/source/mobile/android/base/resources/values/arrays.xml?rev=861e4bd9e7fe#153 + const titleInTitlebarEnabled = Services.prefs.getIntPref("browser.chrome.titlebarMode") == 0; + Telemetry.addData("FENNEC_TITLE_IN_TITLEBAR_ENABLED", titleInTitlebarEnabled); + }); + InitLater(() => LightWeightThemeWebInstaller.init()); InitLater(() => SpatialNavigation.init(BrowserApp.deck, null), window, "SpatialNavigation"); InitLater(() => CastingApps.init(), window, "CastingApps"); diff --git a/mobile/android/components/AboutRedirector.js b/mobile/android/components/AboutRedirector.js index 97559689ef7..b9969797cc3 100644 --- a/mobile/android/components/AboutRedirector.js +++ b/mobile/android/components/AboutRedirector.js @@ -22,7 +22,9 @@ let modules = { privileged: true, hide: true }, - get firefox() this.fennec, + get firefox() { + return this.fennec + }, // about:blank has some bad loading behavior we can avoid, if we use an alias empty: { diff --git a/mobile/android/components/BlocklistPrompt.js b/mobile/android/components/BlocklistPrompt.js index 81bf52ad7f7..ce7b8e011ed 100644 --- a/mobile/android/components/BlocklistPrompt.js +++ b/mobile/android/components/BlocklistPrompt.js @@ -48,7 +48,7 @@ BlocklistPrompt.prototype = { // Disable softblocked items automatically for (let i = 0; i < aAddons.length; i++) { if (aAddons[i].item instanceof Ci.nsIPluginTag) - addonList[i].item.disabled = true; + aAddons[i].item.disabled = true; else aAddons[i].item.userDisabled = true; } diff --git a/mobile/android/components/ContentDispatchChooser.js b/mobile/android/components/ContentDispatchChooser.js index 951fe19d3d7..62cfa2af6a4 100644 --- a/mobile/android/components/ContentDispatchChooser.js +++ b/mobile/android/components/ContentDispatchChooser.js @@ -5,6 +5,7 @@ const Ci = Components.interfaces; const Cu = Components.utils; const Cc = Components.classes; +const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); diff --git a/mobile/android/components/HelperAppDialog.js b/mobile/android/components/HelperAppDialog.js index fdb5c4e1d52..67953c79f83 100644 --- a/mobile/android/components/HelperAppDialog.js +++ b/mobile/android/components/HelperAppDialog.js @@ -3,6 +3,8 @@ * 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/. */ +/*globals ContentAreaUtils */ + const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; const APK_MIME_TYPE = "application/vnd.android.package-archive"; @@ -20,6 +22,12 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); // HelperApp Launcher Dialog // ----------------------------------------------------------------------- +XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { + let ContentAreaUtils = {}; + Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); + return ContentAreaUtils; +}); + function HelperAppLauncherDialog() { } HelperAppLauncherDialog.prototype = { diff --git a/mobile/android/components/LoginManagerPrompter.js b/mobile/android/components/LoginManagerPrompter.js index d2fce47489f..be0596d0a2a 100644 --- a/mobile/android/components/LoginManagerPrompter.js +++ b/mobile/android/components/LoginManagerPrompter.js @@ -6,9 +6,10 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; +const Cu = Components.utils; -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); /* Constants for password prompt telemetry. * Mirrored in nsLoginManagerPrompter.js */ @@ -443,7 +444,7 @@ LoginManagerPrompter.prototype = { // If the URI explicitly specified a port, only include it when // it's not the default. (We never want "http://foo.com:80") - port = uri.port; + let port = uri.port; if (port != -1) { var handler = Services.io.getProtocolHandler(scheme); if (port != handler.defaultPort) diff --git a/mobile/android/components/PromptService.js b/mobile/android/components/PromptService.js index ab009e4d01c..798cc5f3769 100644 --- a/mobile/android/components/PromptService.js +++ b/mobile/android/components/PromptService.js @@ -374,11 +374,11 @@ InternalPrompt.prototype = { }, nsIAuthPrompt_promptUsernameAndPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass) { - return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass); + return this.nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass); }, nsIAuthPrompt_promptPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aPass) { - return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, null, aPass); + return this.nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, null, aPass); }, nsIAuthPrompt_loginPrompt: function(aTitle, aPasswordRealm, aSavePassword, aUser, aPass) { @@ -394,11 +394,12 @@ InternalPrompt.prototype = { [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, aUser, aPass); } + // (eslint-disable: see bug 1177904) let ok = false; if (aUser) - ok = this.nsIPrompt_promptUsernameAndPassword(aTitle, aText, aUser, aPass, checkMsg, check); + ok = this.nsIPrompt_promptUsernameAndPassword(aTitle, aText, aUser, aPass, checkMsg, check); // eslint-disable-line no-undef else - ok = this.nsIPrompt_promptPassword(aTitle, aText, aPass, checkMsg, check); + ok = this.nsIPrompt_promptPassword(aTitle, aText, aPass, checkMsg, check); // eslint-disable-line no-undef if (ok && canSave && check.value) PromptUtils.savePassword(hostname, realm, aUser, aPass); @@ -803,7 +804,7 @@ let PromptUtils = { // If the URI explicitly specified a port, only include it when // it's not the default. (We never want "http://foo.com:80") - port = uri.port; + let port = uri.port; if (port != -1) { let handler = Services.io.getProtocolHandler(scheme); if (port != handler.defaultPort) diff --git a/mobile/android/components/Snippets.js b/mobile/android/components/Snippets.js index a7a0edcebf6..c46ab012721 100644 --- a/mobile/android/components/Snippets.js +++ b/mobile/android/components/Snippets.js @@ -251,7 +251,7 @@ function removeSnippet(snippetId) { function writeStat(snippetId, timestamp) { let data = gEncoder.encode(snippetId + "," + timestamp + ";"); - Task.spawn(function() { + Task.spawn(function* () { try { let file = yield OS.File.open(gStatsPath, { append: true, write: true }); try { diff --git a/mobile/android/gradle/base/lint.xml b/mobile/android/gradle/base/lint.xml index 38007536ca9..b1cc083dee2 100644 --- a/mobile/android/gradle/base/lint.xml +++ b/mobile/android/gradle/base/lint.xml @@ -2,4 +2,7 @@ + + + diff --git a/mobile/android/locales/en-US/chrome/aboutAddons.dtd b/mobile/android/locales/en-US/chrome/aboutAddons.dtd index 05405bbb2cc..d4c89146b72 100644 --- a/mobile/android/locales/en-US/chrome/aboutAddons.dtd +++ b/mobile/android/locales/en-US/chrome/aboutAddons.dtd @@ -10,3 +10,6 @@ + + + diff --git a/mobile/android/modules/DelayedInit.jsm b/mobile/android/modules/DelayedInit.jsm index 301f432516e..3f9be3aa62d 100644 --- a/mobile/android/modules/DelayedInit.jsm +++ b/mobile/android/modules/DelayedInit.jsm @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict" +/*globals MessageLoop */ + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; this.EXPORTED_SYMBOLS = ["DelayedInit"]; diff --git a/mobile/android/modules/HelperApps.jsm b/mobile/android/modules/HelperApps.jsm index 2d6fa98ebe6..0ac478da09c 100644 --- a/mobile/android/modules/HelperApps.jsm +++ b/mobile/android/modules/HelperApps.jsm @@ -3,6 +3,8 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +/* globals ContentAreaUtils */ + const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); diff --git a/mobile/android/modules/HomeProvider.jsm b/mobile/android/modules/HomeProvider.jsm index bca8fa526ef..7d302274202 100644 --- a/mobile/android/modules/HomeProvider.jsm +++ b/mobile/android/modules/HomeProvider.jsm @@ -5,6 +5,8 @@ "use strict"; +/*globals gSyncCheckIntervalSecs, gUpdateTimerManager, Sqlite, DB_PATH */ + this.EXPORTED_SYMBOLS = [ "HomeProvider" ]; const { utils: Cu, classes: Cc, interfaces: Ci } = Components; @@ -125,18 +127,18 @@ function syncTimerCallback(timer) { } } -this.HomeStorage = function(datasetId) { +let HomeStorage = function(datasetId) { this.datasetId = datasetId; }; -this.ValidationError = function(message) { +let ValidationError = function(message) { this.name = "ValidationError"; this.message = message; }; ValidationError.prototype = new Error(); ValidationError.prototype.constructor = ValidationError; -this.HomeProvider = Object.freeze({ +let HomeProvider = Object.freeze({ ValidationError: ValidationError, /** @@ -213,7 +215,7 @@ var gDatabaseEnsured = false; * Creates the database schema. */ function createDatabase(db) { - return Task.spawn(function create_database_task() { + return Task.spawn(function* create_database_task() { yield db.execute(SQL.createItemsTable); }); } @@ -222,7 +224,7 @@ function createDatabase(db) { * Migrates the database schema to a new version. */ function upgradeDatabase(db, oldVersion, newVersion) { - return Task.spawn(function upgrade_database_task() { + return Task.spawn(function* upgrade_database_task() { switch (oldVersion) { case 1: // Migration from v1 to latest: @@ -251,7 +253,7 @@ function upgradeDatabase(db, oldVersion, newVersion) { * @resolves Handle on an opened SQLite database. */ function getDatabaseConnection() { - return Task.spawn(function get_database_connection_task() { + return Task.spawn(function* get_database_connection_task() { let db = yield Sqlite.openConnection({ path: DB_PATH }); if (gDatabaseEnsured) { throw new Task.Result(db); @@ -350,10 +352,10 @@ HomeStorage.prototype = { ": you cannot save more than " + MAX_SAVE_COUNT + " items at once"; } - return Task.spawn(function save_task() { + return Task.spawn(function* save_task() { let db = yield getDatabaseConnection(); try { - yield db.executeTransaction(function save_transaction() { + yield db.executeTransaction(function* save_transaction() { if (options && options.replace) { yield db.executeCached(SQL.deleteFromDataset, { dataset_id: this.datasetId }); } @@ -392,7 +394,7 @@ HomeStorage.prototype = { * @resolves When the operation has completed. */ deleteAll: function() { - return Task.spawn(function delete_all_task() { + return Task.spawn(function* delete_all_task() { let db = yield getDatabaseConnection(); try { let params = { dataset_id: this.datasetId }; diff --git a/mobile/android/modules/Notifications.jsm b/mobile/android/modules/Notifications.jsm index 9dde00cfd75..aa1e64fa038 100644 --- a/mobile/android/modules/Notifications.jsm +++ b/mobile/android/modules/Notifications.jsm @@ -19,7 +19,7 @@ let _handlersMap = {}; function Notification(aId, aOptions) { this._id = aId; - this._when = (new Date).getTime(); + this._when = (new Date()).getTime(); this.fillWithOptions(aOptions); } diff --git a/mobile/android/modules/Sanitizer.jsm b/mobile/android/modules/Sanitizer.jsm index 4c78bb42e1c..4096a1e48db 100644 --- a/mobile/android/modules/Sanitizer.jsm +++ b/mobile/android/modules/Sanitizer.jsm @@ -3,6 +3,8 @@ * 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/. */ +/*globals LoadContextInfo, FormHistory, Accounts */ + let Cc = Components.classes; let Ci = Components.interfaces; let Cu = Components.utils; diff --git a/mobile/android/modules/SharedPreferences.jsm b/mobile/android/modules/SharedPreferences.jsm index f0170d66116..ee50b0ffdfd 100644 --- a/mobile/android/modules/SharedPreferences.jsm +++ b/mobile/android/modules/SharedPreferences.jsm @@ -61,7 +61,7 @@ let SharedPreferences = { */ function SharedPreferencesImpl(options = {}) { if (!(this instanceof SharedPreferencesImpl)) { - return new SharedPreferencesImpl(level); + return new SharedPreferencesImpl(options); } if (options.scope == null || options.scope == undefined) { diff --git a/mobile/android/modules/WebappManagerWorker.js b/mobile/android/modules/WebappManagerWorker.js index 86386a75264..4f04d666d6e 100644 --- a/mobile/android/modules/WebappManagerWorker.js +++ b/mobile/android/modules/WebappManagerWorker.js @@ -10,7 +10,8 @@ let Log = require("resource://gre/modules/AndroidLog.jsm"); // the "debug" priority and a log tag. let log = Log.d.bind(null, "WebappManagerWorker"); -onmessage = function(event) { +// (eslint-disable: see bug 1177901) +onmessage = function(event) { // eslint-disable-line no-undef let { url, path } = event.data; let file = OS.File.open(path, { truncate: true }); diff --git a/mobile/android/search/java/org/mozilla/search/SearchActivity.java b/mobile/android/search/java/org/mozilla/search/SearchActivity.java index 7d79e764887..fea38333c55 100644 --- a/mobile/android/search/java/org/mozilla/search/SearchActivity.java +++ b/mobile/android/search/java/org/mozilla/search/SearchActivity.java @@ -19,6 +19,7 @@ import org.mozilla.search.providers.SearchEngineManager; import org.mozilla.search.providers.SearchEngineManager.SearchEngineCallback; import android.content.AsyncQueryHandler; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.graphics.Rect; @@ -92,6 +93,16 @@ public class SearchActivity extends Locales.LocaleAwareFragmentActivity private int cardPaddingX; private int cardPaddingY; + /** + * An empty implementation of AsyncQueryHandler to avoid the "HandlerLeak" warning from Android + * Lint. See also {@see org.mozilla.gecko.util.WeakReferenceHandler}. + */ + private static class AsyncQueryHandlerImpl extends AsyncQueryHandler { + public AsyncQueryHandlerImpl(final ContentResolver that) { + super(that); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { GeckoAppShell.ensureCrashHandling(); @@ -108,7 +119,7 @@ public class SearchActivity extends Locales.LocaleAwareFragmentActivity // Initialize the fragments with the selected search engine. searchEngineManager.getEngine(this); - queryHandler = new AsyncQueryHandler(getContentResolver()) {}; + queryHandler = new AsyncQueryHandlerImpl(getContentResolver()); searchBar = (SearchBar) findViewById(R.id.search_bar); searchBar.setOnClickListener(new View.OnClickListener() { diff --git a/mobile/android/services/strings.xml.in b/mobile/android/services/strings.xml.in index 668fc873a40..4e78ef64ecb 100644 --- a/mobile/android/services/strings.xml.in +++ b/mobile/android/services/strings.xml.in @@ -55,7 +55,7 @@ &sync.configure.engines.title.label; &sync.configure.engines.sync.my.title.label; &sync.configure.engines.title.bookmarks; - &sync.configure.engines.title.passwords; + &sync.configure.engines.title.passwords2; &sync.configure.engines.title.history; &sync.configure.engines.title.tabs; @@ -132,7 +132,7 @@ &fxaccount_policy_linkprivacy; &fxaccount_getting_started_welcome_to_sync; -&fxaccount_getting_started_description; +&fxaccount_getting_started_description2; &fxaccount_getting_started_get_started; &fxaccount_getting_started_old_firefox; @@ -203,7 +203,7 @@ &fxaccount_status_needs_finish_migrating; &fxaccount_status_bookmarks; &fxaccount_status_history; -&fxaccount_status_passwords; +&fxaccount_status_passwords2; &fxaccount_status_tabs; &reading_list_title; &fxaccount_status_legal; diff --git a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java index 83e7ef95d38..193de992345 100644 --- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java +++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java @@ -17,6 +17,7 @@ import android.util.Log; import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.AppGlobals.ActiveOrPassiveStumbling; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -72,13 +73,7 @@ public class CellScanner { new IntentFilter(Reporter.ACTION_NEW_BUNDLE)); // This is to ensure the broadcast happens from the same thread the CellScanner start() is on - mBroadcastScannedHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - Intent intent = (Intent) msg.obj; - LocalBroadcastManager.getInstance(mContext).sendBroadcastSync(intent); - } - }; + mBroadcastScannedHandler = new BroadcastScannedHandler(this); mCellScannerImplementation.start(); @@ -159,4 +154,25 @@ public class CellScanner { mReportWasFlushed.set(true); } } + + // Note: this reimplements org.mozilla.gecko.util.WeakReferenceHandler because it's not available here. + private static class BroadcastScannedHandler extends Handler { + private WeakReference mTarget; + + public BroadcastScannedHandler(final CellScanner that) { + super(); + mTarget = new WeakReference<>(that); + } + + @Override + public void handleMessage(Message msg) { + final CellScanner that = mTarget.get(); + if (that == null) { + return; + } + + final Intent intent = (Intent) msg.obj; + LocalBroadcastManager.getInstance(that.mContext).sendBroadcastSync(intent); + } + }; } diff --git a/mobile/android/tests/.eslintrc b/mobile/android/tests/.eslintrc new file mode 100644 index 00000000000..6784be2d6ef --- /dev/null +++ b/mobile/android/tests/.eslintrc @@ -0,0 +1,18 @@ +globals: + # TODO: Verify that these are correct. + Point: false + SpecialPowers: false + XPCNativeWrapper: false + add_task: false + add_test: false + do_check_eq: false + do_check_false: false + do_check_neq: false + do_check_true: false + do_print: false + do_register_cleanup: false + do_report_result: false + do_test_finished: false + do_test_pending: false + do_throw: false + run_next_test: false diff --git a/mobile/android/tests/browser/robocop/StringHelper.java b/mobile/android/tests/browser/robocop/StringHelper.java index 084922d079b..67366e34622 100644 --- a/mobile/android/tests/browser/robocop/StringHelper.java +++ b/mobile/android/tests/browser/robocop/StringHelper.java @@ -133,6 +133,8 @@ public class StringHelper { public final String TEXT_SIZE_LABEL; public final String TITLE_BAR_LABEL = "Title bar"; public final String SCROLL_TITLE_BAR_LABEL; + public final String VOICE_INPUT_TITLE_LABEL; + public final String VOICE_INPUT_SUMMARY_LABEL; public final String TEXT_REFLOW_LABEL; public final String CHARACTER_ENCODING_LABEL; public final String PLUGINS_LABEL; @@ -145,7 +147,7 @@ public class StringHelper { public final String TRACKING_PROTECTION_LABEL; public final String DNT_LABEL; public final String COOKIES_LABEL; - public final String REMEMBER_PASSWORDS_LABEL; + public final String REMEMBER_LOGINS_LABEL; public final String MANAGE_LOGINS_LABEL; public final String MASTER_PASSWORD_LABEL; public final String CLEAR_PRIVATE_DATA_LABEL; @@ -318,6 +320,8 @@ public class StringHelper { // Display TEXT_SIZE_LABEL = res.getString(R.string.pref_text_size); SCROLL_TITLE_BAR_LABEL = res.getString(R.string.pref_scroll_title_bar2); + VOICE_INPUT_TITLE_LABEL = res.getString(R.string.pref_voice_input); + VOICE_INPUT_SUMMARY_LABEL = res.getString(R.string.pref_voice_input_summary); TEXT_REFLOW_LABEL = res.getString(R.string.pref_reflow_on_zoom); CHARACTER_ENCODING_LABEL = res.getString(R.string.pref_char_encoding); PLUGINS_LABEL = res.getString(R.string.pref_plugins); @@ -326,7 +330,7 @@ public class StringHelper { TRACKING_PROTECTION_LABEL = res.getString(R.string.pref_tracking_protection_title); DNT_LABEL = res.getString(R.string.pref_donottrack_title); COOKIES_LABEL = res.getString(R.string.pref_cookies_menu); - REMEMBER_PASSWORDS_LABEL = res.getString(R.string.pref_remember_signons); + REMEMBER_LOGINS_LABEL = res.getString(R.string.pref_remember_signons); MANAGE_LOGINS_LABEL = res.getString(R.string.pref_manage_logins); MASTER_PASSWORD_LABEL = res.getString(R.string.pref_use_master_password); CLEAR_PRIVATE_DATA_LABEL = res.getString(R.string.pref_clear_private_data); diff --git a/mobile/android/tests/browser/robocop/testAboutLogins.js b/mobile/android/tests/browser/robocop/testAboutLogins.js index 1d987ce4603..8118da24eb1 100644 --- a/mobile/android/tests/browser/robocop/testAboutLogins.js +++ b/mobile/android/tests/browser/robocop/testAboutLogins.js @@ -43,7 +43,7 @@ function add_login(login) { add_test(function password_setup() { add_login(LOGIN_FIELDS); - // Load about:passwords. + // Load about:logins. BrowserApp = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp; browser = BrowserApp.addTab("about:logins", { selected: true, parentId: BrowserApp.selectedTab.id }).browser; diff --git a/mobile/android/tests/browser/robocop/testAndroidLog.js b/mobile/android/tests/browser/robocop/testAndroidLog.js index 9a8b74875d3..a235219a8ac 100644 --- a/mobile/android/tests/browser/robocop/testAndroidLog.js +++ b/mobile/android/tests/browser/robocop/testAndroidLog.js @@ -3,6 +3,8 @@ * 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/. */ +/*globals AndroidLog */ + const TAG = "AndroidLogTest"; const VERBOSE_MESSAGE = "This is a verbose message."; diff --git a/mobile/android/tests/browser/robocop/testReadingListCache.js b/mobile/android/tests/browser/robocop/testReadingListCache.js index df118fcc8a6..3a6e9c9933f 100644 --- a/mobile/android/tests/browser/robocop/testReadingListCache.js +++ b/mobile/android/tests/browser/robocop/testReadingListCache.js @@ -3,6 +3,8 @@ * 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/. */ +/*globals ReaderMode */ + const { utils: Cu } = Components; Cu.import("resource://gre/modules/ReaderMode.jsm"); diff --git a/mobile/android/tests/browser/robocop/testSettingsMenuItems.java b/mobile/android/tests/browser/robocop/testSettingsMenuItems.java index 21dde507a80..43c498ae83c 100644 --- a/mobile/android/tests/browser/robocop/testSettingsMenuItems.java +++ b/mobile/android/tests/browser/robocop/testSettingsMenuItems.java @@ -7,8 +7,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.mozilla.gecko.R; +import org.mozilla.gecko.Actions; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.util.HardwareUtils; +import org.mozilla.gecko.util.InputOptionsUtils; /** This patch tests the Sections present in the Settings Menu and the * default values for them @@ -99,10 +102,10 @@ public class testSettingsMenuItems extends PixelTest { TRACKING_PROTECTION_LABEL_ARR, { mStringHelper.DNT_LABEL }, { mStringHelper.COOKIES_LABEL, "Enabled", "Enabled, excluding 3rd party", "Disabled" }, - { mStringHelper.REMEMBER_PASSWORDS_LABEL }, + { mStringHelper.REMEMBER_LOGINS_LABEL }, MANAGE_LOGINS_ARR, { mStringHelper.MASTER_PASSWORD_LABEL }, - { mStringHelper.CLEAR_PRIVATE_DATA_LABEL, "", "Browsing history", "Search history", "Downloads", "Form history", "Cookies & active logins", "Saved passwords", "Cache", "Offline website data", "Site settings", "Clear data" }, + { mStringHelper.CLEAR_PRIVATE_DATA_LABEL, "", "Browsing history", "Search history", "Downloads", "Form history", "Cookies & active logins", mStringHelper.CLEAR_PRIVATE_DATA_LABEL, "Cache", "Offline website data", "Site settings", "Clear data" }, }; PATH_MOZILLA = new String[] { mStringHelper.MOZILLA_SECTION_LABEL }; @@ -225,6 +228,12 @@ public class testSettingsMenuItems extends PixelTest { if (HardwareUtils.isTablet()) { settingsMap.get(PATH_DISPLAY).remove(TITLE_BAR_LABEL_ARR); } + + // Voice input + if (AppConstants.NIGHTLY_BUILD && InputOptionsUtils.supportsVoiceRecognizer(this.getActivity().getApplicationContext(), this.getActivity().getResources().getString(R.string.voicesearch_prompt))) { + String[] voiceInputUi = { mStringHelper.VOICE_INPUT_TITLE_LABEL, mStringHelper.VOICE_INPUT_SUMMARY_LABEL }; + settingsMap.get(PATH_DISPLAY).add(voiceInputUi); + } } public void checkMenuHierarchy(Map> settingsMap) { diff --git a/mobile/android/tests/browser/robocop/testSimpleDiscovery.js b/mobile/android/tests/browser/robocop/testSimpleDiscovery.js index c5fd1c9994a..a226f41e3f2 100644 --- a/mobile/android/tests/browser/robocop/testSimpleDiscovery.js +++ b/mobile/android/tests/browser/robocop/testSimpleDiscovery.js @@ -4,6 +4,8 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +/*globals SimpleServiceDiscovery */ + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); diff --git a/mobile/android/tests/browser/robocop/testVideoDiscovery.js b/mobile/android/tests/browser/robocop/testVideoDiscovery.js index 0b158d6b70d..29f78750776 100644 --- a/mobile/android/tests/browser/robocop/testVideoDiscovery.js +++ b/mobile/android/tests/browser/robocop/testVideoDiscovery.js @@ -5,6 +5,8 @@ "use strict"; +/*globals SimpleServiceDiscovery */ + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); diff --git a/mobile/android/themes/core/aboutAddons.css b/mobile/android/themes/core/aboutAddons.css index 83e11f19cd2..69f2e51ea75 100644 --- a/mobile/android/themes/core/aboutAddons.css +++ b/mobile/android/themes/core/aboutAddons.css @@ -5,6 +5,15 @@ %filter substitution %include defines.inc +a { + text-decoration: none; + color: #0096DD; +} + +a:active { + color: #0082C6; +} + .details { width: 100%; } @@ -25,6 +34,21 @@ text-overflow: ellipsis; } +.warn-unsigned { + border-top: 1px solid @color_about_item_border@; + padding: 1em; + -moz-padding-start: calc(var(--icon-size) + var(--icon-margin) * 2); + background-image: url("chrome://browser/skin/images/grey-caution.svg"); + background-size: var(--icon-size); + background-position: var(--icon-margin); + background-repeat: no-repeat; + display: none; +} + +.addon-item[isUnsigned] .warn-unsigned { + display: block; +} + .status { border-top: 1px solid @color_about_item_border@; font-weight: bold; diff --git a/mobile/android/themes/core/aboutPrivateBrowsing.css b/mobile/android/themes/core/aboutPrivateBrowsing.css index 9378cdcb059..02625b70b3d 100644 --- a/mobile/android/themes/core/aboutPrivateBrowsing.css +++ b/mobile/android/themes/core/aboutPrivateBrowsing.css @@ -20,7 +20,7 @@ div.contentSection { } body.private { - background-color: #292c29; + background-color: #45494E; /* tabs_tray_grey_pressed */ color: #afb1b3; /* tabs_tray_icon_grey */ } diff --git a/mobile/android/themes/core/images/grey-caution.svg b/mobile/android/themes/core/images/grey-caution.svg new file mode 100644 index 00000000000..1b810214fff --- /dev/null +++ b/mobile/android/themes/core/images/grey-caution.svg @@ -0,0 +1,14 @@ + + + + Rectangle 62 + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/mobile/android/themes/core/jar.mn b/mobile/android/themes/core/jar.mn index e9f966d10b4..088d8e68a6a 100644 --- a/mobile/android/themes/core/jar.mn +++ b/mobile/android/themes/core/jar.mn @@ -73,6 +73,7 @@ chrome.jar: skin/images/errorpage-warning.png (images/errorpage-warning.png) skin/images/exitfullscreen-hdpi.png (images/exitfullscreen-hdpi.png) skin/images/fullscreen-hdpi.png (images/fullscreen-hdpi.png) + skin/images/grey-caution.svg (images/grey-caution.svg) skin/images/certerror-warning.png (images/certerror-warning.png) skin/images/errorpage-larry-white.png (images/errorpage-larry-white.png) skin/images/errorpage-larry-black.png (images/errorpage-larry-black.png) diff --git a/security/manager/ssl/StaticHPKPins.h b/security/manager/ssl/StaticHPKPins.h index 4c7a9c61e1d..0e99c66476b 100644 --- a/security/manager/ssl/StaticHPKPins.h +++ b/security/manager/ssl/StaticHPKPins.h @@ -786,6 +786,7 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { { "glass.google.com", true, false, false, -1, &kPinset_google_root_pems }, { "gmail.com", false, false, false, -1, &kPinset_google_root_pems }, { "goo.gl", true, false, false, -1, &kPinset_google_root_pems }, + { "google", true, false, false, -1, &kPinset_google_root_pems }, { "google-analytics.com", true, false, false, -1, &kPinset_google_root_pems }, { "google.ac", true, false, false, -1, &kPinset_google_root_pems }, { "google.ad", true, false, false, -1, &kPinset_google_root_pems }, @@ -1092,8 +1093,8 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems }, }; -// Pinning Preload List Length = 353; +// Pinning Preload List Length = 354; static const int32_t kUnknownId = -1; -static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1443263062798000); +static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1443867905693000); diff --git a/security/manager/ssl/nsSTSPreloadList.errors b/security/manager/ssl/nsSTSPreloadList.errors index 11be864bd3c..b3ec3e22d8b 100644 --- a/security/manager/ssl/nsSTSPreloadList.errors +++ b/security/manager/ssl/nsSTSPreloadList.errors @@ -2,12 +2,15 @@ 56ct.com: could not connect to host activiti.alfresco.com: did not receive HSTS header adamkostecki.de: could not connect to host +adamstas.com: could not connect to host admin.google.com: did not receive HSTS header (error ignored - included regardless) adsfund.org: could not connect to host +aes256.ru: could not connect to host afp548.tk: could not connect to host agrimap.com: did not receive HSTS header airbnb.com: did not receive HSTS header aiticon.de: did not receive HSTS header +akselimedia.fi: did not receive HSTS header alpha.irccloud.com: did not receive HSTS header alphabit-secure.com: could not connect to host altmv.com: max-age too low: 7776000 @@ -31,12 +34,14 @@ au.search.yahoo.com: did not receive HSTS header aurainfosec.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] auraredeye.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] auraredshield.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] +auszeit.bio: did not receive HSTS header auth.mail.ru: did not receive HSTS header auto4trade.nl: did not receive HSTS header +av.de: did not receive HSTS header az.search.yahoo.com: did not receive HSTS header azabani.com: did not receive HSTS header azprep.us: could not connect to host -baldwinkoo.com: did not receive HSTS header +baldwinkoo.com: could not connect to host barcodeberlin.com: did not receive HSTS header bassh.net: could not connect to host bccx.com: could not connect to host @@ -83,17 +88,19 @@ chippy.ch: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_ chit.search.yahoo.com: did not receive HSTS header chm.vn: did not receive HSTS header chontalpa.pw: did not receive HSTS header +chriswarrick.com: did not receive HSTS header chrome-devtools-frontend.appspot.com: did not receive HSTS header (error ignored - included regardless) chrome.google.com: did not receive HSTS header (error ignored - included regardless) cimballa.com: did not receive HSTS header cl.search.yahoo.com: did not receive HSTS header climaprecio.es: did not receive HSTS header +cloudns.com.au: could not connect to host cn.search.yahoo.com: did not receive HSTS header co.search.yahoo.com: did not receive HSTS header code.google.com: did not receive HSTS header (error ignored - included regardless) codereview.chromium.org: did not receive HSTS header (error ignored - included regardless) coding.net: did not receive HSTS header -coffeeetc.co.uk: did not receive HSTS header +coffeeetc.co.uk: could not connect to host console.python.org: did not receive HSTS header copperhead.co: max-age too low: 0 coursella.com: did not receive HSTS header @@ -118,6 +125,7 @@ devh.de: did not receive HSTS header diedrich.co: did not receive HSTS header digitaldaddy.net: could not connect to host discovery.lookout.com: did not receive HSTS header +dixmag.com: could not connect to host dk.search.yahoo.com: did not receive HSTS header dl.google.com: did not receive HSTS header (error ignored - included regardless) do.search.yahoo.com: did not receive HSTS header @@ -145,7 +153,6 @@ esec.rs: did not receive HSTS header espanol.search.yahoo.com: did not receive HSTS header espra.com: could not connect to host etsysecure.com: could not connect to host -exiahost.com: did not receive HSTS header ezimoeko.net: did not receive HSTS header eztv.ch: did not receive HSTS header fabianfischer.de: did not receive HSTS header @@ -170,8 +177,8 @@ gl.search.yahoo.com: did not receive HSTS header glass.google.com: did not receive HSTS header (error ignored - included regardless) gm.search.yahoo.com: did not receive HSTS header gmail.com: did not receive HSTS header (error ignored - included regardless) -gmantra.org: could not connect to host goodwin43.ru: did not receive HSTS header +google: could not connect to host (error ignored - included regardless) googlemail.com: did not receive HSTS header (error ignored - included regardless) googleplex.com: could not connect to host googleplex.com: could not connect to host (error ignored - included regardless) @@ -180,17 +187,17 @@ gotowned.org: did not receive HSTS header gparent.org: did not receive HSTS header gr.search.yahoo.com: did not receive HSTS header grandmascookieblog.com: did not receive HSTS header +greensolid.biz: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] greplin.com: could not connect to host groups.google.com: did not receive HSTS header (error ignored - included regardless) gtraxapp.com: could not connect to host -guidetoiceland.is: did not receive HSTS header gvt2.com: could not connect to host gvt2.com: could not connect to host (error ignored - included regardless) gvt3.com: could not connect to host gvt3.com: could not connect to host (error ignored - included regardless) hangouts.google.com: did not receive HSTS header (error ignored - included regardless) hatoko.net: could not connect to host -history.google.com: did not receive HSTS header (error ignored - included regardless) +hda.me: did not receive HSTS header hk.search.yahoo.com: did not receive HSTS header hn.search.yahoo.com: did not receive HSTS header hoerbuecher-und-hoerspiele.de: did not receive HSTS header @@ -255,10 +262,10 @@ li.search.yahoo.com: did not receive HSTS header library.linode.com: did not receive HSTS header libraryfreedomproject.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] lifeguard.aecom.com: max-age too low: 86400 +linguaquote.com: did not receive HSTS header lists.fedoraproject.org: did not receive HSTS header login.corp.google.com: max-age too low: 7776000 (error ignored - included regardless) logotype.se: did not receive HSTS header -lolicore.ch: could not connect to host lovelycorral.com: did not receive HSTS header lt.search.yahoo.com: did not receive HSTS header lu.search.yahoo.com: did not receive HSTS header @@ -284,6 +291,7 @@ megashur.se: did not receive HSTS header megaxchange.com: did not receive HSTS header meinebo.it: could not connect to host micropple.net: could not connect to host +miketabor.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] minikneet.nl: could not connect to host minora.io: did not receive HSTS header mirindadomo.ru: did not receive HSTS header @@ -305,8 +313,8 @@ mykolab.com: did not receive HSTS header mykreuzfahrt.de: did not receive HSTS header myni.io: could not connect to host neftaly.com: did not receive HSTS header +nemovement.org: did not receive HSTS header neonisi.com: could not connect to host -netrelay.email: did not receive HSTS header netzbit.de: did not receive HSTS header netzpolitik.org: did not receive HSTS header netztest.at: did not receive HSTS header @@ -320,9 +328,11 @@ nijm.nl: max-age too low: 0 nl.search.yahoo.com: did not receive HSTS header no.search.yahoo.com: did not receive HSTS header noexpect.org: could not connect to host +noobs-r-us.co.uk: did not receive HSTS header np.search.yahoo.com: did not receive HSTS header npw.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] -null-sec.ru: could not connect to host +null-sec.ru: did not receive HSTS header +nwa.xyz: could not connect to host nz.search.yahoo.com: did not receive HSTS header nzb.cat: did not receive HSTS header offshore-firma.org: could not connect to host @@ -348,7 +358,6 @@ pl.search.yahoo.com: did not receive HSTS header platform.lookout.com: could not connect to host play.google.com: did not receive HSTS header (error ignored - included regardless) poiema.com.sg: did not receive HSTS header -polymathematician.com: could not connect to host popcorntime.ws: max-age too low: 2592000 powerplannerapp.com: did not receive HSTS header pr.search.yahoo.com: did not receive HSTS header @@ -370,11 +379,12 @@ rid-wan.com: max-age too low: 0 riseup.net: did not receive HSTS header rme.li: did not receive HSTS header ro.search.yahoo.com: did not receive HSTS header -roan24.pl: did not receive HSTS header +roan24.pl: max-age too low: 0 roddis.net: did not receive HSTS header ru.search.yahoo.com: did not receive HSTS header rw.search.yahoo.com: did not receive HSTS header sah3.net: could not connect to host +sanatfilan.com: could not connect to host saturngames.co.uk: could not connect to host savetheinternet.eu: did not receive HSTS header script.google.com: did not receive HSTS header (error ignored - included regardless) @@ -390,6 +400,7 @@ seowarp.net: max-age too low: 1576800 serverdensity.io: did not receive HSTS header sg.search.yahoo.com: did not receive HSTS header shiinko.com: could not connect to host +shoprose.ru: did not receive HSTS header shops.neonisi.com: could not connect to host siammedia.co: did not receive HSTS header silentcircle.org: could not connect to host @@ -398,7 +409,7 @@ simplyfixit.co.uk: [Exception... "Component returned failure code: 0x80004005 (N sistemy48.ru: did not receive HSTS header sites.google.com: did not receive HSTS header (error ignored - included regardless) smartlend.se: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] -sockeye.cc: did not receive HSTS header +soccergif.com: could not connect to host sol.io: could not connect to host souyar.de: could not connect to host souyar.net: could not connect to host @@ -416,19 +427,18 @@ sunshinepress.org: could not connect to host surfeasy.com: did not receive HSTS header suzukikenichi.com: did not receive HSTS header sv.search.yahoo.com: did not receive HSTS header -sylaps.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] syzygy-tables.info: did not receive HSTS header t.facebook.com: did not receive HSTS header tablet.facebook.com: did not receive HSTS header +taglondon.org: did not receive HSTS header talk.google.com: could not connect to host talk.google.com: could not connect to host (error ignored - included regardless) -talkgadget.google.com: did not receive HSTS header (error ignored - included regardless) tallr.se: could not connect to host tandarts-haarlem.nl: did not receive HSTS header tapka.cz: did not receive HSTS header -taskotron.fedoraproject.org: could not connect to host taxsquirrel.com: could not connect to host tc-bonito.de: did not receive HSTS header +tdrs.info: could not connect to host tektoria.de: did not receive HSTS header temehu.com: did not receive HSTS header temp.pm: did not receive HSTS header @@ -450,6 +460,7 @@ translate.googleapis.com: did not receive HSTS header (error ignored - included translatoruk.co.uk: did not receive HSTS header triop.se: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] tuturulianda.com: did not receive HSTS header +tuxgeo.com: did not receive HSTS header tv.search.yahoo.com: could not connect to host tw.search.yahoo.com: did not receive HSTS header tzappa.net: did not receive HSTS header @@ -460,6 +471,7 @@ unbanthe.net: did not receive HSTS header univz.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] uonstaffhub.com: could not connect to host uprotect.it: could not connect to host +ust.space: did not receive HSTS header ustr.gov: max-age too low: 86400 uy.search.yahoo.com: did not receive HSTS header uz.search.yahoo.com: did not receive HSTS header @@ -472,7 +484,7 @@ webeau.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR webmail.mayfirst.org: did not receive HSTS header when-release.com: did not receive HSTS header wikidsystems.com: did not receive HSTS header -withustrading.com: did not receive HSTS header +withustrading.com: could not connect to host wiz.biz: could not connect to host wohnungsbau-ludwigsburg.de: did not receive HSTS header www.apollo-auto.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] diff --git a/security/manager/ssl/nsSTSPreloadList.inc b/security/manager/ssl/nsSTSPreloadList.inc index 22631404c57..181c7c59d52 100644 --- a/security/manager/ssl/nsSTSPreloadList.inc +++ b/security/manager/ssl/nsSTSPreloadList.inc @@ -8,7 +8,7 @@ /*****************************************************************************/ #include -const PRTime gPreloadListExpirationTime = INT64_C(1445682256437000); +const PRTime gPreloadListExpirationTime = INT64_C(1446287099409000); class nsSTSPreload { @@ -75,7 +75,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "aiticon.com", true }, { "ajouin.com", true }, { "akachanikuji.com", true }, - { "akselimedia.fi", true }, { "akselinurmio.fi", true }, { "al-shami.net", true }, { "aladdinschools.appspot.com", true }, @@ -162,10 +161,8 @@ static const nsSTSPreload kSTSPreloadList[] = { { "aurainfosec.com.au", true }, { "auraredeye.com", true }, { "auraredshield.com", true }, - { "auszeit.bio", true }, { "authentication.io", true }, { "autoledky.sk", true }, - { "av.de", true }, { "axka.com", false }, { "azirevpn.com", true }, { "badges.fedoraproject.org", true }, @@ -326,7 +323,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "cheesetart.my", false }, { "chrisirwin.ca", true }, { "chrisjean.com", true }, - { "chriswarrick.com", true }, { "chrome-devtools-frontend.appspot.com", true }, { "chrome.com", false }, { "chrome.google.com", true }, @@ -560,6 +556,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "everhome.de", true }, { "eveshamglass.co.uk", true }, { "evstatus.com", true }, + { "exiahost.com", false }, { "exon.io", true }, { "expatads.com", true }, { "explodie.org", true }, @@ -701,6 +698,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "gokmenguresci.com", true }, { "goldendata.io", true }, { "golfscape.com", false }, + { "google", true }, { "googlemail.com", false }, { "googleplex.com", true }, { "gopay.cz", true }, @@ -726,6 +724,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "gtraxapp.com", true }, { "gudini.net", true }, { "gugga.dk", false }, + { "guidetoiceland.is", true }, { "gunnarhafdal.com", true }, { "guphi.net", true }, { "guru-naradi.cz", true }, @@ -754,7 +753,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "hausverbrauch.de", true }, { "haveibeenpwned.com", true }, { "hboeck.de", true }, - { "hda.me", true }, { "healthcare.gov", false }, { "heartlandrentals.com", true }, { "heavystresser.com", true }, @@ -778,7 +776,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "hg.python.org", true }, { "hicn.gq", true }, { "hicoria.com", true }, - { "history.google.com", true }, + { "history.google.com", false }, { "hiv.gov", true }, { "hledejpravnika.cz", true }, { "hobbyspeed.com", true }, @@ -1013,7 +1011,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "limitededitionsolutions.com", true }, { "limpid.nl", true }, { "lingolia.com", true }, - { "linguaquote.com", true }, { "linode.com", false }, { "linorman1997.me", true }, { "linux-admin-california.com", true }, @@ -1232,11 +1229,11 @@ static const nsSTSPreload kSTSPreloadList[] = { { "nellacms.com", true }, { "nellacms.org", true }, { "nellafw.org", true }, - { "nemovement.org", true }, { "nerven.se", true }, { "net-safe.info", true }, { "netbox.cc", true }, { "netera.se", true }, + { "netrelay.email", true }, { "netrider.net.au", true }, { "newstarnootropics.com", true }, { "nextend.net", true }, @@ -1250,7 +1247,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "nodari.com.ar", true }, { "noemax.com", true }, { "noob-box.net", true }, - { "noobs-r-us.co.uk", true }, { "nopex.no", true }, { "northernmuscle.ca", true }, { "nos-oignons.net", true }, @@ -1271,7 +1267,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "nu3.fr", true }, { "nu3.no", true }, { "nu3.se", true }, - { "null-sec.ru", true }, { "null.tips", true }, { "nutsandboltsmedia.com", true }, { "nuvini.com", true }, @@ -1547,7 +1542,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "security-carpet.com", true }, { "security.google.com", true }, { "securityheaders.com", true }, - { "securitysnobs.com", true }, + { "securitysnobs.com", false }, { "secuvera.de", true }, { "seifried.org", true }, { "sellocdn.com", true }, @@ -1570,7 +1565,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "shipard.com", true }, { "shodan.io", true }, { "shopontarget.com", true }, - { "shoprose.ru", true }, { "shortdiary.me", true }, { "sidium.de", true }, { "siewert-kau.de", true }, @@ -1615,6 +1609,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "sny.no", true }, { "soccergif.com", true }, { "soci.ml", true }, + { "sockeye.cc", true }, { "soia.ca", true }, { "solihullcarnival.co.uk", true }, { "solihulllionsclub.org.uk", true }, @@ -1691,7 +1686,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "t23m-navi.jp", false }, { "tadigitalstore.com", true }, { "tageau.com", true }, - { "taglondon.org", true }, { "taken.pl", true }, { "talideon.com", true }, { "talk.google.com", true }, @@ -1819,7 +1813,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "tucuxi.org", true }, { "tuitle.com", true }, { "tunebitfm.de", true }, - { "tuxgeo.com", true }, { "tuxplace.nl", true }, { "twentymilliseconds.com", true }, { "twisto.cz", true }, @@ -1851,7 +1844,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "usaa.com", false }, { "uscntalk.com", true }, { "uspsoig.gov", true }, - { "ust.space", true }, { "utilityapi.com", true }, { "utleieplassen.no", true }, { "vaddder.com", true }, @@ -1934,7 +1926,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "whitestagforge.com", true }, { "whocalld.com", true }, { "whonix.org", true }, - { "widememory.com", true }, + { "widememory.com", false }, { "wieninternational.at", true }, { "wifirst.net", true }, { "wiki.python.org", true }, diff --git a/testing/mozharness/mozharness.json b/testing/mozharness/mozharness.json index 05ac8458228..39f3525224c 100644 --- a/testing/mozharness/mozharness.json +++ b/testing/mozharness/mozharness.json @@ -1,4 +1,4 @@ { "repo": "https://hg.mozilla.org/build/mozharness", - "revision": "4135b4ce5aa1" + "revision": "30d82783408b" } diff --git a/testing/talos/talos.json b/testing/talos/talos.json index a2ea2fcd55c..f02270f7454 100644 --- a/testing/talos/talos.json +++ b/testing/talos/talos.json @@ -5,7 +5,7 @@ }, "global": { "talos_repo": "https://hg.mozilla.org/build/talos", - "talos_revision": "39db01f6dabb" + "talos_revision": "33449734f99f" }, "extra_options": { "android": [ "--apkPath=%(apk_path)s" ] diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 4807bc4ec67..b06b4fb0dfd 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -8,12 +8,14 @@ this.EXPORTED_SYMBOLS = [ "LoginManagerContent", "UserAutoCompleteResult" ]; const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent", "resource://gre/modules/LoginRecipes.jsm"); @@ -93,6 +95,30 @@ var LoginManagerContent = { _messages: [ "RemoteLogins:loginsFound", "RemoteLogins:loginsAutoCompleted" ], + /** + * WeakMap of the root element of a FormLike to the FormLike representing its fields. + * + * This is used to be able to lookup an existing FormLike for a given root element since multiple + * calls to FormLikeFactory won't give the exact same object. When batching fills we don't always + * want to use the most recent list of elements for a FormLike since we may end up doing multiple + * fills for the same set of elements when a field gets added between arming and running the + * DeferredTask. + * + * @type {WeakMap} + */ + _formLikeByRootElement: new WeakMap(), + + /** + * WeakMap of the root element of a WeakMap to the DeferredTask to fill its fields. + * + * This is used to be able to throttle fills for a FormLike since onDOMInputPasswordAdded gets + * dispatched for each password field added to a document but we only want to fill once per + * FormLike when multiple fields are added at once. + * + * @type {WeakMap} + */ + _deferredPasswordAddedTasksByRootElement: new WeakMap(), + // Map from form login requests to information about that request. _requests: new Map(), @@ -219,6 +245,11 @@ var LoginManagerContent = { let form = aElement.form; let win = doc.defaultView; + if (!form) { + return Promise.reject("Bug 1173583: _autoCompleteSearchAsync needs to be " + + "updated to work outside of
"); + } + let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI); let actionOrigin = LoginUtils._getActionOrigin(form); @@ -246,7 +277,69 @@ var LoginManagerContent = { } let form = event.target; + let formLike = FormLikeFactory.createFromForm(form); + log("onDOMFormHasPassword:", form, formLike); + this._fetchLoginsFromParentAndFillForm(formLike, window); + }, + onDOMInputPasswordAdded(event, window) { + if (!event.isTrusted) { + return; + } + + let pwField = event.target; + if (pwField.form) { + // Handled by onDOMFormHasPassword which is already throttled. + return; + } + + let formLike = FormLikeFactory.createFromPasswordField(pwField); + log("onDOMInputPasswordAdded:", pwField, formLike); + + let deferredTask = this._deferredPasswordAddedTasksByRootElement.get(formLike.rootElement); + if (!deferredTask) { + log("Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon"); + this._formLikeByRootElement.set(formLike.rootElement, formLike); + + deferredTask = new DeferredTask(function* deferredInputProcessing() { + // Get the updated formLike instead of the one at the time of creating the DeferredTask via + // a closure since it could be stale since FormLike.elements isn't live. + let formLike2 = this._formLikeByRootElement.get(formLike.rootElement); + log("Running deferred processing of onDOMInputPasswordAdded", formLike2); + this._deferredPasswordAddedTasksByRootElement.delete(formLike2.rootElement); + this._fetchLoginsFromParentAndFillForm(formLike2, window); + this._formLikeByRootElement.delete(formLike.rootElement); + }.bind(this), PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS); + + this._deferredPasswordAddedTasksByRootElement.set(formLike.rootElement, deferredTask); + } + + if (deferredTask.isArmed) { + log("DeferredTask is already armed so just updating the FormLike"); + // We update the FormLike so it (most important .elements) is fresh when the task eventually + // runs since changes to the elements could affect our field heuristics. + this._formLikeByRootElement.set(formLike.rootElement, formLike); + } else { + if (window.document.readyState == "complete") { + log("Arming the DeferredTask we just created since document.readyState == 'complete'"); + deferredTask.arm(); + } else { + window.addEventListener("DOMContentLoaded", function armPasswordAddedTask() { + window.removeEventListener("DOMContentLoaded", armPasswordAddedTask); + log("Arming the onDOMInputPasswordAdded DeferredTask due to DOMContentLoaded"); + deferredTask.arm(); + }); + } + } + }, + + /** + * Fetch logins from the parent for a given form and then attempt to fill it. + * + * @param {FormLike} form to fetch the logins for then try autofill. + * @param {Window} window + */ + _fetchLoginsFromParentAndFillForm(form, window) { // Always record the most recently added form with a password field. this.stateForDocument(form.ownerDocument).loginForm = form; @@ -259,7 +352,6 @@ var LoginManagerContent = { return; } - log("onDOMFormHasPassword for", form.ownerDocument.documentURI); this._getLoginDataFromParent(form, { showMasterPassword: true }) .then(this.loginsFound.bind(this)) .then(null, Cu.reportError); @@ -402,7 +494,7 @@ var LoginManagerContent = { if (!this._isUsernameFieldType(acInputField)) return; - var acForm = acInputField.form; + var acForm = acInputField.form; // XXX: Bug 1173583 - This doesn't work outside of . if (!acForm) return; @@ -515,8 +607,10 @@ var LoginManagerContent = { fieldOverrideRecipe.passwordSelector ); if (pwOverrideField) { + // The field from the password override may be in a different FormLike. + let formLike = FormLikeFactory.createFromPasswordField(pwOverrideField); pwFields = [{ - index : [...pwOverrideField.form.elements].indexOf(pwOverrideField), + index : [...formLike.elements].indexOf(pwOverrideField), element : pwOverrideField, }]; } @@ -557,15 +651,15 @@ var LoginManagerContent = { if (!usernameField) log("(form -- no username field found)"); else - log("Username field id/name/value is: ", usernameField.id, " / ", - usernameField.name, " / ", usernameField.value); + log("Username field ", usernameField, "has name/value:", + usernameField.name, "/", usernameField.value); // If we're not submitting a form (it's a page load), there are no // password field values for us to use for identifying fields. So, // just assume the first password field is the one to be filled in. if (!isSubmission || pwFields.length == 1) { var passwordField = pwFields[0].element; - log("Password field id/name is: ", passwordField.id, " / ", passwordField.name); + log("Password field", passwordField, "has name: ", passwordField.name); return [usernameField, passwordField, null]; } @@ -907,13 +1001,14 @@ var LoginManagerContent = { passwordField.setUserInput(selectedLogin.password); } + log("_fillForm succeeded"); recordAutofillResult(AUTOFILL_RESULT.FILLED); let doc = form.ownerDocument; let win = doc.defaultView; let messageManager = messageManagerFromWindow(win); messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful"); } finally { - Services.obs.notifyObservers(form, "passwordmgr-processed-form", null); + Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form", null); } }, @@ -1088,6 +1183,7 @@ let FormLikeFactory = { formLike[prop] = aForm[prop]; } + this._addToJSONProperty(formLike); return formLike; }, @@ -1117,7 +1213,7 @@ let FormLikeFactory = { let doc = aPasswordField.ownerDocument; log("Created non-form FormLike for rootElement:", doc.documentElement); - return { + let formLike = { action: LoginUtils._getPasswordOrigin(doc.baseURI), autocomplete: "on", // Exclude elements inside the rootElement that are already in a as @@ -1126,5 +1222,54 @@ let FormLikeFactory = { ownerDocument: doc, rootElement: doc.documentElement, }; + + this._addToJSONProperty(formLike); + return formLike; + }, + + /** + * Add a `toJSON` property to a FormLike so logging which ends up going + * through dump doesn't include usless garbage from DOM objects. + */ + _addToJSONProperty(aFormLike) { + function prettyElementOutput(aElement) { + let idText = aElement.id ? "#" + aElement.id : ""; + let classText = [for (className of aElement.classList) "." + className].join(""); + return `<${aElement.nodeName + idText + classText}>`; + } + + Object.defineProperty(aFormLike, "toJSON", { + value: () => { + let cleansed = {}; + for (let key of Object.keys(aFormLike)) { + let value = aFormLike[key]; + let cleansedValue = value; + + switch (key) { + case "elements": { + cleansedValue = [for (element of value) prettyElementOutput(element)]; + break; + } + + case "ownerDocument": { + cleansedValue = { + location: { + href: value.location.href, + }, + }; + break; + } + + case "rootElement": { + cleansedValue = prettyElementOutput(value); + break; + } + } + + cleansed[key] = cleansedValue; + } + return cleansed; + } + }); }, }; diff --git a/toolkit/components/passwordmgr/LoginRecipes.jsm b/toolkit/components/passwordmgr/LoginRecipes.jsm index 45782d9a171..518b025c6d0 100644 --- a/toolkit/components/passwordmgr/LoginRecipes.jsm +++ b/toolkit/components/passwordmgr/LoginRecipes.jsm @@ -33,17 +33,8 @@ function LoginRecipesParent(aOptions = { defaults: true }) { if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { throw new Error("LoginRecipesParent should only be used from the main process"); } - - this._recipesByHost = new Map(); - - if (aOptions.defaults) { - // XXX: Bug 1134850 will handle reading recipes from a file. - this.initializationPromise = this.load(DEFAULT_RECIPES).then(resolve => { - return this; - }); - } else { - this.initializationPromise = Promise.resolve(this); - } + this._defaults = aOptions.defaults; + this.reset(); } LoginRecipesParent.prototype = { @@ -54,6 +45,11 @@ LoginRecipesParent.prototype = { */ initializationPromise: null, + /** + * @type {bool} Whether default recipes were loaded at construction time. + */ + _defaults: null, + /** * @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes. * e.g. "example.com:8080" => Set({...}) @@ -84,6 +80,23 @@ LoginRecipesParent.prototype = { return Promise.resolve(); }, + /** + * Reset the set of recipes to the ones from the time of construction. + */ + reset() { + log.debug("Resetting recipes with defaults:", this._defaults); + this._recipesByHost = new Map(); + + if (this._defaults) { + // XXX: Bug 1134850 will handle reading recipes from a file. + this.initializationPromise = this.load(DEFAULT_RECIPES).then(resolve => { + return this; + }); + } else { + this.initializationPromise = Promise.resolve(this); + } + }, + /** * Validate the recipe is sane and then add it to the set of recipes. * diff --git a/toolkit/components/passwordmgr/test/chrome.ini b/toolkit/components/passwordmgr/test/chrome.ini index 31754515dab..7530842ae7e 100644 --- a/toolkit/components/passwordmgr/test/chrome.ini +++ b/toolkit/components/passwordmgr/test/chrome.ini @@ -5,6 +5,7 @@ support-files = notification_common.js pwmgr_common.js +[test_formless_autofill.html] [test_formless_submit.html] [test_privbrowsing_perwindowpb.html] skip-if = true # Bug 1173337 diff --git a/toolkit/components/passwordmgr/test/pwmgr_common.js b/toolkit/components/passwordmgr/test/pwmgr_common.js index 6c4eed670a5..a501911785b 100644 --- a/toolkit/components/passwordmgr/test/pwmgr_common.js +++ b/toolkit/components/passwordmgr/test/pwmgr_common.js @@ -182,11 +182,11 @@ function commonInit(selfFilling) { form.appendChild(password); var observer = SpecialPowers.wrapCallback(function(subject, topic, data) { - var form = subject.QueryInterface(SpecialPowers.Ci.nsIDOMNode); - if (form.id !== 'observerforcer') + var formLikeRoot = subject.QueryInterface(SpecialPowers.Ci.nsIDOMNode); + if (formLikeRoot.id !== 'observerforcer') return; SpecialPowers.removeObserver(observer, "passwordmgr-processed-form"); - form.parentNode.removeChild(form); + formLikeRoot.remove(); SimpleTest.executeSoon(() => { var event = new Event("runTests"); window.dispatchEvent(event); @@ -259,6 +259,23 @@ function dumpLogin(label, login) { ok(true, label + loginText); } +/** + * Resolves when a specified number of forms have been processed. + */ +function promiseFormsProcessed(expectedCount = 1) { + var processedCount = 0; + return new Promise((resolve, reject) => { + function onProcessedForm(subject, topic, data) { + processedCount++; + if (processedCount == expectedCount) { + SpecialPowers.removeObserver(onProcessedForm, "passwordmgr-processed-form"); + resolve(subject, data); + } + } + SpecialPowers.addObserver(onProcessedForm, "passwordmgr-processed-form", false); + }); +} + // Code to run when loaded as a chrome script in tests via loadChromeScript if (this.addMessageListener) { const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; @@ -285,4 +302,12 @@ if (this.addMessageListener) { globalMM.addMessageListener("RemoteLogins:onFormSubmit", function onFormSubmit(message) { sendAsyncMessage("formSubmissionProcessed", message.data, message.objects); }); +} else { + // Code to only run in the mochitest pages (not in the chrome script). + SimpleTest.registerCleanupFunction(() => { + var { LoginManagerParent } = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerParent.jsm", {}); + return LoginManagerParent.recipeParentPromise.then((recipeParent) => { + SpecialPowers.wrap(recipeParent).reset(); + }); + }); } diff --git a/toolkit/components/passwordmgr/test/test_formless_autofill.html b/toolkit/components/passwordmgr/test/test_formless_autofill.html new file mode 100644 index 00000000000..51cb11e532e --- /dev/null +++ b/toolkit/components/passwordmgr/test/test_formless_autofill.html @@ -0,0 +1,148 @@ + + + + + Test autofilling of fields outside of a form + + + + + + + +

+ +
+ +
+

+
+
diff --git a/toolkit/components/passwordmgr/test/test_formless_submit.html b/toolkit/components/passwordmgr/test/test_formless_submit.html
index 7df2a6191f1..9eb1a4552dd 100644
--- a/toolkit/components/passwordmgr/test/test_formless_submit.html
+++ b/toolkit/components/passwordmgr/test/test_formless_submit.html
@@ -4,6 +4,7 @@
   
   Test capturing of fields outside of a form
   
+  
   
 
 
@@ -15,8 +16,6 @@ Cu.import("resource://gre/modules/Task.jsm");
 const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm");
 const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
 
-SpecialPowers.Services.prefs.setBoolPref("signon.debug", true);
-
 let parentScriptURL = SimpleTest.getTestFileURL("pwmgr_common.js");
 let mm = SpecialPowers.loadChromeScript(parentScriptURL);
 
@@ -31,21 +30,20 @@ document.addEventListener("DOMContentLoaded", () => {
 mm.addMessageListener("doneSetup", function doneSetup() {
   mm.sendAsyncMessage("loadRecipes", {
     siteRecipes: [{
-      hosts: ["mochi.test:8888"],
-      usernameSelector: "input[name='uname1']",
-      passwordSelector: "input[name='pword2']",
+      hosts: ["test1.mochi.test:8888"],
+      usernameSelector: "input[name='recipeuname']",
+      passwordSelector: "input[name='recipepword']",
     }],
   });
 });
 
 mm.addMessageListener("loadedRecipes", () => runTest());
 
-const DEFAULT_ORIGIN = "http://mochi.test:8888";
+const DEFAULT_ORIGIN = "http://test1.mochi.test:8888";
 const TESTCASES = [
   {
     // Inputs
     document: ``,
-    //documentURL: DEFAULT_ORIGIN,
     inputIndexForFormLike: 0,
 
     // Expected outputs similar to RemoteLogins:onFormSubmit
@@ -103,6 +101,19 @@ const TESTCASES = [
     newPasswordFieldValue: "pass1",
     oldPasswordFieldValue: null,
   },
+  {
+    document: `
+      
+      
+      
+      `,
+    inputIndexForFormLike: 2,
+    hostname: DEFAULT_ORIGIN,
+    formSubmitURL: DEFAULT_ORIGIN,
+    usernameFieldValue: "username from recipe",
+    newPasswordFieldValue: "pass2",
+    oldPasswordFieldValue: null,
+  },
 ];
 
 function getSubmitMessage() {
@@ -160,7 +171,7 @@ let runTest = Task.async(function*() {
 

- +

 
diff --git a/toolkit/components/places/UnifiedComplete.js b/toolkit/components/places/UnifiedComplete.js
index 20c29613ab3..76af4013f99 100644
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -1038,7 +1038,7 @@ Search.prototype = {
   _addSearchEngineMatch(match, query, suggestion) {
     let actionURLParams = {
       engineName: match.engineName,
-      input: this._originalSearchString,
+      input: suggestion || this._originalSearchString,
       searchQuery: query,
     };
     if (suggestion)
@@ -1190,8 +1190,9 @@ Search.prototype = {
     if (this._searchSuggestionController) {
       let [match, suggestion] = this._searchSuggestionController.consume();
       if (suggestion) {
-        this._addSearchEngineMatch(match, this._originalSearchString,
-                                   suggestion);
+        // Don't include the restrict token, if present.
+        let searchString = this._searchTokens.join(" ");
+        this._addSearchEngineMatch(match, searchString, suggestion);
         return true;
       }
     }
diff --git a/toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js b/toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js
index 777107194e0..f0500575ead 100644
--- a/toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchSuggestions.js
@@ -6,12 +6,21 @@ const SUGGEST_PREF = "browser.urlbar.suggest.searches";
 const SUGGEST_ENABLED_PREF = "browser.search.suggest.enabled";
 const SUGGEST_RESTRICT_TOKEN = "$";
 
-// Set this to some other function to change how the server converts search
-// strings into suggestions.
-let suggestionsFromSearchString = searchStr => {
-  let suffixes = ["foo", "bar"];
-  return suffixes.map(s => searchStr + " " + s);
-};
+let suggestionsFn;
+let previousSuggestionsFn;
+
+function setSuggestionsFn(fn) {
+  previousSuggestionsFn = suggestionsFn;
+  suggestionsFn = fn;
+}
+
+function* cleanUpSuggestions() {
+  yield cleanup();
+  if (previousSuggestionsFn) {
+    suggestionsFn = previousSuggestionsFn;
+    previousSuggestionsFn = null;
+  }
+}
 
 add_task(function* setUp() {
   // Set up a server that provides some suggestions by appending strings onto
@@ -21,19 +30,21 @@ add_task(function* setUp() {
     // URL query params are x-www-form-urlencoded, which converts spaces into
     // plus signs, so un-convert any plus signs back to spaces.
     let searchStr = decodeURIComponent(req.queryString.replace(/\+/g, " "));
-    let suggestions = suggestionsFromSearchString(searchStr);
+    let suggestions = suggestionsFn(searchStr);
     let data = [searchStr, suggestions];
     resp.setHeader("Content-Type", "application/json", false);
     resp.write(JSON.stringify(data));
   });
+  setSuggestionsFn(searchStr => {
+    let suffixes = ["foo", "bar"];
+    return suffixes.map(s => searchStr + " " + s);
+  });
 
   // Install the test engine.
   let oldCurrentEngine = Services.search.currentEngine;
   do_register_cleanup(() => Services.search.currentEngine = oldCurrentEngine);
   let engine = yield addTestEngine(ENGINE_NAME, server);
   Services.search.currentEngine = engine;
-
-  yield cleanup();
 });
 
 add_task(function* disabled() {
@@ -42,21 +53,20 @@ add_task(function* disabled() {
     search: "hello",
     matches: [],
   });
-  yield cleanup();
+  yield cleanUpSuggestions();
 });
 
 add_task(function* singleWordQuery() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
   Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
 
-  let searchStr = "hello";
   yield check_autocomplete({
-    search: searchStr,
+    search: "hello",
     matches: [{
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "hello foo",
+        searchQuery: "hello",
         searchSuggestion: "hello foo",
       }),
       title: ENGINE_NAME,
@@ -65,8 +75,8 @@ add_task(function* singleWordQuery() {
     }, {
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "hello bar",
+        searchQuery: "hello",
         searchSuggestion: "hello bar",
       }),
       title: ENGINE_NAME,
@@ -75,21 +85,20 @@ add_task(function* singleWordQuery() {
     }],
   });
 
-  yield cleanup();
+  yield cleanUpSuggestions();
 });
 
 add_task(function* multiWordQuery() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
   Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
 
-  let searchStr = "hello world";
   yield check_autocomplete({
-    search: searchStr,
+    search: "hello world",
     matches: [{
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "hello world foo",
+        searchQuery: "hello world",
         searchSuggestion: "hello world foo",
       }),
       title: ENGINE_NAME,
@@ -98,8 +107,8 @@ add_task(function* multiWordQuery() {
     }, {
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "hello world bar",
+        searchQuery: "hello world",
         searchSuggestion: "hello world bar",
       }),
       title: ENGINE_NAME,
@@ -108,27 +117,25 @@ add_task(function* multiWordQuery() {
     }],
   });
 
-  yield cleanup();
+  yield cleanUpSuggestions();
 });
 
 add_task(function* suffixMatch() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
   Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
 
-  let oldFn = suggestionsFromSearchString;
-  suggestionsFromSearchString = searchStr => {
+  setSuggestionsFn(searchStr => {
     let prefixes = ["baz", "quux"];
     return prefixes.map(p => p + " " + searchStr);
-  };
+  });
 
-  let searchStr = "hello";
   yield check_autocomplete({
-    search: searchStr,
+    search: "hello",
     matches: [{
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "baz hello",
+        searchQuery: "hello",
         searchSuggestion: "baz hello",
       }),
       title: ENGINE_NAME,
@@ -137,8 +144,8 @@ add_task(function* suffixMatch() {
     }, {
       uri: makeActionURI(("searchengine"), {
         engineName: ENGINE_NAME,
-        input: searchStr,
-        searchQuery: searchStr,
+        input: "quux hello",
+        searchQuery: "hello",
         searchSuggestion: "quux hello",
       }),
       title: ENGINE_NAME,
@@ -147,8 +154,42 @@ add_task(function* suffixMatch() {
     }],
   });
 
-  suggestionsFromSearchString = oldFn;
-  yield cleanup();
+  yield cleanUpSuggestions();
+});
+
+add_task(function* queryIsNotASubstring() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, true);
+
+  setSuggestionsFn(searchStr => {
+    return ["aaa", "bbb"];
+  });
+
+  yield check_autocomplete({
+    search: "hello",
+    matches: [{
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: "aaa",
+        searchQuery: "hello",
+        searchSuggestion: "aaa",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }, {
+      uri: makeActionURI(("searchengine"), {
+        engineName: ENGINE_NAME,
+        input: "bbb",
+        searchQuery: "hello",
+        searchSuggestion: "bbb",
+      }),
+      title: ENGINE_NAME,
+      style: ["action", "searchengine"],
+      icon: "",
+    }],
+  });
+
+  yield cleanUpSuggestions();
 });
 
 add_task(function* restrictToken() {
@@ -176,9 +217,8 @@ add_task(function* restrictToken() {
 
   // Do an unrestricted search to make sure everything appears in it, including
   // the visit and bookmark.
-  let searchStr = "hello";
   yield check_autocomplete({
-    search: searchStr,
+    search: "hello",
     matches: [
       {
         uri: NetUtil.newURI("http://example.com/hello-visit"),
@@ -192,8 +232,8 @@ add_task(function* restrictToken() {
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
-          input: searchStr,
-          searchQuery: searchStr,
+          input: "hello foo",
+          searchQuery: "hello",
           searchSuggestion: "hello foo",
         }),
         title: ENGINE_NAME,
@@ -203,8 +243,8 @@ add_task(function* restrictToken() {
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
-          input: searchStr,
-          searchQuery: searchStr,
+          input: "hello bar",
+          searchQuery: "hello",
           searchSuggestion: "hello bar",
         }),
         title: ENGINE_NAME,
@@ -215,15 +255,14 @@ add_task(function* restrictToken() {
   });
 
   // Now do a restricted search to make sure only suggestions appear.
-  searchStr = SUGGEST_RESTRICT_TOKEN + " hello";
   yield check_autocomplete({
-    search: searchStr,
+    search: SUGGEST_RESTRICT_TOKEN + " hello",
     matches: [
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
-          input: searchStr,
-          searchQuery: searchStr,
+          input: "hello foo",
+          searchQuery: "hello",
           searchSuggestion: "hello foo",
         }),
         title: ENGINE_NAME,
@@ -233,8 +272,8 @@ add_task(function* restrictToken() {
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
-          input: searchStr,
-          searchQuery: searchStr,
+          input: "hello bar",
+          searchQuery: "hello",
           searchSuggestion: "hello bar",
         }),
         title: ENGINE_NAME,
@@ -244,5 +283,5 @@ add_task(function* restrictToken() {
     ],
   });
 
-  yield cleanup();
+  yield cleanUpSuggestions();
 });
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index 9195ea884c3..274737a928b 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8259,5 +8259,11 @@
     "expires_in_version": "never",
     "kind": "flag",
     "description": "An Attr node that's not namespace-aware and was created from an HTML document is set on a non-HTML element and has a name containing uppercase ASCII chars"
+  },
+  "FENNEC_TITLE_IN_TITLEBAR_ENABLED": {
+    "alert_emails": ["michael.l.comella@gmail.com"],
+    "expires_in_version": "43",
+    "kind": "boolean",
+    "description": "Is the title shown in the url bar (as determined by the titlebar title or url preference)?"
   }
 }
diff --git a/toolkit/content/widgets/autocomplete.xml b/toolkit/content/widgets/autocomplete.xml
index cd566f0bf32..c8da2542642 100644
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1651,14 +1651,36 @@ extends="chrome://global/content/bindings/popup.xml#popup">
               let engineStr = "- " +
                 this._stringBundle.formatStringFromName("searchWithEngine",
                                                         [engineName], 1);
-              let suggestedPart = "";
+
+              // Make the title by generating an array of pairs and its
+              // corresponding interpolation string (e.g., "%1$S") to pass to
+              // _generateEmphasisPairs.
+              let pairs;
               if (searchSuggestion) {
-                suggestedPart = searchSuggestion.substr(searchQuery.length);
+                // Check if the search query appears in the suggestion.  It may
+                // not.  If it does, then emphasize the query in the suggestion
+                // and otherwise just include the suggestion without emphasis.
+                let idx = searchSuggestion.indexOf(searchQuery);
+                if (idx >= 0) {
+                  pairs = [
+                    [searchSuggestion.substring(0, idx), ""],
+                    [searchQuery, "match"],
+                    [searchSuggestion.substring(idx + searchQuery.length), ""],
+                  ];
+                } else {
+                  pairs = [
+                    [searchSuggestion, ""],
+                  ];
+                }
+              } else {
+                pairs = [
+                  [searchQuery, ""],
+                ];
               }
-              title = this._generateEmphasisPairs(`%1$S${suggestedPart} %2$S`, [
-                                                    [searchQuery, "match"],
-                                                    [engineStr, "selected"],
-                                                  ]);
+              pairs.push([engineStr, "selected"]);
+              let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join("");
+              title = this._generateEmphasisPairs(interpStr, pairs);
+
               // If this is a default search match, we remove the image so we
               // can style it ourselves with a generic search icon.
               // We don't do this when matching an aliased search engine,
diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml
index 0f63b73eca1..f65979e42d4 100644
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1171,6 +1171,9 @@
         ]]>
         
       
+
+      
+      true
     
 
     
diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm
index fa9ac65c2a1..4c36ae72daa 100644
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -739,6 +739,7 @@ DebuggerClient.prototype = {
 
     let request = new Request(aRequest);
     request.format = "json";
+    request.stack = Components.stack;
     if (aOnResponse) {
       request.on("json-reply", aOnResponse);
     }
@@ -1032,7 +1033,13 @@ DebuggerClient.prototype = {
     }
 
     if (activeRequest) {
-      activeRequest.emit("json-reply", aPacket);
+      let emitReply = () => activeRequest.emit("json-reply", aPacket);
+      if (activeRequest.stack) {
+        Cu.callFunctionWithAsyncStack(emitReply, activeRequest.stack,
+                                      "DevTools RDP");
+      } else {
+        emitReply();
+      }
     }
   },
 
diff --git a/toolkit/devtools/server/protocol.js b/toolkit/devtools/server/protocol.js
index 9073f2bdd83..e7dce7aec68 100644
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -4,7 +4,7 @@
 
 "use strict";
 
-let { Cu } = require("chrome");
+let { Cu, components } = require("chrome");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let Services = require("Services");
 let promise = require("promise");
@@ -1172,7 +1172,8 @@ let Front = Class({
     this._requests.push({
       deferred,
       to: to || this.actorID,
-      type
+      type,
+      stack: components.stack,
     });
     this.send(packet);
     return deferred.promise;
@@ -1209,20 +1210,22 @@ let Front = Class({
       throw err;
     }
 
-    let { deferred } = this._requests.shift();
-    if (packet.error) {
-      // "Protocol error" is here to avoid TBPL heuristics. See also
-      // https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
-      let message;
-      if (packet.error && packet.message) {
-        message = "Protocol error (" + packet.error + "): " + packet.message;
+    let { deferred, stack } = this._requests.shift();
+    Cu.callFunctionWithAsyncStack(() => {
+      if (packet.error) {
+        // "Protocol error" is here to avoid TBPL heuristics. See also
+        // https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
+        let message;
+        if (packet.error && packet.message) {
+          message = "Protocol error (" + packet.error + "): " + packet.message;
+        } else {
+          message = packet.error;
+        }
+        deferred.reject(message);
       } else {
-        message = packet.error;
+        deferred.resolve(packet);
       }
-      deferred.reject(message);
-    } else {
-      deferred.resolve(packet);
-    }
+    }, stack, "DevTools RDP");
   }
 });
 exports.Front = Front;
diff --git a/toolkit/devtools/server/tests/unit/test_client_request.js b/toolkit/devtools/server/tests/unit/test_client_request.js
index 4a1a5f274c6..870665c16e9 100644
--- a/toolkit/devtools/server/tests/unit/test_client_request.js
+++ b/toolkit/devtools/server/tests/unit/test_client_request.js
@@ -51,6 +51,25 @@ function init()
   });
 }
 
+function checkStack(expectedName) {
+  if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) {
+    do_print("Async stacks are disabled.");
+    return;
+  }
+
+  let stack = Components.stack;
+  while (stack) {
+    do_print(stack.name);
+    if (stack.name == expectedName) {
+      // Reached back to outer function before request
+      ok(true, "Complete stack");
+      return;
+    }
+    stack = stack.asyncCaller || stack.caller;
+  }
+  ok(false, "Incomplete stack");
+}
+
 function test_client_request_callback()
 {
   // Test that DebuggerClient.request accepts a `onResponse` callback as 2nd argument
@@ -60,6 +79,7 @@ function test_client_request_callback()
   }, response => {
     do_check_eq(response.from, gActorId);
     do_check_eq(response.hello, "world");
+    checkStack("test_client_request_callback");
     run_next_test();
   });
 }
@@ -75,6 +95,7 @@ function test_client_request_promise()
   request.then(response => {
     do_check_eq(response.from, gActorId);
     do_check_eq(response.hello, "world");
+    checkStack("test_client_request_promise");
     run_next_test();
   });
 }
@@ -94,6 +115,7 @@ function test_client_request_promise_error()
     do_check_eq(response.from, gActorId);
     do_check_eq(response.error, "code");
     do_check_eq(response.message, "human message");
+    checkStack("test_client_request_promise_error");
     run_next_test();
   });
 }
@@ -108,6 +130,7 @@ function test_client_request_event_emitter()
   request.on("json-reply", reply => {
     do_check_eq(reply.from, gActorId);
     do_check_eq(reply.hello, "world");
+    checkStack("test_client_request_event_emitter");
     run_next_test();
   });
 }
@@ -117,4 +140,3 @@ function close_client() {
     run_next_test()
   });
 }
-
diff --git a/toolkit/devtools/server/tests/unit/test_protocol_stack.js b/toolkit/devtools/server/tests/unit/test_protocol_stack.js
new file mode 100644
index 00000000000..861e82e35a0
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_protocol_stack.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Client request stacks should span the entire process from before making the
+ * request to handling the reply from the server.  The server frames are not
+ * included, nor can they be in most cases, since the server can be a remote
+ * device.
+ */
+
+let protocol = devtools.require("devtools/server/protocol");
+let {method, Arg, Option, RetVal} = protocol;
+let events = devtools.require("sdk/event/core");
+
+function simpleHello() {
+  return {
+    from: "root",
+    applicationType: "xpcshell-tests",
+    traits: [],
+  };
+}
+
+let RootActor = protocol.ActorClass({
+  typeName: "root",
+  initialize: function(conn) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    // Root actor owns itself.
+    this.manage(this);
+    this.actorID = "root";
+    this.sequence = 0;
+  },
+
+  sayHello: simpleHello,
+
+  simpleReturn: method(function() {
+    return this.sequence++;
+  }, {
+    response: { value: RetVal() },
+  })
+});
+
+let RootFront = protocol.FrontClass(RootActor, {
+  initialize: function(client) {
+    this.actorID = "root";
+    protocol.Front.prototype.initialize.call(this, client);
+    // Root owns itself.
+    this.manage(this);
+  }
+});
+
+function run_test() {
+  if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) {
+    do_print("Async stacks are disabled.");
+    return;
+  }
+
+  DebuggerServer.createRootActor = RootActor;
+  DebuggerServer.init();
+
+  let trace = connectPipeTracing();
+  let client = new DebuggerClient(trace);
+  let rootClient;
+
+  client.connect(function onConnect() {
+    rootClient = RootFront(client);
+
+    rootClient.simpleReturn().then(() => {
+      let stack = Components.stack;
+      while (stack) {
+        do_print(stack.name);
+        if (stack.name == "onConnect") {
+          // Reached back to outer function before request
+          ok(true, "Complete stack");
+          return;
+        }
+        stack = stack.asyncCaller || stack.caller;
+      }
+      ok(false, "Incomplete stack");
+    }, () => {
+      ok(false, "Request failed unexpectedly");
+    }).then(() => {
+      client.close(() => {
+        do_test_finished();
+      });
+    })
+  });
+
+  do_test_pending();
+}
diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini
index 5903eb8b302..4214bb5d49d 100644
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -98,6 +98,7 @@ support-files =
 [test_protocol_formtype.js]
 [test_protocol_longstring.js]
 [test_protocol_simple.js]
+[test_protocol_stack.js]
 [test_protocol_unregister.js]
 [test_breakpoint-01.js]
 [test_register_actor.js]
diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp
index fd3a649db52..08111ddb79e 100644
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -462,6 +462,24 @@ NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell,
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsContentTreeOwner::ShouldAddToSessionHistory(nsIDocShell *aDocShell,
+                                              nsIURI *aURI,
+                                              bool *_retval)
+{
+  NS_ENSURE_STATE(mXULWindow);
+
+  nsCOMPtr xulBrowserWindow;
+  mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+  if (xulBrowserWindow) {
+    return xulBrowserWindow->ShouldAddToSessionHistory(aDocShell, aURI, _retval);
+  }
+
+  *_retval = true;
+  return NS_OK;
+}
+
 //*****************************************************************************
 // nsContentTreeOwner::nsIWebBrowserChrome2
 //*****************************************************************************   
diff --git a/xpfe/appshell/nsIXULBrowserWindow.idl b/xpfe/appshell/nsIXULBrowserWindow.idl
index de69319f12d..a3e8602731a 100644
--- a/xpfe/appshell/nsIXULBrowserWindow.idl
+++ b/xpfe/appshell/nsIXULBrowserWindow.idl
@@ -19,7 +19,7 @@ interface nsITabParent;
  * internals of the browser area to tell the containing xul window to update
  * its ui. 
  */
-[scriptable, uuid(db89f748-9736-40b2-a172-3928aa1194b2)]
+[scriptable, uuid(8974a499-d49b-43e1-8b32-c9b3ed81be3f)]
 interface nsIXULBrowserWindow : nsISupports
 {
   /**
@@ -62,6 +62,9 @@ interface nsIXULBrowserWindow : nsISupports
   bool shouldLoadURI(in nsIDocShell    aDocShell,
                      in nsIURI         aURI,
                      in nsIURI         aReferrer);
+
+  bool shouldAddToSessionHistory(in nsIDocShell aDocShell,
+                                 in nsIURI      aURI);
   /**
    * Show/hide a tooltip (when the user mouses over a link, say).
    */