From 804475f701648285086b67f58cb19956070e782b Mon Sep 17 00:00:00 2001 From: Shane Caraveo Date: Thu, 20 Mar 2014 00:57:33 -0700 Subject: [PATCH 01/20] Bug 984628 fix social button states, r=markh --- browser/base/content/browser-social.js | 9 ++- .../base/content/test/social/browser_share.js | 75 +++++++++++++------ .../test/social/browser_social_marks.js | 31 ++++++++ browser/base/content/test/social/head.js | 5 +- 4 files changed, 95 insertions(+), 25 deletions(-) diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js index eebca83da8f5..37680d877ab0 100644 --- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -177,6 +177,7 @@ SocialUI = { SocialShare.populateProviderMenu(); SocialStatus.populateToolbarPalette(); SocialMarks.populateToolbarPalette(); + SocialShare.update(); }, // This handles "ActivateSocialFeature" events fired against content documents @@ -514,7 +515,7 @@ SocialShare = { if (!provider) provider = SocialSidebar.provider; // if our provider has no shareURL, select the first one that does - if (provider && !provider.shareURL) { + if (!provider || !provider.shareURL) { let providers = [p for (p of Social.providers) if (p.shareURL)]; provider = providers.length > 0 && providers[0]; } @@ -582,7 +583,10 @@ SocialShare = { // also update the relevent command's disabled state so the keyboard // shortcut only works when available. let cmd = document.getElementById("Social:SharePage"); - cmd.setAttribute("disabled", shareButton.disabled ? "true" : "false"); + if (shareButton.disabled) + cmd.setAttribute("disabled", "true"); + else + cmd.removeAttribute("disabled"); }, onShowing: function() { @@ -1411,6 +1415,7 @@ SocialMarks = { for (let cfg of contextMenus) { this._populateContextPopup(cfg, providers); } + this.updatePanelButtons(); }, MENU_LIMIT: 3, // adjustable for testing diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js index 0c6fb13acabf..16987afe0f5d 100644 --- a/browser/base/content/test/social/browser_share.js +++ b/browser/base/content/test/social/browser_share.js @@ -1,20 +1,20 @@ +let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService; + let baseURL = "https://example.com/browser/browser/base/content/test/social/"; +let manifest = { // normal provider + name: "provider 1", + origin: "https://example.com", + workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js", + iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png", + shareURL: "https://example.com/browser/browser/base/content/test/social/share.html" +}; + function test() { waitForExplicitFinish(); - let manifest = { // normal provider - name: "provider 1", - origin: "https://example.com", - sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html", - workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js", - iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png", - shareURL: "https://example.com/browser/browser/base/content/test/social/share.html" - }; - runSocialTestWithProvider(manifest, function (finishcb) { - runSocialTests(tests, undefined, undefined, finishcb); - }); + runSocialTests(tests); } let corpus = [ @@ -78,7 +78,7 @@ function loadURLInTab(url, callback) { tab.linkedBrowser.addEventListener("load", function listener() { is(tab.linkedBrowser.currentURI.spec, url, "tab loaded") tab.linkedBrowser.removeEventListener("load", listener, true); - callback(tab); + executeSoon(function() { callback(tab) }); }, true); } @@ -101,10 +101,46 @@ function hasoptions(testOptions, options) { } var tests = { + testShareDisabledOnActivation: function(next) { + // starting on about:blank page, share should be visible but disabled when + // adding provider + is(gBrowser.contentDocument.location.href, "about:blank"); + SocialService.addProvider(manifest, function(provider) { + is(SocialUI.enabled, true, "SocialUI is enabled"); + checkSocialUI(); + // share should not be enabled since we only have about:blank page + let shareButton = SocialShare.shareButton; + is(shareButton.disabled, true, "share button is disabled"); + // verify the attribute for proper css + is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + SocialService.removeProvider(manifest.origin, next); + }); + }, + testShareEnabledOnActivation: function(next) { + // starting from *some* page, share should be visible and enabled when + // activating provider + let testData = corpus[0]; + loadURLInTab(testData.url, function(tab) { + SocialService.addProvider(manifest, function(provider) { + is(SocialUI.enabled, true, "SocialUI is enabled"); + checkSocialUI(); + // share should not be enabled since we only have about:blank page + let shareButton = SocialShare.shareButton; + is(shareButton.disabled, false, "share button is enabled"); + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + gBrowser.removeTab(tab); + next(); + }); + }); + }, testSharePage: function(next) { - let panel = document.getElementById("social-flyout-panel"); - SocialSidebar.show(); - let port = SocialSidebar.provider.getWorkerPort(); + let provider = Social._getProviderFromOrigin(manifest.origin); + let port = provider.getWorkerPort(); ok(port, "provider has a port"); let testTab; let testIndex = 0; @@ -120,22 +156,19 @@ var tests = { port.onmessage = function (e) { let topic = e.data.topic; switch (topic) { - case "got-sidebar-message": - // open a tab with share data, then open the share panel - runOneTest(); - break; case "got-share-data-message": gBrowser.removeTab(testTab); hasoptions(testData.options, e.data.result); testData = corpus[testIndex++]; if (testData) { - runOneTest(); + executeSoon(runOneTest); } else { - next(); + SocialService.removeProvider(manifest.origin, next); } break; } } port.postMessage({topic: "test-init"}); + executeSoon(runOneTest); } } diff --git a/browser/base/content/test/social/browser_social_marks.js b/browser/base/content/test/social/browser_social_marks.js index b1dd0b1454eb..234e85b06a6d 100644 --- a/browser/base/content/test/social/browser_social_marks.js +++ b/browser/base/content/test/social/browser_social_marks.js @@ -70,6 +70,28 @@ function test() { } var tests = { + testButtonDisabledOnActivate: function(next) { + // starting on about:blank page, share should be visible but disabled when + // adding provider + is(gBrowser.contentDocument.location.href, "about:blank"); + SocialService.addProvider(manifest2, function(provider) { + is(provider.origin, manifest2.origin, "provider is installed"); + let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin); + let widget = CustomizableUI.getWidget(id).forWindow(window) + ok(widget.node, "button added to widget set"); + + // bypass widget go directly to dom, check attribute states + let button = document.getElementById(id); + is(button.disabled, true, "mark button is disabled"); + // verify the attribute for proper css + is(button.getAttribute("disabled"), "true", "mark button attribute is disabled"); + // button should be visible + is(button.hidden, false, "mark button is visible"); + + checkSocialUI(window); + SocialService.removeProvider(manifest2.origin, next); + }); + }, testNoButtonOnEnable: function(next) { // we expect the addon install dialog to appear, we need to accept the // install from the dialog. @@ -117,6 +139,15 @@ var tests = { let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin); let widget = CustomizableUI.getWidget(id).forWindow(window) ok(widget.node, "button added to widget set"); + + // bypass widget go directly to dom, check attribute states + let button = document.getElementById(id); + is(button.disabled, false, "mark button is disabled"); + // verify the attribute for proper css + ok(!button.hasAttribute("disabled"), "mark button attribute is disabled"); + // button should be visible + is(button.hidden, false, "mark button is visible"); + checkSocialUI(window); gBrowser.removeTab(tab); next(); diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js index cfb65cc8809b..2c47f347dbf8 100644 --- a/browser/base/content/test/social/head.js +++ b/browser/base/content/test/social/head.js @@ -207,6 +207,7 @@ function checkSocialUI(win) { let enabled = win.SocialUI.enabled; let active = Social.providers.length > 0 && !win.SocialUI._chromeless && !PrivateBrowsingUtils.isWindowPrivate(win); + let sidebarEnabled = win.SocialSidebar.provider ? enabled : false; // if we have enabled providers, we should also have instances of those // providers @@ -235,7 +236,7 @@ function checkSocialUI(win) { function isbool(a, b, msg) { _is(!!a, !!b, msg); } - isbool(win.SocialSidebar.canShow, enabled, "social sidebar active?"); + isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?"); isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?"); isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?"); @@ -276,7 +277,7 @@ function checkSocialUI(win) { } // and for good measure, check all the social commands. - isbool(!doc.getElementById("Social:ToggleSidebar").hidden, enabled, "Social:ToggleSidebar visible?"); + isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?"); isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?"); isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?"); isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?"); From 28124f15ce09b6656e799d60923fedad568fe35f Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Thu, 20 Mar 2014 11:01:39 +0100 Subject: [PATCH 02/20] Bug 985523 - Convert to Promise.jsm in Places. r=mak --- toolkit/components/places/PlacesUtils.jsm | 2 +- toolkit/components/places/tests/browser/head.js | 2 +- toolkit/components/places/tests/head_common.js | 2 +- toolkit/components/places/tests/unit/test_result_sort.js | 8 +++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/toolkit/components/places/PlacesUtils.jsm b/toolkit/components/places/PlacesUtils.jsm index 5124f9aa7414..1c2a9a85066b 100644 --- a/toolkit/components/places/PlacesUtils.jsm +++ b/toolkit/components/places/PlacesUtils.jsm @@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", "resource://gre/modules/Deprecated.jsm"); diff --git a/toolkit/components/places/tests/browser/head.js b/toolkit/components/places/tests/browser/head.js index c381f4f62962..39ae3f3c3b0e 100644 --- a/toolkit/components/places/tests/browser/head.js +++ b/toolkit/components/places/tests/browser/head.js @@ -6,7 +6,7 @@ const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED; Components.utils.import("resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); diff --git a/toolkit/components/places/tests/head_common.js b/toolkit/components/places/tests/head_common.js index 366b926b1b58..780cd34436a0 100644 --- a/toolkit/components/places/tests/head_common.js +++ b/toolkit/components/places/tests/head_common.js @@ -27,7 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", diff --git a/toolkit/components/places/tests/unit/test_result_sort.js b/toolkit/components/places/tests/unit/test_result_sort.js index 049b24313a62..3431287dec0c 100644 --- a/toolkit/components/places/tests/unit/test_result_sort.js +++ b/toolkit/components/places/tests/unit/test_result_sort.js @@ -112,12 +112,14 @@ add_task(function test() { checkOrder(id1, id3, id2); // Add a visit, then check frecency ordering. - yield promiseAddVisits({ uri: uri2, - transition: TRANSITION_TYPED}); + // When the bookmarks service gets onVisit, it asynchronously fetches all // items for that visit, and then notifies onItemVisited. Thus we must // explicitly wait for that. - yield promiseOnItemVisited(); + let waitForVisited = promiseOnItemVisited(); + yield promiseAddVisits({ uri: uri2, + transition: TRANSITION_TYPED}); + yield waitForVisited; do_print("Sort by frecency desc"); result.sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING; From 9af8f10a0531f7d0dc72d1c58561ed3cbb06d48d Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Thu, 20 Mar 2014 11:04:37 +0100 Subject: [PATCH 03/20] Bug 985318 - Convert to Promise.jsm in the Toolkit "content" folder. r=mak --- .../test/browser_901207_searchbar_in_panel.js | 28 +++++++++++-------- toolkit/content/contentAreaUtils.js | 2 +- .../content/tests/browser/browser_findbar.js | 2 +- .../tests/widgets/test_popupreflows.xul | 2 +- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js index 10e310bfd45b..d5194cecf14e 100644 --- a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js +++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js @@ -12,11 +12,20 @@ openUILinkIn = (aUrl, aWhichTab) => { is(aWhichTab, "current", "Should use the current tab for the search page."); openUILinkInCalled = true; if (!expectOpenUILinkInCall) { - ok(false, "OpenUILink in was called when it shouldn't have been."); + ok(false, "OpenUILinkIn was called when it shouldn't have been."); } }; logActiveElement(); +function* waitForSearchBarFocus() +{ + let searchbar = document.getElementById("searchbar"); + yield waitForCondition(function () { + logActiveElement(); + return document.activeElement === searchbar.textbox.inputField; + }); +} + // Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel. add_task(function() { let searchbar = document.getElementById("searchbar"); @@ -28,8 +37,7 @@ add_task(function() { sendWebSearchKeyCommand(); yield shownPanelPromise; - logActiveElement(); - is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); + yield waitForSearchBarFocus(); let hiddenPanelPromise = promisePanelHidden(window); EventUtils.synthesizeKey("VK_ESCAPE", {}); @@ -49,8 +57,8 @@ add_task(function() { yield shownPanelPromise; sendWebSearchKeyCommand(); - logActiveElement(); - is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); + + yield waitForSearchBarFocus(); let hiddenPanelPromise = promisePanelHidden(window); EventUtils.synthesizeKey("VK_ESCAPE", {}); @@ -68,7 +76,6 @@ add_task(function() { window.resizeTo(360, window.outerHeight); yield waitForCondition(() => navbar.getAttribute("overflowing") == "true"); ok(!navbar.querySelector("#search-container"), "Search container should be overflowing"); - let searchbar = document.getElementById("searchbar"); let shownPanelPromise = promiseOverflowShown(window); sendWebSearchKeyCommand(); @@ -76,8 +83,8 @@ add_task(function() { let chevron = document.getElementById("nav-bar-overflow-button"); yield waitForCondition(function() chevron.open); - logActiveElement(); - is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); + + yield waitForSearchBarFocus(); let hiddenPanelPromise = promiseOverflowHidden(window); EventUtils.synthesizeKey("VK_ESCAPE", {}); @@ -90,13 +97,12 @@ add_task(function() { // Ctrl+K should focus the search bar if it is in the navbar and not overflowing. add_task(function() { - let searchbar = document.getElementById("searchbar"); let placement = CustomizableUI.getPlacementOfWidget("search-container"); is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar"); sendWebSearchKeyCommand(); - logActiveElement(); - is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); + + yield waitForSearchBarFocus(); }); // Ctrl+K should open the search page if the search bar has been customized out. diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index d8ed762c3340..eebe39c1bf13 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS", XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", diff --git a/toolkit/content/tests/browser/browser_findbar.js b/toolkit/content/tests/browser/browser_findbar.js index 1d680e6a58da..a1130e9b1bd5 100644 --- a/toolkit/content/tests/browser/browser_findbar.js +++ b/toolkit/content/tests/browser/browser_findbar.js @@ -1,5 +1,5 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/commonjs/sdk/core/promise.js"); + "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); Components.utils.import("resource://gre/modules/Timer.jsm", this); diff --git a/toolkit/content/tests/widgets/test_popupreflows.xul b/toolkit/content/tests/widgets/test_popupreflows.xul index 8fb9389976d9..b103414ae06a 100644 --- a/toolkit/content/tests/widgets/test_popupreflows.xul +++ b/toolkit/content/tests/widgets/test_popupreflows.xul @@ -20,7 +20,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); +Cu.import("resource://gre/modules/Promise.jsm"); let panel, anchor, arrow; From 282892fdd55cae1941f8529fbedb72f8330ce568 Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Thu, 20 Mar 2014 11:11:36 +0100 Subject: [PATCH 04/20] Bug 984809 - Remove deprecated promise.js usage in B2G. r=ochameau --- b2g/chrome/content/shell.js | 2 +- b2g/simulator/lib/main.js | 2 +- b2g/simulator/lib/simulator-process.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index d1bfd1162d7e..78364be04648 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -1134,7 +1134,7 @@ let RemoteDebugger = { */ DebuggerServer.createRootActor = function createRootActor(connection) { - let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; + let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); let parameters = { // We do not expose browser tab actors yet, // but we still have to define tabList.getList(), diff --git a/b2g/simulator/lib/main.js b/b2g/simulator/lib/main.js index 73333e075b48..e27124f3b313 100644 --- a/b2g/simulator/lib/main.js +++ b/b2g/simulator/lib/main.js @@ -6,7 +6,7 @@ const { Cc, Ci, Cu } = require("chrome"); const { SimulatorProcess } = require("./simulator-process"); -const Promise = require("sdk/core/promise"); +const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); const Self = require("sdk/self"); const System = require("sdk/system"); const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm"); diff --git a/b2g/simulator/lib/simulator-process.js b/b2g/simulator/lib/simulator-process.js index 6d35a08330ff..0ffa0a159948 100644 --- a/b2g/simulator/lib/simulator-process.js +++ b/b2g/simulator/lib/simulator-process.js @@ -17,7 +17,7 @@ const Runtime = require("sdk/system/runtime"); const Self = require("sdk/self"); const URL = require("sdk/url"); const Subprocess = require("subprocess"); -const Promise = require("sdk/core/promise"); +const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); const { rootURI: ROOT_URI } = require('@loader/options'); const PROFILE_URL = ROOT_URI + "profile/"; From 73c20c43ba82d889fa625ad524340bf50ed28c27 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Thu, 20 Mar 2014 14:29:54 +0200 Subject: [PATCH 05/20] Bug 969724 - Fix for intermittent browser_webconsole_bug_597136_external_script_errors.js | Timed out while waiting for: external script error message; r=me --- ...nsole_bug_597136_external_script_errors.js | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js index c634b11bdd75..fa8905af08ca 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js @@ -8,36 +8,29 @@ * * ***** END LICENSE BLOCK ***** */ +"use strict"; + const TEST_URI = "http://example.com/browser/browser/devtools/" + "webconsole/test/test-bug-597136-external-script-" + "errors.html"; function test() { - addTab(TEST_URI); - browser.addEventListener("load", function onLoad() { - browser.removeEventListener("load", onLoad, true); - openConsole(null, function(hud) { - executeSoon(function() { - consoleOpened(hud); - }); + Task.spawn(function* () { + const {tab} = yield loadTab(TEST_URI); + const hud = yield openConsole(tab); + + let button = content.document.querySelector("button"); + + expectUncaughtException(); + EventUtils.sendMouseEvent({ type: "click" }, button, content); + + yield waitForMessages({ + webconsole: hud, + messages: [{ + text: "bogus is not defined", + category: CATEGORY_JS, + severity: SEVERITY_ERROR, + }], }); - }, true); -} - -function consoleOpened(hud) { - let button = content.document.querySelector("button"); - let outputNode = hud.outputNode; - - expectUncaughtException(); - EventUtils.sendMouseEvent({ type: "click" }, button, content); - - waitForSuccess({ - name: "external script error message", - validatorFn: function() - { - return outputNode.textContent.indexOf("bogus is not defined") > -1; - }, - successFn: finishTest, - failureFn: finishTest, - }); + }).then(finishTest); } From 96195de851681dd72fbb79c94f950acf72a2afbe Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Thu, 20 Mar 2014 14:44:06 +0200 Subject: [PATCH 06/20] Bug 982231 - Fix for intermittent browser_console_variables_view.js | Timed out while waiting for: property deleted; r=me --- .../devtools/webconsole/test/browser_console_variables_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/devtools/webconsole/test/browser_console_variables_view.js b/browser/devtools/webconsole/test/browser_console_variables_view.js index 4f30ddede3b5..78887d0748a3 100644 --- a/browser/devtools/webconsole/test/browser_console_variables_view.js +++ b/browser/devtools/webconsole/test/browser_console_variables_view.js @@ -175,6 +175,7 @@ function testPropDelete(aProp) waitForSuccess({ name: "property deleted", + timeout: 60000, validatorFn: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj), successFn: finishTest, failureFn: finishTest, From bf284cf8c657a09bf8fbd2a597803cb8e20d5886 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Thu, 20 Mar 2014 15:16:58 +0200 Subject: [PATCH 07/20] Bug 982627 - Fix for intermittent browser_webconsole_bug_580030_errors_after_page_reload.js | Timed out while waiting for: error message after page reload; r=me --- ...ole_bug_580030_errors_after_page_reload.js | 83 +++++++------------ 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js b/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js index bc325d9ce88d..de208ee06960 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js @@ -4,64 +4,39 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Tests that errors still show up in the Web Console after a page reload. +// See bug 580030: the error handler fails silently after page reload. +// https://bugzilla.mozilla.org/show_bug.cgi?id=580030 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html"; function test() { - expectUncaughtException(); - addTab(TEST_URI); - browser.addEventListener("load", onLoad, true); -} + Task.spawn(function*() { + const {tab} = yield loadTab(TEST_URI); + const hud = yield openConsole(tab); + info("console opened"); -// see bug 580030: the error handler fails silently after page reload. -// https://bugzilla.mozilla.org/show_bug.cgi?id=580030 -function onLoad(aEvent) { - browser.removeEventListener(aEvent.type, onLoad, true); - - openConsole(null, function(hud) { - hud.jsterm.clearOutput(); - browser.addEventListener("load", testErrorsAfterPageReload, true); - content.location.reload(); - }); -} - -function testErrorsAfterPageReload(aEvent) { - browser.removeEventListener(aEvent.type, testErrorsAfterPageReload, true); - - // dispatch a click event to the button in the test page and listen for - // errors. - - Services.console.registerListener(consoleObserver); - - let button = content.document.querySelector("button").wrappedJSObject; - ok(button, "button found"); - EventUtils.sendMouseEvent({type: "click"}, button, content.wrappedJSObject); -} - -var consoleObserver = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), - - observe: function test_observe(aMessage) - { - // Ignore errors we don't care about. - if (!(aMessage instanceof Ci.nsIScriptError) || - aMessage.category != "content javascript") { - return; - } - - Services.console.unregisterListener(this); - - let outputNode = HUDService.getHudByWindow(content).outputNode; - - waitForSuccess({ - name: "error message after page reload", - validatorFn: function() - { - return outputNode.textContent.indexOf("fooBazBaz") > -1; - }, - successFn: finishTest, - failureFn: finishTest, + executeSoon(() => { + hud.jsterm.clearOutput(); + info("wait for reload"); + content.location.reload(); }); - } -}; + yield hud.target.once("navigate"); + info("target navigated"); + + let button = content.document.querySelector("button"); + ok(button, "button found"); + + expectUncaughtException(); + EventUtils.sendMouseEvent({type: "click"}, button, content); + + yield waitForMessages({ + webconsole: hud, + messages: [{ + text: "fooBazBaz is not defined", + category: CATEGORY_JS, + severity: SEVERITY_ERROR, + }], + }); + }).then(finishTest); +} From a206a9418d098067766ad8edeaadc81b100bec34 Mon Sep 17 00:00:00 2001 From: Eugen Sawin Date: Wed, 19 Mar 2014 21:24:59 +0100 Subject: [PATCH 08/20] Bug 950417 - Enable safe direct request add-on installs. r=wesj, r=Mossop --- mobile/android/app/mobile.js | 2 + mobile/android/base/AndroidManifest.xml.in | 19 +++++++++ mobile/android/chrome/content/browser.js | 23 ++++++++++- .../locales/en-US/chrome/browser.properties | 2 + .../extensions/internal/XPIProvider.jsm | 32 +++++++++++++-- .../extensions/test/xpinstall/browser.ini | 3 ++ .../test/xpinstall/browser_localfile3.js | 37 +++++++++++++++++ .../test/xpinstall/browser_localfile4.js | 40 +++++++++++++++++++ .../test/xpinstall/browser_whitelist7.js | 30 ++++++++++++++ 9 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 49aef04a30da..66ff4291626d 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -170,6 +170,8 @@ pref("dom.experimental_forms", true); pref("dom.forms.number", true); /* extension manager and xpinstall */ +pref("xpinstall.whitelist.directRequest", false); +pref("xpinstall.whitelist.fileRequest", false); pref("xpinstall.whitelist.add", "addons.mozilla.org"); pref("xpinstall.whitelist.add.180", "marketplace.firefox.com"); diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in index ae8aca65fa27..2deb7d055c72 100644 --- a/mobile/android/base/AndroidManifest.xml.in +++ b/mobile/android/base/AndroidManifest.xml.in @@ -141,6 +141,25 @@ + + + + + + + + + + + + + + + + + + + #ifdef MOZ_ANDROID_BEAM diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index e01916f3480e..4692d39ae46b 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -5695,7 +5695,10 @@ var XPInstallObserver = { if (!tab) return; - let host = installInfo.originatingURI.host; + let host = null; + if (installInfo.originatingURI) { + host = installInfo.originatingURI.host; + } let brandShortName = Strings.brand.GetStringFromName("brandShortName"); let notificationName, buttons, message; @@ -5723,7 +5726,23 @@ var XPInstallObserver = { } } else { notificationName = "xpinstall"; - message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2); + if (host) { + // We have a host which asked for the install. + message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2); + } else { + // Without a host we address the add-on as the initiator of the install. + let addon = null; + if (installInfo.installs.length > 0) { + addon = installInfo.installs[0].name; + } + if (addon) { + // We have an addon name, show the regular message. + message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2); + } else { + // We don't have an addon name, show an alternative message. + message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1); + } + } buttons = [{ label: strings.GetStringFromName("xpinstallPromptAllowButton"), diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index cc70a295e645..c227d90b2534 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -71,6 +71,8 @@ blockPopups.label=Block Popups # XPInstall xpinstallPromptWarning2=%S prevented this site (%S) from asking you to install software on your device. +xpinstallPromptWarningLocal=%S prevented this add-on (%S) from installing on your device. +xpinstallPromptWarningDirect=%S prevented an add-on from installing on your device. xpinstallPromptAllowButton=Allow xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator. xpinstallDisabledMessage2=Software installation is currently disabled. Press Enable and try again. diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index bc40244b97a2..30cc53eeef91 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -67,6 +67,8 @@ const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; const PREF_XPI_ENABLED = "xpinstall.enabled"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; +const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; +const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; const PREF_XPI_UNPACK = "extensions.alwaysUnpack"; const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; @@ -3445,6 +3447,28 @@ var XPIProvider = { return Prefs.getBoolPref(PREF_XPI_ENABLED, true); }, + /** + * Called to test whether installing XPI add-ons by direct URL requests is + * whitelisted. + * + * @return true if installing by direct requests is whitelisted + */ + isDirectRequestWhitelisted: function XPI_isDirectRequestWhitelisted() { + // Default to whitelisted if the preference does not exist. + return Prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true); + }, + + /** + * Called to test whether installing XPI add-ons from file referrers is + * whitelisted. + * + * @return true if installing from file referrers is whitelisted + */ + isFileRequestWhitelisted: function XPI_isFileRequestWhitelisted() { + // Default to whitelisted if the preference does not exist. + return Prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true); + }, + /** * Called to test whether installing XPI add-ons from a URI is allowed. * @@ -3456,11 +3480,13 @@ var XPIProvider = { if (!this.isInstallEnabled()) return false; + // Direct requests without a referrer are either whitelisted or blocked. if (!aUri) - return true; + return this.isDirectRequestWhitelisted(); - // file: and chrome: don't need whitelisted hosts - if (aUri.schemeIs("chrome") || aUri.schemeIs("file")) + // Local referrers can be whitelisted. + if (this.isFileRequestWhitelisted() && + (aUri.schemeIs("chrome") || aUri.schemeIs("file"))) return true; this.importPermissions(); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser.ini b/toolkit/mozapps/extensions/test/xpinstall/browser.ini index 8afba225eb99..9cdb880c417f 100644 --- a/toolkit/mozapps/extensions/test/xpinstall/browser.ini +++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini @@ -62,6 +62,8 @@ support-files = [browser_installchrome.js] [browser_localfile.js] [browser_localfile2.js] +[browser_localfile3.js] +[browser_localfile4.js] [browser_multipackage.js] [browser_navigateaway.js] [browser_navigateaway2.js] @@ -84,3 +86,4 @@ support-files = [browser_whitelist4.js] [browser_whitelist5.js] [browser_whitelist6.js] +[browser_whitelist7.js] diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js new file mode 100644 index 000000000000..5bf07d29789c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------------------- +// Tests installing an add-on from a local file with whitelisting disabled. +// This should be blocked by the whitelist check. +function test() { + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Disable direct request whitelisting, installing from file should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false); + + var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] + .getService(Components.interfaces.nsIChromeRegistry); + + var chromeroot = extractChromeRoot(gTestPath); + try { + var xpipath = cr.convertChromeURL(makeURI(chromeroot + "unsigned.xpi")).spec; + } catch (ex) { + var xpipath = chromeroot + "unsigned.xpi"; //scenario where we are running from a .jar and already extracted + } + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(xpipath); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.prefs.clearUserPref("xpinstall.whitelist.directRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js new file mode 100644 index 000000000000..2e8263f19cba --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------- +// Tests installing an add-on from a local file with whitelisting disabled. +// This should be blocked by the whitelist check. +function test() { + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Disable file request whitelisting, installing by file referrer should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.fileRequest", false); + + var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] + .getService(Components.interfaces.nsIChromeRegistry); + + var chromeroot = extractChromeRoot(gTestPath); + try { + var xpipath = cr.convertChromeURL(makeURI(chromeroot)).spec; + } catch (ex) { + var xpipath = chromeroot; //scenario where we are running from a .jar and already extracted + } + var triggers = encodeURIComponent(JSON.stringify({ + "Unsigned XPI": TESTROOT + "unsigned.xpi" + })); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(xpipath + "installtrigger.html?" + triggers); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.prefs.clearUserPref("xpinstall.whitelist.fileRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js b/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js new file mode 100644 index 000000000000..26ae2202d907 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on through a direct install request from +// web content. This should be blocked by the whitelist check because we disable +// direct request whitelisting, even though the target URI is whitelisted. +function test() { + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Disable direct request whitelisting, installing should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(TESTROOT + "unsigned.xpi"); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.perms.remove("example.org", "install"); + Services.prefs.clearUserPref("xpinstall.whitelist.directRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} From 3d6441b768654ae6dca5532fb8f576d66c2560c1 Mon Sep 17 00:00:00 2001 From: Joel Maher Date: Thu, 20 Mar 2014 09:33:15 -0400 Subject: [PATCH 09/20] Bug 970240 - Disable browser_inspector_markup_edit_outerhtml.js on Linux debug. r=bgrins --- browser/devtools/markupview/test/browser.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/devtools/markupview/test/browser.ini b/browser/devtools/markupview/test/browser.ini index a790dd8ede19..57aca062ed8e 100644 --- a/browser/devtools/markupview/test/browser.ini +++ b/browser/devtools/markupview/test/browser.ini @@ -19,6 +19,7 @@ skip-if = true [browser_inspector_markup_edit_4.js] [browser_inspector_markup_add_attributes.js] [browser_inspector_markup_edit_outerhtml.js] +skip-if = os == 'linux' && debug # bug 970240 [browser_inspector_markup_edit_outerhtml2.js] [browser_inspector_markup_mutation.js] [browser_inspector_markup_mutation_flashing.js] From 027942980d7f85162d376d4d55668eb9a0463a2f Mon Sep 17 00:00:00 2001 From: Girish Sharma Date: Thu, 20 Mar 2014 11:45:42 +0530 Subject: [PATCH 10/20] Bug 984141 - Style editor should suggest valid CSS values without need of typing an initial character - exception fix. r=harth --- browser/devtools/sourceeditor/css-autocompleter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/devtools/sourceeditor/css-autocompleter.js b/browser/devtools/sourceeditor/css-autocompleter.js index 96ff3f76e858..d1a7a810b575 100644 --- a/browser/devtools/sourceeditor/css-autocompleter.js +++ b/browser/devtools/sourceeditor/css-autocompleter.js @@ -766,7 +766,7 @@ CSSCompleter.prototype = { completeProperties: function(startProp) { let finalList = []; if (!startProp) - return finalList; + return Promise.resolve(finalList); let length = propertyNames.length; let i = 0, count = 0; From a827f413c9f6340be43e12570b9a3b98c915dae7 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Thu, 20 Mar 2014 09:33:18 -0400 Subject: [PATCH 11/20] Bug 985555 - Prevent creating multiple markup view iframes when browsing in history. r=bgrins --- browser/devtools/inspector/inspector-panel.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js index 9c4fe10a78ca..bd0d5000f6be 100644 --- a/browser/devtools/inspector/inspector-panel.js +++ b/browser/devtools/inspector/inspector-panel.js @@ -318,7 +318,13 @@ InspectorPanel.prototype = { this._destroyMarkup(); this.isDirty = false; - this._getDefaultNodeForSelection().then(defaultNode => { + let onNodeSelected = defaultNode => { + // Cancel this promise resolution as a new one had + // been queued up. + if (this._pendingSelection != onNodeSelected) { + return; + } + this._pendingSelection = null; this.selection.setNodeFront(defaultNode, "navigateaway"); this._initMarkup(); @@ -330,7 +336,9 @@ InspectorPanel.prototype = { this.setupSearchBox(); this.emit("new-root"); }); - }); + }; + this._pendingSelection = onNodeSelected; + this._getDefaultNodeForSelection().then(onNodeSelected); }, _selectionCssSelector: null, From 3eccbde5593537e9d3e14d83bc5d68fdc83afada Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Wed, 19 Mar 2014 16:21:00 +1300 Subject: [PATCH 12/20] Bug 985729 - Construct the [[Prototype]] chain given to DBAddonInternal instances without using mutable __proto__. r=Unfocused --- .../extensions/internal/XPIProviderUtils.js | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index a7a4a8340249..f4db5911cc93 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -341,31 +341,36 @@ function DBAddonInternal(aLoaded) { }); } -DBAddonInternal.prototype = { - applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - this.targetApplications.forEach(function(aTargetApp) { - aUpdate.targetApplications.forEach(function(aUpdateTarget) { - if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || - Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { - aTargetApp.minVersion = aUpdateTarget.minVersion; - aTargetApp.maxVersion = aUpdateTarget.maxVersion; - XPIDatabase.saveChanges(); - } +function DBAddonInternalPrototype() +{ + this.applyCompatibilityUpdate = + function(aUpdate, aSyncCompatibility) { + this.targetApplications.forEach(function(aTargetApp) { + aUpdate.targetApplications.forEach(function(aUpdateTarget) { + if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || + Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { + aTargetApp.minVersion = aUpdateTarget.minVersion; + aTargetApp.maxVersion = aUpdateTarget.maxVersion; + XPIDatabase.saveChanges(); + } + }); }); - }); - XPIProvider.updateAddonDisabledState(this); - }, + XPIProvider.updateAddonDisabledState(this); + }; - get inDatabase() { - return true; - }, + this.toJSON = + function() { + return copyProperties(this, PROP_JSON_FIELDS); + }; - toJSON: function() { - return copyProperties(this, PROP_JSON_FIELDS); - } + Object.defineProperty(this, "inDatabase", + { get: function() { return true; }, + enumerable: true, + configurable: true }); } +DBAddonInternalPrototype.prototype = AddonInternal.prototype; -DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; +DBAddonInternal.prototype = new DBAddonInternalPrototype(); /** * Internal interface: find an addon from an already loaded addonDB From 8e3d7eee0b229a05b8c0068ecd012c4df59f8bf4 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Thu, 20 Mar 2014 15:15:42 +0000 Subject: [PATCH 13/20] Bug 974454 - Allow showing only description in panel items (r=margaret) --- mobile/android/base/home/PanelItemView.java | 23 +++++++++++-------- .../resources/layout/panel_article_item.xml | 2 ++ .../resources/layout/panel_image_item.xml | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/mobile/android/base/home/PanelItemView.java b/mobile/android/base/home/PanelItemView.java index 782894876674..0327a9a9e5e8 100644 --- a/mobile/android/base/home/PanelItemView.java +++ b/mobile/android/base/home/PanelItemView.java @@ -42,20 +42,23 @@ class PanelItemView extends LinearLayout { // Only show title if the item has one final boolean hasTitle = !TextUtils.isEmpty(title); - mTitleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE); + mTitle.setVisibility(hasTitle ? View.VISIBLE : View.GONE); if (hasTitle) { mTitle.setText(title); - - int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION); - final String description = cursor.getString(descriptionIndex); - - final boolean hasDescription = !TextUtils.isEmpty(description); - mDescription.setVisibility(hasDescription ? View.VISIBLE : View.GONE); - if (hasDescription) { - mDescription.setText(description); - } } + int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION); + final String description = cursor.getString(descriptionIndex); + + // Only show description if the item has one + final boolean hasDescription = !TextUtils.isEmpty(description); + mDescription.setVisibility(hasDescription ? View.VISIBLE : View.GONE); + if (hasDescription) { + mDescription.setText(description); + } + + mTitleDescContainer.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE); + int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL); final String imageUrl = cursor.getString(imageIndex); diff --git a/mobile/android/base/resources/layout/panel_article_item.xml b/mobile/android/base/resources/layout/panel_article_item.xml index cb915e682a55..0de9d925774e 100644 --- a/mobile/android/base/resources/layout/panel_article_item.xml +++ b/mobile/android/base/resources/layout/panel_article_item.xml @@ -9,6 +9,7 @@ android:layout_width="54dp" android:layout_height="44dp" android:layout_marginTop="10dip" + android:layout_marginBottom="10dip" android:layout_marginLeft="10dip" android:scaleType="centerCrop"/> @@ -34,6 +35,7 @@ android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="2" + android:gravity="center_vertical" android:maxLength="1024"/> diff --git a/mobile/android/base/resources/layout/panel_image_item.xml b/mobile/android/base/resources/layout/panel_image_item.xml index fb0824355008..3b8774d933c6 100644 --- a/mobile/android/base/resources/layout/panel_image_item.xml +++ b/mobile/android/base/resources/layout/panel_image_item.xml @@ -35,7 +35,7 @@ android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" - android:layout_marginTop="3dp" + android:gravity="center_vertical" android:singleLine="true" android:maxLength="1024"/> From d35ee16d639b78e4c92468eb4a65fff6c920f6a2 Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Thu, 20 Mar 2014 11:19:19 -0400 Subject: [PATCH 14/20] Bug 985089 - Allow QA to set a pref to control experiment sampling values, r=felipe --- browser/experiments/Experiments.jsm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm index 90bd9e7a0adf..52c5f5b7f5f4 100644 --- a/browser/experiments/Experiments.jsm +++ b/browser/experiments/Experiments.jsm @@ -52,6 +52,7 @@ const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump"; // experiments.logging const PREF_MANIFEST_URI = "manifest.uri"; // experiments.logging.manifest.uri const PREF_MANIFEST_CHECKCERT = "manifest.cert.checkAttributes"; // experiments.manifest.cert.checkAttributes const PREF_MANIFEST_REQUIREBUILTIN = "manifest.cert.requireBuiltin"; // experiments.manifest.cert.requireBuiltin +const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled"; @@ -164,6 +165,18 @@ Experiments.Policy.prototype = { }, random: function () { + let pref = gPrefs.get(PREF_FORCE_SAMPLE); + if (pref !== undefined) { + let val = Number.parseFloat(pref); + gLogger.debug("Experiments::Policy::random sample forced: " + val); + if (IsNaN(val) || val < 0) { + return 0; + } + if (val > 1) { + return 1; + } + return val; + } return Math.random(); }, From f8c4280718ec768efad9a6bded20c0fdd8439a39 Mon Sep 17 00:00:00 2001 From: Gregg Lind Date: Thu, 20 Mar 2014 11:19:19 -0400 Subject: [PATCH 15/20] Bug 981842 - Show details about current and past teleemtry experiments in about:support, r=bsmedberg --- toolkit/content/aboutSupport.js | 15 ++++++++++++++ toolkit/content/aboutSupport.xhtml | 33 ++++++++++++++++++++++++++++++ toolkit/modules/Troubleshoot.jsm | 19 +++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js index dae1e1c97849..f5a70cbc0422 100644 --- a/toolkit/content/aboutSupport.js +++ b/toolkit/content/aboutSupport.js @@ -111,6 +111,21 @@ let snapshotFormatters = { })); }, + experiments: function experiments(data) { + $.append($("experiments-tbody"), data.map(function (experiment) { + return $.new("tr", [ + $.new("td", experiment.name), + $.new("td", experiment.id), + $.new("td", experiment.description), + $.new("td", experiment.active), + $.new("td", experiment.endDate), + $.new("td", [ + $.new("a", experiment.detailURL, null, {href : experiment.detailURL,}) + ]), + ]); + })); + }, + modifiedPreferences: function modifiedPreferences(data) { $.append($("prefs-tbody"), sortedArrayFromObject(data).map( function ([name, value]) { diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml index 1844fb56ba2f..3fb1765af654 100644 --- a/toolkit/content/aboutSupport.xhtml +++ b/toolkit/content/aboutSupport.xhtml @@ -317,6 +317,39 @@ + +

