From 812a782093d26aa42e7d0ba95de041a0102a965e Mon Sep 17 00:00:00 2001 From: Edouard Oger Date: Thu, 9 Mar 2017 15:53:08 -0500 Subject: [PATCH] Bug 1269277 - Add Manage Device button in Synced Tabs menu/sidebar. r=markh MozReview-Commit-ID: 2BMgLeI0DNF --HG-- extra : rebase_source : e5ace2f6dfd828c14edfd3f3d84da2b5043361b2 --- browser/base/content/browser-fxaccounts.js | 7 ++ browser/base/content/browser.xul | 4 + .../customizableui/content/panelUI.inc.xul | 4 + browser/components/nsBrowserGlue.js | 14 ++- browser/components/syncedtabs/TabListView.js | 5 +- .../browser/browser_sidebar_syncedtabslist.js | 5 +- .../locales/en-US/chrome/browser/browser.dtd | 3 + services/fxaccounts/FxAccounts.jsm | 109 ++++++++---------- .../tests/browser/browser_device_connected.js | 18 +-- 9 files changed, 96 insertions(+), 73 deletions(-) diff --git a/browser/base/content/browser-fxaccounts.js b/browser/base/content/browser-fxaccounts.js index 7c6b16ab5456..f3e48aa51ae2 100644 --- a/browser/base/content/browser-fxaccounts.js +++ b/browser/base/content/browser-fxaccounts.js @@ -323,6 +323,13 @@ var gFxAccounts = { this.openAccountsPage("reauth", { entrypoint: entryPoint }); }, + async openDevicesManagementPage(entryPoint) { + let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint); + switchToTabHavingURI(url, true, { + replaceQueryString: true + }); + }, + sendTabToDevice(url, clientId, title) { Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title); }, diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 081e566075e0..c09f2d5da768 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -478,6 +478,10 @@ + diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul index 9753a4f52cab..04ba6abfa837 100644 --- a/browser/components/customizableui/content/panelUI.inc.xul +++ b/browser/components/customizableui/content/panelUI.inc.xul @@ -114,6 +114,10 @@ class="subviewbutton" observes="viewTabsSidebar" label="&appMenuRemoteTabs.sidebar.label;"/> + { Services.obs.notifyObservers(null, "test-smart-bookmarks-done", null); }); + } else if (data == "mock-fxaccounts") { + Object.defineProperty(this, "fxAccounts", { + value: subject.wrappedJSObject + }); } break; case "initial-migration-will-import-default-bookmarks": @@ -2302,21 +2308,21 @@ BrowserGlue.prototype = { let body = accountsBundle.formatStringFromName("deviceConnectedBody" + (deviceName ? "" : ".noDeviceName"), [deviceName], 1); - let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.devices.uri"); - function clickCallback(subject, topic, data) { + let clickCallback = async (subject, topic, data) => { if (topic != "alertclickcallback") return; + let url = await this.fxAccounts.promiseAccountsManageDevicesURI("device-connected-notification"); let win = RecentWindow.getMostRecentBrowserWindow({private: false}); if (!win) { Services.appShell.hiddenDOMWindow.open(url); } else { win.gBrowser.addTab(url); } - } + }; try { - AlertsService.showAlertNotification(null, title, body, true, url, clickCallback); + AlertsService.showAlertNotification(null, title, body, true, null, clickCallback); } catch (ex) { Cu.reportError("Error notifying of a new Sync device: " + ex); } diff --git a/browser/components/syncedtabs/TabListView.js b/browser/components/syncedtabs/TabListView.js index fc68cd73dea1..e87a2c535c2e 100644 --- a/browser/components/syncedtabs/TabListView.js +++ b/browser/components/syncedtabs/TabListView.js @@ -524,7 +524,8 @@ TabListView.prototype = { while (el) { let show = false; if (showTabOptions) { - if (el.getAttribute("id") != "syncedTabsOpenAllInTabs") { + if (el.getAttribute("id") != "syncedTabsOpenAllInTabs" && + el.getAttribute("id") != "syncedTabsManageDevices") { show = true; } } else if (el.getAttribute("id") == "syncedTabsOpenAllInTabs") { @@ -532,6 +533,8 @@ TabListView.prototype = { show = tabs.length > 0; } else if (el.getAttribute("id") == "syncedTabsRefresh") { show = true; + } else if (el.getAttribute("id") == "syncedTabsManageDevices") { + show = true; } el.hidden = !show; diff --git a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js index 3677f08ac311..c42de0989d32 100644 --- a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js +++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js @@ -310,6 +310,7 @@ add_task(function* testSyncedTabsSidebarContextMenu() { ["menuitem#syncedTabsCopySelected", { hidden: false }], ["menuseparator", { hidden: false }], ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }], + ["menuitem#syncedTabsManageDevices", { hidden: true }], ["menuitem#syncedTabsRefresh", { hidden: false }], ]; yield* testContextMenu(syncedTabsDeckComponent, @@ -317,7 +318,7 @@ add_task(function* testSyncedTabsSidebarContextMenu() { "#tab-7cqCr77ptzX3-0", tabMenuItems); - info("Right-clicking a client should show the Open All in Tabs action"); + info("Right-clicking a client should show the Open All in Tabs and Manage devices actions"); let sidebarMenuItems = [ ["menuitem#syncedTabsOpenSelected", { hidden: true }], ["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }], @@ -328,6 +329,7 @@ add_task(function* testSyncedTabsSidebarContextMenu() { ["menuitem#syncedTabsCopySelected", { hidden: true }], ["menuseparator", { hidden: true }], ["menuitem#syncedTabsOpenAllInTabs", { hidden: false }], + ["menuitem#syncedTabsManageDevices", { hidden: false }], ["menuitem#syncedTabsRefresh", { hidden: false }], ]; yield* testContextMenu(syncedTabsDeckComponent, @@ -346,6 +348,7 @@ add_task(function* testSyncedTabsSidebarContextMenu() { ["menuitem#syncedTabsCopySelected", { hidden: true }], ["menuseparator", { hidden: true }], ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }], + ["menuitem#syncedTabsManageDevices", { hidden: false }], ["menuitem#syncedTabsRefresh", { hidden: false }], ]; yield* testContextMenu(syncedTabsDeckComponent, diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index d3056804e967..a3fe0967c1e8 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -377,6 +377,7 @@ These should match what Safari and other Apple applications use on OS X Lion. -- + @@ -785,6 +786,8 @@ you can use these alternative items. Otherwise, their values should be empty. - + + diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index 1beccc190dd7..e719261dfcf8 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -7,6 +7,8 @@ this.EXPORTED_SYMBOLS = ["fxAccounts", "FxAccounts"]; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +Cu.importGlobalProperties(["URL"]); + Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://services-common/utils.js"); @@ -59,6 +61,7 @@ var publicProperties = [ "promiseAccountsChangeProfileURI", "promiseAccountsForceSigninURI", "promiseAccountsManageURI", + "promiseAccountsManageDevicesURI", "promiseAccountsSignUpURI", "promiseAccountsSignInURI", "removeCachedOAuthToken", @@ -1263,80 +1266,66 @@ FxAccountsInternal.prototype = { return FxAccountsConfig.promiseAccountsSignInURI(); }, - // Returns a promise that resolves with the URL to use to force a re-signin - // of the current account. - promiseAccountsForceSigninURI: Task.async(function *() { - yield FxAccountsConfig.ensureConfigured(); - let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.force_auth.uri"); - if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting + /** + * Pull an URL defined in the user preferences, add the current UID and email + * to the query string, add entrypoint and extra params to the query string if + * requested. + * @param {string} prefName The preference name from where to pull the URL to format. + * @param {string} [entrypoint] "entrypoint" searchParam value. + * @param {Object.} [extraParams] Additionnal searchParam key and values. + * @returns {Promise.} A promise that resolves to the formatted URL + */ + async _formatPrefURL(prefName, entrypoint, extraParams) { + let url = new URL(Services.urlFormatter.formatURLPref(prefName)); + if (this.requiresHttps() && url.protocol != "https:") { throw new Error("Firefox Accounts server must use HTTPS"); } - let currentState = this.currentAccountState; - // but we need to append the email address onto a query string. - return this.getSignedInUser().then(accountData => { - if (!accountData) { - return null; + let accountData = await this.getSignedInUser(); + if (!accountData) { + return Promise.resolve(null); + } + url.searchParams.append("uid", accountData.uid); + url.searchParams.append("email", accountData.email); + if (entrypoint) { + url.searchParams.append("entrypoint", entrypoint); + } + if (extraParams) { + for (let [k, v] of Object.entries(extraParams)) { + url.searchParams.append(k, v); } - let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&"; - newQueryPortion += "email=" + encodeURIComponent(accountData.email); - return url + newQueryPortion; - }).then(result => currentState.resolve(result)); - }), + } + return this.currentAccountState.resolve(url.href); + }, + + // Returns a promise that resolves with the URL to use to force a re-signin + // of the current account. + async promiseAccountsForceSigninURI() { + await FxAccountsConfig.ensureConfigured(); + return this._formatPrefURL("identity.fxaccounts.remote.force_auth.uri"); + }, // Returns a promise that resolves with the URL to use to change // the current account's profile image. // if settingToEdit is set, the profile page should hightlight that setting // for the user to edit. - promiseAccountsChangeProfileURI(entrypoint, settingToEdit = null) { - let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri"); - + async promiseAccountsChangeProfileURI(entrypoint, settingToEdit = null) { + let extraParams; if (settingToEdit) { - url += (url.indexOf("?") == -1 ? "?" : "&") + - "setting=" + encodeURIComponent(settingToEdit); + extraParams = { setting: settingToEdit }; } - - if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting - throw new Error("Firefox Accounts server must use HTTPS"); - } - let currentState = this.currentAccountState; - // but we need to append the email address onto a query string. - return this.getSignedInUser().then(accountData => { - if (!accountData) { - return null; - } - let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&"; - newQueryPortion += "email=" + encodeURIComponent(accountData.email); - newQueryPortion += "&uid=" + encodeURIComponent(accountData.uid); - if (entrypoint) { - newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint); - } - return url + newQueryPortion; - }).then(result => currentState.resolve(result)); + return this._formatPrefURL("identity.fxaccounts.settings.uri", entrypoint, extraParams); }, // Returns a promise that resolves with the URL to use to manage the current // user's FxA acct. - promiseAccountsManageURI(entrypoint) { - let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri"); - if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting - throw new Error("Firefox Accounts server must use HTTPS"); - } - let currentState = this.currentAccountState; - // but we need to append the uid and email address onto a query string - // (if the server has no matching uid it will offer to sign in with the - // email address) - return this.getSignedInUser().then(accountData => { - if (!accountData) { - return null; - } - let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&"; - newQueryPortion += "uid=" + encodeURIComponent(accountData.uid) + - "&email=" + encodeURIComponent(accountData.email); - if (entrypoint) { - newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint); - } - return url + newQueryPortion; - }).then(result => currentState.resolve(result)); + async promiseAccountsManageURI(entrypoint) { + return this._formatPrefURL("identity.fxaccounts.settings.uri", entrypoint); + }, + + // Returns a promise that resolves with the URL to use to manage the devices in + // the current user's FxA acct. + async promiseAccountsManageDevicesURI(entrypoint) { + return this._formatPrefURL("identity.fxaccounts.settings.devices.uri", entrypoint); }, /** diff --git a/services/fxaccounts/tests/browser/browser_device_connected.js b/services/fxaccounts/tests/browser/browser_device_connected.js index 42f66c51aa98..c378d4004f53 100644 --- a/services/fxaccounts/tests/browser/browser_device_connected.js +++ b/services/fxaccounts/tests/browser/browser_device_connected.js @@ -5,14 +5,22 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); const { MockRegistrar } = Cu.import("resource://testing-common/MockRegistrar.jsm", {}); - -let accountsBundle = Services.strings.createBundle( +const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"] + .getService(Ci.nsIObserver); +const accountsBundle = Services.strings.createBundle( "chrome://browser/locale/accounts.properties" ); +const DEVICES_URL = "http://localhost/devices"; let expectedBody; add_task(async function setup() { + let fxAccounts = { + promiseAccountsManageDevicesURI() { + return Promise.resolve(DEVICES_URL); + } + }; + gBrowserGlue.observe({wrappedJSObject: fxAccounts}, "browser-glue-test", "mock-fxaccounts"); const alertsService = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService, Ci.nsISupports]), showAlertNotification: (image, title, text, clickable, cookie, clickCallback) => { @@ -33,8 +41,6 @@ async function testDeviceConnected(deviceName) { gBrowser.selectedBrowser.loadURI("about:robots"); await waitForDocLoadComplete(); - Preferences.set("identity.fxaccounts.settings.devices.uri", "http://localhost/devices"); - let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); Services.obs.notifyObservers(null, "fxaccounts:device_connected", deviceName); @@ -42,9 +48,7 @@ async function testDeviceConnected(deviceName) { let tab = await waitForTabPromise; Assert.ok("Tab successfully opened"); - let expectedURI = Preferences.get("identity.fxaccounts.settings.devices.uri", - "prefundefined"); - Assert.equal(tab.linkedBrowser.currentURI.spec, expectedURI); + Assert.equal(tab.linkedBrowser.currentURI.spec, DEVICES_URL); await BrowserTestUtils.removeTab(tab); }