author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 03 Mar 2016 11:53:32 +0100 | |
changeset 324684 | 7e43bdd93e439b8c8d6e62d48d9de3e293655560 |
parent 324650 | 4ea7408b3eef059aa248f4b00328f8fdb4475112 (current diff) |
parent 324683 | afa990f7034a12b78e1cba02b532a254c2d1990f (diff) |
child 324814 | 2b5237c178ea02133a777396c24dd2b713f2b8ee |
push id | 1128 |
push user | jlund@mozilla.com |
push date | Wed, 01 Jun 2016 01:31:59 +0000 |
treeherder | mozilla-release@fe0d30de989d [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 47.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
mobile/android/base/resources/drawable-hdpi/ic_url_bar_star.png | file | annotate | diff | comparison | revisions | |
mobile/android/base/resources/drawable-xhdpi/ic_url_bar_star.png | file | annotate | diff | comparison | revisions |
--- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -1374,54 +1374,47 @@ var BookmarkingUI = { options.maxResults = kMaxResults; let query = PlacesUtils.history.getNewQuery(); while (aHeaderItem.nextSibling && aHeaderItem.nextSibling.localName == "menuitem") { aHeaderItem.nextSibling.remove(); } - PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) - .asyncExecuteLegacyQueries([query], 1, options, { - handleResult: function (aResultSet) { - let onItemCommand = function (aEvent) { - let item = aEvent.target; - openUILink(item.getAttribute("targetURI"), aEvent); - CustomizableUI.hidePanelForNode(item); - }; + let onItemCommand = function (aEvent) { + let item = aEvent.target; + openUILink(item.getAttribute("targetURI"), aEvent); + CustomizableUI.hidePanelForNode(item); + }; - let fragment = document.createDocumentFragment(); - let row; - while ((row = aResultSet.getNextRow())) { - let uri = row.getResultByIndex(1); - let title = row.getResultByIndex(2); - let icon = row.getResultByIndex(6); + let fragment = document.createDocumentFragment(); + let root = PlacesUtils.history.executeQuery(query, options).root; + root.containerOpen = true; + for (let i = 0; i < root.childCount; i++) { + let node = root.getChild(i); + let uri = node.uri; + let title = node.title; + let icon = node.icon; - let item = - document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", - "menuitem"); - item.setAttribute("label", title || uri); - item.setAttribute("targetURI", uri); - item.setAttribute("class", "menuitem-iconic menuitem-with-favicon bookmark-item " + - extraCSSClass); - item.addEventListener("command", onItemCommand); - if (icon) { - let iconURL = "moz-anno:favicon:" + icon; - item.setAttribute("image", iconURL); - } - fragment.appendChild(item); - } - aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling); - }, - handleError: function (aError) { - Cu.reportError("Error while attempting to show recent bookmarks: " + aError); - }, - handleCompletion: function (aReason) { - }, - }); + let item = + document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "menuitem"); + item.setAttribute("label", title || uri); + item.setAttribute("targetURI", uri); + item.setAttribute("class", "menuitem-iconic menuitem-with-favicon bookmark-item " + + extraCSSClass); + item.addEventListener("command", onItemCommand); + if (icon) { + let iconURL = "moz-anno:favicon:" + icon; + item.setAttribute("image", iconURL); + } + fragment.appendChild(item); + } + root.containerOpen = false; + aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling); }, /** * Handles star styling based on page proxy state changes. */ onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) { if (!this._shouldUpdateStarState() || !this.star) { return;
--- a/browser/base/content/contentSearchUI.js +++ b/browser/base/content/contentSearchUI.js @@ -247,17 +247,18 @@ ContentSearchUIController.prototype = { search: function (aEvent) { if (!this.defaultEngine) { return; // Not initialized yet. } let searchText = this.input; let searchTerms; if (this._table.hidden || - aEvent.originalTarget.id == "contentSearchDefaultEngineHeader") { + aEvent.originalTarget.id == "contentSearchDefaultEngineHeader" || + aEvent instanceof KeyboardEvent) { searchTerms = searchText.value; } else { searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value; } // Send an event that will perform a search and Firefox Health Report will // record that a search from the healthReportKey passed to the constructor. let eventData = {
--- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -285,17 +285,16 @@ tags = mcb tags = mcb [browser_bug970746.js] [browser_bug1015721.js] skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel [browser_bug1064280_changeUrlInPinnedTab.js] [browser_bug1070778.js] [browser_accesskeys.js] [browser_canonizeURL.js] -skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only [browser_clipboard.js] [browser_contentAreaClick.js] [browser_contextmenu.js] skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558 [browser_ctrlTab.js] [browser_datachoices_notification.js] skip-if = !datareporting [browser_devedition.js]
--- a/browser/base/content/test/general/browser_canonizeURL.js +++ b/browser/base/content/test/general/browser_canonizeURL.js @@ -1,13 +1,8 @@ -function test() { - waitForExplicitFinish(); - testNext(); -} - var pairs = [ ["example", "http://www.example.net/"], ["ex-ample", "http://www.ex-ample.net/"], [" example ", "http://www.example.net/"], [" example/foo ", "http://www.example.net/foo"], [" example/foo bar ", "http://www.example.net/foo%20bar"], ["example.net", "http://example.net/"], ["http://example", "http://example/"], @@ -15,42 +10,61 @@ var pairs = [ ["ex-ample.foo", "http://ex-ample.foo/"], ["example.foo/bar ", "http://example.foo/bar"], ["1.1.1.1", "http://1.1.1.1/"], ["ftp://example", "ftp://example/"], ["ftp.example.bar", "ftp://ftp.example.bar/"], ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec], ]; -function testNext() { - if (!pairs.length) { - finish(); - return; - } +add_task(function*() { + for (let [inputValue, expectedURL] of pairs) { + let focusEventPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus"); + let messagePromise = BrowserTestUtils.waitForMessage(gBrowser.selectedBrowser.messageManager, + "browser_canonizeURL:start"); - let [inputValue, expectedURL] = pairs.shift(); + let stoppedLoadPromise = ContentTask.spawn(gBrowser.selectedBrowser, [inputValue, expectedURL], + function([inputValue, expectedURL]) { + return new Promise(resolve => { + let wpl = { + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_START && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { + if (!aRequest || !(aRequest instanceof Ci.nsIChannel)) { + return; + } + aRequest.QueryInterface(Ci.nsIChannel); + is(aRequest.originalURI.spec, expectedURL, + "entering '" + inputValue + "' loads expected URL"); - gBrowser.addProgressListener({ - onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { - if (aStateFlags & Ci.nsIWebProgressListener.STATE_START && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { - is(aRequest.originalURI.spec, expectedURL, - "entering '" + inputValue + "' loads expected URL"); + webProgress.removeProgressListener(filter); + filter.removeProgressListener(wpl); + docShell.QueryInterface(Ci.nsIWebNavigation); + docShell.stop(docShell.STOP_ALL); + resolve(); + } + }, + }; + let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] + .createInstance(Ci.nsIWebProgress); + filter.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL); - gBrowser.removeProgressListener(this); - gBrowser.stop(); - - executeSoon(testNext); + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL); + // We're sending this off to trigger the start of the this test, when all the + // listeners are in place: + sendAsyncMessage("browser_canonizeURL:start"); + }); } - } - }); + ); - gURLBar.addEventListener("focus", function onFocus() { - gURLBar.removeEventListener("focus", onFocus); + gBrowser.selectedBrowser.focus(); + gURLBar.focus(); + + yield Promise.all([focusEventPromise, messagePromise]); + gURLBar.inputField.value = inputValue.slice(0, -1); EventUtils.synthesizeKey(inputValue.slice(-1) , {}); EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }); - }); - - gBrowser.selectedBrowser.focus(); - gURLBar.focus(); - -} + yield stoppedLoadPromise; + } +});
--- a/browser/components/controlcenter/content/panel.inc.xul +++ b/browser/components/controlcenter/content/panel.inc.xul @@ -100,17 +100,17 @@ <description class="identity-popup-connection-not-secure" value="&identity.connectionNotSecure;" when-connection="not-secure secure-cert-user-overridden"/> <description class="identity-popup-connection-secure" value="&identity.connectionSecure;" when-connection="secure secure-ev"/> </vbox> - <vbox id="identity-popup-securityView-body"> + <vbox id="identity-popup-securityView-body" flex="1"> <!-- (EV) Certificate Information --> <description id="identity-popup-content-verified-by" when-connection="secure-ev">&identity.connectionVerified1;</description> <description id="identity-popup-content-owner" when-connection="secure-ev" class="header"/> <description id="identity-popup-content-supplemental" when-connection="secure-ev"/> @@ -163,17 +163,19 @@ <button when-mixedcontent="active-blocked" label="&identity.disableMixedContentBlocking.label;" accesskey="&identity.disableMixedContentBlocking.accesskey;" oncommand="gIdentityHandler.disableMixedContentProtection()"/> <button when-mixedcontent="active-loaded" label="&identity.enableMixedContentBlocking.label;" accesskey="&identity.enableMixedContentBlocking.accesskey;" oncommand="gIdentityHandler.enableMixedContentProtection()"/> + </vbox> + <vbox id="identity-popup-securityView-footer"> <!-- More Security Information --> <button label="&identity.moreInfoLinkText2;" oncommand="gIdentityHandler.handleMoreInfoClick(event);"/> </vbox> </panelview> </panelmultiview> </panel>
--- a/browser/components/distribution.js +++ b/browser/components/distribution.js @@ -341,72 +341,111 @@ DistributionCustomizer.prototype = { partnerAbout = this._ini.getString("Global", "about"); } defaults.set("distribution.about", partnerAbout); } catch (e) { /* ignore bad prefs due to bug 895473 and move on */ Cu.reportError(e); } + var usedPreferences = []; + + if (sections["Preferences-" + this._locale]) { + for (let key of enumerate(this._ini.getKeys("Preferences-" + this._locale))) { + try { + let value = this._ini.getString("Preferences-" + this._locale, key); + if (value) { + Preferences.set(key, parseValue(value)); + } + usedPreferences.push(key); + } catch (e) { /* ignore bad prefs and move on */ } + } + } + + if (sections["Preferences-" + this._language]) { + for (let key of enumerate(this._ini.getKeys("Preferences-" + this._language))) { + if (usedPreferences.indexOf(key) > -1) { + continue; + } + try { + let value = this._ini.getString("Preferences-" + this._language, key); + if (value) { + Preferences.set(key, parseValue(value)); + } + usedPreferences.push(key); + } catch (e) { /* ignore bad prefs and move on */ } + } + } + if (sections["Preferences"]) { for (let key of enumerate(this._ini.getKeys("Preferences"))) { + if (usedPreferences.indexOf(key) > -1) { + continue; + } try { - let value = parseValue(this._ini.getString("Preferences", key)); - Preferences.set(key, value); + let value = this._ini.getString("Preferences", key); + if (value) { + value = value.replace(/%LOCALE%/g, this._locale); + value = value.replace(/%LANGUAGE%/g, this._language); + Preferences.set(key, parseValue(value)); + } } catch (e) { /* ignore bad prefs and move on */ } } } let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"]. createInstance(Ci.nsIPrefLocalizedString); var usedLocalizablePreferences = []; if (sections["LocalizablePreferences-" + this._locale]) { for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) { try { - let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._locale, key)); - if (value !== undefined) { + let value = this._ini.getString("LocalizablePreferences-" + this._locale, key); + if (value) { + value = parseValue(value); localizedStr.data = "data:text/plain," + key + "=" + value; defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); } usedLocalizablePreferences.push(key); } catch (e) { /* ignore bad prefs and move on */ } } } if (sections["LocalizablePreferences-" + this._language]) { for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._language))) { if (usedLocalizablePreferences.indexOf(key) > -1) { continue; } try { - let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._language, key)); - if (value !== undefined) { + let value = this._ini.getString("LocalizablePreferences-" + this._language, key); + if (value) { + value = parseValue(value); localizedStr.data = "data:text/plain," + key + "=" + value; defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); } usedLocalizablePreferences.push(key); } catch (e) { /* ignore bad prefs and move on */ } } } if (sections["LocalizablePreferences"]) { for (let key of enumerate(this._ini.getKeys("LocalizablePreferences"))) { if (usedLocalizablePreferences.indexOf(key) > -1) { continue; } try { - let value = parseValue(this._ini.getString("LocalizablePreferences", key)); - if (value !== undefined) { + let value = this._ini.getString("LocalizablePreferences", key); + if (value) { + value = parseValue(value); value = value.replace(/%LOCALE%/g, this._locale); value = value.replace(/%LANGUAGE%/g, this._language); localizedStr.data = "data:text/plain," + key + "=" + value; - defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); } + defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); } catch (e) { /* ignore bad prefs and move on */ } } } return this._checkCustomizationComplete(); }, _checkCustomizationComplete: function DIST__checkCustomizationComplete() {
--- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -120,16 +120,29 @@ global.IconDetails = { }; global.makeWidgetId = id => { id = id.toLowerCase(); // FIXME: This allows for collisions. return id.replace(/[^a-z0-9_-]/g, "_"); }; +function promisePopupShown(popup) { + return new Promise(resolve => { + if (popup.state == "open") { + resolve(); + } else { + popup.addEventListener("popupshown", function onPopupShown(event) { + popup.removeEventListener("popupshown", onPopupShown); + resolve(); + }); + } + }); +} + class BasePopup { constructor(extension, viewNode, popupURL) { let popupURI = Services.io.newURI(popupURL, null, extension.baseURI); Services.scriptSecurityManager.checkLoadURIWithPrincipal( extension.principal, popupURI, Services.scriptSecurityManager.DISALLOW_SCRIPT); @@ -249,16 +262,20 @@ class BasePopup { this.browser.addEventListener("load", this, true); this.browser.addEventListener("DOMTitleChanged", this, true); this.browser.addEventListener("DOMWindowClose", this, true); }); } // Resizes the browser to match the preferred size of the content. resizeBrowser() { + if (!this.browser) { + return; + } + let width, height; try { let w = {}, h = {}; this.browser.docShell.contentViewer.getContentSize(w, h); width = w.value / this.window.devicePixelRatio; height = h.value / this.window.devicePixelRatio; @@ -305,17 +322,22 @@ global.PanelPopup = class PanelPopup ext } destroy() { super.destroy(); this.viewNode.remove(); } closePopup() { - this.viewNode.hidePopup(); + promisePopupShown(this.viewNode).then(() => { + // Make sure we're not already destroyed. + if (this.viewNode) { + this.viewNode.hidePopup(); + } + }); } }; global.ViewPopup = class ViewPopup extends BasePopup { get DESTROY_EVENT() { return "ViewHiding"; }
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js @@ -1,35 +1,42 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; function* testInArea(area) { + let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head></html>`; + let extension = ExtensionTestUtils.loadExtension({ manifest: { "background": { "page": "data/background.html", }, "browser_action": { "default_popup": "popup-a.html", }, }, files: { - "popup-a.html": `<script src="popup-a.js"></script>`, + "popup-a.html": scriptPage("popup-a.js"), "popup-a.js": function() { browser.runtime.sendMessage("from-popup-a"); + browser.runtime.onMessage.addListener(msg => { + if (msg == "close-popup") { + window.close(); + } + }); }, - "data/popup-b.html": `<script src="popup-b.js"></script>`, + "data/popup-b.html": scriptPage("popup-b.js"), "data/popup-b.js": function() { browser.runtime.sendMessage("from-popup-b"); }, - "data/background.html": `<script src="background.js"></script>`, + "data/background.html": scriptPage("background.js"), "data/background.js": function() { let sendClick; let tests = [ () => { sendClick({expectEvent: false, expectPopup: "a"}); }, () => { @@ -46,50 +53,65 @@ function* testInArea(area) { browser.browserAction.setPopup({popup: ""}); sendClick({expectEvent: true, expectPopup: null}); }, () => { sendClick({expectEvent: true, expectPopup: null}); }, () => { browser.browserAction.setPopup({popup: "/popup-a.html"}); - sendClick({expectEvent: false, expectPopup: "a"}); + sendClick({expectEvent: false, expectPopup: "a", runNextTest: true}); + }, + () => { + browser.test.sendMessage("next-test", {expectClosed: true}); }, ]; let expect = {}; - sendClick = ({expectEvent, expectPopup}) => { - expect = {event: expectEvent, popup: expectPopup}; + sendClick = ({expectEvent, expectPopup, runNextTest}) => { + expect = {event: expectEvent, popup: expectPopup, runNextTest}; browser.test.sendMessage("send-click"); }; browser.runtime.onMessage.addListener(msg => { - if (expect.popup) { + if (msg == "close-popup") { + return; + } else if (expect.popup) { browser.test.assertEq(msg, `from-popup-${expect.popup}`, "expected popup opened"); } else { - browser.test.fail("unexpected popup"); + browser.test.fail(`unexpected popup: ${msg}`); } expect.popup = null; - browser.test.sendMessage("next-test"); + if (expect.runNextTest) { + expect.runNextTest = false; + tests.shift()(); + } else { + browser.test.sendMessage("next-test"); + } }); browser.browserAction.onClicked.addListener(() => { if (expect.event) { browser.test.succeed("expected click event received"); } else { browser.test.fail("unexpected click event"); } expect.event = false; browser.test.sendMessage("next-test"); }); browser.test.onMessage.addListener((msg) => { + if (msg == "close-popup") { + browser.runtime.sendMessage("close-popup"); + return; + } + if (msg != "next-test") { browser.test.fail("Expecting 'next-test' message"); } if (tests.length) { let test = tests.shift(); test(); } else { @@ -102,23 +124,33 @@ function* testInArea(area) { }, }); extension.onMessage("send-click", () => { clickBrowserAction(extension); }); let widget; - extension.onMessage("next-test", Task.async(function* () { + extension.onMessage("next-test", Task.async(function* (expecting = {}) { if (!widget) { widget = getBrowserActionWidget(extension); CustomizableUI.addWidgetToArea(widget.id, area); } + if (expecting.expectClosed) { + let panel = getBrowserActionPopup(extension); + ok(panel, "Expect panel to exist"); + yield promisePopupShown(panel); - yield closeBrowserAction(extension); + extension.sendMessage("close-popup"); + + yield promisePopupHidden(panel); + ok(true, "Panel is closed"); + } else { + yield closeBrowserAction(extension); + } extension.sendMessage("next-test"); })); yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]); yield extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js @@ -14,16 +14,21 @@ add_task(function* testPageActionPopup() "default_popup": "popup-a.html", }, }, files: { "popup-a.html": scriptPage("popup-a.js"), "popup-a.js": function() { browser.runtime.sendMessage("from-popup-a"); + browser.runtime.onMessage.addListener(msg => { + if (msg == "close-popup") { + window.close(); + } + }); }, "data/popup-b.html": scriptPage("popup-b.js"), "data/popup-b.js": function() { browser.runtime.sendMessage("from-popup-b"); }, "data/background.html": scriptPage("background.js"), @@ -50,50 +55,65 @@ add_task(function* testPageActionPopup() browser.pageAction.setPopup({tabId, popup: ""}); sendClick({expectEvent: true, expectPopup: null}); }, () => { sendClick({expectEvent: true, expectPopup: null}); }, () => { browser.pageAction.setPopup({tabId, popup: "/popup-a.html"}); - sendClick({expectEvent: false, expectPopup: "a"}); + sendClick({expectEvent: false, expectPopup: "a", runNextTest: true}); + }, + () => { + browser.test.sendMessage("next-test", {expectClosed: true}); }, ]; let expect = {}; - sendClick = ({expectEvent, expectPopup}) => { - expect = {event: expectEvent, popup: expectPopup}; + sendClick = ({expectEvent, expectPopup, runNextTest}) => { + expect = {event: expectEvent, popup: expectPopup, runNextTest}; browser.test.sendMessage("send-click"); }; browser.runtime.onMessage.addListener(msg => { - if (expect.popup) { + if (msg == "close-popup") { + return; + } else if (expect.popup) { browser.test.assertEq(msg, `from-popup-${expect.popup}`, "expected popup opened"); } else { - browser.test.fail("unexpected popup"); + browser.test.fail(`unexpected popup: ${msg}`); } expect.popup = null; - browser.test.sendMessage("next-test"); + if (expect.runNextTest) { + expect.runNextTest = false; + tests.shift()(); + } else { + browser.test.sendMessage("next-test"); + } }); browser.pageAction.onClicked.addListener(() => { if (expect.event) { browser.test.succeed("expected click event received"); } else { browser.test.fail("unexpected click event"); } expect.event = false; browser.test.sendMessage("next-test"); }); browser.test.onMessage.addListener((msg) => { + if (msg == "close-popup") { + browser.runtime.sendMessage("close-popup"); + return; + } + if (msg != "next-test") { browser.test.fail("Expecting 'next-test' message"); } if (tests.length) { let test = tests.shift(); test(); } else { @@ -113,22 +133,32 @@ add_task(function* testPageActionPopup() let pageActionId = makeWidgetId(extension.id) + "-page-action"; let panelId = makeWidgetId(extension.id) + "-panel"; extension.onMessage("send-click", () => { clickPageAction(extension); }); - extension.onMessage("next-test", Task.async(function* () { + extension.onMessage("next-test", Task.async(function* (expecting = {}) { let panel = document.getElementById(panelId); - if (panel) { + if (expecting.expectClosed) { + ok(panel, "Expect panel to exist"); + yield promisePopupShown(panel); + + extension.sendMessage("close-popup"); + + yield promisePopupHidden(panel); + ok(true, `Panel is closed`); + } else if (panel) { yield promisePopupShown(panel); panel.hidePopup(); + } + if (panel) { panel = document.getElementById(panelId); is(panel, null, "panel successfully removed from document after hiding"); } extension.sendMessage("next-test"); }));
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js +++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js @@ -133,16 +133,19 @@ add_task(function* testWebNavigationFram getAllFramesDetails, getFrameResults, } = yield extension.awaitMessage("webNavigationFrames.done"); is(getAllFramesDetails.length, 3, "expected number of frames found"); is(getAllFramesDetails.length, collectedDetails.length, "number of frames found should equal the number onCompleted events collected"); + is(getAllFramesDetails[0].frameId, 0, "the root frame has the expected frameId"); + is(getAllFramesDetails[0].parentFrameId, -1, "the root frame has the expected parentFrameId"); + // ordered by frameId let sortByFrameId = (el1, el2) => { let val1 = el1 ? el1.frameId : -1; let val2 = el2 ? el2.frameId : -1; return val1 - val2; }; collectedDetails = collectedDetails.sort(sortByFrameId);
--- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -2,17 +2,17 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; /* exported CustomizableUI makeWidgetId focusWindow forceGC * getBrowserActionWidget * clickBrowserAction clickPageAction * getBrowserActionPopup getPageActionPopup * closeBrowserAction closePageAction - * promisePopupShown + * promisePopupShown promisePopupHidden */ var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm"); var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm"); // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable // times in debug builds, which results in intermittent timeouts. Until we have // a better solution, we force a GC after certain strategic tests, which tend to @@ -54,25 +54,37 @@ function promisePopupShown(popup) { popup.removeEventListener("popupshown", onPopupShown); resolve(); }; popup.addEventListener("popupshown", onPopupShown); } }); } +function promisePopupHidden(popup) { + return new Promise(resolve => { + let onPopupHidden = event => { + popup.removeEventListener("popuphidden", onPopupHidden); + resolve(); + }; + popup.addEventListener("popuphidden", onPopupHidden); + }); +} + function getBrowserActionWidget(extension) { return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action"); } function getBrowserActionPopup(extension, win = window) { let group = getBrowserActionWidget(extension); if (group.areaType == CustomizableUI.TYPE_TOOLBAR) { return win.document.getElementById("customizationui-widget-panel"); + } else { + return win.PanelUI.panel; } return null; } var clickBrowserAction = Task.async(function* (extension, win = window) { let group = getBrowserActionWidget(extension); let widget = group.forWindow(win);
--- a/browser/components/places/tests/browser/browser.ini +++ b/browser/components/places/tests/browser/browser.ini @@ -41,15 +41,14 @@ skip-if = e10s # Bug ?????? - test fails [browser_library_left_pane_select_hierarchy.js] [browser_library_middleclick.js] [browser_library_open_leak.js] [browser_library_openFlatContainer.js] [browser_library_panel_leak.js] [browser_library_search.js] [browser_library_views_liveupdate.js] [browser_markPageAsFollowedLink.js] -skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly (test does EventUtils.sendMouseEvent...) [browser_sidebarpanels_click.js] skip-if = true # temporarily disabled for breaking the treeview - bug 658744 [browser_sort_in_library.js] [browser_toolbar_migration.js] [browser_toolbarbutton_menu_context.js] [browser_views_liveupdate.js]
--- a/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js +++ b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js @@ -1,86 +1,67 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - /** * Tests that visits across frames are correctly represented in the database. */ const BASE_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser"; const PAGE_URL = BASE_URL + "/framedPage.html"; const LEFT_URL = BASE_URL + "/frameLeft.html"; const RIGHT_URL = BASE_URL + "/frameRight.html"; -var gTabLoaded = false; -var gLeftFrameVisited = false; +add_task(function* test() { + // We must wait for both frames to be loaded and the visits to be registered. + let deferredLeftFrameVisit = PromiseUtils.defer(); + let deferredRightFrameVisit = PromiseUtils.defer(); -var observer = { - observe: function(aSubject, aTopic, aData) - { - let url = aSubject.QueryInterface(Ci.nsIURI).spec; - if (url == LEFT_URL ) { - is(getTransitionForUrl(url), null, - "Embed visits should not get a database entry."); - gLeftFrameVisited = true; - maybeClickLink(); - } - else if (url == RIGHT_URL ) { - is(getTransitionForUrl(url), PlacesUtils.history.TRANSITION_FRAMED_LINK, - "User activated visits should get a FRAMED_LINK transition."); - finish(); - } - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) -}; -Services.obs.addObserver(observer, "uri-visit-saved", false); + Services.obs.addObserver(function observe(subject) { + Task.spawn(function* () { + let url = subject.QueryInterface(Ci.nsIURI).spec; + if (url == LEFT_URL ) { + is((yield getTransitionForUrl(url)), null, + "Embed visits should not get a database entry."); + deferredLeftFrameVisit.resolve(); + } + else if (url == RIGHT_URL ) { + is((yield getTransitionForUrl(url)), + PlacesUtils.history.TRANSITION_FRAMED_LINK, + "User activated visits should get a FRAMED_LINK transition."); + Services.obs.removeObserver(observe, "uri-visit-saved"); + deferredRightFrameVisit.resolve(); + } + }); + }, "uri-visit-saved", false); + + // Open a tab and wait for all the subframes to load. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); -function test() -{ - waitForExplicitFinish(); - gBrowser.selectedTab = gBrowser.addTab(PAGE_URL); - let frameCount = 0; - gBrowser.selectedBrowser.addEventListener("DOMContentLoaded", - function (event) - { - // Wait for all the frames. - if (frameCount++ < 2) - return; - gBrowser.selectedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, false) - gTabLoaded = true; - maybeClickLink(); - }, false - ); -} + // Wait for the left frame visit to be registered. + info("Waiting left frame visit"); + yield deferredLeftFrameVisit.promise; + + // Click on the link in the left frame to cause a page load in the + // right frame. + info("Clicking link"); + yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { + content.frames[0].document.getElementById("clickme").click(); + }); + + // Wait for the right frame visit to be registered. + info("Waiting right frame visit"); + yield deferredRightFrameVisit.promise; -function maybeClickLink() { - if (gTabLoaded && gLeftFrameVisited) { - // Click on the link in the left frame to cause a page load in the - // right frame. - EventUtils.sendMouseEvent({type: "click"}, "clickme", content.frames[0]); + yield BrowserTestUtils.removeTab(tab); +}); + +function* getTransitionForUrl(url) { + // Ensure all the transactions completed. + yield PlacesTestUtils.promiseAsyncUpdates(); + let db = yield PlacesUtils.promiseDBConnection(); + let rows = yield db.execute(` + SELECT visit_type + FROM moz_historyvisits + WHERE place_id = (SELECT id FROM moz_places WHERE url = :url)`, + { url }); + if (rows.length) { + return rows[0].getResultByName("visit_type"); } + return null; } - -function getTransitionForUrl(aUrl) -{ - let dbConn = PlacesUtils.history - .QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; - let stmt = dbConn.createStatement( - "SELECT visit_type FROM moz_historyvisits WHERE place_id = " + - "(SELECT id FROM moz_places WHERE url = :page_url)"); - stmt.params.page_url = aUrl; - try { - if (!stmt.executeStep()) { - return null; - } - return stmt.row.visit_type; - } - finally { - stmt.finalize(); - } -} - -registerCleanupFunction(function () -{ - gBrowser.removeTab(gBrowser.selectedTab); - Services.obs.removeObserver(observer, "uri-visit-saved"); -})
--- a/browser/components/tests/unit/distribution.ini +++ b/browser/components/tests/unit/distribution.ini @@ -8,16 +8,38 @@ about=Test distribution file about.en-US=TÃ¨Æ¨Æ Î´Ã¯Æ¨ÆÅ™Ã¯Î²ÃºÆÃ¯Ã´Ã± ƒïℓè [Preferences] distribution.test.string="Test String" distribution.test.string.noquotes=Test String distribution.test.int=777 distribution.test.bool.true=true distribution.test.bool.false=false +distribution.test.empty= + +distribution.test.pref.locale="%LOCALE%" +distribution.test.pref.language.reset="Preference Set" +distribution.test.pref.locale.reset="Preference Set" +distribution.test.pref.locale.set="Preference Set" +distribution.test.pref.language.set="Preference Set" + +[Preferences-en] +distribution.test.pref.language.en="en" +distribution.test.pref.language.reset= +distribution.test.pref.language.set="Language Set" +distribution.test.pref.locale.set="Language Set" + +[Preferences-en-US] +distribution.test.pref.locale.en-US="en-US" +distribution.test.pref.locale.reset= +distribution.test.pref.locale.set="Locale Set" + + +[Preferences-de] +distribution.test.pref.language.de="de" [LocalizablePreferences] distribution.test.locale="%LOCALE%" distribution.test.language.reset="Preference Set" distribution.test.locale.reset="Preference Set" distribution.test.locale.set="Preference Set" distribution.test.language.set="Preference Set" @@ -28,9 +50,9 @@ distribution.test.language.set="Language distribution.test.locale.set="Language Set" [LocalizablePreferences-en-US] distribution.test.locale.en-US="en-US" distribution.test.locale.reset= distribution.test.locale.set="Locale Set" [LocalizablePreferences-de] -distribution.test.locale.de="de" +distribution.test.language.de="de"
--- a/browser/components/tests/unit/test_distribution.js +++ b/browser/components/tests/unit/test_distribution.js @@ -66,20 +66,40 @@ add_task(function* () { Assert.equal(Services.prefs.getCharPref("distribution.version"), "1.0"); Assert.equal(Services.prefs.getComplexValue("distribution.about", Ci.nsISupportsString).data, "TÃ¨Æ¨Æ Î´Ã¯Æ¨ÆÅ™Ã¯Î²ÃºÆÃ¯Ã´Ã± ƒïℓè"); Assert.equal(Services.prefs.getCharPref("distribution.test.string"), "Test String"); Assert.equal(Services.prefs.getCharPref("distribution.test.string.noquotes"), "Test String"); Assert.equal(Services.prefs.getIntPref("distribution.test.int"), 777); Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.true"), true); Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.false"), false); + + Assert.throws(() => Services.prefs.getCharPref("distribution.test.empty")); + Assert.throws(() => Services.prefs.getIntPref("distribution.test.empty")); + Assert.throws(() => Services.prefs.getBoolPref("distribution.test.empty")); + + Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale"), "en-US"); + Assert.equal(Services.prefs.getCharPref("distribution.test.pref.language.en"), "en"); + Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale.en-US"), "en-US"); + Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.language.de")); + // This value was never set because of the empty language specific pref + Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.language.reset")); + // This value was never set because of the empty locale specific pref + Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.locale.reset")); + // This value was overridden by a locale specific setting + Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale.set"), "Locale Set"); + // This value was overridden by a language specific setting + Assert.equal(Services.prefs.getCharPref("distribution.test.pref.language.set"), "Language Set"); + // Language should not override locale + Assert.notEqual(Services.prefs.getCharPref("distribution.test.pref.locale.set"), "Language Set"); + Assert.equal(Services.prefs.getComplexValue("distribution.test.locale", Ci.nsIPrefLocalizedString).data, "en-US"); Assert.equal(Services.prefs.getComplexValue("distribution.test.language.en", Ci.nsIPrefLocalizedString).data, "en"); Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.en-US", Ci.nsIPrefLocalizedString).data, "en-US"); - Assert.throws(() => Services.prefs.getComplexValue("distribution.test.locale.de", Ci.nsIPrefLocalizedString)); + Assert.throws(() => Services.prefs.getComplexValue("distribution.test.language.de", Ci.nsIPrefLocalizedString)); // This value was never set because of the empty language specific pref Assert.throws(() => Services.prefs.getComplexValue("distribution.test.language.reset", Ci.nsIPrefLocalizedString)); // This value was never set because of the empty locale specific pref Assert.throws(() => Services.prefs.getComplexValue("distribution.test.locale.reset", Ci.nsIPrefLocalizedString)); // This value was overridden by a locale specific setting Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Locale Set"); // This value was overridden by a language specific setting Assert.equal(Services.prefs.getComplexValue("distribution.test.language.set", Ci.nsIPrefLocalizedString).data, "Language Set");
--- a/browser/modules/ContentClick.jsm +++ b/browser/modules/ContentClick.jsm @@ -56,31 +56,31 @@ var ContentClick = { , "location" , "keyword" ] }, window); return; } // Note: We don't need the sidebar code here. + // Mark the page as a user followed link. This is done so that history can + // distinguish automatic embed visits from user activated ones. For example + // pages loaded in frames are embed visits and lost with the session, while + // visits across frames should be preserved. + try { + if (!PrivateBrowsingUtils.isWindowPrivate(window)) + PlacesUIUtils.markPageAsFollowedLink(json.href); + } catch (ex) { /* Skip invalid URIs. */ } + // This part is based on handleLinkClick. var where = window.whereToOpenLink(json); if (where == "current") return; // Todo(903022): code for where == save let params = { charset: browser.characterSet, referrerURI: browser.documentURI, referrerPolicy: json.referrerPolicy, noReferrer: json.noReferrer }; window.openLinkIn(json.href, where, params); - - // Mark the page as a user followed link. This is done so that history can - // distinguish automatic embed visits from user activated ones. For example - // pages loaded in frames are embed visits and lost with the session, while - // visits across frames should be preserved. - try { - if (!PrivateBrowsingUtils.isWindowPrivate(window)) - PlacesUIUtils.markPageAsFollowedLink(json.href); - } catch (ex) { /* Skip invalid URIs. */ } } };
--- a/browser/themes/osx/controlcenter/panel.css +++ b/browser/themes/osx/controlcenter/panel.css @@ -12,16 +12,25 @@ .identity-popup-expander:-moz-focusring { padding: 2px; } .identity-popup-expander:-moz-focusring > .button-box { @hudButtonFocused@ } +#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews { + border-bottom-right-radius: 3.5px; +} + +#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews:-moz-locale-dir(rtl) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 3.5px; +} + #tracking-action-block, #tracking-action-unblock, #tracking-action-unblock-private, #identity-popup-securityView-body > button { @hudButton@ min-height: 30px; }
--- a/browser/themes/shared/controlcenter/panel.inc.css +++ b/browser/themes/shared/controlcenter/panel.inc.css @@ -78,39 +78,38 @@ } #identity-popup-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="main"] > .panel-subviews:-moz-locale-dir(rtl) { transform: translateX(-100%); } #identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews { background: var(--panel-arrowcontent-background); - border-bottom-right-radius: 3.5px; padding: 0; } -#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews:-moz-locale-dir(rtl) { - border-bottom-right-radius: 0; - border-bottom-left-radius: 3.5px; -} - .identity-popup-section:not(:first-child) { border-top: 1px solid var(--panel-separator-color); } #identity-popup-securityView, #identity-popup-security-content, #identity-popup-permissions-content, #tracking-protection-content { + background-repeat: no-repeat; + background-position: 1em 1em; + background-size: 24px auto; +} + +#identity-popup-security-content, +#identity-popup-permissions-content, +#tracking-protection-content { padding: 0.5em 0 1em; -moz-padding-start: calc(2em + 24px); -moz-padding-end: 1em; - background-repeat: no-repeat; - background-position: 1em 1em; - background-size: 24px auto; } #identity-popup-securityView:-moz-locale-dir(rtl), #identity-popup-security-content:-moz-locale-dir(rtl), #identity-popup-permissions-content:-moz-locale-dir(rtl), #tracking-protection-content:-moz-locale-dir(rtl) { background-position: calc(100% - 1em) 1em; } @@ -203,17 +202,16 @@ color: #418220; } .identity-popup-connection-not-secure { color: #d74345; } #identity-popup-securityView { - padding-bottom: 2em; overflow: hidden; } #identity-popup-securityView, #identity-popup-security-content { background-image: url(chrome://browser/skin/controlcenter/conn-not-secure.svg); } @@ -247,25 +245,56 @@ background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg); } #identity-popup-security-descriptions > description { margin-top: 6px; color: Graytext; } +#identity-popup-securityView-header, +#identity-popup-securityView-body { + -moz-margin-start: calc(2em + 24px); + -moz-margin-end: 1em; +} + #identity-popup-securityView-header { + margin-top: 0.5em; border-bottom: 1px solid var(--panel-separator-color); padding-bottom: 1em; } #identity-popup-securityView-body { -moz-padding-end: 1em; } +#identity-popup-securityView-footer { + margin-top: 1em; + background-color: hsla(210,4%,10%,.07); +} + +#identity-popup-securityView-footer > button { + -moz-appearance: none; + margin: 0; + border: none; + border-top: 1px solid #ccc; + padding: 8px 20px; + color: ButtonText; + background-color: transparent; +} + +#identity-popup-securityView-footer > button:hover, +#identity-popup-securityView-footer > button:focus { + background-color: hsla(210,4%,10%,.07); +} + +#identity-popup-securityView-footer > button:hover:active { + background-color: hsla(210,4%,10%,.12); +} + #identity-popup-content-verifier ~ description { margin-top: 1em; color: Graytext; } description#identity-popup-content-verified-by, description#identity-popup-content-owner, description#identity-popup-content-verifier,
--- a/devtools/bootstrap.js +++ b/devtools/bootstrap.js @@ -3,16 +3,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const Cu = Components.utils; const Ci = Components.interfaces; const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); +function actionOccurred(id) { + let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); + let Telemetry = require("devtools/client/shared/telemetry");; + let telemetry = new Telemetry(); + telemetry.actionOccurred(id); +} + // Helper to listen to a key on all windows function MultiWindowKeyListener({ keyCode, ctrlKey, altKey, callback }) { let keyListener = function (event) { if (event.ctrlKey == !!ctrlKey && event.altKey == !!altKey && event.keyCode === keyCode) { callback(event); @@ -145,25 +152,29 @@ function reload(event) { let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {}); setTimeout(() => { let { TargetFactory } = devtools.require("devtools/client/framework/target"); let { gDevTools } = devtools.require("devtools/client/framework/devtools"); let target = TargetFactory.forTab(top.gBrowser.selectedTab); gDevTools.showToolbox(target); }, 1000); } + + actionOccurred("reloadAddonReload"); } let listener; function startup() { dump("DevTools addon started.\n"); listener = new MultiWindowKeyListener({ keyCode: Ci.nsIDOMKeyEvent.DOM_VK_R, ctrlKey: true, altKey: true, callback: reload }); listener.start(); } function shutdown() { listener.stop(); listener = null; } -function install() {} +function install() { + actionOccurred("reloadAddonInstalled"); +} function uninstall() {}
--- a/devtools/client/debugger/debugger-controller.js +++ b/devtools/client/debugger/debugger-controller.js @@ -77,17 +77,20 @@ const EVENTS = { // After the stackframes are cleared and debugger won't pause anymore. AFTER_FRAMES_CLEARED: "Debugger:AfterFramesCleared", // When the options popup is showing or hiding. OPTIONS_POPUP_SHOWING: "Debugger:OptionsPopupShowing", OPTIONS_POPUP_HIDDEN: "Debugger:OptionsPopupHidden", // When the widgets layout has been changed. - LAYOUT_CHANGED: "Debugger:LayoutChanged" + LAYOUT_CHANGED: "Debugger:LayoutChanged", + + // When a worker has been selected. + WORKER_SELECTED: "Debugger::WorkerSelected" }; // Descriptions for what a stack frame represents after the debugger pauses. const FRAME_TYPE = { NORMAL: 0, CONDITIONAL_BREAKPOINT_EVAL: 1, WATCH_EXPRESSIONS_EVAL: 2, PUBLIC_CLIENT_EVAL: 3 @@ -103,17 +106,17 @@ Cu.import("resource://devtools/client/sh Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm"); /** * Localization convenience methods. */ var L10N = new ViewHelpers.L10N(DBG_STRINGS_URI); Cu.import("resource://devtools/client/shared/browser-loader.js"); -const require = BrowserLoader("resource://devtools/client/debugger/", this).require; +const require = BrowserLoader("resource://devtools/client/debugger/", window).require; XPCOMUtils.defineConstant(this, "require", require); const { gDevTools } = require("devtools/client/framework/devtools"); // React const React = require("devtools/client/shared/vendor/react"); const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { Provider } = require("devtools/client/shared/vendor/react-redux"); @@ -486,39 +489,40 @@ Workers.prototype = { this._tabClient.listWorkers((response) => { let workerForms = Object.create(null); for (let worker of response.workers) { workerForms[worker.actor] = worker; } for (let workerActor in this._workerForms) { if (!(workerActor in workerForms)) { + DebuggerView.Workers.removeWorker(this._workerForms[workerActor]); delete this._workerForms[workerActor]; - DebuggerView.Workers.removeWorker(workerActor); } } for (let workerActor in workerForms) { if (!(workerActor in this._workerForms)) { let workerForm = workerForms[workerActor]; this._workerForms[workerActor] = workerForm; - DebuggerView.Workers.addWorker(workerActor, workerForm.url); + DebuggerView.Workers.addWorker(workerForm); } } }); }, _onWorkerListChanged: function () { this._updateWorkerList(); }, - _onWorkerSelect: function (workerActor) { - DebuggerController.client.attachWorker(workerActor, (response, workerClient) => { - gDevTools.showToolbox(TargetFactory.forWorker(workerClient), - "jsdebugger", Toolbox.HostType.WINDOW); + _onWorkerSelect: function (workerForm) { + DebuggerController.client.attachWorker(workerForm.actor, (response, workerClient) => { + let toolbox = gDevTools.showToolbox(TargetFactory.forWorker(workerClient), + "jsdebugger", Toolbox.HostType.WINDOW); + window.emit(EVENTS.WORKER_SELECTED, toolbox); }); } }; /** * ThreadState keeps the UI up to date with the state of the * thread (paused/attached/etc.). */
--- a/devtools/client/debugger/test/mochitest/browser.ini +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -40,16 +40,19 @@ support-files = code_ugly-2.js code_ugly-3.js code_ugly-4.js code_ugly-5.js code_ugly-6.js code_ugly-7.js code_ugly-8 code_ugly-8^headers^ + code_worker-source-map.coffee + code_worker-source-map.js + code_worker-source-map.js.map code_WorkerActor.attach-worker1.js code_WorkerActor.attach-worker2.js code_WorkerActor.attachThread-worker.js doc_auto-pretty-print-01.html doc_auto-pretty-print-02.html doc_binary_search.html doc_blackboxing.html doc_blackboxing_unblackbox.html @@ -107,16 +110,17 @@ support-files = doc_script-switching-02.html doc_split-console-paused-reload.html doc_step-many-statements.html doc_step-out.html doc_terminate-on-tab-close.html doc_watch-expressions.html doc_watch-expression-button.html doc_with-frame.html + doc_worker-source-map.html doc_WorkerActor.attach-tab1.html doc_WorkerActor.attach-tab2.html doc_WorkerActor.attachThread-tab.html head.js sjs_random-javascript.sjs testactors.js [browser_dbg_aaa_run_first_leaktest.js] @@ -587,16 +591,18 @@ skip-if = e10s && debug [browser_dbg_variables-view-webidl.js] skip-if = e10s && debug [browser_dbg_watch-expressions-01.js] skip-if = e10s && debug [browser_dbg_watch-expressions-02.js] skip-if = e10s && debug [browser_dbg_worker-console.js] skip-if = e10s && debug +[browser_dbg_worker-source-map.js] +skip-if = e10s && debug [browser_dbg_worker-window.js] skip-if = e10s && debug [browser_dbg_WorkerActor.attach.js] skip-if = e10s && debug [browser_dbg_WorkerActor.attachThread.js] skip-if = e10s && debug [browser_dbg_split-console-keypress.js] skip-if = e10s && debug
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js @@ -0,0 +1,85 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TAB_URL = EXAMPLE_URL + "doc_worker-source-map.html"; +const WORKER_URL = "code_worker-source-map.js"; +const COFFEE_URL = EXAMPLE_URL + "code_worker-source-map.coffee"; + +function selectWorker(aPanel, aURL) { + let panelWin = aPanel.panelWin; + let promise = waitForDebuggerEvents(aPanel, panelWin.EVENTS.WORKER_SELECTED); + let Workers = panelWin.DebuggerView.Workers; + let item = Workers.getItemForAttachment((workerForm) => { + return workerForm.url === aURL; + }); + Workers.selectedItem = item; + return promise; +} + +function test() { + return Task.spawn(function* () { + yield pushPrefs(["devtools.debugger.workers", true]); + + let [tab,, panel] = yield initDebugger(TAB_URL); + let toolbox = yield selectWorker(panel, WORKER_URL); + let workerPanel = toolbox.getCurrentPanel(); + yield waitForSourceShown(workerPanel, ".coffee"); + let panelWin = workerPanel.panelWin; + let Sources = panelWin.DebuggerView.Sources; + let editor = panelWin.DebuggerView.editor; + let threadClient = panelWin.gThreadClient; + + isnot(Sources.selectedItem.attachment.source.url.indexOf(".coffee"), -1, + "The debugger should show the source mapped coffee source file."); + is(Sources.selectedValue.indexOf(".js"), -1, + "The debugger should not show the generated js source file."); + is(editor.getText().indexOf("isnt"), 211, + "The debugger's editor should have the coffee source source displayed."); + is(editor.getText().indexOf("function"), -1, + "The debugger's editor should not have the JS source displayed."); + + yield threadClient.interrupt(); + let sourceForm = getSourceForm(Sources, COFFEE_URL); + let source = threadClient.source(sourceForm); + let response = yield source.setBreakpoint({ line: 5 }); + + ok(!response.error, + "Should be able to set a breakpoint in a coffee source file."); + ok(!response.actualLocation, + "Should be able to set a breakpoint on line 5."); + + let promise = new Promise((resolve) => { + threadClient.addOneTimeListener("paused", (event, packet) => { + is(packet.type, "paused", + "We should now be paused again."); + is(packet.why.type, "breakpoint", + "and the reason we should be paused is because we hit a breakpoint."); + + // Check that we stopped at the right place, by making sure that the + // environment is in the state that we expect. + is(packet.frame.environment.bindings.variables.start.value, 0, + "'start' is 0."); + is(packet.frame.environment.bindings.variables.stop.value.type, "undefined", + "'stop' hasn't been assigned to yet."); + is(packet.frame.environment.bindings.variables.pivot.value.type, "undefined", + "'pivot' hasn't been assigned to yet."); + + waitForCaretUpdated(workerPanel, 5).then(resolve); + }); + }); + + // This will cause the breakpoint to be hit, and put us back in the + // paused state. + yield threadClient.resume(); + callInTab(tab, "binary_search", [0, 2, 3, 5, 7, 10], 5); + yield promise; + + yield threadClient.resume(); + yield toolbox.destroy(); + yield closeDebuggerAndFinish(panel); + + yield popPrefs(); + }); +}
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee @@ -0,0 +1,22 @@ +# Uses a binary search algorithm to locate a value in the specified array. +binary_search = (items, value) -> + + start = 0 + stop = items.length - 1 + pivot = Math.floor (start + stop) / 2 + + while items[pivot] isnt value and start < stop + + # Adjust the search area. + stop = pivot - 1 if value < items[pivot] + start = pivot + 1 if value > items[pivot] + + # Recalculate the pivot. + pivot = Math.floor (stop + start) / 2 + + # Make sure we've found the correct value. + if items[pivot] is value then pivot else -1 + +self.onmessage = (event) -> + data = event.data + binary_search(data.items, data.value)
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js @@ -0,0 +1,35 @@ +// Generated by CoffeeScript 1.10.0 +(function() { + var binary_search; + + binary_search = function(items, value) { + var pivot, start, stop; + start = 0; + stop = items.length - 1; + pivot = Math.floor((start + stop) / 2); + while (items[pivot] !== value && start < stop) { + if (value < items[pivot]) { + stop = pivot - 1; + } + if (value > items[pivot]) { + start = pivot + 1; + } + pivot = Math.floor((stop + start) / 2); + } + if (items[pivot] === value) { + return pivot; + } else { + return -1; + } + }; + + self.onmessage = function(event) { + console.log("EUTA"); + var data; + data = event.data; + return binary_search(data.items, data.value); + }; + +}).call(this); + +//# sourceMappingURL=code_worker-source-map.js.map
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "code_worker-source-map.js", + "sourceRoot": "", + "sources": [ + "code_worker-source-map.coffee" + ], + "names": [], + "mappings": ";AACA;AAAA,MAAA;;EAAA,aAAA,GAAgB,SAAC,KAAD,EAAQ,KAAR;AAEd,QAAA;IAAA,KAAA,GAAQ;IACR,IAAA,GAAQ,KAAK,CAAC,MAAN,GAAe;IACvB,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,KAAA,GAAQ,IAAT,CAAA,GAAiB,CAA5B;AAER,WAAM,KAAM,CAAA,KAAA,CAAN,KAAkB,KAAlB,IAA4B,KAAA,GAAQ,IAA1C;MAGE,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,IAAA,GAAQ,KAAA,GAAQ,EAAhB;;MACA,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,KAAA,GAAQ,KAAA,GAAQ,EAAhB;;MAGA,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,IAAA,GAAO,KAAR,CAAA,GAAiB,CAA5B;IAPV;IAUA,IAAG,KAAM,CAAA,KAAA,CAAN,KAAgB,KAAnB;aAA8B,MAA9B;KAAA,MAAA;aAAyC,CAAC,EAA1C;;EAhBc;;EAkBhB,IAAI,CAAC,SAAL,GAAiB,SAAC,KAAD;AACf,QAAA;IAAA,IAAA,GAAO,KAAK,CAAC;WACb,aAAA,CAAc,IAAI,CAAC,KAAnB,EAA0B,IAAI,CAAC,KAA/B;EAFe;AAlBjB" +}
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_worker-source-map.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <script> + var worker = new Worker("code_worker-source-map.js"); + + function binary_search(items, value) { + worker.postMessage({ + items: items, + value: value + }); + } + </script> + </head> + <body> + </body> +</html>
--- a/devtools/client/debugger/views/workers-view.js +++ b/devtools/client/debugger/views/workers-view.js @@ -23,30 +23,32 @@ WorkersView.prototype = Heritage.extend( this.widget = new SideMenuWidget(document.getElementById("workers"), { showArrows: true, }); this.emptyText = L10N.getStr("noWorkersText"); this.widget.addEventListener("select", this._onWorkerSelect, false); }, - addWorker: function (actor, name) { + addWorker: function (workerForm) { let element = document.createElement("label"); element.className = "plain dbg-worker-item"; - element.setAttribute("value", name); + element.setAttribute("value", workerForm.url); element.setAttribute("flex", "1"); - this.push([element, actor], {}); + this.push([element, workerForm.actor], { + attachment: workerForm + }); }, - removeWorker: function (actor) { - this.remove(this.getItemByValue(actor)); + removeWorker: function (workerForm) { + this.remove(this.getItemByValue(workerForm.actor)); }, _onWorkerSelect: function () { if (this.selectedItem !== null) { - DebuggerController.Workers._onWorkerSelect(this.selectedItem.value); + DebuggerController.Workers._onWorkerSelect(this.selectedItem.attachment); this.selectedItem = null; } } }); DebuggerView.Workers = new WorkersView();
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -907,16 +907,20 @@ Toolbox.prototype = { } this.setToolboxButtonsVisibility(); // Old servers don't have a GCLI Actor, so just return if (!this.target.hasActor("gcli")) { return promise.resolve(); } + // Disable gcli in browser toolbox until there is usages of it + if (this.target.chrome) { + return promise.resolve(); + } const options = { environment: CommandUtils.createEnvironment(this, '_target') }; return CommandUtils.createRequisition(this.target, options).then(requisition => { this._requisition = requisition; const spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
--- a/devtools/client/shared/telemetry.js +++ b/devtools/client/shared/telemetry.js @@ -228,17 +228,25 @@ Telemetry.prototype = { webideImportProject: { histogram: "DEVTOOLS_WEBIDE_IMPORT_PROJECT_COUNT", userHistogram: "DEVTOOLS_WEBIDE_IMPORT_PROJECT_PER_USER_FLAG", }, custom: { histogram: "DEVTOOLS_CUSTOM_OPENED_COUNT", userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS" - } + }, + reloadAddonInstalled: { + histogram: "DEVTOOLS_RELOAD_ADDON_INSTALLED_COUNT", + userHistogram: "DEVTOOLS_RELOAD_ADDON_INSTALLED_PER_USER_FLAG", + }, + reloadAddonReload: { + histogram: "DEVTOOLS_RELOAD_ADDON_RELOAD_COUNT", + userHistogram: "DEVTOOLS_RELOAD_ADDON_RELOAD_PER_USER_FLAG", + }, }, /** * Add an entry to a histogram. * * @param {String} id * Used to look up the relevant histogram ID and log true to that * histogram.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java @@ -1145,16 +1145,18 @@ public final class BrowserDatabaseHelper case 25: upgradeDatabaseFrom24to25(db); break; case 26: upgradeDatabaseFrom25to26(db); break; + // case 27 occurs in UrlMetadataTable.onUpgrade + case 28: upgradeDatabaseFrom27to28(db); break; case 29: upgradeDatabaseFrom28to29(db); } }
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java @@ -7,16 +7,17 @@ package org.mozilla.gecko.db; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.mozilla.gecko.AboutPages; import org.mozilla.gecko.GeckoProfile; +import org.mozilla.gecko.R; import org.mozilla.gecko.db.BrowserContract.Bookmarks; import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserContract.FaviconColumns; import org.mozilla.gecko.db.BrowserContract.Favicons; import org.mozilla.gecko.db.BrowserContract.History; import org.mozilla.gecko.db.BrowserContract.Schema; import org.mozilla.gecko.db.BrowserContract.Tabs; import org.mozilla.gecko.db.BrowserContract.Thumbnails; @@ -689,82 +690,79 @@ public class BrowserProvider extends Sha // tricks to generate these: // 1. The list of free ids is small, hence we can do a self-join to generate rowids. // 2. The topsites list is larger, hence we use a temporary table, which automatically provides rowids. final SQLiteDatabase db = getWritableDatabase(uri); final String TABLE_TOPSITES = "topsites"; - final String totalLimit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); - final String suggestedGridLimit = uri.getQueryParameter(BrowserContract.PARAM_SUGGESTEDSITES_LIMIT); + final String limitParam = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); + final String gridLimitParam = uri.getQueryParameter(BrowserContract.PARAM_SUGGESTEDSITES_LIMIT); - final String[] suggestedGridLimitArgs = new String[] { - suggestedGridLimit - }; + final int totalLimit; + final int suggestedGridLimit; - final String[] totalLimitArgs = new String[] { - totalLimit - }; + if (limitParam == null) { + totalLimit = 50; + } else { + totalLimit = Integer.parseInt(limitParam, 10); + } - final String pinnedSitesFromClause = "FROM " + TABLE_BOOKMARKS + " WHERE " + Bookmarks.PARENT + " == ?"; - final String[] pinnedSitesArgs = new String[] { - String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) - }; + if (gridLimitParam == null) { + suggestedGridLimit = getContext().getResources().getInteger(R.integer.number_of_top_sites); + } else { + suggestedGridLimit = Integer.parseInt(gridLimitParam, 10); + } + + final String pinnedSitesFromClause = "FROM " + TABLE_BOOKMARKS + " WHERE " + Bookmarks.PARENT + " == " + Bookmarks.FIXED_PINNED_LIST_ID; // Ideally we'd use a recursive CTE to generate our sequence, e.g. something like this worked at one point: // " WITH RECURSIVE" + // " cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x < 6)" + // However that requires SQLite >= 3.8.3 (available on Android >= 5.0), so in the meantime // we use a temporary numbers table. // Note: SQLite rowids are 1-indexed, whereas we're expecting 0-indexed values for the position. Our numbers // table starts at position = 0, which ensures the correct results here. final String freeIDSubquery = " SELECT count(free_ids.position) + 1 AS rowid, numbers.position AS " + Bookmarks.POSITION + " FROM (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS numbers" + " LEFT OUTER JOIN " + " (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS free_ids" + " ON numbers.position > free_ids.position" + " GROUP BY numbers.position" + " ORDER BY numbers.position ASC" + - " LIMIT ?"; - - final String[] freeIDArgs = DBUtils.concatenateSelectionArgs( - pinnedSitesArgs, - pinnedSitesArgs, - suggestedGridLimitArgs); + " LIMIT " + suggestedGridLimit; // Filter out: unvisited pages (history_id == -1) pinned (and other special) sites, deleted sites, // and about: pages. final String ignoreForTopSitesWhereClause = "(" + Combined.HISTORY_ID + " IS NOT -1)" + " AND " + Combined.URL + " NOT IN (SELECT " + Bookmarks.URL + " FROM bookmarks WHERE " + - DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < ? AND " + + DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < " + Bookmarks.FIXED_ROOT_ID + " AND " + DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)" + " AND " + "(" + Combined.URL + " NOT LIKE ?)"; final String[] ignoreForTopSitesArgs = new String[] { - String.valueOf(Bookmarks.FIXED_ROOT_ID), AboutPages.URL_FILTER }; - // Stuff the suggested sites into SQL: this allows us to filter pinned and topsites out of the suggested // sites list as part of the final query (as opposed to walking cursors in java) final SuggestedSites suggestedSites = GeckoProfile.get(getContext(), uri.getQueryParameter(BrowserContract.PARAM_PROFILE)).getDB().getSuggestedSites(); StringBuilder suggestedSitesBuilder = new StringBuilder(); // We could access the underlying data here, however SuggestedSites also performs filtering on the suggested // sites list, which means we'd need to process the lists within SuggestedSites in any case. If we're doing // that processing, there is little real between us using a MatrixCursor, or a Map (or List) instead of the // MatrixCursor. - final Cursor suggestedSitesCursor = suggestedSites.get(Integer.parseInt(suggestedGridLimit)); + final Cursor suggestedSitesCursor = suggestedSites.get(suggestedGridLimit); String[] suggestedSiteArgs = new String[0]; boolean hasProcessedAnySuggestedSites = true; final int idColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks._ID); final int urlColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks.URL); final int titleColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks.TITLE); @@ -786,21 +784,18 @@ public class BrowserProvider extends Sha suggestedSitesCursor.getString(idColumnIndex), suggestedSitesCursor.getString(urlColumnIndex), suggestedSitesCursor.getString(titleColumnIndex) }); } // To restrict suggested sites to the grid, we simply subtract the number of topsites (which have already had // the pinned sites filtered out), and the number of pinned sites. - // SQLite completely ignores negative limits, hence we need to manually totalLimit to 0 in this case. - final String suggestedLimitClause = " LIMIT MAX(0, (? - (SELECT COUNT(*) FROM " + TABLE_TOPSITES + ") - (SELECT COUNT(*) " + pinnedSitesFromClause + "))) "; - - final String[] suggestedLimitArgs = DBUtils.concatenateSelectionArgs(suggestedGridLimitArgs, - pinnedSitesArgs); + // SQLite completely ignores negative limits, hence we need to manually limit to 0 in this case. + final String suggestedLimitClause = " LIMIT MAX(0, (" + suggestedGridLimit + " - (SELECT COUNT(*) FROM " + TABLE_TOPSITES + ") - (SELECT COUNT(*) " + pinnedSitesFromClause + "))) "; db.beginTransaction(); try { db.execSQL("DROP TABLE IF EXISTS " + TABLE_TOPSITES); db.execSQL("CREATE TEMP TABLE " + TABLE_TOPSITES + " AS" + " SELECT " + Bookmarks._ID + ", " + @@ -808,20 +803,19 @@ public class BrowserProvider extends Sha Combined.HISTORY_ID + ", " + Bookmarks.URL + ", " + Bookmarks.TITLE + ", " + Combined.HISTORY_ID + ", " + TopSites.TYPE_TOP + " AS " + TopSites.TYPE + " FROM " + Combined.VIEW_NAME + " WHERE " + ignoreForTopSitesWhereClause + " ORDER BY " + BrowserContract.getFrecencySortOrder(true, false) + - " LIMIT ?", + " LIMIT " + totalLimit, - DBUtils.appendSelectionArgs(ignoreForTopSitesArgs, - totalLimitArgs)); + ignoreForTopSitesArgs); if (!hasProcessedAnySuggestedSites) { db.execSQL("INSERT INTO " + TABLE_TOPSITES + // We need to LIMIT _after_ selecting the relevant suggested sites, which requires us to // use an additional internal subquery, since we cannot LIMIT a subquery that is part of UNION ALL. // Hence the weird SELECT * FROM (SELECT ...relevant suggested sites... LIMIT ?) " SELECT * FROM (SELECT " + Bookmarks._ID + ", " + @@ -833,29 +827,37 @@ public class BrowserProvider extends Sha TopSites.TYPE_SUGGESTED + " as " + TopSites.TYPE + " FROM ( " + suggestedSitesBuilder.toString() + " )" + " WHERE " + Bookmarks.URL + " NOT IN (SELECT url FROM " + TABLE_TOPSITES + ")" + " AND " + Bookmarks.URL + " NOT IN (SELECT url " + pinnedSitesFromClause + ")" + suggestedLimitClause + " )", - DBUtils.concatenateSelectionArgs(suggestedSiteArgs, - pinnedSitesArgs, - suggestedLimitArgs)); + suggestedSiteArgs); } + // If we retrieve more topsites than we have free positions for in the freeIdSubquery, + // we will have topsites that don't receive a position when joining TABLE_TOPSITES + // with freeIdSubquery. Hence we need to coalesce the position with a generated position. + // We know that the difference in positions will be at most suggestedGridLimit, hence we + // can add that to the rowid to generate a safe position. + // I.e. if we have 6 pinned sites then positions 0..5 are filled, the JOIN results in + // the first N rows having positions 6..(N+6), so row N+1 should receive a position that is at + // least N+1+6, which is equal to rowid + 6. final SQLiteCursor c = (SQLiteCursor) db.rawQuery( "SELECT " + Bookmarks._ID + ", " + TopSites.BOOKMARK_ID + ", " + TopSites.HISTORY_ID + ", " + Bookmarks.URL + ", " + Bookmarks.TITLE + ", " + - Bookmarks.POSITION + ", " + + "COALESCE(" + Bookmarks.POSITION + ", " + + DBUtils.qualifyColumn(TABLE_TOPSITES, "rowid") + " + " + suggestedGridLimit + + ")" + " AS " + Bookmarks.POSITION + ", " + Combined.HISTORY_ID + ", " + TopSites.TYPE + " FROM " + TABLE_TOPSITES + " LEFT OUTER JOIN " + // TABLE_IDS + "(" + freeIDSubquery + ") AS id_results" + " ON " + DBUtils.qualifyColumn(TABLE_TOPSITES, "rowid") + " = " + DBUtils.qualifyColumn("id_results", "rowid") + @@ -866,22 +868,21 @@ public class BrowserProvider extends Sha Bookmarks._ID + " AS " + TopSites.BOOKMARK_ID + ", " + " -1 AS " + TopSites.HISTORY_ID + ", " + Bookmarks.URL + ", " + Bookmarks.TITLE + ", " + Bookmarks.POSITION + ", " + "NULL AS " + Combined.HISTORY_ID + ", " + TopSites.TYPE_PINNED + " as " + TopSites.TYPE + " FROM " + TABLE_BOOKMARKS + - " WHERE " + Bookmarks.PARENT + " == ? " + + " WHERE " + Bookmarks.PARENT + " == " + Bookmarks.FIXED_PINNED_LIST_ID + " ORDER BY " + Bookmarks.POSITION, - DBUtils.appendSelectionArgs(freeIDArgs, - pinnedSitesArgs)); + null); c.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI); // Force the cursor to be compiled and the cursor-window filled now: // (A) without compiling the cursor now we won't have access to the TEMP table which // is removed as soon as we close our connection. // (B) this might also mitigate the situation causing this crash where we're accessing
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java @@ -13,16 +13,17 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.util.ThreadUtils; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.support.v4.util.LruCache; import android.util.Log; @@ -74,40 +75,26 @@ public class LocalURLMetadata implements } try { JSONObject icons; if (obj.has("touchIconList") && (icons = obj.getJSONObject("touchIconList")).length() > 0) { int preferredSize = GeckoAppShell.getPreferredIconSize(); - int bestSizeFound = -1; Iterator<String> keys = icons.keys(); ArrayList<Integer> sizes = new ArrayList<Integer>(icons.length()); while (keys.hasNext()) { sizes.add(new Integer(keys.next())); } - Collections.sort(sizes); - for (int size : sizes) { - if (size >= preferredSize) { - bestSizeFound = size; - break; - } - } - - // If all icons are smaller than the preferred size then we don't have an icon - // selected yet (bestSizeFound == -1), therefore just take the largest (last) icon. - if (bestSizeFound == -1) { - bestSizeFound = sizes.get(sizes.size() - 1); - } - - String iconURL = icons.getString(Integer.toString(bestSizeFound)); + final int bestSize = Favicons.selectBestSizeFromList(sizes, preferredSize); + final String iconURL = icons.getString(Integer.toString(bestSize)); data.put(URLMetadataTable.TOUCH_ICON_COLUMN, iconURL); } } catch (JSONException e) { Log.w(LOGTAG, "Exception processing touchIconList for LocalURLMetadata; ignoring.", e); } return Collections.unmodifiableMap(data);
--- a/mobile/android/base/java/org/mozilla/gecko/favicons/Favicons.java +++ b/mobile/android/base/java/org/mozilla/gecko/favicons/Favicons.java @@ -140,16 +140,42 @@ public class Favicons { */ public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) { pageURLMappings.put(pageURL, faviconURL); } private static FaviconCache faviconsCache; /** + * Select the closest icon size from a list of icon sizes. + * We just find the first icon that is larger than the preferred size if available, or otherwise select the + * largest icon (if all icons are smaller than the preferred size). + * + * @return The closes icon size, or -1 if no sizes are supplied. + */ + public static int selectBestSizeFromList(final List<Integer> sizes, final int preferredSize) { + Collections.sort(sizes); + + for (int size : sizes) { + if (size >= preferredSize) { + return size; + } + } + + // If all icons are smaller than the preferred size then we don't have an icon + // selected yet, therefore just take the largest (last) icon. + if (sizes.size() > 0) { + return sizes.get(sizes.size() - 1); + } else { + // This isn't ideal, however current code assumes this as an error value for now. + return -1; + } + } + + /** * Returns either NOT_LOADING, or LOADED if the onFaviconLoaded call could * be made on the main thread. * If no listener is provided, NOT_LOADING is returned. */ static int dispatchResult(final String pageUrl, final String faviconURL, final Bitmap image, final OnFaviconLoadedListener listener) { if (listener == null) { return NOT_LOADING; @@ -579,11 +605,11 @@ public class Favicons { * * Deduces the favicon URL from the browser database, guessing if necessary. * * @param url page URL to get a large favicon image for. * @param onFaviconLoadedListener listener to call back with the result. */ public static void getPreferredSizeFaviconForPage(Context context, String url, String iconURL, OnFaviconLoadedListener onFaviconLoadedListener) { int preferredSize = GeckoAppShell.getPreferredIconSize(); - loadUncachedFavicon(context, url, iconURL, 0, preferredSize, onFaviconLoadedListener); + loadUncachedFavicon(context, url, iconURL, LoadFaviconTask.FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS, preferredSize, onFaviconLoadedListener); } }
--- a/mobile/android/base/java/org/mozilla/gecko/favicons/LoadFaviconTask.java +++ b/mobile/android/base/java/org/mozilla/gecko/favicons/LoadFaviconTask.java @@ -20,22 +20,24 @@ import org.mozilla.gecko.GeckoProfile; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.favicons.decoders.FaviconDecoder; import org.mozilla.gecko.favicons.decoders.LoadFaviconResult; import org.mozilla.gecko.util.GeckoJarReader; import org.mozilla.gecko.util.IOUtils; import org.mozilla.gecko.util.ThreadUtils; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import static org.mozilla.gecko.util.IOUtils.ConsumedInputStream; /** * Class representing the asynchronous task to load a Favicon which is not currently in the in-memory * cache. @@ -45,16 +47,24 @@ import static org.mozilla.gecko.util.IOU public class LoadFaviconTask { private static final String LOGTAG = "LoadFaviconTask"; // Access to this map needs to be synchronized prevent multiple jobs loading the same favicon // from executing concurrently. private static final HashMap<String, LoadFaviconTask> loadsInFlight = new HashMap<>(); public static final int FLAG_PERSIST = 1; + /** + * Bypass all caches - this is used to directly retrieve the requested icon. Without this flag, + * favicons will first be pushed into the memory cache (and possibly permanent cache if using FLAG_PERSIST), + * where they will be downscaled to the maximum cache size, before being retrieved from the cache (resulting + * in a possibly smaller icon size). + */ + public static final int FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS = 2; + private static final int MAX_REDIRECTS_TO_FOLLOW = 5; // The default size of the buffer to use for downloading Favicons in the event no size is given // by the server. public static final int DEFAULT_FAVICON_BUFFER_SIZE = 25000; private static final AtomicInteger nextFaviconLoadId = new AtomicInteger(0); private final Context context; private final int id; @@ -412,20 +422,23 @@ public class LoadFaviconTask { // chain onto the same parent task. loadsInFlight.put(faviconURL, this); } if (isCancelled()) { return null; } + LoadFaviconResult loadedBitmaps = null; // If there are no valid bitmaps decoded, the returned LoadFaviconResult is null. - LoadFaviconResult loadedBitmaps = loadFaviconFromDb(db); - if (loadedBitmaps != null) { - return pushToCacheAndGetResult(loadedBitmaps); + if ((flags & FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS) == 0) { + loadedBitmaps = loadFaviconFromDb(db); + if (loadedBitmaps != null) { + return pushToCacheAndGetResult(loadedBitmaps); + } } if (onlyFromLocal || isCancelled()) { return null; } // Let's see if it's in a JAR. image = fetchJARFavicon(faviconURL); @@ -440,21 +453,35 @@ public class LoadFaviconTask { } catch (URISyntaxException e) { Log.e(LOGTAG, "The provided favicon URL is not valid"); return null; } catch (Exception e) { Log.e(LOGTAG, "Couldn't download favicon.", e); } if (loadedBitmaps != null) { - // Fetching bytes to store can fail. saveFaviconToDb will - // do the right thing, but we still choose to cache the - // downloaded icon in memory. - saveFaviconToDb(db, loadedBitmaps.getBytesForDatabaseStorage()); - return pushToCacheAndGetResult(loadedBitmaps); + if ((flags & FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS) == 0) { + // Fetching bytes to store can fail. saveFaviconToDb will + // do the right thing, but we still choose to cache the + // downloaded icon in memory. + saveFaviconToDb(db, loadedBitmaps.getBytesForDatabaseStorage()); + return pushToCacheAndGetResult(loadedBitmaps); + } else { + final Map<Integer, Bitmap> iconMap = new HashMap<>(); + final List<Integer> sizes = new ArrayList<>(); + + while (loadedBitmaps.getBitmaps().hasNext()) { + final Bitmap b = loadedBitmaps.getBitmaps().next(); + iconMap.put(b.getWidth(), b); + sizes.add(b.getWidth()); + } + + int bestSize = Favicons.selectBestSizeFromList(sizes, targetWidth); + return iconMap.get(bestSize); + } } if (isUsingDefaultURL) { Favicons.putFaviconInFailedCache(faviconURL); return null; } if (isCancelled()) { @@ -540,21 +567,25 @@ public class LoadFaviconTask { // image now, or the call into the cache in processResult will fetch the right one. t.processResult(image); } } } private void processResult(Bitmap image) { Favicons.removeLoadTask(id); - Bitmap scaled = image; + final Bitmap scaled; // Notify listeners, scaling if required. - if (targetWidth != -1 && image != null && image.getWidth() != targetWidth) { + if ((flags & FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS) != 0) { + scaled = Bitmap.createScaledBitmap(image, targetWidth, targetWidth, true); + } else if (targetWidth != -1 && image != null && image.getWidth() != targetWidth) { scaled = Favicons.getSizedFaviconFromCache(faviconURL, targetWidth); + } else { + scaled = image; } Favicons.dispatchResult(pageUrl, faviconURL, scaled, listener); } void onCancelled() { Favicons.removeLoadTask(id);
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java +++ b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java @@ -20,29 +20,30 @@ import org.mozilla.gecko.widget.FaviconV import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class TwoLinePageRow extends LinearLayout implements Tabs.OnTabsChangedListener { protected static final int NO_ICON = 0; private final TextView mTitle; private final TextView mUrl; + private final ImageView mStatusIcon; private int mSwitchToTabIconId; - private int mPageTypeIconId; private final FaviconView mFavicon; private boolean mShowIcons; private int mLoadFaviconJobId = Favicons.NOT_LOADING; // Only holds a reference to the FaviconView itself, so if the row gets // discarded while a task is outstanding, we'll leak less memory. @@ -85,21 +86,24 @@ public class TwoLinePageRow extends Line } public TwoLinePageRow(Context context, AttributeSet attrs) { super(context, attrs); setGravity(Gravity.CENTER_VERTICAL); LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this); + // Merge layouts lose their padding, so set it dynamically. + setPadding(0, 0, (int) getResources().getDimension(R.dimen.page_row_padding_right), 0); + mTitle = (TextView) findViewById(R.id.title); mUrl = (TextView) findViewById(R.id.url); + mStatusIcon = (ImageView) findViewById(R.id.status_icon_bookmark); mSwitchToTabIconId = NO_ICON; - mPageTypeIconId = NO_ICON; mShowIcons = true; mFavicon = (FaviconView) findViewById(R.id.icon); mFaviconListener = new UpdateViewFaviconLoadedListener(mFavicon); } @Override protected void onAttachedToWindow() { @@ -168,26 +172,22 @@ public class TwoLinePageRow extends Line } protected void setSwitchToTabIcon(int iconId) { if (mSwitchToTabIconId == iconId) { return; } mSwitchToTabIconId = iconId; - mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, mPageTypeIconId, 0); + mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, 0, 0); } - private void setPageTypeIcon(int iconId) { - if (mPageTypeIconId == iconId) { - return; - } - - mPageTypeIconId = iconId; - mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, mPageTypeIconId, 0); + private void showBookmarkIcon(boolean toShow) { + final int visibility = toShow ? VISIBLE : GONE; + mStatusIcon.setVisibility(visibility); } /** * Stores the page URL, so that we can use it to replace "Switch to tab" if the open * tab changes or is closed. */ private void updateDisplayedUrl(String url) { mPageUrl = url; @@ -226,23 +226,20 @@ public class TwoLinePageRow extends Line public void update(String title, String url) { update(title, url, 0); } protected void update(String title, String url, long bookmarkId) { if (mShowIcons) { // The bookmark id will be 0 (null in database) when the url // is not a bookmark. - if (bookmarkId == 0) { - setPageTypeIcon(NO_ICON); - } else { - setPageTypeIcon(R.drawable.ic_url_bar_star); - } + final boolean isBookmark = bookmarkId != 0; + showBookmarkIcon(isBookmark); } else { - setPageTypeIcon(NO_ICON); + showBookmarkIcon(false); } // Use the URL instead of an empty title for consistency with the normal URL // bar view - this is the equivalent of getDisplayTitle() in Tab.java setTitle(TextUtils.isEmpty(title) ? url : title); // No point updating the below things if URL has not changed. Prevents evil Favicon flicker. if (url.equals(mPageUrl)) {
deleted file mode 100644 index bf18ebeb035c34c5fd29a866987abced913c4c5b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..641a0e1b5dc2fe2049f3f8ed19576d8ae9602ed8 GIT binary patch literal 600 zc$@)P0;m0nP)<h;3K|Lk000e1NJLTq001Qb001Ni1^@s64#M-M0006XNkl<Zcmd6r z0}NYH6o&uVRxoeDY}>ZNc?;%fijXSQwIwzqp;J3F4Y;v^aB-dBCeAGGha-!)*H z<*y5X_&gNff^wEbN}%)(Ph1kOT+&(OsQO3fkC<+s>{{C9FU6OTG!{C`g7~+ul!UBS zND8Fa`O5qW>9a^UOP`kp@y}pV5^^*}LQ%Xlq>zM9B4I3lSp=lNhn13W10)3Ec@YUo z_z2?4=6Bh#NY@APmq?`~3$0#I&OETiviCVaToVheeo#COr1wLy<InIn4e=|GJ__PF zP}X3OvtCeH`GWK)C_VtCpPB!$xvVs^;&E8Qg&+{0NVrJQ@O`*kX&_CLGE(|0goQjH zWu%D5;}68slP|?<;Buw)UCUGwrM6+SD;<214o18(@lB@pdxSC~7-Fk<a{{G~V}+F& zVOiwL0OCCf=+x(Bm57-Au1qqa#jnY(VUf;du>XFQ6-u9pgVbHmZ|8?G<fvj-BK8Lh za&(D9Um9vtN{@-dBsmwhP<nYBqz>7YIv0oLvusN7hd9*KFPSh6k5PH!H%~n)4<i;j z>O@-uk?-3bU15Z>`b4~bEwXA!>4|qmI9H7^m6{ml$FE`$w+vjvd@_vG3K&7WDahz~ z2*o{Qs}qWaR%;Mn5AqwQO{KQ@l<qkc50vdqq<F;T+(-JckJuU0tDa^c{oa#no{Tab m#=;3#ddOK%t|32(Xb9X`rPzV9yB`_=0000<MNUMnLSTa8%@#WV
deleted file mode 100644 index 5f6f96db027d017d004a6c3b333d19fd0537b080..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
--- a/mobile/android/base/resources/layout/two_line_page_row.xml +++ b/mobile/android/base/resources/layout/two_line_page_row.xml @@ -9,34 +9,43 @@ tools:context=".BrowserApp"> <org.mozilla.gecko.widget.FaviconView android:id="@+id/icon" android:layout_width="@dimen/favicon_bg" android:layout_height="@dimen/favicon_bg" android:layout_margin="16dp" tools:background="@drawable/favicon_globe"/> - <LinearLayout android:layout_width="match_parent" + <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_weight="1" android:layout_gravity="center_vertical" - android:orientation="vertical" - android:paddingRight="25dp"> + android:paddingRight="10dp" + android:orientation="vertical"> <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/title" style="@style/Widget.TwoLinePageRow.Title" android:layout_width="match_parent" android:layout_height="wrap_content" - gecko:fadeWidth="30dp" + gecko:fadeWidth="90dp" tools:text="This is a long test title"/> - <TextView android:id="@+id/url" + <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/url" style="@style/Widget.TwoLinePageRow.Url" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawablePadding="5dp" android:maxLength="1024" + gecko:fadeWidth="90dp" tools:text="http://test.com/test" - tools:drawableLeft="@drawable/ic_url_bar_tab" - tools:drawableRight="@drawable/ic_url_bar_star"/> + tools:drawableLeft="@drawable/ic_url_bar_tab"/> </LinearLayout> + + <ImageView android:id="@+id/status_icon_bookmark" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_gravity="center" + android:visibility="gone" + android:src="@drawable/bookmarked_star"/> + </merge>
--- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -74,16 +74,18 @@ <dimen name="browser_toolbar_site_security_padding_vertical">7dp</dimen> <dimen name="browser_toolbar_site_security_padding_horizontal">7dp</dimen> <!-- If one of these values changes, they all should. --> <dimen name="browser_toolbar_site_security_margin_bottom">.5dp</dimen> <dimen name="site_security_unknown_inset_top">1dp</dimen> <dimen name="site_security_unknown_inset_bottom">-1dp</dimen> + <dimen name="page_row_padding_right">15dp</dimen> + <!-- Regular page row on about:home --> <dimen name="page_row_height">64dp</dimen> <!-- Group/heading page row on about:home --> <dimen name="page_group_height">56dp</dimen> <!-- Reading list row on about:home --> <dimen name="reading_list_row_height">128dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -121,17 +121,16 @@ <style name="Widget.TwoLinePageRow.Title"> <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item> </style> <style name="Widget.TwoLinePageRow.Url"> <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item> <item name="android:includeFontPadding">false</item> <item name="android:singleLine">true</item> - <item name="android:ellipsize">middle</item> </style> <style name="Widget.ReadingListRow" /> <style name="Widget.ReadingListRow.Title"> <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item> <item name="android:maxLines">2</item> <item name="android:ellipsize">end</item> @@ -408,17 +407,19 @@ </style> <style name="TextAppearance.Widget.Home" /> <style name="TextAppearance.Widget.Home.Header" parent="TextAppearance.Small"> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium"/> + <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance"> + <item name="android:textSize">16dp</item> + </style> <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro"> <item name="android:textColor">@color/tabs_tray_icon_grey</item> </style> <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small"> <item name="android:textColor">?android:attr/textColorHint</item> </style>
--- a/mobile/android/chrome/content/ActionBarHandler.js +++ b/mobile/android/chrome/content/ActionBarHandler.js @@ -58,18 +58,19 @@ var ActionBarHandler = { // Open a closed ActionBar if carets actually visible. if (!this._selectionID && e.caretVisuallyVisible) { this._init(); return; } // Else, update an open ActionBar. if (this._selectionID) { - if ([this._targetElement, this._contentWindow] === - [Services.focus.focusedElement, Services.focus.focusedWindow]) { + let [element, win] = this._getSelectionTargets(); + if (this._targetElement === element && + this._contentWindow === win) { // We have the same focused window/element as before. Trigger "TextSelection:ActionbarStatus" // message only if available actions differ from when last we checked. this._sendActionBarActions(); } else { // We have a new focused window/element pair. this._uninit(false); this._init(); } @@ -220,18 +221,24 @@ var ActionBarHandler = { * the previous. * @param By default we only send an ActionBarStatus update message if * there is a change from the previous state. sendAlways can be * set by init() for example, where we want to always send the * current state. */ _sendActionBarActions: function(sendAlways) { let actions = this._getActionBarActions(); + let actionCountUnchanged = this._actionBarActions && + actions.length === this._actionBarActions.length; + let actionsMatch = actionCountUnchanged && + this._actionBarActions.every((e,i) => { + return e.id === actions[i].id; + }); - if (sendAlways || actions !== this._actionBarActions) { + if (sendAlways || !actionsMatch) { Messaging.sendRequest({ type: "TextSelection:ActionbarStatus", actions: actions, }); } this._actionBarActions = actions; },
--- a/services/sync/modules/SyncedTabs.jsm +++ b/services/sync/modules/SyncedTabs.jsm @@ -203,16 +203,19 @@ let SyncedTabsInternal = { Preferences.set("services.sync.lastTabFetch", Math.floor(Date.now() / 1000)); Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null); break; case "weave:service:start-over": // start-over needs to notify so consumers find no tabs. Preferences.reset("services.sync.lastTabFetch"); Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null); break; + case "nsPref:changed": + Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null); + break; default: break; } }, // Returns true if Sync is configured to Sync tabs, false otherwise get isConfiguredToSyncTabs() { if (!weaveXPCService.ready) { @@ -227,16 +230,20 @@ let SyncedTabsInternal = { get hasSyncedThisSession() { let engine = Weave.Service.engineManager.get("tabs"); return engine && engine.hasSyncedThisSession; }, }; Services.obs.addObserver(SyncedTabsInternal, "weave:engine:sync:finish", false); Services.obs.addObserver(SyncedTabsInternal, "weave:service:start-over", false); +// Observe the pref the indicates the state of the tabs engine has changed. +// This will force consumers to re-evaluate the state of sync and update +// accordingly. +Services.prefs.addObserver("services.sync.engine.tabs", SyncedTabsInternal, false); // The public interface. this.SyncedTabs = { // A mock-point for tests. _internal: SyncedTabsInternal, // We make the topic for the observer notification public. TOPIC_TABS_CHANGED,
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm +++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm @@ -587,16 +587,37 @@ this.BrowserTestUtils = { mm.sendAsyncMessage("Test:SynthesizeMouse", {target, targetFn, x: offsetX, y: offsetY, event: event}, {object: cpowObject}); }); }, /** + * Wait for a message to be fired from a particular message manager + * + * @param {nsIMessageManager} messageManager + * The message manager that should be used. + * @param {String} message + * The message we're waiting for. + * @param {Function} checkFn (optional) + * Optional function to invoke to check the message. + */ + waitForMessage(messageManager, message, checkFn) { + return new Promise(resolve => { + messageManager.addMessageListener(message, function onMessage(msg) { + if (!checkFn || checkFn(msg)) { + messageManager.removeMessageListener(message, onMessage); + resolve(); + } + }); + }); + }, + + /** * Version of synthesizeMouse that uses the center of the target as the mouse * location. Arguments and the return value are the same. */ synthesizeMouseAtCenter(target, event, browser) { // Use a flag to indicate to center rather than having a separate message. event.centered = true; return BrowserTestUtils.synthesizeMouse(target, 0, 0, event, browser);
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7016,16 +7016,30 @@ "DEVTOOLS_CUSTOM_OPENED_COUNT": { "alert_emails": ["dev-developer-tools@lists.mozilla.org"], "expires_in_version": "never", "kind": "count", "bug_numbers": [1247985], "description": "Number of times a custom developer tool has been opened.", "releaseChannelCollection": "opt-out" }, + "DEVTOOLS_RELOAD_ADDON_INSTALLED_COUNT": { + "alert_emails": ["dev-developer-tools@lists.mozilla.org"], + "expires_in_version": "55", + "kind": "count", + "description": "Number of times the reload addon has been installed.", + "bug_numbers": [1248435] + }, + "DEVTOOLS_RELOAD_ADDON_RELOAD_COUNT": { + "alert_emails": ["dev-developer-tools@lists.mozilla.org"], + "expires_in_version": "55", + "kind": "count", + "description": "Number of times the tools have been reloaded by the reload addon.", + "bug_numbers": [1248435] + }, "DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", "description": "Number of users that have opened the DevTools toolbox." }, "DEVTOOLS_OPTIONS_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", @@ -7199,16 +7213,30 @@ "kind": "flag", "description": "Number of users that have imported a project into the DevTools WebIDE." }, "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", "description": "Number of users that have opened a custom developer tool via the toolbox button." }, + "DEVTOOLS_RELOAD_ADDON_INSTALLED_PER_USER_FLAG": { + "alert_emails": ["dev-developer-tools@lists.mozilla.org"], + "expires_in_version": "55", + "kind": "flag", + "description": "Records once per browser version if the reload add-on is installed.", + "bug_numbers": [1248435] + }, + "DEVTOOLS_RELOAD_ADDON_RELOAD_PER_USER_FLAG": { + "alert_emails": ["dev-developer-tools@lists.mozilla.org"], + "expires_in_version": "55", + "kind": "flag", + "description": "Records once per browser version if the tools have been reloaded via the reload add-on.", + "bug_numbers": [1248435] + }, "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", "high": 10000000, "n_buckets": 100, "description": "How long has the toolbox been active (seconds)" }, "DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS": { @@ -8447,16 +8475,25 @@ }, "LOOP_ROOM_SESSION_WITHCHAT": { "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"], "expires_in_version": "50", "kind": "count", "releaseChannelCollection": "opt-out", "description": "Number of sessions where at least one chat message was exchanged" }, + "LOOP_INFOBAR_ACTION_BUTTONS": { + "alert_emails": ["firefox-dev@mozilla.org", "mbanner@mozilla.com"], + "expires_in_version": "51", + "kind": "enumerated", + "n_values": 4, + "releaseChannelCollection": "opt-out", + "bug_numbers": [1245486], + "description": "Number times info bar buttons are clicked (0=PAUSED, 1=CREATED)" + }, "E10S_STATUS": { "alert_emails": ["firefox-dev@mozilla.org"], "expires_in_version": "never", "kind": "enumerated", "n_values": 12, "releaseChannelCollection": "opt-out", "bug_numbers": [1241294], "description": "Why e10s is enabled or disabled (0=ENABLED_BY_USER, 1=ENABLED_BY_DEFAULT, 2=DISABLED_BY_USER, 3=DISABLED_IN_SAFE_MODE, 4=DISABLED_FOR_ACCESSIBILITY, 5=DISABLED_FOR_MAC_GFX, 6=DISABLED_FOR_BIDI, 7=DISABLED_FOR_ADDONS)"
new file mode 100644 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function doUpdate(update) { + const { classes: Cc, interfaces: Ci, results: Cr } = Components; + + let listener = { + QueryInterface: function(iid) + { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIUrlClassifierUpdateObserver)) + return this; + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + updateUrlRequested: function(url) { }, + streamFinished: function(status) { }, + updateError: function(errorCode) { + sendAsyncMessage("updateError", { errorCode }); + }, + updateSuccess: function(requestedTimeout) { + sendAsyncMessage("loadTestFrame"); + } + }; + + let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"] + .getService(Ci.nsIUrlClassifierDBService); + + dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", ""); + dbService.beginStream("", ""); + dbService.updateStream(update); + dbService.finishStream(); + dbService.finishUpdate(); +} + +addMessageListener("doUpdate", ({ testUpdate }) => { + doUpdate(testUpdate); +});
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini +++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini @@ -1,12 +1,13 @@ [DEFAULT] -skip-if = buildapp == 'b2g' || e10s +skip-if = buildapp == 'b2g' support-files = classifiedAnnotatedPBFrame.html + classifierCommon.js classifierFrame.html cleanWorker.js good.js evil.css evil.js evil.js^headers^ evilWorker.js import.css
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html @@ -8,77 +8,57 @@ <p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script class="testbody" type="text/javascript"> -var Cc = SpecialPowers.Cc; -var Ci = SpecialPowers.Ci; var firstLoad = true; // Add some URLs to the malware database. var testData = "malware.example.com/"; var testUpdate = "n:1000\ni:test-malware-simple\nad:1\n" + "a:524:32:" + testData.length + "\n" + testData; testData = "unwanted.example.com/"; testUpdate += "n:1000\ni:test-unwanted-simple\nad:1\n" + "a:524:32:" + testData.length + "\n" + testData; -var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"] - .getService(Ci.nsIUrlClassifierDBService); - function loadTestFrame() { document.getElementById("testFrame").src = "classifierFrame.html"; } -function doUpdate(update) { - var listener = { - QueryInterface: SpecialPowers.wrapCallback(function(iid) - { - if (iid.equals(Ci.nsISupports) || - iid.equals(Ci.nsIUrlClassifierUpdateObserver)) - return this; +const CLASSIFIER_COMMON_URL = SimpleTest.getTestFileURL("classifierCommon.js"); +let classifierCommonScript = SpecialPowers.loadChromeScript(CLASSIFIER_COMMON_URL); + +// Expected finish() call is in "classifierFrame.html". +SimpleTest.waitForExplicitFinish(); - throw Cr.NS_ERROR_NO_INTERFACE; - }), - updateUrlRequested: function(url) { }, - streamFinished: function(status) { }, - updateError: function(errorCode) { - ok(false, "Couldn't update classifier."); - // Abort test. - SimpleTest.finish(); - }, - updateSuccess: function(requestedTimeout) { - SpecialPowers.pushPrefEnv( - {"set" : [["browser.safebrowsing.malware.enabled", true]]}, - loadTestFrame); - } - }; - - dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", ""); - dbService.beginStream("", ""); - dbService.updateStream(update); - dbService.finishStream(); - dbService.finishUpdate(); -} +classifierCommonScript.addMessageListener("loadTestFrame", () => { + SpecialPowers.pushPrefEnv( + {"set" : [["browser.safebrowsing.malware.enabled", true]]}, + loadTestFrame); +}); +classifierCommonScript.addMessageListener("updateError", ({ errorCode }) => { + ok(false, "Couldn't update classifier. Error code: " + errorCode); + // Abort test. + SimpleTest.finish(); +}); SpecialPowers.pushPrefEnv( {"set" : [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"], ["urlclassifier.phishTable", "test-phish-simple"]]}, - function() { doUpdate(testUpdate); }); - -// Expected finish() call is in "classifierFrame.html". -SimpleTest.waitForExplicitFinish(); + function() { + classifierCommonScript.sendAsyncMessage("doUpdate", { testUpdate }); + }); </script> </pre> <iframe id="testFrame" onload=""></iframe> </body> </html>
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html @@ -8,85 +8,65 @@ <p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script class="testbody" type="text/javascript"> -var Cc = SpecialPowers.Cc; -var Ci = SpecialPowers.Ci; - // Add some URLs to the malware database. var testData = "example.com/tests/toolkit/components/url-classifier/tests/mochitest/evilWorker.js"; var testUpdate = "n:1000\ni:test-malware-simple\nad:550\n" + "a:550:32:" + testData.length + "\n" + testData; testData = "example.com/tests/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js"; testUpdate += "n:1000\ni:test-unwanted-simple\nad:550\n" + "a:550:32:" + testData.length + "\n" + testData; -var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"] - .getService(Ci.nsIUrlClassifierDBService); - -function doUpdate(update) { - var listener = { - QueryInterface: SpecialPowers.wrapCallback(function(iid) - { - if (iid.equals(Ci.nsISupports) || - iid.equals(Ci.nsIUrlClassifierUpdateObserver)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }), - updateUrlRequested: function(url) { }, - streamFinished: function(status) { }, - updateError: function(errorCode) { - ok(false, "Couldn't update classifier."); - // Abort test. - SimpleTest.finish(); - }, - updateSuccess: function(requestedTimeout) { - SpecialPowers.pushPrefEnv( - {"set" : [["browser.safebrowsing.malware.enabled", true]]}, - function loadTestFrame() { - document.getElementById("testFrame").src = - "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/workerFrame.html"; - } - ); - } - }; - - dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", ""); - dbService.beginStream("", ""); - dbService.updateStream(update); - dbService.finishStream(); - dbService.finishUpdate(); +function loadTestFrame() { + document.getElementById("testFrame").src = + "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/workerFrame.html"; } function onmessage(event) { var pieces = event.data.split(':'); if (pieces[0] == "finish") { SimpleTest.finish(); return; } is(pieces[0], "success", pieces[1]); } +const CLASSIFIER_COMMON_URL = SimpleTest.getTestFileURL("classifierCommon.js"); +let classifierCommonScript = SpecialPowers.loadChromeScript(CLASSIFIER_COMMON_URL); + +classifierCommonScript.addMessageListener("loadTestFrame", () => { + SpecialPowers.pushPrefEnv( + {"set" : [["browser.safebrowsing.malware.enabled", true]]}, + loadTestFrame); +}); +classifierCommonScript.addMessageListener("updateError", ({ errorCode }) => { + ok(false, "Couldn't update classifier. Error code: " + errorCode); + // Abort test. + SimpleTest.finish(); +}); + SpecialPowers.pushPrefEnv( {"set" : [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"], ["urlclassifier.phishTable", "test-phish-simple"]]}, - function() { doUpdate(testUpdate); }); + function() { + classifierCommonScript.sendAsyncMessage("doUpdate", { testUpdate }); + }); window.addEventListener("message", onmessage, false); SimpleTest.waitForExplicitFinish(); </script> </pre>
--- a/uriloader/exthandler/tests/mochitest/mochitest.ini +++ b/uriloader/exthandler/tests/mochitest/mochitest.ini @@ -1,12 +1,12 @@ [DEFAULT] skip-if = buildapp == 'b2g' support-files = handlerApp.xhtml handlerApps.js + unsafeBidi_chromeScript.js unsafeBidiFileName.sjs [test_badMimeType.html] [test_handlerApps.xhtml] skip-if = (toolkit == 'android' || os == 'mac') || e10s # OS X: bug 786938 [test_unsafeBidiChars.xhtml] -skip-if = e10s
--- a/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml +++ b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml @@ -1,27 +1,28 @@ <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Test for Handling of unsafe bidi chars</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> -<body onload="load();"> +<body> <p id="display"></p> <iframe id="test"></iframe> <script type="text/javascript"> <![CDATA[ -var unsafeBidiChars = { - LRE: "\xe2\x80\xaa", - RLE: "\xe2\x80\xab", - PDF: "\xe2\x80\xac", - LRO: "\xe2\x80\xad", - RLO: "\xe2\x80\xae" -}; +var unsafeBidiChars = [ + "\xe2\x80\xaa", // LRE + "\xe2\x80\xab", // RLE + "\xe2\x80\xac", // PDF + "\xe2\x80\xad", // LRO + "\xe2\x80\xae" // RLO +]; var tests = [ "{1}.test", "{1}File.test", "Fi{1}le.test", "File{1}.test", "File.{1}test", "File.te{1}st", @@ -32,105 +33,45 @@ var tests = [ function replace(name, x) { return name.replace(/\{1\}/, x); } function sanitize(name) { return replace(name, '_'); } -var gTests = []; -function make_test(param, expected) { - gTests.push({ - param: param, - expected: expected, - }); -} - -SimpleTest.waitForExplicitFinish(); - -function load() { - var iframe = document.getElementById("test"); - var gCallback = null; - function run_test(test, cb) { - var url = "unsafeBidiFileName.sjs?name=" + encodeURIComponent(test.param); - gCallback = cb; - iframe.src = url; - } - - var gCounter = -1; - function run_next_test() { - if (++gCounter == gTests.length) - finish_test(); - else - run_test(gTests[gCounter], run_next_test); - } - - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - - const HELPERAPP_DIALOG_CONTRACT = "@mozilla.org/helperapplauncherdialog;1"; - const HELPERAPP_DIALOG_CID = SpecialPowers.wrap(SpecialPowers.Components).ID(SpecialPowers.Cc[HELPERAPP_DIALOG_CONTRACT].number); - - const FAKE_CID = SpecialPowers.Cc["@mozilla.org/uuid-generator;1"]. - getService(SpecialPowers.Ci.nsIUUIDGenerator).generateUUID(); +add_task(function* () { + let url = SimpleTest.getTestFileURL("unsafeBidi_chromeScript.js"); + let chromeScript = SpecialPowers.loadChromeScript(url); - function HelperAppLauncherDialog() {} - HelperAppLauncherDialog.prototype = { - REASON_CANTHANDLE: 0, - REASON_SERVERREQUEST: 1, - REASON_TYPESNIFFED: 2, - show: function(aLauncher, aWindowContext, aReason) { - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - var test = gTests[gCounter]; - is(aLauncher.suggestedFileName, test.expected, - "The filename should be correctly sanitized"); - gCallback(); - }, - QueryInterface: function(aIID) { - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - if (aIID.equals(SpecialPowers.Ci.nsISupports) || - aIID.equals(SpecialPowers.Ci.nsIHelperAppLauncherDialog)) - return this; - throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE; - } - }; - - var factory = { - createInstance: function(aOuter, aIID) { - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - if (aOuter != null) - throw SpecialPowers.Cr.NS_ERROR_NO_AGGREGATION; - return new HelperAppLauncherDialog().QueryInterface(aIID); - } - }; - - SpecialPowers.wrap(SpecialPowers.Components).manager - .QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar) - .registerFactory(FAKE_CID, "", - HELPERAPP_DIALOG_CONTRACT, - factory); - - function finish_test() { - SpecialPowers.wrap(SpecialPowers.Components).manager - .QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar) - .registerFactory(HELPERAPP_DIALOG_CID, "", - HELPERAPP_DIALOG_CONTRACT, - null); - SimpleTest.finish(); - } - - var i,j; - - for (i = 0; i < tests.length; ++i) { - for (j in unsafeBidiChars) { - make_test(replace(tests[i], unsafeBidiChars[j]), - sanitize(tests[i])); + for (let test of tests) { + for (let char of unsafeBidiChars) { + let promiseName = new Promise(function(resolve) { + chromeScript.addMessageListener("suggestedFileName", + function listener(data) { + chromeScript.removeMessageListener("suggestedFileName", listener); + resolve(data); + }); + }); + let name = replace(test, char); + let expected = sanitize(test); + document.getElementById("test").src = + "unsafeBidiFileName.sjs?name=" + encodeURIComponent(name); + is((yield promiseName), expected, "got the expected sanitized name"); } } - run_next_test(); -} + let promise = new Promise(function(resolve) { + chromeScript.addMessageListener("unregistered", function listener() { + chromeScript.removeMessageListener("unregistered", listener); + resolve(); + }); + }); + chromeScript.sendAsyncMessage("unregister"); + yield promise; + + chromeScript.destroy(); +}); ]]> </script> </body> </html> -
new file mode 100644 --- /dev/null +++ b/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js @@ -0,0 +1,28 @@ +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const HELPERAPP_DIALOG_CONTRACT = "@mozilla.org/helperapplauncherdialog;1"; +const HELPERAPP_DIALOG_CID = + Components.ID(Cc[HELPERAPP_DIALOG_CONTRACT].number); + +const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"]. + getService(Ci.nsIUUIDGenerator).generateUUID(); + +function HelperAppLauncherDialog() {} +HelperAppLauncherDialog.prototype = { + show: function(aLauncher, aWindowContext, aReason) { + sendAsyncMessage("suggestedFileName", aLauncher.suggestedFileName); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]) +}; + +var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +registrar.registerFactory(FAKE_CID, "", HELPERAPP_DIALOG_CONTRACT, + XPCOMUtils._getFactory(HelperAppLauncherDialog)); + +addMessageListener("unregister", function() { + registrar.registerFactory(HELPERAPP_DIALOG_CID, "", + HELPERAPP_DIALOG_CONTRACT, null); + sendAsyncMessage("unregistered"); +});