Skip to content

Commit

Permalink
Bug 677372 - Send Tab/Page/Link to device. r=markh, a=KWierso
Browse files Browse the repository at this point in the history
MozReview-Commit-ID: 6xnBjTPkiUQ
MozReview-Commit-ID: D7sdRrsWRCF
  • Loading branch information
eoger committed Jun 27, 2016
1 parent a914a01 commit fa92e32
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 15 deletions.
6 changes: 6 additions & 0 deletions browser/app/profile/firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,12 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
// user's tabs and bookmarks. Note this pref is also synced.
pref("services.sync.syncedTabs.showRemoteIcons", true);

#ifdef NIGHTLY_BUILD
pref("services.sync.sendTabToDevice.enabled", true);
#else
pref("services.sync.sendTabToDevice.enabled", false);
#endif

// Developer edition preferences
#ifdef MOZ_DEV_EDITION
sticky_pref("lightweightThemes.selectedThemeID", "[email protected]");
Expand Down
16 changes: 16 additions & 0 deletions browser/base/content/browser-context.inc
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@
label="&savePageCmd.label;"
accesskey="&savePageCmd.accesskey2;"
oncommand="gContextMenu.savePageAs();"/>
<menuseparator id="context-sep-sendpagetodevice" hidden="true"/>
<menu id="context-sendpagetodevice"
label="&sendPageToDevice.label;"
accesskey="&sendPageToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendpagetodevice-popup"
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
</menu>
<menu id="context-markpageMenu" label="&social.markpageMenu.label;"
accesskey="&social.markpageMenu.accesskey;">
<menupopup/>
Expand Down Expand Up @@ -326,6 +334,14 @@
oncommand="AddKeywordForSearchField();"/>
<menuitem id="context-searchselect"
oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
<menuseparator id="context-sep-sendlinktodevice" hidden="true"/>
<menu id="context-sendlinktodevice"
label="&sendLinkToDevice.label;"
accesskey="&sendLinkToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendlinktodevice-popup"
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
</menu>
<menuitem id="context-shareselect"
label="&shareSelect.label;"
accesskey="&shareSelect.accesskey;"
Expand Down
84 changes: 84 additions & 0 deletions browser/base/content/browser-fxaccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ var gFxAccounts = {
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},

get sendTabToDeviceEnabled() {
return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
},

get remoteClients() {
return Weave.Service.clientsEngine.remoteClients
.sort((a, b) => a.name.localeCompare(b.name));
},

init: function () {
// Bail out if we're already initialized and for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
Expand Down Expand Up @@ -361,6 +370,81 @@ var gFxAccounts = {
openSignInAgainPage: function (entryPoint) {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},

sendTabToDevice: function (url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
},

populateSendTabToDevicesMenu: function (devicesPopup, url, title) {
// remove existing menu items
while (devicesPopup.hasChildNodes()) {
devicesPopup.removeChild(devicesPopup.firstChild);
}

const fragment = document.createDocumentFragment();

const onTargetDeviceCommand = (event) => {
const clientId = event.target.getAttribute("clientId");
const clients = clientId
? [clientId]
: this.remoteClients.map(client => client.id);

clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
}

function addTargetDevice(clientId, name) {
const targetDevice = document.createElement("menuitem");
targetDevice.addEventListener("command", onTargetDeviceCommand, true);
targetDevice.setAttribute("class", "sendtab-target");
targetDevice.setAttribute("clientId", clientId);
targetDevice.setAttribute("label", name);
fragment.appendChild(targetDevice);
}

const clients = this.remoteClients;
for (let client of clients) {
addTargetDevice(client.id, client.name);
}

// "All devices" menu item
const separator = document.createElement("menuseparator");
fragment.appendChild(separator);
const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);

devicesPopup.appendChild(fragment);
},

updateTabContextMenu: function (aPopupMenu) {
if (!this.sendTabToDeviceEnabled) {
return;
}

const remoteClientPresent = this.remoteClients.length > 0;
["context_sendTabToDevice", "context_sendTabToDevice_separator"]
.forEach(id => { document.getElementById(id).hidden = !remoteClientPresent });
},

initPageContextMenu: function (contextMenu) {
if (!this.sendTabToDeviceEnabled) {
return;
}

const remoteClientPresent = this.remoteClients.length > 0;
// showSendLink and showSendPage are mutually exclusive
const showSendLink = remoteClientPresent
&& (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
const showSendPage = !showSendLink && remoteClientPresent
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
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));
}
};