+ &aboutSupport.experimentsTitle; +

+ + + + + + + + + + + + + + +
+ &aboutSupport.experimentName; + + &aboutSupport.experimentId; + + &aboutSupport.experimentDescription; + + &aboutSupport.experimentActive; + + &aboutSupport.experimentEndDate; + + &aboutSupport.experimentHomepage; +
+ + diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index 02387d343049..1d72f5f552d5 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -15,6 +15,13 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/CrashReports.jsm"); #endif +let Experiments; +try { + Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments; +} +catch (e) { +} + // We use a preferences whitelist to make sure we only show preferences that // are useful for support and won't compromise the user's privacy. Note that // entries are *prefixes*: for example, "accessibility." applies to all prefs @@ -174,6 +181,18 @@ let dataProviders = { }); }, + experiments: function experiments(done) { + if (Experiments === undefined) { + done([]); + return; + } + + // getExperiments promises experiment history + Experiments.instance().getExperiments().then( + experiments => done(experiments) + ); + }, + modifiedPreferences: function modifiedPreferences(done) { function getPref(name) { let table = {}; From 5d124d547a8f5d3cb2dd2d7d5ba7faf762987ee3 Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Thu, 20 Mar 2014 11:19:19 -0400 Subject: [PATCH 16/20] Bug 985682 - Experiment IDs aren't loaded correctly from the cache. Tests will be separate, r=gfritzsche --- browser/experiments/Experiments.jsm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm index 52c5f5b7f5f4..4e425dc6f6db 100644 --- a/browser/experiments/Experiments.jsm +++ b/browser/experiments/Experiments.jsm @@ -638,7 +638,7 @@ Experiments.Experiments.prototype = { if (!entry.initFromCacheData(item)) { continue; } - experiments.set(item.id, entry); + experiments.set(entry.id, entry); } this._experiments = experiments; @@ -679,7 +679,7 @@ Experiments.Experiments.prototype = { continue; } - experiments.set(data.id, entry); + experiments.set(entry.id, entry); } // Make sure we keep experiments that are or were running. From d59cc7bd3489e4186994bf46f0351bc180f0c597 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Thu, 20 Mar 2014 15:28:21 +0000 Subject: [PATCH 17/20] Bug 972098 - Factor out code to restart a dataset loader (r=margaret) --- mobile/android/base/home/DynamicPanel.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mobile/android/base/home/DynamicPanel.java b/mobile/android/base/home/DynamicPanel.java index 956afad8619a..5b26251613d2 100644 --- a/mobile/android/base/home/DynamicPanel.java +++ b/mobile/android/base/home/DynamicPanel.java @@ -156,6 +156,15 @@ public class DynamicPanel extends HomeFragment { return datasetId.hashCode(); } + private void restartDatasetLoader(DatasetRequest request) { + final Bundle bundle = new Bundle(); + bundle.putParcelable(DATASET_REQUEST, request); + + // Ensure one loader per dataset + final int loaderId = generateLoaderId(request.getDatasetId()); + getLoaderManager().restartLoader(loaderId, bundle, mLoaderCallbacks); + } + /** * Used by the PanelLayout to make load and reset requests to * the holding fragment. @@ -171,12 +180,7 @@ public class DynamicPanel extends HomeFragment { return; } - final Bundle bundle = new Bundle(); - bundle.putParcelable(DATASET_REQUEST, request); - - // Ensure one loader per dataset - final int loaderId = generateLoaderId(request.getDatasetId()); - getLoaderManager().restartLoader(loaderId, bundle, mLoaderCallbacks); + restartDatasetLoader(request); } @Override From 60cbf7f1884be47d72d55a5559e827370cf959a1 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Thu, 20 Mar 2014 15:28:22 +0000 Subject: [PATCH 18/20] Bug 972098 - Refresh DynamicPanels when the dataset changes (r=margaret) --- mobile/android/base/home/DynamicPanel.java | 62 +++++++++++++++++++++- mobile/android/modules/HomeProvider.jsm | 11 ++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/mobile/android/base/home/DynamicPanel.java b/mobile/android/base/home/DynamicPanel.java index 5b26251613d2..17164523c483 100644 --- a/mobile/android/base/home/DynamicPanel.java +++ b/mobile/android/base/home/DynamicPanel.java @@ -5,12 +5,18 @@ package org.mozilla.gecko.home; +import org.json.JSONException; +import org.json.JSONObject; + +import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.db.BrowserContract.HomeItems; import org.mozilla.gecko.db.DBUtils; import org.mozilla.gecko.home.HomeConfig.PanelConfig; import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; import org.mozilla.gecko.home.PanelLayout.DatasetHandler; import org.mozilla.gecko.home.PanelLayout.DatasetRequest; +import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.ThreadUtils; import android.app.Activity; import android.content.ContentResolver; @@ -45,7 +51,8 @@ import android.view.ViewGroup; * See {@code PanelLayout} for more details on how {@code DynamicPanel} * receives dataset requests and delivers them back to the {@code PanelLayout}. */ -public class DynamicPanel extends HomeFragment { +public class DynamicPanel extends HomeFragment + implements GeckoEventListener { private static final String LOGTAG = "GeckoDynamicPanel"; // Dataset ID to be used by the loader @@ -116,12 +123,15 @@ public class DynamicPanel extends HomeFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this); } @Override public void onDestroyView() { super.onDestroyView(); mLayout = null; + + GeckoAppShell.unregisterEventListener("HomePanels:RefreshDataset", this); } @Override @@ -152,10 +162,60 @@ public class DynamicPanel extends HomeFragment { mLayout.load(); } + @Override + public void handleMessage(String event, final JSONObject message) { + if (event.equals("HomePanels:RefreshDataset")) { + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + handleDatasetRefreshRequest(message); + } + }); + } + } + private static int generateLoaderId(String datasetId) { return datasetId.hashCode(); } + /** + * Handles a dataset refresh request from Gecko. This is usually + * triggered by a HomeStorage.save() call in an add-on. + */ + private void handleDatasetRefreshRequest(JSONObject message) { + final String datasetId; + try { + datasetId = message.getString("datasetId"); + } catch (JSONException e) { + Log.e(LOGTAG, "Failed to handle dataset refresh", e); + return; + } + + Log.d(LOGTAG, "Refresh request for dataset: " + datasetId); + + final int loaderId = generateLoaderId(datasetId); + + final LoaderManager lm = getLoaderManager(); + final Loader loader = (Loader) lm.getLoader(loaderId); + + // Only restart a loader if there's already an active one + // for the given dataset ID. Do nothing otherwise. + if (loader != null) { + final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader; + final DatasetRequest request = datasetLoader.getRequest(); + + // Ensure the refresh request doesn't affect the view's filter + // stack (i.e. use DATASET_LOAD type) but keep the current + // dataset ID and filter. + final DatasetRequest newRequest = + new DatasetRequest(DatasetRequest.Type.DATASET_LOAD, + request.getDatasetId(), + request.getFilterDetail()); + + restartDatasetLoader(newRequest); + } + } + private void restartDatasetLoader(DatasetRequest request) { final Bundle bundle = new Bundle(); bundle.putParcelable(DATASET_REQUEST, request); diff --git a/mobile/android/modules/HomeProvider.jsm b/mobile/android/modules/HomeProvider.jsm index f58c36ae3642..8364fab9bfe7 100644 --- a/mobile/android/modules/HomeProvider.jsm +++ b/mobile/android/modules/HomeProvider.jsm @@ -9,6 +9,7 @@ this.EXPORTED_SYMBOLS = [ "HomeProvider" ]; const { utils: Cu, classes: Cc, interfaces: Ci } = Components; +Cu.import("resource://gre/modules/Messaging.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -315,6 +316,11 @@ HomeStorage.prototype = { } finally { yield db.close(); } + + sendMessageToJava({ + type: "HomePanels:RefreshDataset", + datasetId: this.datasetId, + }); }.bind(this)); }, @@ -333,6 +339,11 @@ HomeStorage.prototype = { } finally { yield db.close(); } + + sendMessageToJava({ + type: "HomePanels:RefreshDataset", + datasetId: this.datasetId, + }); }.bind(this)); } }; From 5cdf6d897c8923b5eedfaf365b2f9c7d90d52159 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Thu, 20 Mar 2014 15:31:42 +0000 Subject: [PATCH 19/20] Bug 969043 - Log warning in save()/deleteAll() calls outside of sync window (r=margaret) --- mobile/android/modules/HomeProvider.jsm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mobile/android/modules/HomeProvider.jsm b/mobile/android/modules/HomeProvider.jsm index 8364fab9bfe7..fac442d79d88 100644 --- a/mobile/android/modules/HomeProvider.jsm +++ b/mobile/android/modules/HomeProvider.jsm @@ -89,6 +89,10 @@ var gTimerRegistered = false; // Map of datasetId -> { interval: , callback: } var gSyncCallbacks = {}; +// Whether or not writes to the provider are expected. +// e.g. save() and deleteAll() +var gWritesAreExpected = false; + /** * nsITimerCallback implementation. Checks to see if it's time to sync any registered datasets. * @@ -154,7 +158,10 @@ this.HomeProvider = Object.freeze({ return false; } + gWritesAreExpected = true; callback(datasetId); + gWritesAreExpected = false; + return true; }, @@ -292,6 +299,10 @@ HomeStorage.prototype = { * @resolves When the operation has completed. */ save: function(data) { + if (!gWritesAreExpected) { + Cu.reportError("HomeStorage: save() called outside of sync window"); + } + return Task.spawn(function save_task() { let db = yield getDatabaseConnection(); try { @@ -331,6 +342,10 @@ HomeStorage.prototype = { * @resolves When the operation has completed. */ deleteAll: function() { + if (!gWritesAreExpected) { + Cu.reportError("HomeStorage: deleteAll() called outside of sync window"); + } + return Task.spawn(function delete_all_task() { let db = yield getDatabaseConnection(); try { From b95d18d5eb9eb94fdb1b5db5e584a72801033bea Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Thu, 20 Mar 2014 13:50:05 -0400 Subject: [PATCH 20/20] Fix the test for bug 981842 to expect the new data on a CLOSED TREE. --- toolkit/modules/tests/browser/browser_Troubleshoot.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/toolkit/modules/tests/browser/browser_Troubleshoot.js b/toolkit/modules/tests/browser/browser_Troubleshoot.js index b688fcd8b5ea..c8260f3ea98d 100644 --- a/toolkit/modules/tests/browser/browser_Troubleshoot.js +++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js @@ -366,6 +366,9 @@ const SNAPSHOT_SCHEMA = { }, }, }, + experiments: { + type: "array", + }, }, };