Bug 1480907 - Implement ability to bookmark a selection of tabs through drag and drop. r=Felipe
authorJared Wein <jwein@mozilla.com>
Wed, 05 Sep 2018 14:16:59 +0000
changeset 434995 7056aff16fb8124f1d6043538b9947017c4623e4
parent 434994 e88260ac431b47c80fc5b051421a1978250bdd05
child 434996 2f4adf14e6231a1668558dd78ecbe56a421591b6
push id107530
push userapavel@mozilla.com
push dateThu, 06 Sep 2018 04:44:27 +0000
treeherdermozilla-inbound@5f5d7a3ce332 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFelipe
bugs1480907
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1480907 - Implement ability to bookmark a selection of tabs through drag and drop. r=Felipe Differential Revision: https://phabricator.services.mozilla.com/D4589
browser/base/content/tabbrowser.xml
browser/base/content/test/tabs/browser.ini
browser/base/content/test/tabs/browser_multiselect_tabs_drag_to_bookmarks_toolbar.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -821,38 +821,45 @@
           return tabs.length;
         ]]></body>
       </method>
 
       <method name="_getDropEffectForTabDrag">
         <parameter name="event"/>
         <body><![CDATA[
           var dt = event.dataTransfer;
-          if (dt.mozItemCount == 1) {
-            var types = dt.mozTypesAt(0);
+
+          let isMovingTabs = dt.mozItemCount > 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";
             }
           }
 
           if (browserDragAndDrop.canDropLink(event)) {
             return "link";
           }
           return "none";
         ]]></body>
@@ -1194,36 +1201,43 @@
         event.preventDefault();
       ]]></handler>
 
       <handler event="dragstart"><![CDATA[
         var tab = this._getDragTargetTab(event, false);
         if (!tab || this._isCustomizing)
           return;
 
+        let selectedTabs = gBrowser.selectedTabs;
+        let otherSelectedTabs = selectedTabs.filter(selectedTab => selectedTab != tab);
+        let dataTransferOrderedTabs = [tab].concat(otherSelectedTabs);
+
         let dt = event.dataTransfer;
-        dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
-        let browser = tab.linkedBrowser;
+        for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
+          let dtTab = dataTransferOrderedTabs[i];
 
-        // 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);
+          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";
 
         // Set the tab as the source of the drag, which ensures we have a stable
         // 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
           let insertAtPos = draggedTabPos - 1;
           for (let i = selectedTabs.indexOf(tab) - 1; i > -1; i--) {
             let movingTab = selectedTabs[i];
             gBrowser.moveTabTo(movingTab, insertAtPos);
             insertAtPos--;
@@ -1252,16 +1266,17 @@
           canvas.style.height = "100%";
           canvas.mozOpaque = true;
         }
 
         canvas.width = 160 * scale;
         canvas.height = 90 * scale;
         let toDrag = canvas;
         let dragImageOffset = -16;
+        let browser = tab.linkedBrowser;
         if (gMultiProcessBrowser) {
           var context = canvas.getContext("2d");
           context.fillStyle = "white";
           context.fillRect(0, 0, canvas.width, canvas.height);
 
           let captureListener;
           let platform = AppConstants.platform;
           // On Windows and Mac we can update the drag image during a drag
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -22,16 +22,17 @@ support-files =
 [browser_multiselect_tabs_active_tab_selected_by_default.js]
 [browser_multiselect_tabs_bookmark.js]
 [browser_multiselect_tabs_clear_selection_when_tab_switch.js]
 [browser_multiselect_tabs_close_other_tabs.js]
 [browser_multiselect_tabs_close_tabs_to_the_right.js]
 [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]
 [browser_multiselect_tabs_move_to_new_window_contextmenu.js]
 [browser_multiselect_tabs_mute_unmute.js]
 [browser_multiselect_tabs_pin_unpin.js]
 [browser_multiselect_tabs_positional_attrs.js]
 [browser_multiselect_tabs_reload.js]
new file mode 100644
--- /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);
+});