From d7a8b85f4cd027d3bcb3f8ea40f6d09177d8114b Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Wed, 5 Sep 2018 14:16:59 +0000 Subject: [PATCH] Bug 1480907 - Implement ability to bookmark a selection of tabs through drag and drop. r=Felipe Differential Revision: https://phabricator.services.mozilla.com/D4589 --HG-- extra : moz-landing-system : lando --- browser/base/content/tabbrowser.xml | 73 +++++++++++-------- browser/base/content/test/tabs/browser.ini | 1 + ...tiselect_tabs_drag_to_bookmarks_toolbar.js | 59 +++++++++++++++ 3 files changed, 104 insertions(+), 29 deletions(-) create mode 100644 browser/base/content/test/tabs/browser_multiselect_tabs_drag_to_bookmarks_toolbar.js diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index 4541db608325..8caedc54717e 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -826,28 +826,35 @@ 0; + for (let i = 0; i < dt.mozItemCount; i++) { // tabs are always added as the first type - if (types[0] == TAB_DROP_TYPE) { - let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0); - if (sourceNode instanceof XULElement && - sourceNode.localName == "tab" && - sourceNode.ownerGlobal.isChromeWindow && - sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" && - sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.parentNode) { - // Do not allow transfering a private tab to a non-private window - // and vice versa. - if (PrivateBrowsingUtils.isWindowPrivate(window) != - PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerGlobal)) - return "none"; + let types = dt.mozTypesAt(0); + if (types[0] != TAB_DROP_TYPE) { + isMovingTabs = false; + break; + } + } - if (window.gMultiProcessBrowser != - sourceNode.ownerGlobal.gMultiProcessBrowser) - return "none"; + if (isMovingTabs) { + let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + if (sourceNode instanceof XULElement && + sourceNode.localName == "tab" && + sourceNode.ownerGlobal.isChromeWindow && + sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" && + sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.parentNode) { + // Do not allow transfering a private tab to a non-private window + // and vice versa. + if (PrivateBrowsingUtils.isWindowPrivate(window) != + PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerGlobal)) + return "none"; - return dt.dropEffect == "copy" ? "copy" : "move"; - } + if (window.gMultiProcessBrowser != + sourceNode.ownerGlobal.gMultiProcessBrowser) + return "none"; + + return dt.dropEffect == "copy" ? "copy" : "move"; } } @@ -1199,14 +1206,22 @@ if (!tab || this._isCustomizing) return; - let dt = event.dataTransfer; - dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0); - let browser = tab.linkedBrowser; + let selectedTabs = gBrowser.selectedTabs; + let otherSelectedTabs = selectedTabs.filter(selectedTab => selectedTab != tab); + let dataTransferOrderedTabs = [tab].concat(otherSelectedTabs); - // We must not set text/x-moz-url or text/plain data here, - // otherwise trying to deatch the tab by dropping it on the desktop - // may result in an "internet shortcut" - dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0); + let dt = event.dataTransfer; + for (let i = 0; i < dataTransferOrderedTabs.length; i++) { + let dtTab = dataTransferOrderedTabs[i]; + + dt.mozSetDataAt(TAB_DROP_TYPE, dtTab, i); + let dtBrowser = dtTab.linkedBrowser; + + // We must not set text/x-moz-url or text/plain data here, + // otherwise trying to detach the tab by dropping it on the desktop + // may result in an "internet shortcut" + dt.mozSetDataAt("text/x-moz-text-internal", dtBrowser.currentURI.spec, i); + } // Set the cursor to an arrow during tab drags. dt.mozCursor = "default"; @@ -1215,10 +1230,9 @@ // node to deliver the `dragend` event. See bug 1345473. dt.addElement(tab); - // Regroup all selected tabs around the dragged tab - // for multiple tabs dragging if (tab.multiselected) { - let selectedTabs = gBrowser.selectedTabs; + // Regroup all selected tabs around the dragged tab + // for multiple tabs dragging let draggedTabPos = tab._tPos; // Move left selected tabs @@ -1257,6 +1271,7 @@ canvas.height = 90 * scale; let toDrag = canvas; let dragImageOffset = -16; + let browser = tab.linkedBrowser; if (gMultiProcessBrowser) { var context = canvas.getContext("2d"); context.fillStyle = "white"; diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini index daf5103fd784..a2dc9b9a7e4a 100644 --- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -27,6 +27,7 @@ support-files = [browser_multiselect_tabs_close_using_shortcuts.js] [browser_multiselect_tabs_close.js] [browser_multiselect_tabs_copy_through_drag_and_drop.js] +[browser_multiselect_tabs_drag_to_bookmarks_toolbar.js] [browser_multiselect_tabs_duplicate.js] [browser_multiselect_tabs_event.js] [browser_multiselect_tabs_move_to_another_window_drag.js] diff --git a/browser/base/content/test/tabs/browser_multiselect_tabs_drag_to_bookmarks_toolbar.js b/browser/base/content/test/tabs/browser_multiselect_tabs_drag_to_bookmarks_toolbar.js new file mode 100644 index 000000000000..fd398e0aa873 --- /dev/null +++ b/browser/base/content/test/tabs/browser_multiselect_tabs_drag_to_bookmarks_toolbar.js @@ -0,0 +1,59 @@ +const PREF_MULTISELECT_TABS = "browser.tabs.multiselect"; +const PREF_ANIMATIONS_ENABLED = "toolkit.cosmeticAnimations.enabled"; + +add_task(async function setPref() { + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_MULTISELECT_TABS, true], + [PREF_ANIMATIONS_ENABLED, false], + ], + }); +}); + +add_task(async function test() { + // Open Bookmarks Toolbar + let bookmarksToolbar = document.getElementById("PersonalToolbar"); + setToolbarVisibility(bookmarksToolbar, true); + ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now"); + + let tab1 = await addTab("http://mochi.test:8888/1"); + let tab2 = await addTab("http://mochi.test:8888/2"); + let tab3 = await addTab("http://mochi.test:8888/3"); + let tab4 = await addTab("http://mochi.test:8888/4"); + let tab5 = await addTab("http://mochi.test:8888/5"); + + is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs"); + + await BrowserTestUtils.switchTab(gBrowser, tab2); + await triggerClickOn(tab1, { ctrlKey: true }); + + ok(tab1.multiselected, "Tab1 is multiselected"); + ok(tab2.multiselected, "Tab2 is multiselected"); + ok(!tab3.multiselected, "Tab3 is not multiselected"); + ok(!tab4.multiselected, "Tab4 is not multiselected"); + ok(!tab5.multiselected, "Tab5 is not multiselected"); + is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs"); + + // Use getElementsByClassName so the list is live and will update as items change. + let currentBookmarks = bookmarksToolbar.getElementsByClassName("bookmark-item"); + let startBookmarksLength = currentBookmarks.length; + + let lastBookmark = currentBookmarks[currentBookmarks.length - 1]; + await EventUtils.synthesizePlainDragAndDrop({srcElement: tab1, destElement: lastBookmark}); + await TestUtils.waitForCondition(() => currentBookmarks.length == startBookmarksLength + 2, + "waiting for 2 bookmarks"); + is(currentBookmarks.length, startBookmarksLength + 2, "Bookmark count should have increased by 2"); + + // Drag non-selection to the bookmarks toolbar + startBookmarksLength = currentBookmarks.length; + await EventUtils.synthesizePlainDragAndDrop({srcElement: tab3, destElement: lastBookmark}); + await TestUtils.waitForCondition(() => currentBookmarks.length == startBookmarksLength + 1, + "waiting for 1 bookmark"); + is(currentBookmarks.length, startBookmarksLength + 1, "Bookmark count should have increased by 1"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(tab3); + BrowserTestUtils.removeTab(tab4); + BrowserTestUtils.removeTab(tab5); +});