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
|
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
XPCOMUtils.defineLazyGetter(this, "docShell", () => {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
});
const EXPECTED_REFLOWS = [
// tabbrowser.adjustTabstrip() call after tabopen animation has finished
"adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
"_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
"onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
// switching focus in updateCurrentBrowser() causes reflows
"_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml|" +
"updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
"onselect@chrome://browser/content/browser.xul|",
// switching focus in openLinkIn() causes reflows
"openLinkIn@chrome://browser/content/utilityOverlay.js|" +
"openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
"BrowserOpenTab@chrome://browser/content/browser.js|",
// accessing element.scrollPosition in _fillTrailingGap() flushes layout
"get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
"_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
"_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
"onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
// SessionStore.getWindowDimensions()
"ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
// selection change notification may cause querying the focused editor content
// by IME and that will cause reflow.
"select@chrome://global/content/bindings/textbox.xml|" +
"focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
"openLinkIn@chrome://browser/content/utilityOverlay.js|" +
"openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
"BrowserOpenTab@chrome://browser/content/browser.js|",
];
const PREF_PRELOAD = "browser.newtab.preload";
const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
/*
* This test ensures that there are no unexpected
* uninterruptible reflows when opening new tabs.
*/
add_task(function*() {
let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
// resolves promise when directory links are downloaded and written to disk
function watchLinksChangeOnce() {
let deferred = Promise.defer();
let observer = {
onManyLinksChanged: () => {
DirectoryLinksProvider.removeObserver(observer);
NewTabUtils.links.populateCache(() => {
NewTabUtils.allPages.update();
deferred.resolve();
}, true);
}
};
observer.onDownloadFail = observer.onManyLinksChanged;
DirectoryLinksProvider.addObserver(observer);
return deferred.promise;
}
let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PREF_PRELOAD);
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
return watchLinksChangeOnce();
});
Services.prefs.setBoolPref(PREF_PRELOAD, false);
// set directory source to dummy/empty links
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
// run tests when directory source change completes
yield watchLinksChangeOnce();
// Perform a click in the top left of content to ensure the mouse isn't
// hovering over any of the tiles
let target = gBrowser.selectedBrowser;
let rect = target.getBoundingClientRect();
let left = rect.left + 1;
let top = rect.top + 1;
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
// Add a reflow observer and open a new tab.
docShell.addWeakReflowObserver(observer);
BrowserOpenTab();
// Wait until the tabopen animation has finished.
yield waitForTransitionEnd();
// Remove reflow observer and clean up.
docShell.removeWeakReflowObserver(observer);
gBrowser.removeCurrentTab();
});
var observer = {
reflow: function (start, end) {
// Gather information about the current code path.
let path = (new Error().stack).split("\n").slice(1).map(line => {
return line.replace(/:\d+:\d+$/, "");
}).join("|");
let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
// Stack trace is empty. Reflow was triggered by native code.
if (path === "") {
return;
}
// Check if this is an expected reflow.
for (let stack of EXPECTED_REFLOWS) {
if (path.startsWith(stack)) {
ok(true, "expected uninterruptible reflow '" + stack + "'");
return;
}
}
ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
},
reflowInterruptible: function (start, end) {
// We're not interested in interruptible reflows.
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
Ci.nsISupportsWeakReference])
};
function waitForTransitionEnd() {
return new Promise(resolve => {
let tab = gBrowser.selectedTab;
tab.addEventListener("transitionend", function onEnd(event) {
if (event.propertyName === "max-width") {
tab.removeEventListener("transitionend", onEnd);
resolve();
}
});
});
}
|