Bug 1543607 - Store the tabbrowser scrollbox button width when tabs are closed with the mouse so it can be used on underflow to keep the close button under the mouse r=dao
authorBrian Grinstead <bgrinstead@mozilla.com>
Mon, 06 May 2019 15:42:13 +0000
changeset 531533 2f0db0b04a913275f91ede9fc984a40065c08266
parent 531532 335579594d1cdf26497b50ffa46dbe7ff16a6896
child 531534 d499ab0ad0da468509074f5d46bfb9b57995e4ea
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1543607
milestone68.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 1543607 - Store the tabbrowser scrollbox button width when tabs are closed with the mouse so it can be used on underflow to keep the close button under the mouse r=dao The button is already hidden when underflow fires, so the clientWidth is 0. Instead, store it during _lockTabSizing so we know how much space to fill when tabs are being closed by the mouse, to allow the close button to remain underneath the mouse cursor. Differential Revision: https://phabricator.services.mozilla.com/D29227
browser/base/content/tabbrowser.xml
browser/base/content/test/tabs/browser.ini
browser/base/content/test/tabs/browser_tabCloseSpacer.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -148,17 +148,17 @@
                 event.detail == 0 ||
                 !this.hasAttribute("overflow")) {
               return;
             }
 
             this.removeAttribute("overflow");
 
             if (this._lastTabClosedByMouse) {
-              this._expandSpacerBy(this.arrowScrollbox._scrollButtonDown.clientWidth);
+              this._expandSpacerBy(this._scrollButtonWidth);
             }
 
             for (let tab of Array.from(gBrowser._removingTabs)) {
               gBrowser.removeTab(tab);
             }
 
             this._positionPinnedTabs();
           }, true);
@@ -406,16 +406,17 @@
 
       <field name="_closingTabsSpacer">
         document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
       </field>
 
       <field name="_tabDefaultMaxWidth">NaN</field>
       <field name="_lastTabClosedByMouse">false</field>
       <field name="_hasTabTempMaxWidth">false</field>
+      <field name="_scrollButtonWidth">0</field>
 
       <!-- Try to keep the active tab's close button under the mouse cursor -->
       <method name="_lockTabSizing">
         <parameter name="aTab"/>
         <parameter name="aTabWidth"/>
         <body><![CDATA[
           let tabs = this._getVisibleTabs();
           if (!tabs.length) {
@@ -424,16 +425,17 @@
 
           var isEndTab = (aTab._tPos > tabs[tabs.length - 1]._tPos);
 
           if (!this._tabDefaultMaxWidth) {
             this._tabDefaultMaxWidth =
               parseFloat(window.getComputedStyle(aTab).maxWidth);
           }
           this._lastTabClosedByMouse = true;
+          this._scrollButtonWidth = window.windowUtils.getBoundsWithoutFlushing(this.arrowScrollbox._scrollButtonDown).width;
 
           if (this.getAttribute("overflow") == "true") {
             // Don't need to do anything if we're in overflow mode and aren't scrolled
             // all the way to the right, or if we're closing the last tab.
             if (isEndTab || !this.arrowScrollbox._scrollButtonDown.disabled) {
               return;
             }
             // If the tab has an owner that will become the active tab, the owner will
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -66,16 +66,17 @@ support-files = file_anchor_elements.htm
 [browser_pinnedTabs_clickOpen.js]
 [browser_pinnedTabs_closeByKeyboard.js]
 [browser_pinnedTabs.js]
 [browser_positional_attributes.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_preloadedBrowser_zoom.js]
 [browser_reload_deleted_file.js]
 skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug 1421183, disabled on Linux/OSX for leaked windows
+[browser_tabCloseSpacer.js]
 [browser_tab_label_during_reload.js]
 [browser_tabCloseProbes.js]
 [browser_tabContextMenu_keyboard.js]
 [browser_tabReorder_overflow.js]
 [browser_tabReorder.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSuccessors.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_tabCloseSpacer.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that while clicking to close tabs, the close button remains under the mouse
+ * even when an underflow happens.
+ */
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["toolkit.cosmeticAnimations.enabled", false],
+    ],
+  });
+
+  let downButton = gBrowser.tabContainer.arrowScrollbox._scrollButtonDown;
+  let closingTabsSpacer = gBrowser.tabContainer._closingTabsSpacer;
+
+  await overflowTabs();
+  ok(gBrowser.tabContainer.hasAttribute("overflow"), "Tab strip should be overflowing");
+  isnot(downButton.clientWidth, 0, "down button has some width");
+  is(closingTabsSpacer.clientWidth, 0, "spacer has no width");
+
+  let originalCloseButtonLocation = getLastCloseButtonLocation();
+
+  info("Removing half the tabs and making sure the last close button doesn't move");
+  let numTabs = gBrowser.tabs.length / 2;
+  while (gBrowser.tabs.length > numTabs) {
+    let lastCloseButtonLocation = getLastCloseButtonLocation();
+    Assert.deepEqual(lastCloseButtonLocation, originalCloseButtonLocation, "Close button hasn't moved");
+
+    EventUtils.synthesizeMouseAtCenter(getLastCloseButton(), {});
+    await new Promise(r => requestAnimationFrame(r));
+  }
+
+  ok(!gBrowser.tabContainer.hasAttribute("overflow"), "not overflowing");
+  ok(gBrowser.tabContainer.hasAttribute("using-closing-tabs-spacer"), "using spacer");
+  is(downButton.clientWidth, 0, "down button has no width");
+  isnot(closingTabsSpacer.clientWidth, 0, "spacer has some width");
+});
+
+async function overflowTabs() {
+  let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox;
+  let width = ele => ele.getBoundingClientRect().width;
+  let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
+  let tabCountForOverflow = Math.ceil(width(arrowScrollbox) / tabMinWidth * 1.1);
+  while (gBrowser.tabs.length < tabCountForOverflow) {
+    BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true, index: 0 });
+  }
+  await window.promiseDocumentFlushed(() => {});
+}
+
+function getLastCloseButton() {
+  let lastTab = gBrowser.tabs[gBrowser.tabs.length - 1];
+  return document.getAnonymousElementByAttribute(lastTab, "anonid", "close-button");
+}
+
+function getLastCloseButtonLocation() {
+  let rect = getLastCloseButton().getBoundingClientRect();
+  return {
+    left: Math.round(rect.left),
+    top: Math.round(rect.top),
+    width: Math.round(rect.width),
+    height: Math.round(rect.height),
+  };
+}
+
+registerCleanupFunction(() => {
+  while (gBrowser.tabs.length > 1) {
+    BrowserTestUtils.removeTab(gBrowser.tabs[0]);
+  }
+});