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
authorJared Wein <jwein@mozilla.com>
Tue, 02 May 2017 15:51:13 -0400
changeset 357146 709528f44f789e33698e8793413643ca584afbef
parent 357145 3655533be7c7a54ee2f9b98d247a7af2cac1321a
child 357147 8ce8c07e3e2928ef9d27c53225a6bb6a121799ba
push id90049
push userkwierso@gmail.com
push dateMon, 08 May 2017 23:14:57 +0000
treeherdermozilla-inbound@12c7e8351c78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1355507
milestone55.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 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");
-}
+});