Bug 1458056 - Implement ability to move a selection of tabs into another window through drag and drop. r=jaws
authorAbdoulaye O. Ly <ablayelyfondou@gmail.com>
Thu, 09 Aug 2018 18:45:52 +0000
changeset 430803 5ad7b28b1009149d1fb27ed6b8c5e10d2b4727ec
parent 430802 0260bc2917ddcb8166a498c6b37d643b20282276
child 430841 25a65f1051c29d44aca7ecfe6af6bfada92a912f
child 430843 9f6a722db7b300f4cd25be5dfe42c2c43c9882e0
push id34411
push usernerli@mozilla.com
push dateThu, 09 Aug 2018 21:30:25 +0000
treeherdermozilla-central@5ad7b28b1009 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs1458056
milestone63.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 1458056 - Implement ability to move a selection of tabs into another window through drag and drop. r=jaws MozReview-Commit-ID: LFFoE6HsBp8 Differential Revision: https://phabricator.services.mozilla.com/D2986
browser/base/content/tabbrowser.js
browser/base/content/tabbrowser.xml
browser/base/content/test/tabs/browser.ini
browser/base/content/test/tabs/browser_multiselect_tabs_copy_through_drag_and_drop.js
browser/base/content/test/tabs/browser_multiselect_tabs_move_to_another_window_drag.js
browser/base/content/test/tabs/head.js
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -3482,16 +3482,22 @@ window._gBrowser = {
     firstInactiveTab.linkedBrowser.addEventListener("EndSwapDocShells", function() {
       for (let i = 1; i < inactiveTabs.length; i++) {
         win.gBrowser.adoptTab(inactiveTabs[i], i);
       }
 
       if (activeTabNewIndex > -1) {
         win.gBrowser.adoptTab(activeTab, activeTabNewIndex, true /* aSelectTab */);
       }
+
+      // Restore tab selection
+      let winVisibleTabs = win.gBrowser.visibleTabs;
+      let winTabLength = winVisibleTabs.length;
+      win.gBrowser.addRangeToMultiSelectedTabs(winVisibleTabs[0],
+                                               winVisibleTabs[winTabLength - 1]);
     }, { once: true });
 
     win = this.replaceTabWithWindow(firstInactiveTab);
     return win;
   },
 
   _updateTabsAfterInsert() {
     for (let i = 0; i < this.tabs.length; i++) {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1474,17 +1474,24 @@
                 gBrowser.moveTabTo(tab, dropIndex);
                 if (incrementDropIndex)
                   dropIndex++;
               }
             }
           }
         } else if (draggedTab) {
           let newIndex = this._getDropIndex(event, false);
-          gBrowser.adoptTab(draggedTab, newIndex, true);
+          let newTabs = [];
+          for (let tab of movingTabs) {
+            let newTab = gBrowser.adoptTab(tab, newIndex++, tab == draggedTab);
+            newTabs.push(newTab);
+          }
+
+          // Restore tab selection
+          gBrowser.addRangeToMultiSelectedTabs(newTabs[0], newTabs[newTabs.length - 1]);
         } else {
           // Pass true to disallow dropping javascript: or data: urls
           let links;
           try {
             links = browserDragAndDrop.dropLinks(event, true);
           } catch (ex) {}
 
           if (!links || links.length === 0)
@@ -1601,17 +1608,17 @@
           window.moveTo(left, top);
           window.focus();
         } else {
           let props = { screenX: left, screenY: top, suppressanimation: 1 };
           if (AppConstants.platform != "win") {
             props.outerWidth = winWidth;
             props.outerHeight = winHeight;
           }
-          gBrowser.replaceTabWithWindow(draggedTab, props);
+          gBrowser.replaceTabsWithWindow(draggedTab, props);
         }
         event.stopPropagation();
       ]]></handler>
 
       <handler event="dragexit"><![CDATA[
         this._dragTime = 0;
 
         // This does not work at all (see bug 458613)
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -23,16 +23,17 @@ support-files =
 [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_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]
 [browser_multiselect_tabs_reorder.js]
 [browser_multiselect_tabs_using_Ctrl.js]
 [browser_multiselect_tabs_using_selectedTabs.js]
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_copy_through_drag_and_drop.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_copy_through_drag_and_drop.js
@@ -1,12 +1,9 @@
 const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
-function url(tab) {
-  return tab.linkedBrowser.currentURI.spec;
-}
 
 add_task(async function setPref() {
   await SpecialPowers.pushPrefEnv({
     set: [[PREF_MULTISELECT_TABS, true]]
   });
 });
 
 add_task(async function test() {
@@ -43,17 +40,17 @@ add_task(async function test() {
 
   for (let i of [1, 2]) {
     ok(tabs[i].multiselected, "Tab" + i + " is multiselected");
   }
   for (let i of [0, 3, 4, 5]) {
     ok(!tabs[i].multiselected, "Tab" + i + " is not multiselected");
   }
 
-  await BrowserTestUtils.waitForCondition(() => url(tab4) == url(tab1));
-  await BrowserTestUtils.waitForCondition(() => url(tab5) == url(tab2));
+  await BrowserTestUtils.waitForCondition(() => getUrl(tab4) == getUrl(tab1));
+  await BrowserTestUtils.waitForCondition(() => getUrl(tab5) == getUrl(tab2));
 
   ok(true, "Tab1 and tab2 are duplicated succesfully");
 
   for (let tab of tabs.filter(t => t != tab0))
     BrowserTestUtils.removeTab(tab);
 });
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_move_to_another_window_drag.js
@@ -0,0 +1,71 @@
+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() {
+  let tab1 = await addTab();
+  let tab2 = await addTab();
+  let tab3 = await addTab("http://mochi.test:8888/3");
+  let tab4 = await addTab();
+  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");
+
+  let newWindow = gBrowser.replaceTabsWithWindow(tab1);
+
+  // waiting for tab2 to close ensure that the newWindow is created,
+  // thus newWindow.gBrowser used in the second waitForCondition
+  // will not be undefined.
+  await TestUtils.waitForCondition(() => tab2.closing, "Wait for tab2 to close");
+  await TestUtils.waitForCondition(() => newWindow.gBrowser.visibleTabs.length == 2,
+    "Wait for all two tabs to get moved to the new window");
+
+  let gBrowser2 = newWindow.gBrowser;
+  tab1 = gBrowser2.visibleTabs[0];
+  tab2 = gBrowser2.visibleTabs[1];
+
+  if (gBrowser.selectedTab != tab3)
+    await BrowserTestUtils.switchTab(gBrowser, tab3);
+
+  await triggerClickOn(tab5, { ctrlKey: true });
+
+  ok(tab1.multiselected, "Tab1 is multiselected");
+  ok(tab2.multiselected, "Tab2 is multiselected");
+  ok(tab3.multiselected, "Tab3 is multiselected");
+  ok(!tab4.multiselected, "Tab4 is not multiselected");
+  ok(tab5.multiselected, "Tab5 is multiselected");
+
+  await dragAndDrop(tab3, tab1, false, newWindow);
+
+  await TestUtils.waitForCondition(() => gBrowser2.visibleTabs.length == 4,
+    "Moved tab3 and tab5 to second window");
+
+  tab3 = gBrowser2.visibleTabs[1];
+  tab5 = gBrowser2.visibleTabs[2];
+
+  await BrowserTestUtils.waitForCondition(() => getUrl(tab3) == "http://mochi.test:8888/3");
+  await BrowserTestUtils.waitForCondition(() => getUrl(tab5) == "http://mochi.test:8888/5");
+
+  ok(true, "Tab3 and tab5 are duplicated succesfully");
+
+  BrowserTestUtils.closeWindow(newWindow);
+  BrowserTestUtils.removeTab(tab4);
+});
--- a/browser/base/content/test/tabs/head.js
+++ b/browser/base/content/test/tabs/head.js
@@ -152,24 +152,37 @@ async function test_mute_tab(tab, icon, 
   let isAudioPlaying = await is_audio_playing(tab);
   if (isAudioPlaying) {
     await wait_for_tab_playing_event(tab, !expectMuted);
   }
 
   return mutedPromise;
 }
 
-async function dragAndDrop(tab1, tab2, copy) {
+async function dragAndDrop(tab1, tab2, copy, destWindow = window) {
   let rect = tab2.getBoundingClientRect();
   let event = {
     ctrlKey: copy,
     altKey: copy,
     clientX: rect.left + rect.width / 2 + 10,
     clientY: rect.top + rect.height / 2,
   };
 
+  if (destWindow != window) {
+    // Make sure that both tab1 and tab2 are visible
+    window.focus();
+    window.moveTo(rect.left, rect.top + rect.height * 3);
+  }
+
   let originalTPos = tab1._tPos;
-  EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
-  if (!copy) {
+  EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, destWindow, event);
+  if (!copy && destWindow == window) {
     await BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
       "Waiting for tab position to be updated");
+  } else if (destWindow != window) {
+    await BrowserTestUtils.waitForCondition(() => tab1.closing,
+      "Waiting for tab closing");
   }
 }
+
+function getUrl(tab) {
+  return tab.linkedBrowser.currentURI.spec;
+}