--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1430,16 +1430,18 @@ pref("identity.fxaccounts.settings.devic
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
// The remote URL of the FxA OAuth Server
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
// Token server used by the FxA Sync identity.
pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
+// The URL to a page that explains how to connect another device to Sync.
+pref("identity.fxaccounts.remote.connectdevice.uri", "https://accounts.firefox.com/connect_another_device?service=sync&context=fx_desktop_v3");
// URLs for promo links to mobile browsers. Note that consumers are expected to
// append a value for utm_campaign.
pref("identity.mobilepromo.android", "https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=");
pref("identity.mobilepromo.ios", "https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=");
// Migrate any existing Firefox Account data from the default profile to the
// Developer Edition profile.
#ifdef MOZ_DEV_EDITION
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -269,16 +269,22 @@ var gSync = {
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
},
+ openConnectAnotherDevice(entryPoint) {
+ let url = new URL(Services.prefs.getCharPref("identity.fxaccounts.remote.connectdevice.uri"));
+ url.searchParams.append("entrypoint", entryPoint);
+ openUILinkIn(url.href, "tab");
+ },
+
openSendToDevicePromo() {
let url = Services.prefs.getCharPref("app.productInfo.baseURL");
url += "send-tabs/?utm_source=" + Services.appinfo.name.toLowerCase();
switchToTabHavingURI(url, true, { replaceQueryString: true });
},
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title).catch(e => {
@@ -358,67 +364,68 @@ var gSync = {
const allDevicesLabel = this.fxaStrings.GetStringFromName("sendToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel, "");
}
},
_appendSendTabSingleDevice(fragment, createDeviceNodeFn) {
const noDevices = this.fxaStrings.GetStringFromName("sendTabToDevice.singledevice.status");
const learnMore = this.fxaStrings.GetStringFromName("sendTabToDevice.singledevice");
- this._appendSendTabInfoItems(fragment, createDeviceNodeFn, noDevices, learnMore, () => {
- this.openSendToDevicePromo();
- });
+ const connectDevice = this.fxaStrings.GetStringFromName("sendTabToDevice.connectdevice");
+ const actions = [{label: connectDevice, command: () => this.openConnectAnotherDevice("sendtab")},
+ {label: learnMore, command: () => this.openSendToDevicePromo()}];
+ this._appendSendTabInfoItems(fragment, createDeviceNodeFn, noDevices, actions);
},
_appendSendTabVerify(fragment, createDeviceNodeFn) {
const notVerified = this.fxaStrings.GetStringFromName("sendTabToDevice.verify.status");
const verifyAccount = this.fxaStrings.GetStringFromName("sendTabToDevice.verify");
- this._appendSendTabInfoItems(fragment, createDeviceNodeFn, notVerified, verifyAccount, () => {
- this.openPrefs("sendtab");
- });
+ const actions = [{label: verifyAccount, command: () => this.openPrefs("sendtab")}];
+ this._appendSendTabInfoItems(fragment, createDeviceNodeFn, notVerified, actions);
},
_appendSendTabUnconfigured(fragment, createDeviceNodeFn) {
const notConnected = this.fxaStrings.GetStringFromName("sendTabToDevice.unconfigured.status");
const learnMore = this.fxaStrings.GetStringFromName("sendTabToDevice.unconfigured");
- this._appendSendTabInfoItems(fragment, createDeviceNodeFn, notConnected, learnMore, () => {
- this.openSendToDevicePromo();
- });
+ const actions = [{label: learnMore, command: () => this.openSendToDevicePromo()}];
+ this._appendSendTabInfoItems(fragment, createDeviceNodeFn, notConnected, actions);
// Now add a 'sign in to sync' item above the 'learn more' item.
const signInToSync = this.fxaStrings.GetStringFromName("sendTabToDevice.signintosync");
let signInItem = createDeviceNodeFn(null, signInToSync, null);
signInItem.classList.add("sync-menuitem");
signInItem.setAttribute("label", signInToSync);
// Show an icon if opened in the page action panel:
if (signInItem.classList.contains("subviewbutton")) {
signInItem.classList.add("subviewbutton-iconic", "signintosync");
}
signInItem.addEventListener("command", () => {
this.openPrefs("sendtab");
});
fragment.insertBefore(signInItem, fragment.lastChild);
},
- _appendSendTabInfoItems(fragment, createDeviceNodeFn, statusLabel, actionLabel, actionCommand) {
+ _appendSendTabInfoItems(fragment, createDeviceNodeFn, statusLabel, actions) {
const status = createDeviceNodeFn(null, statusLabel, null);
status.setAttribute("label", statusLabel);
status.setAttribute("disabled", true);
status.classList.add("sync-menuitem");
fragment.appendChild(status);
const separator = createDeviceNodeFn(null, null, null);
separator.classList.add("sync-menuitem");
fragment.appendChild(separator);
- const actionItem = createDeviceNodeFn(null, actionLabel, null);
- actionItem.addEventListener("command", actionCommand, true);
- actionItem.classList.add("sync-menuitem");
- actionItem.setAttribute("label", actionLabel);
- fragment.appendChild(actionItem);
+ for (let {label, command} of actions) {
+ const actionItem = createDeviceNodeFn(null, label, null);
+ actionItem.addEventListener("command", command, true);
+ actionItem.classList.add("sync-menuitem");
+ actionItem.setAttribute("label", label);
+ fragment.appendChild(actionItem);
+ }
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
--- a/browser/base/content/test/sync/browser_contextmenu_sendpage.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendpage.js
@@ -33,16 +33,17 @@ add_task(async function test_page_contex
state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
await openContentContextMenu("#moztext", "context-sendpagetodevice");
is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
checkPopup([
{ label: "No Devices Connected", disabled: true },
"----",
+ { label: "Connect Another Device..." },
{ label: "Learn About Sending Tabs..." }
]);
await hideContentContextMenu();
sandbox.restore();
});
add_task(async function test_page_contextmenu_sendtab_one_remote_client() {
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -433,16 +433,21 @@ add_task(async function sendToDevice_noD
attrs: {
label: "No Devices Connected",
},
disabled: true
},
null,
{
attrs: {
+ label: "Connect Another Device..."
+ }
+ },
+ {
+ attrs: {
label: "Learn About Sending Tabs..."
}
}
];
checkSendToDeviceItems(expectedItems);
// Done, hide the panel.
let hiddenPromise = promisePageActionPanelHidden();
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -262,51 +262,16 @@ const CustomizableWidgets = [
// (Note the observer sets many attributes, including label and
// tooltiptext, but we only want the 'syncstatus' attribute for the
// animation)
let doc = aNode.ownerDocument;
let obnode = doc.createElementNS(kNSXUL, "observes");
obnode.setAttribute("element", "sync-status");
obnode.setAttribute("attribute", "syncstatus");
aNode.appendChild(obnode);
-
- // A somewhat complicated dance to format the mobilepromo label.
- let bundle = doc.getElementById("bundle_browser");
- let formatArgs = ["android", "ios"].map(os => {
- let link = doc.createElement("label");
- link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
- link.setAttribute("mobile-promo-os", os);
- link.className = "text-link remotetabs-promo-link";
- return link.outerHTML;
- });
- let promoParentElt = doc.getElementById("PanelUI-remotetabs-mobile-promo");
- // Put it all together...
- let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
- // eslint-disable-next-line no-unsanitized/property
- promoParentElt.innerHTML = contents;
- // We manually manage the "click" event to open the promo links because
- // allowing the "text-link" widget handle it has 2 problems: (1) it only
- // supports button 0 and (2) it's tricky to intercept when it does the
- // open and auto-close the panel. (1) can probably be fixed, but (2) is
- // trickier without hard-coding here the knowledge of exactly what buttons
- // it does support.
- // So we allow left and middle clicks to open the link in a new tab and
- // close the panel; not setting a "href" attribute prevents the text-link
- // widget handling it, and we build the final URL in the click handler to
- // make testing easier (ie, so tests can change the pref after the links
- // were created and have the new pref value used.)
- promoParentElt.addEventListener("click", e => {
- let os = e.target.getAttribute("mobile-promo-os");
- if (!os || e.button > 1) {
- return;
- }
- let link = Services.prefs.getCharPref(`identity.mobilepromo.${os}`) + "synced-tabs";
- doc.defaultView.openUILinkIn(link, "tab");
- CustomizableUI.hidePanelForNode(e.target);
- });
this._initialized = true;
},
onViewShowing(aEvent) {
this._initialize(aEvent.target);
let doc = aEvent.target.ownerDocument;
this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist");
Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED);
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -433,64 +433,66 @@
<!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
<hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box" align="center">
<hbox pack="center">
<image class="fxaSyncIllustrationIssue"/>
</hbox>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
<hbox pack="center">
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ <toolbarbutton class="PanelUI-remotetabs-button"
label="&appMenuRemoteTabs.openprefs.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
</hbox>
</vbox>
</hbox>
<!-- Sync is ready to Sync but we are still fetching the tabs to show -->
<vbox id="PanelUI-remotetabs-fetching">
<!-- Show intentionally blank panel, see bug 1239845 -->
</vbox>
<!-- Sync has only 1 (ie, this) device connected -->
<hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box">
<hbox pack="center">
<image class="fxaSyncIllustrationIssue"/>
</hbox>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
- <!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
- <label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
+ <toolbarbutton id="PanelUI-remotetabs-connect-device-button"
+ class="PanelUI-remotetabs-button"
+ label="&appMenuRemoteTabs.connectdevice.label;"
+ oncommand="gSync.openConnectAnotherDevice('synced-tabs');"/>
</vbox>
</hbox>
</deck>
</vbox>
<!-- a box to ensure contained boxes are centered horizonally -->
<hbox pack="center" flex="1">
<!-- When Sync is not configured -->
<vbox id="PanelUI-remotetabs-setupsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-setup-state">
<image class="fxaSyncIllustration"/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ <toolbarbutton class="PanelUI-remotetabs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
</vbox>
<!-- When Sync needs re-authentication. This uses the exact same messaging
as "Sync is not configured" but remains a separate box so we get
the goodness of observing broadcasters to manage the hidden states -->
<vbox id="PanelUI-remotetabs-reauthsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-reauth-state">
<image class="fxaSyncIllustrationIssue"/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ <toolbarbutton class="PanelUI-remotetabs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
</vbox>
</hbox>
</vbox>
</panelview>
<panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
--- a/browser/components/customizableui/test/browser_synced_tabs_menu.js
+++ b/browser/components/customizableui/test/browser_synced_tabs_menu.js
@@ -82,17 +82,17 @@ async function openPrefsFromMenuPanel(ex
await Promise.all([tabsUpdatedPromise, viewShownPromise]);
ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
// Sync is not configured - verify that state is reflected.
let subpanel = document.getElementById(expectedPanelId);
ok(!subpanel.hidden, "sync setup element is visible");
// Find and click the "setup" button.
- let setupButton = subpanel.querySelector(".PanelUI-remotetabs-prefs-button");
+ let setupButton = subpanel.querySelector(".PanelUI-remotetabs-button");
setupButton.click();
await new Promise(resolve => {
let handler = async (e) => {
if (e.originalTarget != gBrowser.selectedBrowser.contentDocument ||
e.target.location.href == "about:blank") {
info("Skipping spurious 'load' event for " + e.target.location.href);
return;
@@ -140,69 +140,48 @@ add_task(async function() {
add_task(asyncCleanup);
// When Sync is configured in a "needs reauthentication" state.
add_task(async function() {
gSync.updateAllUI({ status: UIState.STATUS_LOGIN_FAILED, email: "foo@bar.com" });
await openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "synced-tabs");
});
-// Test the mobile promo links
+// Test the Connect Another Device button
add_task(async function() {
- // change the preferences for the mobile links.
- Services.prefs.setCharPref("identity.mobilepromo.android", "http://example.com/?os=android&tail=");
- Services.prefs.setCharPref("identity.mobilepromo.ios", "http://example.com/?os=ios&tail=");
+ Services.prefs.setCharPref("identity.fxaccounts.remote.connectdevice.uri", "http://example.com/connectdevice");
gSync.updateAllUI({ status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" });
- let syncPanel = document.getElementById("PanelUI-remotetabs");
- let links = syncPanel.querySelectorAll(".remotetabs-promo-link");
-
- is(links.length, 2, "found 2 links as expected");
+ let button = document.getElementById("PanelUI-remotetabs-connect-device-button");
+ ok(button, "found the button");
- // test each link and left and middle mouse buttons
- for (let link of links) {
- for (let button = 0; button < 2; button++) {
- await document.getElementById("nav-bar").overflowable.show();
- EventUtils.sendMouseEvent({ type: "click", button }, link, window);
- // the panel should have been closed.
- ok(!isOverflowOpen(), "click closed the panel");
- // should be a new tab - wait for the load.
- is(gBrowser.tabs.length, 2, "there's a new tab");
- await new Promise(resolve => {
- if (gBrowser.selectedBrowser.currentURI.spec == "about:blank") {
- gBrowser.selectedBrowser.addEventListener("load", function(e) {
- resolve();
- }, {capture: true, once: true});
- return;
- }
- // the new tab has already transitioned away from about:blank so we
- // are good to go.
+ await document.getElementById("nav-bar").overflowable.show();
+ button.click();
+ // the panel should have been closed.
+ ok(!isOverflowOpen(), "click closed the panel");
+ // should be a new tab - wait for the load.
+ is(gBrowser.tabs.length, 2, "there's a new tab");
+ await new Promise(resolve => {
+ if (gBrowser.selectedBrowser.currentURI.spec == "about:blank") {
+ gBrowser.selectedBrowser.addEventListener("load", function(e) {
resolve();
- });
-
- let os = link.getAttribute("mobile-promo-os");
- let expectedUrl = `http://example.com/?os=${os}&tail=synced-tabs`;
- is(gBrowser.selectedBrowser.currentURI.spec, expectedUrl, "correct URL");
- gBrowser.removeTab(gBrowser.selectedTab);
+ }, {capture: true, once: true});
+ return;
}
- }
+ // the new tab has already transitioned away from about:blank so we
+ // are good to go.
+ resolve();
+ });
- // test each link and right mouse button - should be a noop.
- await document.getElementById("nav-bar").overflowable.show();
- for (let link of links) {
- EventUtils.sendMouseEvent({ type: "click", button: 2 }, link, window);
- // the panel should still be open
- ok(isOverflowOpen(), "panel remains open after right-click");
- is(gBrowser.tabs.length, 1, "no new tab was opened");
- }
- await hideOverflow();
+ let expectedUrl = `http://example.com/connectdevice?entrypoint=synced-tabs`;
+ is(gBrowser.selectedBrowser.currentURI.spec, expectedUrl, "correct URL");
+ gBrowser.removeTab(gBrowser.selectedTab);
- Services.prefs.clearUserPref("identity.mobilepromo.android");
- Services.prefs.clearUserPref("identity.mobilepromo.ios");
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.connectdevice.uri");
});
// Test the "Sync Now" button
add_task(async function() {
gSync.updateAllUI({ status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" });
await document.getElementById("nav-bar").overflowable.show();
let tabsUpdatedPromise = promiseObserverNotified("synced-tabs-menu:test:tabs-updated");
--- a/browser/components/syncedtabs/SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -74,18 +74,17 @@ SyncedTabsDeckComponent.prototype = {
Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
Services.obs.addObserver(this, "weave:service:login:change");
// Go ahead and trigger sync
this._SyncedTabs.syncTabs()
.catch(Cu.reportError);
this._deckView = new this._DeckView(this._window, this.tabListComponent, {
- onAndroidClick: event => this.openAndroidLink(event),
- oniOSClick: event => this.openiOSLink(event),
+ onConnectDeviceClick: event => this.openConnectDevice(event),
onSyncPrefClick: event => this.openSyncPrefs(event)
});
this._deckStore.on("change", state => this._deckView.render(state));
// Trigger the initial rendering of the deck view
// Object.values only in nightly
this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k]));
// Set the initial panel to display
@@ -146,27 +145,17 @@ SyncedTabsDeckComponent.prototype = {
updatePanel() {
// return promise for tests
return this.getPanelStatus()
.then(panelId => this._deckStore.selectPanel(panelId))
.catch(Cu.reportError);
},
- openAndroidLink(event) {
- let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar";
- this._openUrl(href, event);
- },
-
- openiOSLink(event) {
- let href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar";
- this._openUrl(href, event);
+ openSyncPrefs() {
+ this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar");
},
- _openUrl(url, event) {
- this._window.openUILink(url, event);
+ openConnectDevice() {
+ this._getChromeWindow(this._window).gSync.openConnectAnotherDevice("tabs-sidebar");
},
-
- openSyncPrefs() {
- this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar");
- }
};
--- a/browser/components/syncedtabs/SyncedTabsDeckView.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckView.js
@@ -1,18 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
-
let log = Cu.import("resource://gre/modules/Log.jsm", {})
.Log.repository.getLogger("Sync.RemoteTabs");
this.EXPORTED_SYMBOLS = [
"SyncedTabsDeckView"
];
/**
@@ -49,41 +47,20 @@ SyncedTabsDeckView.prototype = {
let tabListWrapper = this._doc.createElement("div");
tabListWrapper.className = "tabs-container sync-state";
this._tabListComponent.init();
tabListWrapper.appendChild(this._tabListComponent.container);
deck.appendChild(tabListWrapper);
this.container.appendChild(deck);
- this._generateDevicePromo();
-
this._attachListeners();
this.update(state);
},
- _getBrowserBundle() {
- return getChromeWindow(this._window).document.getElementById("bundle_browser");
- },
-
- _generateDevicePromo() {
- let bundle = this._getBrowserBundle();
- let formatArgs = ["android", "ios"].map(os => {
- let link = this._doc.createElement("a");
- link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
- link.className = `${os}-link text-link`;
- link.setAttribute("href", "#");
- return link.outerHTML;
- });
- // Put it all together...
- let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
- // eslint-disable-next-line no-unsanitized/property
- this.container.querySelector(".device-promo").innerHTML = contents;
- },
-
destroy() {
this._tabListComponent.uninit();
this.container.remove();
},
update(state) {
// Note that we may also want to update elements that are outside of the
// deck, so use the document to find the class names rather than our
@@ -101,17 +78,16 @@ SyncedTabsDeckView.prototype = {
_clearChilden() {
while (this.container.firstChild) {
this.container.firstChild.remove();
}
},
_attachListeners() {
- this.container.querySelector(".android-link").addEventListener("click", this.props.onAndroidClick);
- this.container.querySelector(".ios-link").addEventListener("click", this.props.oniOSClick);
let syncPrefLinks = this.container.querySelectorAll(".sync-prefs");
for (let link of syncPrefLinks) {
link.addEventListener("click", this.props.onSyncPrefClick);
}
+ this.container.querySelector(".connect-device").addEventListener("click", this.props.onConnectDeviceClick);
},
};
--- a/browser/components/syncedtabs/sidebar.xhtml
+++ b/browser/components/syncedtabs/sidebar.xhtml
@@ -78,17 +78,17 @@
<div class="notAuthedInfo sync-state">
<div class="syncIllustration"></div>
<p class="instructions">&syncedTabs.sidebar.notsignedin.label;</p>
<button class="button sync-prefs">&fxaSignIn.label;</button>
</div>
<div class="singleDeviceInfo sync-state">
<div class="syncIllustrationIssue"></div>
<p class="instructions">&syncedTabs.sidebar.noclients.subtitle;</p>
- <p class="instructions device-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"></p>
+ <button class="button connect-device">&syncedTabs.sidebar.connectAnotherDevice;</button>
</div>
<div class="tabs-disabled sync-state">
<div class="syncIllustrationIssue"></div>
<p class="instructions">&syncedTabs.sidebar.tabsnotsyncing.label;</p>
<button class="button sync-prefs">&syncedTabs.sidebar.openprefs.label;</button>
</div>
</div>
</template>
--- a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
@@ -36,20 +36,18 @@ add_task(async function testInitUninit()
component.init();
Assert.ok(SyncedTabs.syncTabs.called);
SyncedTabs.syncTabs.restore();
Assert.ok(ViewMock.calledWithNew(), "view is instantiated");
Assert.equal(ViewMock.args[0][0], mockWindow);
Assert.equal(ViewMock.args[0][1], listComponent);
- Assert.ok(ViewMock.args[0][2].onAndroidClick,
- "view is passed onAndroidClick prop");
- Assert.ok(ViewMock.args[0][2].oniOSClick,
- "view is passed oniOSClick prop");
+ Assert.ok(ViewMock.args[0][2].onConnectDeviceClick,
+ "view is passed onConnectDeviceClick prop");
Assert.ok(ViewMock.args[0][2].onSyncPrefClick,
"view is passed onSyncPrefClick prop");
Assert.equal(component.container, view.container,
"component returns view's container");
Assert.ok(deckStore.on.calledOnce, "listener is added to store");
Assert.equal(deckStore.on.args[0][0], "change");
@@ -198,39 +196,33 @@ add_task(async function testPanelStatus(
sinon.stub(component, "getPanelStatus", () => Promise.resolve("mock-panelId"));
sinon.spy(deckStore, "selectPanel");
await component.updatePanel();
Assert.ok(deckStore.selectPanel.calledWith("mock-panelId"));
});
add_task(async function testActions() {
- let windowMock = {
- openUILink() {},
- };
+ let windowMock = {};
let chromeWindowMock = {
gSync: {
- openPrefs() {}
+ openPrefs() {},
+ openConnectAnotherDevice() {}
}
};
- sinon.spy(windowMock, "openUILink");
sinon.spy(chromeWindowMock.gSync, "openPrefs");
+ sinon.spy(chromeWindowMock.gSync, "openConnectAnotherDevice");
let getChromeWindowMock = sinon.stub();
getChromeWindowMock.returns(chromeWindowMock);
let component = new SyncedTabsDeckComponent({
window: windowMock,
getChromeWindowMock
});
- let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar";
- component.openAndroidLink("mock-event");
- Assert.ok(windowMock.openUILink.calledWith(href, "mock-event"));
-
- href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar";
- component.openiOSLink("mock-event");
- Assert.ok(windowMock.openUILink.calledWith(href, "mock-event"));
+ component.openConnectDevice();
+ Assert.ok(chromeWindowMock.gSync.openConnectAnotherDevice.called);
component.openSyncPrefs();
Assert.ok(getChromeWindowMock.calledWith(windowMock));
Assert.ok(chromeWindowMock.gSync.openPrefs.called);
});
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -45,21 +45,25 @@ sendToAllDevices.menuitem = Send to All
sendTabToDevice.unconfigured.status = Not Connected to Sync
sendTabToDevice.unconfigured = Learn About Sending Tabs…
# LOCALIZATION NOTE (sendTabToDevice.signintosync)
# Displayed in the Send Tabs context menu and the page action panel when sync is not
# configured. Allows users to immediately sign into sync via the preferences.
sendTabToDevice.signintosync = Sign in to Sync…
-# LOCALIZATION NOTE (sendTabToDevice.singledevice, sendTabToDevice.singledevice.status)
+# LOCALIZATION NOTE (sendTabToDevice.singledevice, sendTabToDevice.connectdevice,
+# sendTabToDevice.singledevice.status)
# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link
-# and the Sync account has only 1 device. Redirects to a marketing page.
+# and the Sync account has only 1 device. The sendTabToDevice.singledevice link
+# redirects to a marketing page, the sendTabToDevice.connectdevice redirects
+# to an FxAccounts page that tells to you to connect another device.
sendTabToDevice.singledevice.status = No Devices Connected
sendTabToDevice.singledevice = Learn About Sending Tabs…
+sendTabToDevice.connectdevice = Connect Another Device…
# LOCALIZATION NOTE (sendTabToDevice.verify, sendTabToDevice.verify.status)
# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link
# and the Sync account is unverified. Redirects to the Sync preferences page.
sendTabToDevice.verify.status = Account Not Verified
sendTabToDevice.verify = Verify Your Account…
# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotificationWithDevice.title,
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -379,16 +379,17 @@ These should match what Safari and other
when Sync is configured but this appears to be the only device attached to
the account. We also show links to download Firefox for android/ios. -->
<!ENTITY appMenuRemoteTabs.noclients.subtitle "Want to see your tabs from other devices here?">
<!ENTITY appMenuRemoteTabs.openprefs.label "Sync Preferences">
<!ENTITY appMenuRemoteTabs.notsignedin.label "Sign in to view a list of tabs from your other devices.">
<!ENTITY appMenuRemoteTabs.signin.label "Sign in to Sync">
<!ENTITY appMenuRemoteTabs.managedevices.label "Manage Devices…">
<!ENTITY appMenuRemoteTabs.sidebar.label "View Synced Tabs Sidebar">
+<!ENTITY appMenuRemoteTabs.connectdevice.label "Connect Another Device">
<!ENTITY appMenuRecentHighlights.label "Recent Highlights">
<!ENTITY customizeMenu.addToToolbar.label "Add to Toolbar">
<!ENTITY customizeMenu.addToToolbar.accesskey "A">
<!ENTITY customizeMenu.addToPanel.label "Add to Menu">
<!ENTITY customizeMenu.addToPanel.accesskey "M">
<!-- LOCALIZATION NOTE (customizeMenu.addToOverflowMenu.label,
@@ -794,16 +795,17 @@ you can use these alternative items. Oth
<!ENTITY syncedTabs.sidebar.noclients.subtitle "Want to see your tabs from other devices here?">
<!ENTITY syncedTabs.sidebar.notsignedin.label "Sign in to view a list of tabs from your other devices.">
<!ENTITY syncedTabs.sidebar.notabs.label "No open tabs">
<!ENTITY syncedTabs.sidebar.openprefs.label "Open &syncBrand.shortName.label; Preferences">
<!-- LOCALIZATION NOTE (syncedTabs.sidebar.tabsnotsyncing.label): This is shown
when Sync is configured but syncing tabs is disabled. -->
<!ENTITY syncedTabs.sidebar.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices.">
<!ENTITY syncedTabs.sidebar.searchPlaceholder "Search synced tabs">
+<!ENTITY syncedTabs.sidebar.connectAnotherDevice "Connect Another Device">
<!-- LOCALIZATION NOTE (syncedTabs.context.open.accesskey,
syncedTabs.context.openAllInTabs.accesskey):
These access keys are identical because their associated menu items are
mutually exclusive -->
<!ENTITY syncedTabs.context.open.label "Open">
<!ENTITY syncedTabs.context.open.accesskey "O">
<!ENTITY syncedTabs.context.openInNewTab.label "Open in a New Tab">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -806,27 +806,16 @@ slowStartup.disableNotificationButton.ac
# LOCALIZATION NOTE - %S is brandShortName
flashHang.message = %S changed some Adobe Flash settings to improve performance.
flashHang.helpButton.label = Learn More…
flashHang.helpButton.accesskey = L
# LOCALIZATION NOTE (customizeMode.tabTitle): %S is brandShortName
customizeMode.tabTitle = Customize %S
-# LOCALIZATION NOTE (appMenuRemoteTabs.mobilePromo.text2):
-# %1$S will be replaced with a link, the text of which is
-# appMenuRemoteTabs.mobilePromo.android and the link will be to
-# https://www.mozilla.org/firefox/android/.
-# %2$S will be replaced with a link, the text of which is
-# appMenuRemoteTabs.mobilePromo.ios
-# and the link will be to https://www.mozilla.org/firefox/ios/.
-appMenuRemoteTabs.mobilePromo.text2 = Download %1$S or %2$S and connect them to your Firefox Account.
-appMenuRemoteTabs.mobilePromo.android = Firefox for Android
-appMenuRemoteTabs.mobilePromo.ios = Firefox for iOS
-
# LOCALIZATION NOTE (e10s.accessibilityNotice.mainMessage,
# e10s.accessibilityNotice.enableAndRestart.label,
# e10s.accessibilityNotice.enableAndRestart.accesskey):
# These strings are related to the messages we display to offer e10s (Multi-process) to users
# on the pre-release channels. They won't be used in release but they will likely be used in
# beta starting from version 41, so it's still useful to have these strings properly localized.
# %S is brandShortName
e10s.accessibilityNotice.mainMessage2 = Accessibility support is partially disabled due to compatibility issues with new %S features.
@@ -921,9 +910,9 @@ permissions.remove.tooltip = Clear this
# between the Firefox version and the "What's new" link in the About dialog,
# e.g.: "48.0.2 (32-bit) <What's new>" or "51.0a1 (2016-09-05) (64-bit)".
aboutDialog.architecture.sixtyFourBit = 64-bit
aboutDialog.architecture.thirtyTwoBit = 32-bit
# LOCALIZATION NOTE (certImminentDistrust.message):
# Shown in the browser console when visiting a website that is trusted today,
# but won't be in the future unless the site operator makes a change.
-certImminentDistrust.message = The security certificate in use on this website will no longer be trusted in a future release. For more information, visit https://wiki.mozilla.org/CA/Upcoming_Distrust_Actions
\ No newline at end of file
+certImminentDistrust.message = The security certificate in use on this website will no longer be trusted in a future release. For more information, visit https://wiki.mozilla.org/CA/Upcoming_Distrust_Actions
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -617,18 +617,17 @@ toolbarbutton[constrain-size="true"][cui
border-inline-start-style: none;
}
#PanelUI-remotetabs {
--panel-ui-sync-illustration-height: 157.5px;
}
.PanelUI-remotetabs-instruction-title,
-.PanelUI-remotetabs-instruction-label,
-#PanelUI-remotetabs-mobile-promo {
+.PanelUI-remotetabs-instruction-label {
/* If you change the margin here, the min-height of the synced tabs panel
(e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
need adjusting (see bug 1248506) */
margin: 15px;
text-align: center;
text-shadow: none;
max-width: 15em;
color: GrayText;
@@ -643,38 +642,38 @@ toolbarbutton[constrain-size="true"][cui
.PanelUI-remotetabs-instruction-box {
/* If you change the padding here, the min-height of the synced tabs panel
(e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
need adjusting (see bug 1248506) */
padding-bottom: 30px;
padding-top: 15px;
}
-.PanelUI-remotetabs-prefs-button {
+.PanelUI-remotetabs-button {
-moz-appearance: none;
background-color: #0060df;
/* !important for the color as an OSX specific rule when a lightweight theme
is used for buttons in the toolbox overrides. See bug 1238531 for details */
color: white !important;
border-radius: 2px;
/* If you change the margin or padding below, the min-height of the synced tabs
panel (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
etc) may need adjusting (see bug 1248506) */
margin-top: 10px;
margin-bottom: 10px;
padding: 8px;
text-shadow: none;
min-width: 200px;
}
-.PanelUI-remotetabs-prefs-button:hover {
+.PanelUI-remotetabs-button:hover {
background-color: #003eaa;
}
-.PanelUI-remotetabs-prefs-button:hover:active {
+.PanelUI-remotetabs-button:hover:active {
background-color: #002275;
}
.remotetabs-promo-link {
margin: 0;
}
.PanelUI-remotetabs-notabsforclient-label {
@@ -698,17 +697,17 @@ toolbarbutton[constrain-size="true"][cui
.fxaSyncIllustration {
list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
}
.fxaSyncIllustrationIssue {
list-style-image: url(chrome://browser/skin/fxa/sync-illustration-issue.svg);
}
-.PanelUI-remotetabs-prefs-button > .toolbarbutton-text {
+.PanelUI-remotetabs-button > .toolbarbutton-text {
/* !important to override ".cui-widget-panel toolbarbutton > .toolbarbutton-text" above. */
text-align: center !important;
text-shadow: none;
}
#PanelUI-remotetabs[mainview] { /* panel anchored to toolbar button might be too skinny */
min-width: 19em;
}
@@ -716,18 +715,18 @@ toolbarbutton[constrain-size="true"][cui
/* Work around bug 1224412 - these boxes will cause scrollbars to appear when
the panel is anchored to a toolbar button.
*/
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-reauthsync,
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-nodevicespane,
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-tabsdisabledpane {
min-height: calc(var(--panel-ui-sync-illustration-height) +
- 20px + /* margin of .PanelUI-remotetabs-prefs-button */
- 16px + /* padding of .PanelUI-remotetabs-prefs-button */
+ 20px + /* margin of .PanelUI-remotetabs-button */
+ 16px + /* padding of .PanelUI-remotetabs-button */
30px + /* margin of .PanelUI-remotetabs-instruction-label */
30px + 15px + /* padding of .PanelUI-remotetabs-instruction-box */
11em);
}
#PanelUI-remotetabs-tabslist > label[itemtype="client"] {
color: GrayText;
}
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ b/services/fxaccounts/FxAccountsConfig.jsm
@@ -23,16 +23,17 @@ const CONFIG_PREFS = [
"identity.fxaccounts.remote.profile.uri",
"identity.sync.tokenserver.uri",
"identity.fxaccounts.remote.webchannel.uri",
"identity.fxaccounts.settings.uri",
"identity.fxaccounts.settings.devices.uri",
"identity.fxaccounts.remote.signup.uri",
"identity.fxaccounts.remote.signin.uri",
"identity.fxaccounts.remote.email.uri",
+ "identity.fxaccounts.remote.connectdevice.uri",
"identity.fxaccounts.remote.force_auth.uri",
];
this.FxAccountsConfig = {
async _getPrefURL(prefName) {
await this.ensureConfigured();
let url = Services.urlFormatter.formatURLPref(prefName);
@@ -140,16 +141,17 @@ this.FxAccountsConfig = {
Services.prefs.getCharPref("identity.fxaccounts.contextParam"));
Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
Services.prefs.setCharPref("identity.fxaccounts.settings.devices.uri", rootURL + "/settings/clients?service=sync&context=" + contextParam);
Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
Services.prefs.setCharPref("identity.fxaccounts.remote.email.uri", rootURL + "/?service=sync&context=" + contextParam + "&action=email");
+ Services.prefs.setCharPref("identity.fxaccounts.remote.connectdevice.uri", rootURL + "/connect_another_device?service=sync&context=" + contextParam);
Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);
// Ensure the webchannel is pointed at the correct uri
EnsureFxAccountsWebChannel();
} catch (e) {
log.error("Failed to initialize configuration preferences from autoconfig object", e);
throw e;
}