--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -992,16 +992,18 @@ pref("app.support.e10sAccessibilityUrl",
// base url for web-based feedback pages
#ifdef MOZ_DEV_EDITION
pref("app.feedback.baseURL", "https://input.mozilla.org/%LOCALE%/feedback/firefoxdev/%VERSION%/");
#else
pref("app.feedback.baseURL", "https://input.mozilla.org/%LOCALE%/feedback/%APP%/%VERSION%/");
#endif
+// base URL for web-based marketing pages
+pref("app.productInfo.baseURL", "https://www.mozilla.org/firefox/features/");
// Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
pref("security.alternate_certificate_error_page", "certerror");
// Whether to start the private browsing mode at application startup
pref("browser.privatebrowsing.autostart", false);
// Don't try to alter this pref, it'll be reset the next time you use the
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -39,16 +39,25 @@ var gSync = {
"chrome://weave/locale/sync.properties"
);
},
get syncReady() {
return Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject.ready;
},
+ // Returns true if sync is configured but hasn't loaded or is yet to determine
+ // if any remote clients exist.
+ get syncConfiguredAndLoading() {
+ return UIState.get().status == UIState.STATUS_SIGNED_IN &&
+ (!this.syncReady ||
+ // lastSync will be non-zero after the first sync
+ Weave.Service.clientsEngine.lastSync == 0);
+ },
+
get isSignedIn() {
return UIState.get().status == UIState.STATUS_SIGNED_IN;
},
get remoteClients() {
return Weave.Service.clientsEngine.remoteClients
.sort((a, b) => a.name.localeCompare(b.name));
},
@@ -277,16 +286,22 @@ var gSync = {
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
},
+ openSendToDevicePromo() {
+ let url = Services.prefs.getCharPref("app.productInfo.baseURL");
+ url += "send-tab/?utm_source=" + Services.appinfo.name.toLowerCase();
+ switchToTabHavingURI(url, true, { replaceQueryString: true });
+ },
+
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title).catch(e => {
console.error("Could not send tab to device", e);
});
},
populateSendTabToDevicesMenu(devicesPopup, url, title, createDeviceNodeFn) {
if (!createDeviceNodeFn) {
@@ -299,52 +314,116 @@ var gSync = {
// remove existing menu items
for (let i = devicesPopup.childNodes.length - 1; i >= 0; --i) {
let child = devicesPopup.childNodes[i];
if (child.classList.contains("sync-menuitem")) {
child.remove();
}
}
+ if (gSync.syncConfiguredAndLoading) {
+ // We can only be in this case in the page action menu.
+ return;
+ }
+
const fragment = document.createDocumentFragment();
- if (this.syncReady) {
- const onTargetDeviceCommand = (event) => {
- let clients = event.target.getAttribute("clientId") ?
- [event.target.getAttribute("clientId")] :
- this.remoteClients.map(client => client.id);
+
+ const state = UIState.get();
+ if (state.status == UIState.STATUS_SIGNED_IN && this.remoteClients.length > 0) {
+ this._appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title);
+ } else if (state.status == UIState.STATUS_SIGNED_IN) {
+ this._appendSendTabSingleDevice(fragment, createDeviceNodeFn);
+ } else if (state.status == UIState.STATUS_NOT_VERIFIED ||
+ state.status == UIState.STATUS_LOGIN_FAILED) {
+ this._appendSendTabVerify(fragment, createDeviceNodeFn);
+ } else /* status is STATUS_NOT_CONFIGURED */ {
+ this._appendSendTabUnconfigured(fragment, createDeviceNodeFn);
+ }
+
+ devicesPopup.appendChild(fragment);
+ },
- clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
- gPageActionButton.panel.hidePopup();
- }
+ _appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title) {
+ const onTargetDeviceCommand = (event) => {
+ let clients = event.target.getAttribute("clientId") ?
+ [event.target.getAttribute("clientId")] :
+ this.remoteClients.map(client => client.id);
+
+ clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
+ gPageActionButton.panel.hidePopup();
+ }
+
+ function addTargetDevice(clientId, name, clientType) {
+ const targetDevice = createDeviceNodeFn(clientId, name, clientType);
+ targetDevice.addEventListener("command", onTargetDeviceCommand, true);
+ targetDevice.classList.add("sync-menuitem", "sendtab-target");
+ targetDevice.setAttribute("clientId", clientId);
+ targetDevice.setAttribute("clientType", clientType);
+ targetDevice.setAttribute("label", name);
+ fragment.appendChild(targetDevice);
+ }
+
+ const clients = this.remoteClients;
+ for (let client of clients) {
+ addTargetDevice(client.id, client.name, client.type);
+ }
- function addTargetDevice(clientId, name, clientType) {
- const targetDevice = createDeviceNodeFn(clientId, name, clientType);
- targetDevice.addEventListener("command", onTargetDeviceCommand, true);
- targetDevice.classList.add("sync-menuitem", "sendtab-target");
- targetDevice.setAttribute("clientId", clientId);
- targetDevice.setAttribute("clientType", clientType);
- targetDevice.setAttribute("label", name);
- fragment.appendChild(targetDevice);
- }
+ // "Send to All Devices" menu item
+ if (clients.length > 1) {
+ const separator = createDeviceNodeFn();
+ separator.classList.add("sync-menuitem");
+ fragment.appendChild(separator);
+ 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();
+ gPageActionButton.panel.hidePopup();
+ });
+ },
- const clients = this.remoteClients;
- for (let client of clients) {
- addTargetDevice(client.id, client.name, client.type);
- }
+ _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");
+ gPageActionButton.panel.hidePopup();
+ });
+ },
- // "Send to All Devices" menu item
- if (clients.length > 1) {
- const separator = createDeviceNodeFn();
- separator.classList.add("sync-menuitem");
- fragment.appendChild(separator);
- const allDevicesLabel = this.fxaStrings.GetStringFromName("sendToAllDevices.menuitem");
- addTargetDevice("", allDevicesLabel, "");
- }
- }
- devicesPopup.appendChild(fragment);
+ _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();
+ gPageActionButton.panel.hidePopup();
+ });
+ },
+
+ _appendSendTabInfoItems(fragment, createDeviceNodeFn, statusLabel, actionLabel, actionCommand) {
+ 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);
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
@@ -361,47 +440,47 @@ var gSync = {
// the length, which we've already addressed.
Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
return true;
}
},
// "Send Tab to Device" menu item
updateTabContextMenu(aPopupMenu, aTargetTab) {
- const show = this.syncReady &&
- this.remoteClients.length > 0 &&
- this.isSendableURI(aTargetTab.linkedBrowser.currentURI.spec);
+ const enabled = !this.syncConfiguredAndLoading &&
+ this.isSendableURI(aTargetTab.linkedBrowser.currentURI.spec);
- ["context_sendTabToDevice", "context_sendTabToDevice_separator"]
- .forEach(id => document.getElementById(id).hidden = !show);
+ document.getElementById("context_sendTabToDevice").disabled = !enabled;
},
// "Send Page to Device" and "Send Link to Device" menu items
- initPageContextMenu(contextMenu) {
- const remoteClientPresent = this.syncReady && this.remoteClients.length > 0;
+ updateContentContextMenu(contextMenu) {
// showSendLink and showSendPage are mutually exclusive
- let showSendLink = remoteClientPresent
- && (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
- const showSendPage = !showSendLink && remoteClientPresent
+ const showSendLink = contextMenu.onSaveableLink || contextMenu.onPlainTextLink;
+ const showSendPage = !showSendLink
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
- contextMenu.onLink || contextMenu.onTextInput)
- && this.isSendableURI(contextMenu.browser.currentURI.spec);
-
- if (showSendLink) {
- // This isn't part of the condition above since we don't want to try and
- // send the page if a link is clicked on or selected but is not sendable.
- showSendLink = this.isSendableURI(contextMenu.linkURL);
- }
+ contextMenu.onLink || contextMenu.onTextInput);
["context-sendpagetodevice", "context-sep-sendpagetodevice"]
.forEach(id => contextMenu.showItem(id, showSendPage));
["context-sendlinktodevice", "context-sep-sendlinktodevice"]
.forEach(id => contextMenu.showItem(id, showSendLink));
+
+ if (!showSendLink && !showSendPage) {
+ return;
+ }
+
+ const targetURI = showSendLink ? contextMenu.linkURL :
+ contextMenu.browser.currentURI.spec;
+ const enabled = !this.syncConfiguredAndLoading && this.isSendableURI(targetURI);
+ contextMenu.setItemAttr(showSendPage ? "context-sendpagetodevice" :
+ "context-sendlinktodevice",
+ "disabled", !enabled || null);
},
// Functions called by observers
onActivityStart() {
clearTimeout(this._syncAnimationTimer);
this._syncStartTime = Date.now();
let broadcaster = document.getElementById("sync-status");
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1480,15 +1480,13 @@ toolbarpaletteitem[place="palette"][hidd
.dragfeedback-tab {
-moz-appearance: none;
opacity: 0.65;
-moz-window-shadow: none;
}
/* Page action menu */
-#page-action-sendToDeviceView-body:not([state="notsignedin"]) > #page-action-sendToDevice-fxa-button,
-#page-action-sendToDeviceView-body:not([state="nodevice"]) > #page-action-no-devices-button,
#page-action-sendToDeviceView-body:not([state="notready"]) > #page-action-sync-not-ready-button {
display: none;
}
%include theme-vars.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7982,46 +7982,31 @@ var gPageActionButton = {
item.classList.add("page-action-sendToDevice-device", "subviewbutton");
if (clientId) {
item.classList.add("subviewbutton-iconic");
}
item.setAttribute("tooltiptext", name);
return item;
});
- if (!gSync.isSignedIn) {
- // Could be unconfigured or unverified
- body.setAttribute("state", "notsignedin");
- return;
- }
-
+ body.removeAttribute("state");
// In the first ~10 sec after startup, Sync may not be loaded and the list
// of devices will be empty.
- if (!gSync.syncReady) {
+ if (gSync.syncConfiguredAndLoading) {
body.setAttribute("state", "notready");
// Force a background Sync
Services.tm.dispatchToMainThread(() => {
Weave.Service.sync([]); // [] = clients engine only
- if (!window.closed && gSync.syncReady) {
+ // There's no way Sync is still syncing at this point, but we check
+ // anyway to avoid infinite looping.
+ if (!window.closed && !gSync.syncConfiguredAndLoading) {
this.setupSendToDeviceView();
}
});
- return;
- }
- if (!gSync.remoteClients.length) {
- body.setAttribute("state", "nodevice");
- return;
- }
-
- body.setAttribute("state", "signedin");
- },
-
- fxaButtonClicked() {
- this.panel.hidePopup();
- gSync.openPrefs();
+ }
},
};
/**
* Fired on the "marionette-remote-control" system notification,
* indicating if the browser session is under remote control.
*/
const gRemoteControl = {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -480,25 +480,16 @@
closemenu="none"
oncommand="gPageActionButton.showSendToDeviceView(this);"/>
</vbox>
</panelview>
<panelview id="page-action-sendToDeviceView"
class="PanelUI-subView"
title="&sendToDevice.viewTitle;">
<vbox id="page-action-sendToDeviceView-body" class="panel-subview-body">
- <toolbarbutton id="page-action-sendToDevice-fxa-button"
- class="subviewbutton subviewbutton-iconic"
- label="&syncBrand.fxAccount.label;"
- shortcut="&sendToDevice.fxaRequired.label;"
- oncommand="gPageActionButton.fxaButtonClicked();"/>
- <toolbarbutton id="page-action-no-devices-button"
- class="subviewbutton"
- label="&sendToDevice.noDevices.label;"
- disabled="true"/>
<toolbarbutton id="page-action-sync-not-ready-button"
class="subviewbutton"
label="&sendToDevice.syncNotReady.label;"
disabled="true"/>
</vbox>
</panelview>
</photonpanelmultiview>
</panel>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -625,17 +625,17 @@ nsContextMenu.prototype = {
return;
}
let popup = document.getElementById("fill-login-popup");
let insertBeforeElement = document.getElementById("fill-login-no-logins");
popup.insertBefore(fragment, insertBeforeElement);
},
initSyncItems() {
- gSync.initPageContextMenu(this);
+ gSync.updateContentContextMenu(this);
},
openPasswordManager() {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
},
inspectNode() {
return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
--- a/browser/base/content/test/contextMenu/browser_contextmenu_mozextension.js
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_mozextension.js
@@ -39,17 +39,21 @@ add_task(async function test_link() {
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-savelink", true,
"context-copylink", true,
- "context-searchselect", true]);
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
+ ]
+ );
});
add_task(async function test_video() {
await test_contextmenu("#video",
["context-media-play", null,
"context-media-mute", null,
"context-media-playbackrate", null,
["context-media-playbackrate-050x", null,
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -33,17 +33,19 @@ add_task(async function test_xul_text_li
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
- "context-searchselect", true
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
// Clean up so won't affect HTML element test cases
lastElementSelector = null;
gBrowser.removeCurrentTab();
});
@@ -84,16 +86,18 @@ add_task(async function test_plaintext()
plainTextItems = ["context-navigation", null,
["context-back", false,
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true, [], null,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
];
await test_contextmenu("#test-text", plainTextItems, {
@@ -110,17 +114,19 @@ add_task(async function test_link() {
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
- "context-searchselect", true
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
});
add_task(async function test_mailto() {
await test_contextmenu("#test-mailto",
["context-copyemail", true,
"context-searchselect", true
@@ -257,16 +263,18 @@ add_task(async function test_iframe() {
["context-navigation", null,
["context-back", false,
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true, [], null,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"frame", null,
["context-showonlythisframe", true,
"context-openframeintab", true,
"context-openframe", true,
"---", null,
@@ -561,16 +569,18 @@ add_task(async function test_pagemenu()
["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
"+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
"+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
"---", null,
"+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true, [], null,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
],
{async postCheckContextMenuFn() {
@@ -593,16 +603,18 @@ add_task(async function test_dom_full_sc
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-leave-dom-fullscreen", true,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true, [], null,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
],
{
@@ -641,16 +653,18 @@ add_task(async function test_pagemenu2()
["context-back", false,
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
"---", null,
+ "context-sendpagetodevice", true, [], null,
+ "---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
],
{maybeScreenshotsPresent: true,
shiftkey: true}
@@ -687,16 +701,18 @@ add_task(async function test_select_text
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
"context-copy", true,
"context-selectall", true,
"---", null,
"context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
"context-viewpartialsource-selection", true
],
{
offsetX: 6,
offsetY: 6,
async preCheckContextMenuFn() {
await selectText("#test-select-text-link");
},
@@ -727,17 +743,19 @@ add_task(async function test_imagelink()
"---", null,
"context-viewimage", true,
"context-copyimage-contents", true,
"context-copyimage", true,
"---", null,
"context-saveimage", true,
"context-sendimage", true,
"context-setDesktopBackground", true,
- "context-viewimageinfo", true
+ "context-viewimageinfo", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
});
add_task(async function test_select_input_text() {
todo(false, "spell checker tests are failing, bug 1246296");
/*
@@ -820,16 +838,18 @@ add_task(async function test_click_to_pl
"context-bookmarkpage", true], null,
"---", null,
"context-ctp-play", true,
"context-ctp-hide", true,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
"---", null,
+ "context-sendpagetodevice", true, [], null,
+ "---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
],
{
maybeScreenshotsPresent: true,
@@ -865,16 +885,18 @@ add_task(async function test_srcdoc() {
["context-back", false,
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
"---", null,
+ "context-sendpagetodevice", true, [], null,
+ "---", null,
"context-viewbgimage", false,
"context-selectall", true,
"frame", null,
["context-reloadframe", true,
"---", null,
"context-saveframe", true,
"---", null,
"context-printframe", true,
@@ -915,51 +937,57 @@ add_task(async function test_svg_link()
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
- "context-searchselect", true
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
await test_contextmenu("#svg-with-link2 > a",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
- "context-searchselect", true
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
await test_contextmenu("#svg-with-link3 > a",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
- "context-searchselect", true
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true, [], null,
]
);
});
add_task(async function test_cleanup_html() {
gBrowser.removeCurrentTab();
});
--- a/browser/base/content/test/general/browser_contextmenu_input.js
+++ b/browser/base/content/test/general/browser_contextmenu_input.js
@@ -193,16 +193,18 @@ add_task(async function test_date_time_c
["context-back", false,
"context-forward", false,
"context-reload", true,
"context-bookmarkpage", true], null,
"---", null,
"context-savepage", true,
...(hasPocket ? ["context-pocket", true] : []),
"---", null,
+ "context-sendpagetodevice", null, [], null,
+ "---", null,
"context-viewbgimage", false,
"context-selectall", null,
"---", null,
"context-viewsource", true,
"context-viewinfo", true], {
// XXX Bug 1345081. Currently the Screenshots menu option is shown for
// various text elements even though it is set to type "page". That bug
// should remove the need for next line.
--- a/browser/base/content/test/sync/browser_contextmenu_sendpage.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendpage.js
@@ -1,92 +1,233 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
-const origRemoteClients = mockReturn(gSync, "remoteClients", remoteClientsFixture);
-const origSyncReady = mockReturn(gSync, "syncReady", true);
-const origIsSendableURI = mockReturn(gSync, "isSendableURI", true);
-
add_task(async function setup() {
+ await promiseSyncReady();
await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
});
add_task(async function test_page_contextmenu() {
- await updateContentContextMenu("#moztext", "context-sendpagetodevice");
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+ 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");
- let devices = document.getElementById("context-sendpagetodevice-popup").childNodes;
- is(devices[0].getAttribute("label"), "Foo", "Foo target is present");
- is(devices[1].getAttribute("label"), "Bar", "Bar target is present");
- is(devices[3].getAttribute("label"), "Send to All Devices", "All Devices target is present");
-});
+ checkPopup([
+ { label: "Foo" },
+ { label: "Bar" },
+ "----",
+ { label: "Send to All Devices" }
+ ]);
+ await hideContentContextMenu();
-add_task(async function test_page_contextmenu_notsendable() {
- const isSendableURIMock = mockReturn(gSync, "isSendableURI", false);
-
- await updateContentContextMenu("#moztext");
- is(document.getElementById("context-sendpagetodevice").hidden, true, "Send tab to device is hidden");
- is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
-
- isSendableURIMock.restore();
+ sandbox.restore();
});
add_task(async function test_page_contextmenu_sendtab_no_remote_clients() {
- let remoteClientsMock = mockReturn(gSync, "remoteClients", []);
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [],
+ 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: "Learn About Sending Tabs..." }
+ ]);
+ await hideContentContextMenu();
+
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_sendtab_one_remote_client() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [{ id: 1, name: "Foo"}],
+ 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: "Foo" }
+ ]);
+ await hideContentContextMenu();
+
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_not_sendable() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
+
+ await openContentContextMenu("#moztext");
+ is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
+ checkPopup();
+ await hideContentContextMenu();
- await updateContentContextMenu("#moztext");
- is(document.getElementById("context-sendpagetodevice").hidden, true, "Send tab to device is hidden");
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_not_synced_yet() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, remoteClients: [],
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+
+ await openContentContextMenu("#moztext");
+ is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
+ checkPopup();
+ await hideContentContextMenu();
+
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_sync_not_ready_configured() {
+ const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+
+ await openContentContextMenu("#moztext");
+ is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
+ checkPopup();
+ await hideContentContextMenu();
+
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
+ const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+ state: UIState.STATUS_NOT_VERIFIED, 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: "Account Not Verified", disabled: true },
+ "----",
+ { label: "Verify Your Account..." }
+ ]);
+ await hideContentContextMenu();
- remoteClientsMock.restore();
+ sandbox.restore();
});
-add_task(async function test_page_contextmenu_sync_not_ready() {
- const syncReadyMock = mockReturn(gSync, "syncReady", false);
+add_task(async function test_page_contextmenu_unconfigured() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: null,
+ state: UIState.STATUS_NOT_CONFIGURED, 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: "Not Connected to Sync", disabled: true },
+ "----",
+ { label: "Learn About Sending Tabs..." }
+ ]);
+
+ await hideContentContextMenu();
- await updateContentContextMenu("#moztext");
- is(document.getElementById("context-sendpagetodevice").hidden, true, "Send tab to device is hidden");
+ sandbox.restore();
+});
+
+add_task(async function test_page_contextmenu_not_verified() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: null,
+ state: UIState.STATUS_NOT_VERIFIED, 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: "Account Not Verified", disabled: true },
+ "----",
+ { label: "Verify Your Account..." }
+ ]);
- syncReadyMock.restore();
+ await hideContentContextMenu();
+
+ sandbox.restore();
});
-// We are not going to bother testing the states of context-sendlinktodevice since they use
-// the exact same code.
-// However, browser_contextmenu.js contains tests that verify the menu item is present.
+add_task(async function test_page_contextmenu_login_failed() {
+ const syncReady = sinon.stub(gSync, "syncReady").get(() => true);
+ const getState = sinon.stub(UIState, "get").returns({ status: UIState.STATUS_LOGIN_FAILED });
+ const isSendableURI = sinon.stub(gSync, "isSendableURI").returns(true);
-add_task(async function cleanup() {
- gBrowser.removeCurrentTab();
- origSyncReady.restore();
- origRemoteClients.restore();
- origIsSendableURI.restore();
+ 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: "Account Not Verified", disabled: true },
+ "----",
+ { label: "Verify Your Account..." }
+ ]);
+
+ await hideContentContextMenu();
+
+ syncReady.restore();
+ getState.restore();
+ isSendableURI.restore();
});
-async function updateContentContextMenu(selector, openSubmenuId = null) {
- let contextMenu = document.getElementById("contentAreaContextMenu");
+// We are not going to bother testing the visibility of context-sendlinktodevice
+// since it uses the exact same code.
+// However, browser_contextmenu.js contains tests that verify its presence.
+
+add_task(async function teardown() {
+ gBrowser.removeCurrentTab();
+});
+
+function checkPopup(expectedItems = null) {
+ const popup = document.getElementById("context-sendpagetodevice-popup");
+ if (!expectedItems) {
+ is(popup.state, "closed", "Popup should be hidden.");
+ return;
+ }
+ const menuItems = popup.children;
+ is(menuItems.length, expectedItems.length, "Popup has the expected children count.");
+ for (let i = 0; i < menuItems.length; i++) {
+ const menuItem = menuItems[i];
+ const expectedItem = expectedItems[i];
+ if (expectedItem === "----") {
+ is(menuItem.nodeName, "menuseparator", "Found a separator");
+ continue;
+ }
+ is(menuItem.nodeName, "menuitem", "Found a menu item");
+ // Bug workaround, menuItem.label "…" encoding is different than ours.
+ is(menuItem.label.normalize("NFKC"), expectedItem.label, "Correct menu item label");
+ is(menuItem.disabled, !!expectedItem.disabled, "Correct menu item disabled state");
+ }
+}
+
+async function openContentContextMenu(selector, openSubmenuId = null) {
+ const contextMenu = document.getElementById("contentAreaContextMenu");
is(contextMenu.state, "closed", "checking if popup is closed");
- let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ const awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
await BrowserTestUtils.synthesizeMouse(selector, 0, 0, {
type: "contextmenu",
button: 2,
shiftkey: false,
centered: true
},
gBrowser.selectedBrowser);
await awaitPopupShown;
if (openSubmenuId) {
- let menuPopup = document.getElementById(openSubmenuId).menupopup;
- let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
+ const menuPopup = document.getElementById(openSubmenuId).menupopup;
+ const menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
menuPopup.showPopup();
await menuPopupPromise;
}
+}
- let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
-
+async function hideContentContextMenu() {
+ const contextMenu = document.getElementById("contentAreaContextMenu");
+ const awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
contextMenu.hidePopup();
await awaitPopupHidden;
}
--- a/browser/base/content/test/sync/browser_contextmenu_sendtab.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendtab.js
@@ -4,82 +4,83 @@
"use strict";
const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
Services.scriptloader.loadSubScript(chrome_base + "head.js", this);
/* import-globals-from ../general/head.js */
const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
-const origRemoteClients = mockReturn(gSync, "remoteClients", remoteClientsFixture);
-const origSyncReady = mockReturn(gSync, "syncReady", true);
-const origIsSendableURI = mockReturn(gSync, "isSendableURI", true);
let [testTab] = gBrowser.visibleTabs;
add_task(async function setup() {
+ await promiseSyncReady();
is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
});
+// We are not testing the devices popup contents, since it is already tested by
+// browser_contextmenu_sendpage.js and the code to populate it is the same.
+
add_task(async function test_tab_contextmenu() {
- await updateTabContextMenu(testTab, openSendTabTargetsSubmenu);
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+
+ await updateTabContextMenu(testTab);
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
- let devices = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
- is(devices[0].getAttribute("label"), "Foo", "Foo target is present");
- is(devices[1].getAttribute("label"), "Bar", "Bar target is present");
- is(devices[3].getAttribute("label"), "Send to All Devices", "All Devices target is present");
+
+ sandbox.restore();
});
-add_task(async function test_tab_contextmenu_only_one_remote_device() {
- const remoteClientsMock = mockReturn(gSync, "remoteClients", [{ id: 1, name: "Foo"}]);
+add_task(async function test_tab_contextmenu_unconfigured() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+ state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
- await updateTabContextMenu(testTab, openSendTabTargetsSubmenu);
+ await updateTabContextMenu(testTab);
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
- let devices = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
- is(devices.length, 1, "There should not be any separator or All Devices item");
- is(devices[0].getAttribute("label"), "Foo", "Foo target is present");
- remoteClientsMock.restore();
+ sandbox.restore();
});
add_task(async function test_tab_contextmenu_not_sendable() {
- const isSendableURIMock = mockReturn(gSync, "isSendableURI", false);
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [{ id: 1, name: "Foo"}],
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
- updateTabContextMenu(testTab);
- is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
- is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
+ await updateTabContextMenu(testTab);
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
- isSendableURIMock.restore();
+ sandbox.restore();
});
-add_task(async function test_tab_contextmenu_no_remote_clients() {
- let remoteClientsMock = mockReturn(gSync, "remoteClients", []);
+add_task(async function test_tab_contextmenu_not_synced_yet() {
+ const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, remoteClients: [],
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
- updateTabContextMenu(testTab);
- is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
- is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
+ await updateTabContextMenu(testTab);
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
- remoteClientsMock.restore();
+ sandbox.restore();
});
-add_task(async function test_tab_contextmenu_sync_not_ready() {
- const syncReadyMock = mockReturn(gSync, "syncReady", false);
+add_task(async function test_tab_contextmenu_sync_not_ready_configured() {
+ const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+ state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
- updateTabContextMenu(testTab);
- is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
- is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
+ await updateTabContextMenu(testTab);
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
- syncReadyMock.restore();
+ sandbox.restore();
});
-add_task(async function cleanup() {
- origSyncReady.restore();
- origRemoteClients.restore();
- origIsSendableURI.restore();
-});
+add_task(async function test_tab_contextmenu_sync_not_ready_other_state() {
+ const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+ state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
-async function openSendTabTargetsSubmenu() {
- let menuPopup = document.getElementById("context_sendTabToDevice").menupopup;
- let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
- menuPopup.showPopup();
- await menuPopupPromise;
-}
+ await updateTabContextMenu(testTab);
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
+
+ sandbox.restore();
+});
--- a/browser/base/content/test/sync/head.js
+++ b/browser/base/content/test/sync/head.js
@@ -1,24 +1,23 @@
-// Mocks a getter or a function
-// This is basically sinon.js (our in-tree version doesn't do getters :/) (see bug 1369855)
-function mockReturn(obj, symbol, fixture) {
- let getter = Object.getOwnPropertyDescriptor(obj, symbol).get;
- if (getter) {
- Object.defineProperty(obj, symbol, {
- get() { return fixture; }
- });
- return {
- restore() {
- Object.defineProperty(obj, symbol, {
- get: getter
- });
- }
- }
- }
- let func = obj[symbol];
- obj[symbol] = () => fixture;
- return {
- restore() {
- obj[symbol] = func;
- }
- }
+/* global sinon */
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
+
+registerCleanupFunction(function() {
+ delete window.sinon;
+});
+
+function promiseSyncReady() {
+ let service = Cc["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ return service.whenLoaded();
}
+
+function setupSendTabMocks({ syncReady, clientsSynced, remoteClients, state, isSendableURI }) {
+ const sandbox = sinon.sandbox.create();
+ sandbox.stub(gSync, "syncReady").get(() => syncReady);
+ sandbox.stub(Weave.Service.clientsEngine, "lastSync").get(() => clientsSynced ? Date.now() : 0);
+ sandbox.stub(gSync, "remoteClients").get(() => remoteClients);
+ sandbox.stub(UIState, "get").returns({ status: state });
+ sandbox.stub(gSync, "isSendableURI").returns(isSendableURI);
+ return sandbox;
+}
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -1,10 +1,17 @@
"use strict";
+/* global sinon */
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
+
+registerCleanupFunction(function() {
+ delete window.sinon;
+});
+
const mockRemoteClients = [
{ id: "0", name: "foo", type: "mobile" },
{ id: "1", name: "bar", type: "desktop" },
{ id: "2", name: "baz", type: "mobile" },
];
add_task(async function bookmark() {
// Open a unique page.
@@ -98,53 +105,110 @@ add_task(async function emailLink() {
await hiddenPromise;
Assert.ok(fnCalled);
});
add_task(async function sendToDevice_nonSendable() {
// Open a tab that's not sendable.
await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await promiseSyncReady();
// Open the panel. Send to Device should be disabled.
await promisePageActionPanelOpen();
let sendToDeviceButton =
document.getElementById("page-action-send-to-device-button");
Assert.ok(sendToDeviceButton.disabled);
let hiddenPromise = promisePageActionPanelHidden();
gPageActionPanel.hidePopup();
await hiddenPromise;
});
});
-add_task(async function sendToDevice_syncNotReady() {
+add_task(async function sendToDevice_syncNotReady_other_states() {
// Open a tab that's sendable.
await BrowserTestUtils.withNewTab("http://example.com/", async () => {
- let syncReadyMock = mockReturn(gSync, "syncReady", false);
- let signedInMock = mockReturn(gSync, "isSignedIn", true);
-
- let remoteClientsMock;
- let origSync = Weave.Service.sync;
- Weave.Service.sync = () => {
- mockReturn(gSync, "syncReady", true);
- remoteClientsMock = mockReturn(gSync, "remoteClients", mockRemoteClients);
- };
-
- let origSetupSendToDeviceView = gPageActionButton.setupSendToDeviceView;
- gPageActionButton.setupSendToDeviceView = () => {
- this.numCall++ || (this.numCall = 1);
- origSetupSendToDeviceView.call(gPageActionButton);
- testSendTabToDeviceMenu(this.numCall);
- }
+ await promiseSyncReady();
+ const sandbox = sinon.sandbox.create();
+ sandbox.stub(gSync, "syncReady").get(() => false);
+ sandbox.stub(Weave.Service.clientsEngine, "lastSync").get(() => 0);
+ sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_NOT_VERIFIED });
+ sandbox.stub(gSync, "isSendableURI").returns(true);
let cleanUp = () => {
- Weave.Service.sync = origSync;
- gPageActionButton.setupSendToDeviceView = origSetupSendToDeviceView;
- signedInMock.restore();
- syncReadyMock.restore();
- remoteClientsMock.restore();
+ sandbox.restore();
+ };
+ registerCleanupFunction(cleanUp);
+
+ // Open the panel.
+ await promisePageActionPanelOpen();
+ let sendToDeviceButton =
+ document.getElementById("page-action-send-to-device-button");
+ Assert.ok(!sendToDeviceButton.disabled);
+
+ // Click Send to Device.
+ let viewPromise = promisePageActionViewShown();
+ EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {});
+ let view = await viewPromise;
+ Assert.equal(view.id, "page-action-sendToDeviceView");
+
+ let expectedItems = [
+ {
+ id: "page-action-sync-not-ready-button",
+ display: "none",
+ disabled: true,
+ },
+ {
+ attrs: {
+ label: "Account Not Verified",
+ },
+ disabled: true
+ },
+ null,
+ {
+ attrs: {
+ label: "Verify Your Account...",
+ },
+ }
+ ];
+ checkSendToDeviceItems(expectedItems);
+
+ // Done, hide the panel.
+ let hiddenPromise = promisePageActionPanelHidden();
+ gPageActionPanel.hidePopup();
+ await hiddenPromise;
+
+ cleanUp();
+ });
+});
+
+add_task(async function sendToDevice_syncNotReady_configured() {
+ // Open a tab that's sendable.
+ await BrowserTestUtils.withNewTab("http://example.com/", async () => {
+ await promiseSyncReady();
+ const sandbox = sinon.sandbox.create();
+ const syncReady = sandbox.stub(gSync, "syncReady").get(() => false);
+ const lastSync = sandbox.stub(Weave.Service.clientsEngine, "lastSync").get(() => 0);
+ sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
+ sandbox.stub(gSync, "isSendableURI").returns(true);
+
+ sandbox.stub(Weave.Service, "sync").callsFake(() => {
+ syncReady.get(() => true);
+ lastSync.get(() => Date.now());
+ sandbox.stub(gSync, "remoteClients").get(() => mockRemoteClients);
+ });
+
+ const setupSendToDeviceView = gPageActionButton.setupSendToDeviceView;
+ sandbox.stub(gPageActionButton, "setupSendToDeviceView").callsFake(() => {
+ this.numCall++ || (this.numCall = 1);
+ setupSendToDeviceView.call(gPageActionButton);
+ testSendTabToDeviceMenu(this.numCall);
+ });
+
+ let cleanUp = () => {
+ sandbox.restore();
};
registerCleanupFunction(cleanUp);
// Open the panel.
await promisePageActionPanelOpen();
let sendToDeviceButton =
document.getElementById("page-action-send-to-device-button");
Assert.ok(!sendToDeviceButton.disabled);
@@ -152,45 +216,27 @@ add_task(async function sendToDevice_syn
// Click Send to Device.
let viewPromise = promisePageActionViewShown();
EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {});
let view = await viewPromise;
Assert.equal(view.id, "page-action-sendToDeviceView");
function testSendTabToDeviceMenu(numCall) {
if (numCall == 1) {
- // The Fxa button should be shown.
+ // "Syncing devices" should be shown.
checkSendToDeviceItems([
{
- id: "page-action-sendToDevice-fxa-button",
- display: "none",
- },
- {
- id: "page-action-no-devices-button",
- display: "none",
- disabled: true,
- },
- {
id: "page-action-sync-not-ready-button",
disabled: true,
},
]);
} else if (numCall == 2) {
// The devices should be shown in the subview.
let expectedItems = [
{
- id: "page-action-sendToDevice-fxa-button",
- display: "none",
- },
- {
- id: "page-action-no-devices-button",
- display: "none",
- disabled: true,
- },
- {
id: "page-action-sync-not-ready-button",
display: "none",
disabled: true,
},
];
for (let client of mockRemoteClients) {
expectedItems.push({
attrs: {
@@ -198,17 +244,19 @@ add_task(async function sendToDevice_syn
label: client.name,
clientType: client.type,
},
});
}
expectedItems.push(
null,
{
- label: "Send to All Devices",
+ attrs: {
+ label: "Send to All Devices"
+ }
}
);
checkSendToDeviceItems(expectedItems);
} else {
ok(false, "This should never happen");
}
}
@@ -232,108 +280,117 @@ add_task(async function sendToDevice_not
Assert.ok(!sendToDeviceButton.disabled);
// Click Send to Device.
let viewPromise = promisePageActionViewShown();
EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {});
let view = await viewPromise;
Assert.equal(view.id, "page-action-sendToDeviceView");
- // The Fxa button should be shown.
- checkSendToDeviceItems([
- {
- id: "page-action-sendToDevice-fxa-button",
- },
- {
- id: "page-action-no-devices-button",
- display: "none",
- disabled: true,
- },
+ let expectedItems = [
{
id: "page-action-sync-not-ready-button",
display: "none",
disabled: true,
},
- ]);
+ {
+ attrs: {
+ label: "Not Connected to Sync",
+ },
+ disabled: true
+ },
+ null,
+ {
+ attrs: {
+ label: "Learn About Sending Tabs..."
+ },
+ }
+ ];
+ checkSendToDeviceItems(expectedItems);
- // Click the Fxa button.
- let body = view.firstChild;
- let fxaButton = body.childNodes[0];
- Assert.equal(fxaButton.id, "page-action-sendToDevice-fxa-button");
- let prefsTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ // Done, hide the panel.
let hiddenPromise = promisePageActionPanelHidden();
- EventUtils.synthesizeMouseAtCenter(fxaButton, {});
- let values = await Promise.all([prefsTabPromise, hiddenPromise]);
- let tab = values[0];
-
- // The Fxa prefs pane should open. The full URL is something like:
- // about:preferences?entrypoint=syncbutton#sync
- // Just make sure it's about:preferences#sync.
- let urlObj = new URL(gBrowser.selectedBrowser.currentURI.spec);
- let url = urlObj.protocol + urlObj.pathname + urlObj.hash;
- Assert.equal(url, "about:preferences#sync");
-
- await BrowserTestUtils.removeTab(tab);
+ gPageActionPanel.hidePopup();
+ await hiddenPromise;
});
});
add_task(async function sendToDevice_noDevices() {
// Open a tab that's sendable.
await BrowserTestUtils.withNewTab("http://example.com/", async () => {
await promiseSyncReady();
- UIState._internal._state = { status: UIState.STATUS_SIGNED_IN };
+ const sandbox = sinon.sandbox.create();
+ sandbox.stub(gSync, "syncReady").get(() => true);
+ sandbox.stub(Weave.Service.clientsEngine, "lastSync").get(() => Date.now());
+ sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
+ sandbox.stub(gSync, "isSendableURI").returns(true);
+ sandbox.stub(gSync, "remoteClients").get(() => []);
+
+ let cleanUp = () => {
+ sandbox.restore();
+ };
+ registerCleanupFunction(cleanUp);
// Open the panel.
await promisePageActionPanelOpen();
let sendToDeviceButton =
document.getElementById("page-action-send-to-device-button");
Assert.ok(!sendToDeviceButton.disabled);
// Click Send to Device.
let viewPromise = promisePageActionViewShown();
EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {});
let view = await viewPromise;
Assert.equal(view.id, "page-action-sendToDeviceView");
- // The no-devices item should be shown.
- checkSendToDeviceItems([
- {
- id: "page-action-sendToDevice-fxa-button",
- display: "none",
- },
- {
- id: "page-action-no-devices-button",
- disabled: true,
- },
+ let expectedItems = [
{
id: "page-action-sync-not-ready-button",
display: "none",
disabled: true,
},
- ]);
+ {
+ attrs: {
+ label: "No Devices Connected",
+ },
+ disabled: true
+ },
+ null,
+ {
+ attrs: {
+ label: "Learn About Sending Tabs..."
+ }
+ }
+ ];
+ checkSendToDeviceItems(expectedItems);
// Done, hide the panel.
let hiddenPromise = promisePageActionPanelHidden();
gPageActionPanel.hidePopup();
await hiddenPromise;
+ cleanUp();
+
await UIState.reset();
});
});
add_task(async function sendToDevice_devices() {
// Open a tab that's sendable.
await BrowserTestUtils.withNewTab("http://example.com/", async () => {
await promiseSyncReady();
- UIState._internal._state = { status: UIState.STATUS_SIGNED_IN };
+ const sandbox = sinon.sandbox.create();
+ sandbox.stub(gSync, "syncReady").get(() => true);
+ sandbox.stub(Weave.Service.clientsEngine, "lastSync").get(() => Date.now());
+ sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
+ sandbox.stub(gSync, "isSendableURI").returns(true);
+ sandbox.stub(gSync, "remoteClients").get(() => mockRemoteClients);
- // Set up mock remote clients.
- let remoteClientsMock = mockReturn(gSync, "remoteClients", mockRemoteClients);
let cleanUp = () => {
- remoteClientsMock.restore();
+ sandbox.restore();
};
registerCleanupFunction(cleanUp);
// Open the panel.
await promisePageActionPanelOpen();
let sendToDeviceButton =
document.getElementById("page-action-send-to-device-button");
Assert.ok(!sendToDeviceButton.disabled);
@@ -342,25 +399,16 @@ add_task(async function sendToDevice_dev
let viewPromise = promisePageActionViewShown();
EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {});
let view = await viewPromise;
Assert.equal(view.id, "page-action-sendToDeviceView");
// The devices should be shown in the subview.
let expectedItems = [
{
- id: "page-action-sendToDevice-fxa-button",
- display: "none",
- },
- {
- id: "page-action-no-devices-button",
- display: "none",
- disabled: true,
- },
- {
id: "page-action-sync-not-ready-button",
display: "none",
disabled: true,
},
];
for (let client of mockRemoteClients) {
expectedItems.push({
attrs: {
@@ -368,28 +416,29 @@ add_task(async function sendToDevice_dev
label: client.name,
clientType: client.type,
},
});
}
expectedItems.push(
null,
{
- label: "Send to All Devices",
+ attrs: {
+ label: "Send to All Devices"
+ }
}
);
checkSendToDeviceItems(expectedItems);
// Done, hide the panel.
let hiddenPromise = promisePageActionPanelHidden();
gPageActionPanel.hidePopup();
await hiddenPromise;
cleanUp();
- await UIState.reset();
});
});
function promiseSyncReady() {
let service = Cc["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
return service.whenLoaded().then(() => {
@@ -413,37 +462,17 @@ function checkSendToDeviceItems(expected
}
let display = "display" in expected ? expected.display : "-moz-box";
Assert.equal(getComputedStyle(actual).display, display);
let disabled = "disabled" in expected ? expected.disabled : false;
Assert.equal(actual.disabled, disabled);
if ("attrs" in expected) {
for (let name in expected.attrs) {
Assert.ok(actual.hasAttribute(name));
- Assert.equal(actual.getAttribute(name), expected.attrs[name]);
+ let attrVal = actual.getAttribute(name)
+ if (name == "label") {
+ attrVal = attrVal.normalize("NFKC"); // There's a bug with …
+ }
+ Assert.equal(attrVal, expected.attrs[name]);
}
}
}
}
-
-// Copied from test/sync/head.js (see bug 1369855)
-function mockReturn(obj, symbol, fixture) {
- let getter = Object.getOwnPropertyDescriptor(obj, symbol).get;
- if (getter) {
- Object.defineProperty(obj, symbol, {
- get() { return fixture; }
- });
- return {
- restore() {
- Object.defineProperty(obj, symbol, {
- get: getter
- });
- }
- }
- }
- let func = obj[symbol];
- obj[symbol] = () => fixture;
- return {
- restore() {
- obj[symbol] = func;
- }
- }
-}
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -34,16 +34,34 @@ syncStartNotification.body2 = %S will be
# These strings are used in a notification shown after Sync was disconnected remotely.
deviceDisconnectedNotification.title = Sync Disconnected
deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
# LOCALIZATION NOTE (sendToAllDevices.menuitem)
# Displayed in the Send Tab/Page/Link to Device context menu when right clicking a tab, a page or a link.
sendToAllDevices.menuitem = Send to All Devices
+# LOCALIZATION NOTE (sendTabToDevice.unconfigured, sendTabToDevice.unconfigured.status)
+# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link
+# and the Sync account is unconfigured. Redirects to a marketing page.
+sendTabToDevice.unconfigured.status = Not Connected to Sync
+sendTabToDevice.unconfigured = Learn About Sending Tabs…
+
+# LOCALIZATION NOTE (sendTabToDevice.singledevice, 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.
+sendTabToDevice.singledevice.status = No Devices Connected
+sendTabToDevice.singledevice = Learn About Sending Tabs…
+
+# 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,
# multipleTabsArrivingNotification.title, unnamedTabsArrivingNotification2.body,
# unnamedTabsArrivingNotificationMultiple2.body, unnamedTabsArrivingNotificationNoDevice.body)
# These strings are used in a notification shown when we're opening tab(s) another device sent us to display.
# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotificationWithDevice.title)
# The body for these is the URL of the tab recieved
tabArrivingNotification.title = Tab Received
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -983,11 +983,9 @@ you can use these alternative items. Oth
<!ENTITY updateRestart.cancelButton.label "Not Now">
<!ENTITY updateRestart.cancelButton.accesskey "N">
<!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
<!ENTITY pageActionButton.tooltip "Page actions">
<!ENTITY sendToDevice.label2 "Send to Device">
<!ENTITY sendToDevice.viewTitle "Send to Device">
-<!ENTITY sendToDevice.fxaRequired.label "Required">
-<!ENTITY sendToDevice.noDevices.label "No Devices Available">
<!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">