Bug 1288247 - Open synced tabs in a new tab on middle click. r?markh
MozReview-Commit-ID: LTE5NolY8V3
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -879,18 +879,17 @@ this.PlacesUIUtils = {
return itemId == this.leftPaneFolderId ||
itemId == this.allBookmarksFolderId;
},
/**
* Gives the user a chance to cancel loading lots of tabs at once
*/
- _confirmOpenInTabs:
- function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) {
+ confirmOpenInTabs(numTabsToOpen, aWindow) {
const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
var reallyOpen = true;
if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
// default to true: if it were false, we wouldn't get this far
var warnOnOpen = { value: true };
@@ -983,28 +982,28 @@ this.PlacesUIUtils = {
.then(aLivemark => {
urlsToOpen = [];
let nodes = aLivemark.getNodesForContainer(aNode);
for (let node of nodes) {
urlsToOpen.push({uri: node.uri, isBookmark: false});
}
- if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ if (this.confirmOpenInTabs(urlsToOpen.length, window)) {
this._openTabset(urlsToOpen, aEvent, window);
}
}, Cu.reportError);
},
openContainerNodeInTabs:
function PUIU_openContainerInTabs(aNode, aEvent, aView) {
let window = aView.ownerWindow;
let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
- if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ if (this.confirmOpenInTabs(urlsToOpen.length, window)) {
this._openTabset(urlsToOpen, aEvent, window);
}
},
openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) {
let window = aView.ownerWindow;
let urlsToOpen = [];
--- a/browser/components/syncedtabs/TabListComponent.js
+++ b/browser/components/syncedtabs/TabListComponent.js
@@ -8,16 +8,18 @@ const {classes: Cc, interfaces: Ci, util
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let log = Cu.import("resource://gre/modules/Log.jsm", {})
.Log.repository.getLogger("Sync.RemoteTabs");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
"resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm");
this.EXPORTED_SYMBOLS = [
"TabListComponent"
];
/**
* TabListComponent
*
@@ -42,16 +44,17 @@ TabListComponent.prototype = {
},
init() {
log.debug("Initializing TabListComponent");
this._view = new this._View(this._window, {
onSelectRow: (...args) => this.onSelectRow(...args),
onOpenTab: (...args) => this.onOpenTab(...args),
+ onOpenTabs: (...args) => this.onOpenTabs(...args),
onMoveSelectionDown: (...args) => this.onMoveSelectionDown(...args),
onMoveSelectionUp: (...args) => this.onMoveSelectionUp(...args),
onToggleBranch: (...args) => this.onToggleBranch(...args),
onBookmarkTab: (...args) => this.onBookmarkTab(...args),
onCopyTabLocation: (...args) => this.onCopyTabLocation(...args),
onSyncRefresh: (...args) => this.onSyncRefresh(...args),
onFilter: (...args) => this.onFilter(...args),
onClearFilter: (...args) => this.onClearFilter(...args),
@@ -108,16 +111,31 @@ TabListComponent.prototype = {
.catch(Cu.reportError);
},
onOpenTab(url, where, params) {
this._window.openUILinkIn(url, where, params);
BrowserUITelemetry.countSyncedTabEvent("open", "sidebar");
},
+ onOpenTabs(urls, where, params) {
+ if (!PlacesUIUtils.confirmOpenInTabs(urls.length, this._window)) {
+ return;
+ }
+ if (where == "window") {
+ this._window.openDialog(this._window.getBrowserURL(), "_blank",
+ "chrome,dialog=no,all", urls.join("|"));
+ } else {
+ for (let url of urls) {
+ this._window.openUILinkIn(url, where, params);
+ }
+ }
+ BrowserUITelemetry.countSyncedTabEvent("openmultiple", "sidebar");
+ },
+
onCopyTabLocation(url) {
this._clipboardHelper.copyString(url);
},
onSyncRefresh() {
this._SyncedTabs.syncTabs(true);
}
};
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -177,16 +177,17 @@ TabListView.prototype = {
this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this));
this.clearFilter.addEventListener("click", this.onClearFilter.bind(this));
this.searchIcon.addEventListener("click", this.onFilterFocus.bind(this));
},
// These listeners have to be re-created every time since we re-create the list
_attachListListeners() {
this.list.addEventListener("click", this.onClick.bind(this));
+ this.list.addEventListener("mouseup", this.onMouseUp.bind(this));
this.list.addEventListener("keydown", this.onKeyDown.bind(this));
},
_updateSearchBox(state) {
if (state.filter) {
this.searchBox.classList.add("filtered");
} else {
this.searchBox.classList.remove("filtered");
@@ -255,30 +256,47 @@ TabListView.prototype = {
itemNode.querySelector(".item-title").textContent = item.title;
if (item.icon) {
let icon = itemNode.querySelector(".item-icon-container");
icon.style.backgroundImage = "url(" + item.icon + ")";
}
},
+ onMouseUp(event) {
+ if (event.which == 2) { // Middle click
+ this.onClick(event);
+ }
+ },
+
onClick(event) {
let itemNode = this._findParentItemNode(event.target);
if (!itemNode) {
return;
}
if (itemNode.classList.contains("tab")) {
let url = itemNode.dataset.url;
if (url) {
this.onOpenSelected(url, event);
}
}
- if (event.target.classList.contains("item-twisty-container")) {
+ // 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, {});
+ }
+ }
+
+ if (event.target.classList.contains("item-twisty-container")
+ && event.which != 2) {
this.props.onToggleBranch(itemNode.dataset.id);
return;
}
let position = this._getSelectionPosition(itemNode);
this.props.onSelectRow(position);
},
--- a/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
+++ b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
@@ -3,16 +3,17 @@
let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
let { TabListComponent } = Cu.import("resource:///modules/syncedtabs/TabListComponent.js", {});
let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {});
let { View } = Cu.import("resource:///modules/syncedtabs/TabListView.js", {});
const ACTION_METHODS = [
"onSelectRow",
"onOpenTab",
+ "onOpenTabs",
"onMoveSelectionDown",
"onMoveSelectionUp",
"onToggleBranch",
"onBookmarkTab",
"onSyncRefresh",
"onFilter",
"onClearFilter",
"onFilterFocus",
@@ -72,16 +73,18 @@ add_task(function* testActions() {
};
let windowMock = {
top: {
PlacesCommandHook: {
bookmarkLink() { return Promise.resolve(); }
},
PlacesUtils: { bookmarksMenuFolderId: "id" }
},
+ getBrowserURL() {},
+ openDialog() {},
openUILinkIn() {}
};
let component = new TabListComponent({
window: windowMock, store, View: null, SyncedTabs,
clipboardHelper: clipboardHelperMock});
sinon.stub(store, "getData");
component.onFilter("query");
@@ -119,16 +122,23 @@ add_task(function* testActions() {
component.onBookmarkTab("uri", "title");
Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][1], "uri");
Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][2], "title");
sinon.spy(windowMock, "openUILinkIn");
component.onOpenTab("uri", "where", "params");
Assert.ok(windowMock.openUILinkIn.calledWith("uri", "where", "params"));
+ component.onOpenTabs(["uri1", "uri2"], "where", "params");
+ Assert.ok(windowMock.openUILinkIn.calledWith("uri1", "where", "params"));
+ Assert.ok(windowMock.openUILinkIn.calledWith("uri2", "where", "params"));
+ sinon.spy(windowMock, "openDialog");
+ component.onOpenTabs(["uri1", "uri2"], "window", "params");
+ Assert.deepEqual(windowMock.openDialog.args[0][3], ["uri1", "uri2"].join("|"));
+
sinon.spy(clipboardHelperMock, "copyString");
component.onCopyTabLocation("uri");
Assert.ok(clipboardHelperMock.copyString.calledWith("uri"));
sinon.stub(SyncedTabs, "syncTabs");
component.onSyncRefresh();
Assert.ok(SyncedTabs.syncTabs.calledWith(true));
SyncedTabs.syncTabs.restore();