tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }