Bug 1355507 - Releasing a tab while dragging through the tabstrip on the same window should show a transition to its final resting place. r=dao draft
authorJared Wein <jwein@mozilla.com>
Tue, 02 May 2017 15:51:13 -0400
changeset 571585 e0ff3800b5536681afcd79d0e105008049d346ef
parent 571414 bfc7b187005cabbc828ed9f5b61daf139c3cfd90
child 626823 5f3124d46c66cbf0fc4fc4ac1828efc16b6b8ffd
push id56859
push userjwein@mozilla.com
push dateWed, 03 May 2017 00:08:42 +0000
reviewersdao
bugs1355507
milestone55.0a1
Bug 1355507 - Releasing a tab while dragging through the tabstrip on the same window should show a transition to its final resting place. r=dao MozReview-Commit-ID: Lb7D1wnifBp
browser/base/content/browser.css
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser_tabReorder.js
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -7,16 +7,17 @@
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
   --panelui-subview-transition-duration: 150ms;
   --lwt-additional-images: none;
   --lwt-background-alignment: right top;
   --lwt-background-tiling: no-repeat;
+  --animation-easing-function: cubic-bezier(.07, .95, 0, 1);
 }
 
 :root:-moz-lwtheme {
   color: var(--lwt-text-color) !important;
 }
 
 :root:-moz-lwtheme:not([customization-lwtheme]) {
   background-color: var(--lwt-accent-color) !important;
@@ -188,18 +189,19 @@ tabbrowser {
 }
 
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
   position: relative;
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
+.tabbrowser-tab[tabdrop-samewindow],
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
-  transition: transform 200ms ease-out;
+  transition: transform 200ms var(--animation-easing-function);
 }
 
 .new-tab-popup,
 #alltabs-popup {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
 }
 
 toolbar[printpreview="true"] {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6191,31 +6191,33 @@
           let pinned = draggedTab.pinned;
           let numPinned = this.tabbrowser._numPinnedTabs;
           let tabs = this.tabbrowser.visibleTabs
                                     .slice(pinned ? 0 : numPinned,
                                            pinned ? numPinned : undefined);
           if (rtl)
             tabs.reverse();
           let tabWidth = draggedTab.getBoundingClientRect().width;
+          draggedTab._dragData.tabWidth = tabWidth;
 
           // Move the dragged tab based on the mouse position.
 
           let leftTab = tabs[0];
           let rightTab = tabs[tabs.length - 1];
           let tabScreenX = draggedTab.boxObject.screenX;
           let translateX = screenX - draggedTab._dragData.screenX;
           if (!pinned)
             translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
           let leftBound = leftTab.boxObject.screenX - tabScreenX;
           let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
                            (tabScreenX + tabWidth);
           translateX = Math.max(translateX, leftBound);
           translateX = Math.min(translateX, rightBound);
           draggedTab.style.transform = "translateX(" + translateX + "px)";
+          draggedTab._dragData.translateX = translateX;
 
           // Determine what tab we're dragging over.
           // * Point of reference is the center of the dragged tab. If that
           //   point touches a background tab, the dragged tab would take that
           //   tab's position when dropped.
           // * We're doing a binary search in order to reduce the amount of
           //   tabs we need to check.
 
@@ -6877,24 +6879,52 @@
         if (draggedTab && dropEffect == "copy") {
           // copy the dropped tab (wherever it's from)
           let newIndex = this._getDropIndex(event, false);
           let newTab = this.tabbrowser.duplicateTab(draggedTab);
           this.tabbrowser.moveTabTo(newTab, newIndex);
           if (draggedTab.parentNode != this || event.shiftKey)
             this.selectedItem = newTab;
         } else if (draggedTab && draggedTab.parentNode == this) {
-          this._finishAnimateTabMove();
-
-          // actually move the dragged tab
-          if ("animDropIndex" in draggedTab._dragData) {
-            let newIndex = draggedTab._dragData.animDropIndex;
-            if (newIndex > draggedTab._tPos)
-              newIndex--;
-            this.tabbrowser.moveTabTo(draggedTab, newIndex);
+          let oldTranslateX = draggedTab._dragData.translateX;
+          let tabWidth = draggedTab._dragData.tabWidth;
+          let translateOffset = oldTranslateX % tabWidth;
+          let newTranslateX = oldTranslateX - translateOffset;
+          if (oldTranslateX > 0 && translateOffset > tabWidth / 2) {
+            newTranslateX += tabWidth;
+          } else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) {
+            newTranslateX -= tabWidth;
+          }
+
+          let dropIndex = "animDropIndex" in draggedTab._dragData &&
+                          draggedTab._dragData.animDropIndex;
+          if (dropIndex && dropIndex > draggedTab._tPos)
+            dropIndex--;
+
+          if (oldTranslateX && oldTranslateX != newTranslateX) {
+            draggedTab.setAttribute("tabdrop-samewindow", "true");
+            draggedTab.style.transform = "translateX(" + newTranslateX + "px)";
+            let onTransitionEnd = transitionendEvent => {
+              if (transitionendEvent.propertyName != "transform" ||
+                  transitionendEvent.originalTarget != draggedTab) {
+                return;
+              }
+              draggedTab.removeEventListener("transitionend", onTransitionEnd);
+
+              draggedTab.removeAttribute("tabdrop-samewindow");
+
+              this._finishAnimateTabMove();
+              if (dropIndex !== false)
+                this.tabbrowser.moveTabTo(draggedTab, dropIndex);
+            }
+            draggedTab.addEventListener("transitionend", onTransitionEnd);
+          } else {
+            this._finishAnimateTabMove();
+            if (dropIndex !== false)
+              this.tabbrowser.moveTabTo(draggedTab, dropIndex);
           }
         } else if (draggedTab) {
           let newIndex = this._getDropIndex(event, false);
           this.tabbrowser.adoptTab(draggedTab, newIndex, true);
         } else {
           // Pass true to disallow dropping javascript: or data: urls
           let links;
           try {
@@ -6925,24 +6955,27 @@
         }
 
         if (draggedTab) {
           delete draggedTab._dragData;
         }
       ]]></handler>
 
       <handler event="dragend"><![CDATA[
-        // Note: while this case is correctly handled here, this event
-        // isn't dispatched when the tab is moved within the tabstrip,
-        // see bug 460801.
+        var dt = event.dataTransfer;
+        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+        // Prevent this code from running if a tabdrop animation is
+        // running since calling _finishAnimateTabMove would clear
+        // any CSS transition that is running.
+        if (draggedTab.hasAttribute("tabdrop-samewindow"))
+          return;
 
         this._finishAnimateTabMove();
 
-        var dt = event.dataTransfer;
-        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
           delete draggedTab._dragData;
           return;
         }
 
         // Disable detach within the browser toolbox
         var eX = event.screenX;
         var eY = event.screenY;
--- a/browser/base/content/test/general/browser_tabReorder.js
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
+add_task(function*() {
   let initialTabsLength = gBrowser.tabs.length;
 
   let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
   let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
   let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
   registerCleanupFunction(function() {
     while (gBrowser.tabs.length > initialTabsLength) {
       gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
@@ -18,32 +18,37 @@ function test() {
   is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
 
   let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                      getService(Ci.mozIJSSubScriptLoader);
   let EventUtils = {};
   scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-  function dragAndDrop(tab1, tab2, copy) {
+  function* dragAndDrop(tab1, tab2, copy) {
     let rect = tab2.getBoundingClientRect();
     let event = {
       ctrlKey: copy,
       altKey: copy,
       clientX: rect.left + rect.width / 2 + 10,
       clientY: rect.top + rect.height / 2,
     };
 
+    let originalTPos = tab1._tPos;
     EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+    if (!copy) {
+      yield BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
+        "Waiting for tab position to be updated");
+    }
   }
 
-  dragAndDrop(newTab1, newTab2, false);
+  yield dragAndDrop(newTab1, newTab2, false);
   is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
 
-  dragAndDrop(newTab2, newTab1, true);
+  yield dragAndDrop(newTab2, newTab1, true);
   is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
   is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
-}
+});