XPCOMUtils.defineLazyGetter(gFxAccounts, "FxAccountsCommon", function () {
Expand Down
2 changes: 2 additions & 0 deletions browser/base/content/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7621,6 +7621,8 @@ var TabContextMenu = {

this.contextTab.addEventListener("TabAttrModified", this, false);
aPopupMenu.addEventListener("popuphiding", this, false);

gFxAccounts.updateTabContextMenu(aPopupMenu);
},
handleEvent(aEvent) {
switch (aEvent.type) {
Expand Down
6 changes: 6 additions & 0 deletions browser/base/content/browser.xul
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
hidden="true"
oncommand="gBrowser.openNonRemoteWindow(TabContextMenu.contextTab);"/>
#endif
<menuseparator id="context_sendTabToDevice_separator" hidden="true"/>
<menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
accesskey="&sendTabToDevice.accesskey;" hidden="true">
<menupopup id="context_sendTabToDevicePopupMenu"
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
</menu>
<menuseparator/>
<menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
tbattr="tabbrowser-multiple-visible"
Expand Down
5 changes: 5 additions & 0 deletions browser/base/content/nsContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ nsContextMenu.prototype = {
this.initLeaveDOMFullScreenItems();
this.initClickToPlayItems();
this.initPasswordManagerItems();
this.initSyncItems();
},

initPageMenuSeparator: function CM_initPageMenuSeparator() {
Expand Down Expand Up @@ -576,6 +577,10 @@ nsContextMenu.prototype = {
popup.insertBefore(fragment, insertBeforeElement);
},

initSyncItems: function() {
gFxAccounts.initPageContextMenu(this);
},

openPasswordManager: function() {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
},
Expand Down
74 changes: 74 additions & 0 deletions browser/base/content/test/general/browser_contextmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,80 @@ add_task(function* test_input_spell_false() {
);
});

const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];

add_task(function* test_plaintext_sendpagetodevice() {
if (!gFxAccounts.sendTabToDeviceEnabled) {
return;
}
const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);

let 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,
["*Foo", true,
"*Bar", true,
"---", null,
"*All Devices", true], null,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true
];
yield test_contextmenu("#test-text", plainTextItems, {
onContextMenuShown() {
yield openMenuItemSubmenu("context-sendpagetodevice");
}
});

restoreRemoteClients(oldGetter);
});

add_task(function* test_link_sendlinktodevice() {
if (!gFxAccounts.sendTabToDeviceEnabled) {
return;
}
const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);

yield test_contextmenu("#test-link",
["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,
"---", null,
"context-sendlinktodevice", true,
["*Foo", true,
"*Bar", true,
"---", null,
"*All Devices", true], null,
],
{
onContextMenuShown() {
yield openMenuItemSubmenu("context-sendlinktodevice");
}
});

restoreRemoteClients(oldGetter);
});

add_task(function* test_cleanup() {
gBrowser.removeCurrentTab();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* 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/. */

const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];

add_task(function* test() {
// There should be one tab when we start the test
let [origTab] = gBrowser.visibleTabs;
Expand All @@ -14,6 +16,21 @@ add_task(function* test() {
is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled");
is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");


if (gFxAccounts.sendTabToDeviceEnabled) {
// Check the send tab to device menu item
const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
yield updateTabContextMenu(origTab, function* () {
yield openMenuItemSubmenu("context_sendTabToDevice");
});
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
let targets = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
restoreRemoteClients(oldGetter);
}

// Hide the original tab.
gBrowser.selectedTab = testTab;
gBrowser.showOnlyTheseTabs([testTab]);
Expand Down
33 changes: 20 additions & 13 deletions browser/base/content/test/general/contextmenu_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,24 @@ function getVisibleMenuItems(aMenu, aData) {
if (key)
key = key.toLowerCase();

var isGenerated = item.hasAttribute("generateditemid");
var isPageMenuItem = item.hasAttribute("generateditemid");

if (item.nodeName == "menuitem") {
var isSpellSuggestion = item.className == "spell-suggestion";
if (isSpellSuggestion) {
is(item.id, "", "child menuitem #" + i + " is a spelling suggestion");
} else if (isGenerated) {
is(item.id, "", "child menuitem #" + i + " is a generated item");
var isGenerated = item.className == "spell-suggestion"
|| item.className == "sendtab-target";
if (isGenerated) {
is(item.id, "", "child menuitem #" + i + " is generated");
} else if (isPageMenuItem) {
is(item.id, "", "child menuitem #" + i + " is a generated page menu item");
} else {
ok(item.id, "child menuitem #" + i + " has an ID");
}
var label = item.getAttribute("label");
ok(label.length, "menuitem " + item.id + " has a label");
if (isSpellSuggestion) {
is(key, "", "Spell suggestions shouldn't have an access key");
if (isGenerated) {
is(key, "", "Generated items shouldn't have an access key");
items.push("*" + label);
} else if (isGenerated) {
} else if (isPageMenuItem) {
items.push("+" + label);
} else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
item.id != "spell-no-suggestions" &&
Expand All @@ -71,10 +72,10 @@ function getVisibleMenuItems(aMenu, aData) {
else
accessKeys[key] = item.id;
}
if (!isSpellSuggestion && !isGenerated) {
if (!isGenerated && !isPageMenuItem) {
items.push(item.id);
}
if (isGenerated) {
if (isPageMenuItem) {
var p = {};
p.type = item.getAttribute("type");
p.icon = item.getAttribute("image");
Expand All @@ -89,11 +90,11 @@ function getVisibleMenuItems(aMenu, aData) {
items.push("---");
items.push(null);
} else if (item.nodeName == "menu") {
if (isGenerated) {
if (isPageMenuItem) {
item.id = "generated-submenu-" + aData.generatedSubmenuId++;
}
ok(item.id, "child menu #" + i + " has an ID");
if (!isGenerated) {
if (!isPageMenuItem) {
ok(key, "menu has an access key");
if (accessKeys[key])
ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
Expand Down Expand Up @@ -242,6 +243,7 @@ let lastElementSelector = null;
* waitForSpellCheck: wait until spellcheck is initialized before
* starting test
* preCheckContextMenuFn: callback to run before opening menu
* onContextMenuShown: callback to run when the context menu is shown
* postCheckContextMenuFn: callback to run after opening menu
* @return {Promise} resolved after the test finishes
*/
Expand Down Expand Up @@ -295,6 +297,11 @@ function* test_contextmenu(selector, menuItems, options={}) {
yield awaitPopupShown;
info("Popup Shown");

if (options.onContextMenuShown) {
yield options.onContextMenuShown();
info("Completed onContextMenuShown");
}

if (menuItems) {
if (Services.prefs.getBoolPref("devtools.inspector.enabled")) {
let inspectItems = ["---", null,
Expand Down
Loading

0 comments on commit fa92e32

Please sign in to comment.