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
202
203
204
205
206
207
208
209
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["MarionetteReftestChild"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
Log: "chrome://marionette/content/log.js",
});
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
/**
* Child JSWindowActor to handle navigation for reftests relying on marionette.
*/
class MarionetteReftestChild extends JSWindowActorChild {
constructor() {
super();
// This promise will resolve with the URL recorded in the "load" event
// handler. This URL will not be impacted by any hash modification that
// might be performed by the test script.
// The harness should be loaded before loading any test page, so the actors
// should be registered before the "load" event is received for a test page.
this._loadedURLPromise = new Promise(
r => (this._resolveLoadedURLPromise = r)
);
}
handleEvent(event) {
if (event.type == "load") {
const url = event.target.location.href;
logger.debug(`Handle load event with URL ${url}`);
this._resolveLoadedURLPromise(url);
}
}
actorCreated() {
logger.trace(
`[${this.browsingContext.id}] Reftest actor created ` +
`for window id ${this.manager.innerWindowId}`
);
}
async receiveMessage(msg) {
const { name, data } = msg;
let result;
switch (name) {
case "MarionetteReftestParent:reftestWait":
result = await this.reftestWait(data);
break;
}
return result;
}
/**
* Wait for a reftest page to be ready for screenshots:
* - wait for the loadedURL to be available (see handleEvent)
* - check if the URL matches the expected URL
* - if present, wait for the "reftest-wait" classname to be removed from the
* document element
*
* @param {Object} options
* @param {String} options.url
* The expected test page URL
* @param {Boolean} options.useRemote
* True when using e10s
* @return {Boolean}
* Returns true when the correct page is loaded and ready for
* screenshots. Returns false if the page loaded bug does not have the
* expected URL.
*/
async reftestWait(options = {}) {
const { url, useRemote } = options;
const loadedURL = await this._loadedURLPromise;
if (loadedURL !== url) {
logger.debug(
`Window URL does not match the expected URL "${loadedURL}" !== "${url}"`
);
return false;
}
const documentElement = this.document.documentElement;
const hasReftestWait = documentElement.classList.contains("reftest-wait");
logger.debug("Waiting for event loop to spin");
await new Promise(resolve =>
this.document.defaultView.setTimeout(resolve, 0)
);
await this.paintComplete(useRemote);
if (hasReftestWait) {
const event = new Event("TestRendered", { bubbles: true });
documentElement.dispatchEvent(event);
logger.info("Emitted TestRendered event");
await this.reftestWaitRemoved();
await this.paintComplete(useRemote);
}
if (
this.document.defaultView.innerWidth < documentElement.scrollWidth ||
this.document.defaultView.innerHeight < documentElement.scrollHeight
) {
logger.warn(
`${url} overflows viewport (width: ${documentElement.scrollWidth}, height: ${documentElement.scrollHeight})`
);
}
return true;
}
paintComplete(useRemote) {
logger.debug("Waiting for rendering");
let windowUtils = this.document.defaultView.windowUtils;
return new Promise(resolve => {
let maybeResolve = () => {
this.flushRendering();
if (useRemote) {
// Flush display (paint)
logger.debug("Force update of layer tree");
windowUtils.updateLayerTree();
}
if (windowUtils.isMozAfterPaintPending) {
logger.debug("isMozAfterPaintPending: true");
this.document.defaultView.addEventListener(
"MozAfterPaint",
maybeResolve,
{
once: true,
}
);
} else {
// resolve at the start of the next frame in case of leftover paints
logger.debug("isMozAfterPaintPending: false");
this.document.defaultView.requestAnimationFrame(() => {
this.document.defaultView.requestAnimationFrame(resolve);
});
}
};
maybeResolve();
});
}
reftestWaitRemoved() {
logger.debug("Waiting for reftest-wait removal");
return new Promise(resolve => {
const documentElement = this.document.documentElement;
let observer = new this.document.defaultView.MutationObserver(() => {
if (!documentElement.classList.contains("reftest-wait")) {
observer.disconnect();
logger.debug("reftest-wait removed");
this.document.defaultView.setTimeout(resolve, 0);
}
});
if (documentElement.classList.contains("reftest-wait")) {
observer.observe(documentElement, { attributes: true });
} else {
this.document.defaultView.setTimeout(resolve, 0);
}
});
}
flushRendering() {
let anyPendingPaintsGeneratedInDescendants = false;
let windowUtils = this.document.defaultView.windowUtils;
function flushWindow(win) {
let utils = win.windowUtils;
let afterPaintWasPending = utils.isMozAfterPaintPending;
let root = win.document.documentElement;
if (root) {
try {
// Flush pending restyles and reflows for this window (layout)
root.getBoundingClientRect();
} catch (e) {
logger.error("flushWindow failed", e);
}
}
if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
anyPendingPaintsGeneratedInDescendants = true;
}
for (let i = 0; i < win.frames.length; ++i) {
flushWindow(win.frames[i]);
}
}
flushWindow(this.document.defaultView);
if (
anyPendingPaintsGeneratedInDescendants &&
!windowUtils.isMozAfterPaintPending
) {
logger.error(
"Descendant frame generated a MozAfterPaint event, " +
"but the root document doesn't have one!"
);
}
}
}
|