summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/performance/browser_tabopen.js
blob: 2457812cb7f92d86193dbe5ff52e5456ecfa9402 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS.
 * Instead of adding reflows to the list, you should be modifying your code to
 * avoid the reflow.
 *
 * See https://firefox-source-docs.mozilla.org/performance/bestpractices.html
 * for tips on how to do that.
 */
const EXPECTED_REFLOWS = [
  /**
   * Nothing here! Please don't add anything new!
   */
];

/*
 * This test ensures that there are no unexpected
 * uninterruptible reflows when opening new tabs.
 */
add_task(async function () {
  // Force-enable tab animations
  gReduceMotionOverride = false;

  // TODO (bug 1702653): Disable tab shadows for tests since the shadow
  // can extend outside of the boundingClientRect. The tabRect will need
  // to grow to include the shadow size.
  gBrowser.tabContainer.setAttribute("noshadowfortests", "true");

  await ensureNoPreloadedBrowser();
  await disableFxaBadge();

  // The test starts on about:blank and opens an about:blank
  // tab which triggers opening the toolbar since
  // ensureNoPreloadedBrowser sets AboutNewTab.newTabURL to about:blank.
  await SpecialPowers.pushPrefEnv({
    set: [["browser.toolbars.bookmarks.visibility", "never"]],
  });

  // Prepare the window to avoid flicker and reflow that's unrelated to our
  // tab opening operation.
  gURLBar.focus();

  let tabStripRect =
    gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect();

  let firstTabRect = gBrowser.selectedTab.getBoundingClientRect();
  let tabPaddingStart = parseFloat(
    getComputedStyle(gBrowser.selectedTab).paddingInlineStart
  );
  let minTabWidth = firstTabRect.width - 2 * tabPaddingStart;
  let maxTabWidth = firstTabRect.width;
  let firstTabLabelRect =
    gBrowser.selectedTab.textLabel.getBoundingClientRect();
  let newTabButtonRect = document
    .getElementById("tabs-newtab-button")
    .getBoundingClientRect();
  let textBoxRect = gURLBar
    .querySelector("moz-input-box")
    .getBoundingClientRect();

  let inRange = (val, min, max) => min <= val && val <= max;

  info(`tabStripRect=${JSON.stringify(tabStripRect)}`);
  info(`firstTabRect=${JSON.stringify(firstTabRect)}`);
  info(`tabPaddingStart=${JSON.stringify(tabPaddingStart)}`);
  info(`firstTabLabelRect=${JSON.stringify(firstTabLabelRect)}`);
  info(`newTabButtonRect=${JSON.stringify(newTabButtonRect)}`);
  info(`textBoxRect=${JSON.stringify(textBoxRect)}`);

  let inTabStrip = function (r) {
    return (
      r.y1 >= tabStripRect.top &&
      r.y2 <= tabStripRect.bottom &&
      r.x1 >= tabStripRect.left &&
      r.x2 <= tabStripRect.right
    );
  };

  const kTabCloseIconWidth = 13;

  let isExpectedChange = function (r) {
    // We expect all changes to be within the tab strip.
    if (!inTabStrip(r)) {
      return false;
    }

    // The first tab should get deselected at the same time as the next tab
    // starts appearing, so we should have one rect that includes the first tab
    // but is wider.
    if (
      inRange(r.w, minTabWidth, maxTabWidth * 2) &&
      inRange(r.x1, firstTabRect.x, firstTabRect.x + tabPaddingStart)
    ) {
      return true;
    }

    // The second tab gets painted several times due to tabopen animation.
    let isSecondTabRect =
      inRange(
        r.x1,
        // When the animation starts the tab close icon overflows.
        // -1 for the border on Win7
        firstTabRect.right - kTabCloseIconWidth - 1,
        firstTabRect.right + firstTabRect.width
      ) &&
      r.x2 <
        firstTabRect.right +
          firstTabRect.width +
          // Sometimes the '+' is in the same rect.
          newTabButtonRect.width;

    if (isSecondTabRect) {
      return true;
    }
    // The '+' icon moves with an animation. At the end of the animation
    // the former and new positions can touch each other causing the rect
    // to have twice the icon's width.
    if (
      r.h == kTabCloseIconWidth &&
      r.w <= 2 * kTabCloseIconWidth + kMaxEmptyPixels
    ) {
      return true;
    }

    // We sometimes have a rect for the right most 2px of the '+' button.
    if (r.h == 2 && r.w == 2) {
      return true;
    }

    // Same for the 'X' icon.
    if (r.h == 10 && r.w <= 2 * 10) {
      return true;
    }

    // Other changes are unexpected.
    return false;
  };

  // Add a reflow observer and open a new tab.
  await withPerfObserver(
    async function () {
      let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
      BrowserOpenTab();
      await BrowserTestUtils.waitForEvent(
        gBrowser.selectedTab,
        "TabAnimationEnd"
      );
      await switchDone;
    },
    {
      expectedReflows: EXPECTED_REFLOWS,
      frames: {
        filter: rects => rects.filter(r => !isExpectedChange(r)),
        exceptions: [
          {
            name:
              "bug 1446452 - the new tab should appear at the same time as the" +
              " previous one gets deselected",
            condition: r =>
              // In tab strip
              r.y1 >= tabStripRect.top &&
              r.y2 <= tabStripRect.bottom &&
              // Position and size of the first tab.
              r.x1 == firstTabRect.left &&
              inRange(
                r.w,
                firstTabRect.width - 1, // -1 as the border doesn't change
                firstTabRect.width
              ),
          },
          {
            name: "the urlbar placeolder moves up and down by a few pixels",
            // This seems to only happen on the second run in --verify
            condition: r =>
              r.x1 >= textBoxRect.left &&
              r.x2 <= textBoxRect.right &&
              r.y1 >= textBoxRect.top &&
              r.y2 <= textBoxRect.bottom,
          },
          {
            name: "bug 1477966 - the name of a deselected tab should appear immediately",
            condition: r =>
              AppConstants.platform == "macosx" &&
              r.x1 >= firstTabLabelRect.x &&
              r.x2 <= firstTabLabelRect.right &&
              r.y1 >= firstTabLabelRect.y &&
              r.y2 <= firstTabLabelRect.bottom,
          },
        ],
      },
    }
  );

  let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
  BrowserTestUtils.removeTab(gBrowser.selectedTab);
  await switchDone;
});