Bug 1324895 - Add 'Open All in Tabs' to client context menu in Synced Tabs. r?markh
MozReview-Commit-ID: IFWtIfl0QZI
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -469,16 +469,19 @@
<menuseparator/>
<menuitem label="&syncedTabs.context.bookmarkSingleTab.label;"
accesskey="&syncedTabs.context.bookmarkSingleTab.accesskey;"
id="syncedTabsBookmarkSelected"/>
<menuitem label="&syncedTabs.context.copy.label;"
accesskey="&syncedTabs.context.copy.accesskey;"
id="syncedTabsCopySelected"/>
<menuseparator/>
+ <menuitem label="&syncedTabs.context.openAllInTabs.label;"
+ accesskey="&syncedTabs.context.openAllInTabs.accesskey;"
+ id="syncedTabsOpenAllInTabs"/>
<menuitem label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
id="syncedTabsRefresh"/>
</menupopup>
<menupopup id="SyncedTabsSidebarTabsFilterContext"
class="textbox-contextmenu">
<menuitem label="&undoCmd.label;"
accesskey="&undoCmd.accesskey;"
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -282,19 +282,17 @@ TabListView.prototype = {
this.onOpenSelected(url, event);
}
}
// Middle click on a client
if (itemNode.classList.contains("client")) {
let where = getChromeWindow(this._window).whereToOpenLink(event);
if (where != "current") {
- const tabs = itemNode.querySelector(".item-tabs-list").childNodes;
- const urls = [...tabs].map(tab => tab.dataset.url);
- this.props.onOpenTabs(urls, where);
+ this._openAllClientTabs(itemNode, where);
}
}
if (event.target.classList.contains("item-twisty-container")
&& event.which != 2) {
this.props.onToggleBranch(itemNode.dataset.id);
return;
}
@@ -350,16 +348,23 @@ TabListView.prototype = {
let where = event.target.getAttribute("where");
let params = {
private: event.target.hasAttribute("private"),
};
this.props.onOpenTab(item.dataset.url, where, params);
}
},
+ onOpenAllInTabs() {
+ let item = this._getSelectedClientNode();
+ if (item) {
+ this._openAllClientTabs(item, "tab");
+ }
+ },
+
onFilter(event) {
let query = event.target.value;
if (query) {
this.props.onFilter(query);
} else {
this.props.onClearFilter();
}
},
@@ -378,16 +383,24 @@ TabListView.prototype = {
_getSelectedTabNode() {
let item = this.container.querySelector('.item.selected');
if (this._isTab(item) && item.dataset.url) {
return item;
}
return null;
},
+ _getSelectedClientNode() {
+ let item = this.container.querySelector('.item.selected');
+ if (this._isClient(item)) {
+ return item;
+ }
+ return null;
+ },
+
// Set up the custom context menu
_setupContextMenu() {
Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
let menu = getMenu(this._window);
menu.addEventListener("popupshowing", this, true);
menu.addEventListener("command", this, true);
}
@@ -456,16 +469,19 @@ TabListView.prototype = {
let id = event.target.getAttribute("id");
switch (id) {
case "syncedTabsOpenSelected":
case "syncedTabsOpenSelectedInTab":
case "syncedTabsOpenSelectedInWindow":
case "syncedTabsOpenSelectedInPrivateWindow":
this.onOpenSelectedFromContextMenu(event);
break;
+ case "syncedTabsOpenAllInTabs":
+ this.onOpenAllInTabs();
+ break;
case "syncedTabsBookmarkSelected":
this.onBookmarkTab();
break;
case "syncedTabsCopySelected":
this.onCopyTabLocation();
break;
case "syncedTabsRefresh":
case "syncedTabsRefreshFilter":
@@ -501,21 +517,28 @@ TabListView.prototype = {
adjustContextMenu(menu) {
let item = this.container.querySelector('.item.selected');
let showTabOptions = this._isTab(item);
let el = menu.firstChild;
while (el) {
- if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") {
- el.hidden = false;
- } else {
- el.hidden = true;
+ let show = false;
+ if (showTabOptions) {
+ if (el.getAttribute("id") != "syncedTabsOpenAllInTabs") {
+ show = true;
+ }
+ } else if (el.getAttribute("id") == "syncedTabsOpenAllInTabs") {
+ const tabs = item.querySelectorAll(".item-tabs-list > .item.tab");
+ show = tabs.length > 0;
+ } else if (el.getAttribute("id") == "syncedTabsRefresh") {
+ show = true;
}
+ el.hidden = !show;
el = el.nextSibling;
}
},
/**
* Find the parent item element, from a given child element.
* @param {Element} node - Child element.
@@ -559,10 +582,20 @@ TabListView.prototype = {
},
_indexOfNode(parent, child) {
return Array.prototype.indexOf.call(parent.childNodes, child);
},
_isTab(item) {
return item && item.classList.contains("tab");
+ },
+
+ _isClient(item) {
+ return item && item.classList.contains("client");
+ },
+
+ _openAllClientTabs(clientNode, where) {
+ const tabs = clientNode.querySelector(".item-tabs-list").childNodes;
+ const urls = [...tabs].map(tab => tab.dataset.url);
+ this.props.onOpenTabs(urls, where);
}
};
--- a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
+++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
@@ -305,39 +305,59 @@ add_task(function* testSyncedTabsSidebar
["menuitem#syncedTabsOpenSelected", { hidden: false }],
["menuitem#syncedTabsOpenSelectedInTab", { hidden: false }],
["menuitem#syncedTabsOpenSelectedInWindow", { hidden: false }],
["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: false }],
["menuseparator", { hidden: false }],
["menuitem#syncedTabsBookmarkSelected", { hidden: false }],
["menuitem#syncedTabsCopySelected", { hidden: false }],
["menuseparator", { hidden: false }],
+ ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }],
["menuitem#syncedTabsRefresh", { hidden: false }],
];
yield* testContextMenu(syncedTabsDeckComponent,
"#SyncedTabsSidebarContext",
"#tab-7cqCr77ptzX3-0",
tabMenuItems);
- info("Right-clicking a client shouldn't show any actions");
+ info("Right-clicking a client should show the Open All in Tabs action");
let sidebarMenuItems = [
["menuitem#syncedTabsOpenSelected", { hidden: true }],
["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }],
["menuitem#syncedTabsOpenSelectedInWindow", { hidden: true }],
["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: true }],
["menuseparator", { hidden: true }],
["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
["menuitem#syncedTabsCopySelected", { hidden: true }],
["menuseparator", { hidden: true }],
+ ["menuitem#syncedTabsOpenAllInTabs", { hidden: false }],
+ ["menuitem#syncedTabsRefresh", { hidden: false }],
+ ];
+ yield* testContextMenu(syncedTabsDeckComponent,
+ "#SyncedTabsSidebarContext",
+ "#item-7cqCr77ptzX3",
+ sidebarMenuItems);
+
+ info("Right-clicking a client without any tabs should not show the Open All in Tabs action");
+ let menuItems = [
+ ["menuitem#syncedTabsOpenSelected", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: true }],
+ ["menuseparator", { hidden: true }],
+ ["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
+ ["menuitem#syncedTabsCopySelected", { hidden: true }],
+ ["menuseparator", { hidden: true }],
+ ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }],
["menuitem#syncedTabsRefresh", { hidden: false }],
];
yield* testContextMenu(syncedTabsDeckComponent,
"#SyncedTabsSidebarContext",
"#item-OL3EJCsdb2JD",
- sidebarMenuItems);
+ menuItems);
});
add_task(testClean);
function checkItem(node, item) {
Assert.ok(node.classList.contains("item"),
"Node should have .item class");
if (item.client) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -751,29 +751,36 @@ 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.">
+<!-- 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">
<!ENTITY syncedTabs.context.openInNewTab.accesskey "w">
<!ENTITY syncedTabs.context.openInNewWindow.label "Open in a New Window">
<!ENTITY syncedTabs.context.openInNewWindow.accesskey "N">
<!ENTITY syncedTabs.context.openInNewPrivateWindow.label "Open in a New Private Window">
<!ENTITY syncedTabs.context.openInNewPrivateWindow.accesskey "P">
<!ENTITY syncedTabs.context.bookmarkSingleTab.label "Bookmark This Tab…">
<!ENTITY syncedTabs.context.bookmarkSingleTab.accesskey "B">
<!ENTITY syncedTabs.context.copy.label "Copy">
<!ENTITY syncedTabs.context.copy.accesskey "C">
+<!ENTITY syncedTabs.context.openAllInTabs.label "Open All in Tabs">
+<!ENTITY syncedTabs.context.openAllInTabs.accesskey "O">
+
<!ENTITY syncBrand.shortName.label "Sync">
<!ENTITY syncSignIn.label "Sign In To &syncBrand.shortName.label;…">
<!ENTITY syncSignIn.accesskey "Y">
<!ENTITY syncSyncNowItem.label "Sync Now">
<!ENTITY syncSyncNowItem.accesskey "S">
<!ENTITY syncReAuthItem.label "Reconnect to &syncBrand.shortName.label;…">