ReftestFissionChild.sys.mjs (12861B)
1 export class ReftestFissionChild extends JSWindowActorChild { 2 forwardAfterPaintEventToParent( 3 rects, 4 originalTargetUri, 5 dispatchToSelfAsWell 6 ) { 7 if (dispatchToSelfAsWell && this.contentWindow) { 8 let event = new this.contentWindow.CustomEvent( 9 "Reftest:MozAfterPaintFromChild", 10 { bubbles: true, detail: { rects, originalTargetUri } } 11 ); 12 this.contentWindow.dispatchEvent(event); 13 } 14 15 let parentContext = this.browsingContext.parent; 16 if (parentContext) { 17 try { 18 this.sendAsyncMessage("ForwardAfterPaintEvent", { 19 toBrowsingContext: parentContext, 20 fromBrowsingContext: this.browsingContext, 21 rects, 22 originalTargetUri, 23 }); 24 } catch (e) { 25 // |this| can be destroyed here and unable to send messages, which is 26 // not a problem, the reftest harness probably torn down the page and 27 // moved on to the next test. 28 console.error(e); 29 } 30 } 31 } 32 33 handleEvent(evt) { 34 switch (evt.type) { 35 case "MozAfterPaint": 36 // We want to forward any after paint events to our parent document so that 37 // that it reaches the root content document where the main reftest harness 38 // code (reftest-content.js) will process it and update the canvas. 39 var rects = []; 40 for (let r of evt.clientRects) { 41 rects.push({ 42 left: r.left, 43 top: r.top, 44 right: r.right, 45 bottom: r.bottom, 46 }); 47 } 48 this.forwardAfterPaintEventToParent( 49 rects, 50 this.document.documentURI, 51 /* dispatchToSelfAsWell */ false 52 ); 53 break; 54 } 55 } 56 57 transformRect(transform, rect) { 58 let p1 = transform.transformPoint({ x: rect.left, y: rect.top }); 59 let p2 = transform.transformPoint({ x: rect.right, y: rect.top }); 60 let p3 = transform.transformPoint({ x: rect.left, y: rect.bottom }); 61 let p4 = transform.transformPoint({ x: rect.right, y: rect.bottom }); 62 let quad = new DOMQuad(p1, p2, p3, p4); 63 return quad.getBounds(); 64 } 65 66 SetupDisplayportRoot() { 67 let returnStrings = { infoStrings: [], errorStrings: [] }; 68 69 let contentRootElement = this.contentWindow.document.documentElement; 70 if (!contentRootElement) { 71 return Promise.resolve(returnStrings); 72 } 73 74 // If we don't have the reftest-async-scroll attribute we only look at 75 // the root element for potential display ports to set. 76 if (!contentRootElement.hasAttribute("reftest-async-scroll")) { 77 let winUtils = this.contentWindow.windowUtils; 78 this.setupDisplayportForElement( 79 contentRootElement, 80 winUtils, 81 returnStrings 82 ); 83 return Promise.resolve(returnStrings); 84 } 85 86 // Send a msg to the parent side to get the parent side to tell all 87 // process roots to do the displayport setting. 88 let browsingContext = this.browsingContext; 89 let promise = this.sendQuery("TellChildrenToSetupDisplayport", { 90 browsingContext, 91 }); 92 return promise.then( 93 function (result) { 94 for (let errorString of result.errorStrings) { 95 returnStrings.errorStrings.push(errorString); 96 } 97 for (let infoString of result.infoStrings) { 98 returnStrings.infoStrings.push(infoString); 99 } 100 return returnStrings; 101 }, 102 function (reason) { 103 returnStrings.errorStrings.push( 104 "SetupDisplayport SendQuery to parent promise rejected: " + reason 105 ); 106 return returnStrings; 107 } 108 ); 109 } 110 111 attrOrDefault(element, attr, def) { 112 return element.hasAttribute(attr) 113 ? Number(element.getAttribute(attr)) 114 : def; 115 } 116 117 setupDisplayportForElement(element, winUtils, returnStrings) { 118 var dpw = this.attrOrDefault(element, "reftest-displayport-w", 0); 119 var dph = this.attrOrDefault(element, "reftest-displayport-h", 0); 120 var dpx = this.attrOrDefault(element, "reftest-displayport-x", 0); 121 var dpy = this.attrOrDefault(element, "reftest-displayport-y", 0); 122 if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) { 123 returnStrings.infoStrings.push( 124 "Setting displayport to <x=" + 125 dpx + 126 ", y=" + 127 dpy + 128 ", w=" + 129 dpw + 130 ", h=" + 131 dph + 132 ">" 133 ); 134 winUtils.setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); 135 } 136 } 137 138 setupDisplayportForElementSubtree(element, winUtils, returnStrings) { 139 this.setupDisplayportForElement(element, winUtils, returnStrings); 140 for (let c = element.firstElementChild; c; c = c.nextElementSibling) { 141 this.setupDisplayportForElementSubtree(c, winUtils, returnStrings); 142 } 143 if ( 144 typeof element.contentDocument !== "undefined" && 145 element.contentDocument 146 ) { 147 returnStrings.infoStrings.push( 148 "setupDisplayportForElementSubtree descending into subdocument" 149 ); 150 this.setupDisplayportForElementSubtree( 151 element.contentDocument.documentElement, 152 element.contentWindow.windowUtils, 153 returnStrings 154 ); 155 } 156 } 157 158 setupAsyncScrollOffsetsForElement( 159 element, 160 winUtils, 161 allowFailure, 162 returnStrings 163 ) { 164 let sx = this.attrOrDefault(element, "reftest-async-scroll-x", 0); 165 let sy = this.attrOrDefault(element, "reftest-async-scroll-y", 0); 166 if (sx != 0 || sy != 0) { 167 try { 168 // This might fail when called from RecordResult since layers 169 // may not have been constructed yet 170 winUtils.setAsyncScrollOffset(element, sx, sy); 171 return true; 172 } catch (e) { 173 if (allowFailure) { 174 returnStrings.infoStrings.push( 175 "setupAsyncScrollOffsetsForElement error calling setAsyncScrollOffset: " + 176 e 177 ); 178 } else { 179 returnStrings.errorStrings.push( 180 "setupAsyncScrollOffsetsForElement error calling setAsyncScrollOffset: " + 181 e 182 ); 183 } 184 } 185 } 186 return false; 187 } 188 189 setupAsyncScrollOffsetsForElementSubtree( 190 element, 191 winUtils, 192 allowFailure, 193 returnStrings 194 ) { 195 let updatedAny = this.setupAsyncScrollOffsetsForElement( 196 element, 197 winUtils, 198 returnStrings 199 ); 200 for (let c = element.firstElementChild; c; c = c.nextElementSibling) { 201 if ( 202 this.setupAsyncScrollOffsetsForElementSubtree( 203 c, 204 winUtils, 205 allowFailure, 206 returnStrings 207 ) 208 ) { 209 updatedAny = true; 210 } 211 } 212 if ( 213 typeof element.contentDocument !== "undefined" && 214 element.contentDocument 215 ) { 216 returnStrings.infoStrings.push( 217 "setupAsyncScrollOffsetsForElementSubtree Descending into subdocument" 218 ); 219 if ( 220 this.setupAsyncScrollOffsetsForElementSubtree( 221 element.contentDocument.documentElement, 222 element.contentWindow.windowUtils, 223 allowFailure, 224 returnStrings 225 ) 226 ) { 227 updatedAny = true; 228 } 229 } 230 return updatedAny; 231 } 232 233 async receiveMessage(msg) { 234 switch (msg.name) { 235 case "ForwardAfterPaintEventToSelfAndParent": { 236 // The embedderElement can be null if the child we got this from was removed. 237 // Not much we can do to transform the rects, but it doesn't matter, the rects 238 // won't reach reftest-content.js. 239 if (msg.data.fromBrowsingContext.embedderElement == null) { 240 this.forwardAfterPaintEventToParent( 241 msg.data.rects, 242 msg.data.originalTargetUri, 243 /* dispatchToSelfAsWell */ true 244 ); 245 return undefined; 246 } 247 248 let translate = new DOMMatrixReadOnly().translate(0, 0); 249 if (this.contentWindow) { 250 // Transform the rects from fromBrowsingContext to us. 251 // We first translate from the content rect to the border rect of the iframe. 252 let style = this.contentWindow.getComputedStyle( 253 msg.data.fromBrowsingContext.embedderElement 254 ); 255 translate = new DOMMatrixReadOnly().translate( 256 parseFloat(style.paddingLeft) + parseFloat(style.borderLeftWidth), 257 parseFloat(style.paddingTop) + parseFloat(style.borderTopWidth) 258 ); 259 } 260 261 // Then we transform from the iframe to our root frame. 262 // We are guaranteed to be the process with the embedderElement for fromBrowsingContext. 263 let transform = 264 msg.data.fromBrowsingContext.embedderElement.getTransformToViewport(); 265 let combined = translate.multiply(transform); 266 267 let newrects = msg.data.rects.map(r => this.transformRect(combined, r)); 268 269 this.forwardAfterPaintEventToParent( 270 newrects, 271 msg.data.originalTargetUri, 272 /* dispatchToSelfAsWell */ true 273 ); 274 break; 275 } 276 277 case "EmptyMessage": 278 return undefined; 279 case "UpdateLayerTree": { 280 let errorStrings = []; 281 try { 282 if (this.manager.isProcessRoot) { 283 this.contentWindow.windowUtils.updateLayerTree(); 284 } 285 } catch (e) { 286 errorStrings.push("updateLayerTree failed: " + e); 287 } 288 return { errorStrings }; 289 } 290 case "FlushRendering": { 291 let errorStrings = []; 292 let warningStrings = []; 293 let infoStrings = []; 294 295 try { 296 let { ignoreThrottledAnimations, needsAnimationFrame } = msg.data; 297 298 if (this.manager.isProcessRoot) { 299 var anyPendingPaintsGeneratedInDescendants = false; 300 301 if (this.contentWindow && needsAnimationFrame) { 302 await new Promise(resolve => 303 this.contentWindow.requestAnimationFrame(resolve) 304 ); 305 } 306 307 function flushWindow(win) { 308 var utils = win.windowUtils; 309 var afterPaintWasPending = utils.isMozAfterPaintPending; 310 311 var root = win.document.documentElement; 312 if (root && !root.classList.contains("reftest-no-flush")) { 313 try { 314 if (ignoreThrottledAnimations) { 315 utils.flushLayoutWithoutThrottledAnimations(); 316 } else { 317 root.getBoundingClientRect(); 318 } 319 } catch (e) { 320 warningStrings.push("flushWindow failed: " + e + "\n"); 321 } 322 } 323 324 if (!afterPaintWasPending && utils.isMozAfterPaintPending) { 325 infoStrings.push( 326 "FlushRendering generated paint for window " + 327 win.location.href 328 ); 329 anyPendingPaintsGeneratedInDescendants = true; 330 } 331 332 for (let i = 0; i < win.frames.length; ++i) { 333 try { 334 if (!Cu.isRemoteProxy(win.frames[i])) { 335 flushWindow(win.frames[i]); 336 } 337 } catch (e) { 338 console.error(e); 339 } 340 } 341 } 342 343 // `contentWindow` will be null if the inner window for this actor 344 // has been navigated away from. 345 if (this.contentWindow) { 346 flushWindow(this.contentWindow); 347 } 348 349 if ( 350 anyPendingPaintsGeneratedInDescendants && 351 !this.contentWindow.windowUtils.isMozAfterPaintPending 352 ) { 353 warningStrings.push( 354 "Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!" 355 ); 356 } 357 } 358 } catch (e) { 359 errorStrings.push("flushWindow failed: " + e); 360 } 361 return { errorStrings, warningStrings, infoStrings }; 362 } 363 364 case "SetupDisplayport": { 365 let contentRootElement = this.document.documentElement; 366 let winUtils = this.contentWindow.windowUtils; 367 let returnStrings = { infoStrings: [], errorStrings: [] }; 368 if (contentRootElement) { 369 this.setupDisplayportForElementSubtree( 370 contentRootElement, 371 winUtils, 372 returnStrings 373 ); 374 } 375 return returnStrings; 376 } 377 378 case "SetupAsyncScrollOffsets": { 379 let returns = { infoStrings: [], errorStrings: [], updatedAny: false }; 380 let contentRootElement = this.document.documentElement; 381 382 if (!contentRootElement) { 383 return returns; 384 } 385 386 let winUtils = this.contentWindow.windowUtils; 387 388 returns.updatedAny = this.setupAsyncScrollOffsetsForElementSubtree( 389 contentRootElement, 390 winUtils, 391 msg.data.allowFailure, 392 returns 393 ); 394 return returns; 395 } 396 } 397 return undefined; 398 } 399 }