Fix HiddenBrowser test failures (#5134)

Wait for 'complete', not 'interactive', so we know the image has been
loaded.

And use single documentIsReady() implementation instead of copy-pasting.
This commit is contained in:
Abe Jellinek 2025-03-20 23:32:02 -04:00 committed by GitHub
parent eb54cd82f6
commit 93b677aaf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 90 additions and 134 deletions

View file

@ -244,6 +244,16 @@ class HiddenBrowser {
}
}
}
/**
* @param {number | false} [allowInteractiveAfter = false] Delay (in milliseconds) before resolving on 'interactive'.
* If false, documentIsReady() won't resolve until 'complete'.
* @returns {Promise<void>}
*/
waitForDocument({ allowInteractiveAfter = false } = {}) {
return this.browsingContext.currentWindowGlobal.getActor('DocumentIsReady')
.sendQuery('waitForDocument', { allowInteractiveAfter });
}
/**
* @param {String[]} props - 'characterSet', 'title', 'bodyText', 'documentHTML', 'cookie', 'channelInfo'

View file

@ -86,3 +86,9 @@ ChromeUtils.registerWindowActor("MendeleyAuth", {
moduleURI: "chrome://zotero/content/actors/MendeleyAuthChild.jsm"
}
});
ChromeUtils.registerWindowActor("DocumentIsReady", {
child: {
moduleURI: "chrome://zotero/content/actors/DocumentIsReadyChild.jsm"
}
});

View file

@ -0,0 +1,14 @@
var EXPORTED_SYMBOLS = ["DocumentIsReadyChild"];
let { documentIsReady } = ChromeUtils.importESModule("chrome://zotero/content/actors/actorUtils.mjs");
class DocumentIsReadyChild extends JSWindowActorChild {
async receiveMessage({ name, data }) {
if (name !== "waitForDocument") {
return null;
}
let { allowInteractiveAfter } = data;
await documentIsReady(this.document, { allowInteractiveAfter });
}
}

View file

@ -2,12 +2,14 @@
var EXPORTED_SYMBOLS = ["MendeleyAuthChild"]; // eslint-disable-line no-unused-vars
let { documentIsReady } = ChromeUtils.importESModule("chrome://zotero/content/actors/actorUtils.mjs");
class MendeleyAuthChild extends JSWindowActorChild { // eslint-disable-line no-unused-vars
async receiveMessage(message) {
let window = this.contentWindow;
let document = window.document;
let document = this.document;
await this.documentIsReady();
// Wait for 'complete'
await documentIsReady(document);
switch (message.name) {
case "login":
@ -36,34 +38,4 @@ class MendeleyAuthChild extends JSWindowActorChild { // eslint-disable-line no-u
return false;
}
// From Mozilla's ScreenshotsComponentChild.jsm
documentIsReady() {
const contentWindow = this.contentWindow;
const document = this.document;
function readyEnough() {
return document.readyState === "complete";
}
if (readyEnough()) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
}
else if (readyEnough()) {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
}
document.addEventListener("readystatechange", onChange);
contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}
}

View file

@ -1,5 +1,7 @@
var EXPORTED_SYMBOLS = ["PageDataChild"];
let { documentIsReady } = ChromeUtils.importESModule("chrome://zotero/content/actors/actorUtils.mjs");
class PageDataChild extends JSWindowActorChild {
async receiveMessage(message) {
// Special case for loadURI: don't wait for document to be ready,
@ -8,10 +10,10 @@ class PageDataChild extends JSWindowActorChild {
return this.loadURI(message.data.uri);
}
let window = this.contentWindow;
let document = window.document;
let document = this.document;
await this.documentIsReady();
// Wait for 'interactive' or 'complete'
await documentIsReady(document, { allowInteractiveAfter: 0 });
switch (message.name) {
case "characterSet":
@ -74,34 +76,4 @@ class PageDataChild extends JSWindowActorChild {
return false;
}
}
// From Mozilla's ScreenshotsComponentChild.jsm
documentIsReady() {
const contentWindow = this.contentWindow;
const document = this.document;
function readyEnough() {
return document.readyState === "complete" || document.readyState === "interactive";
}
if (readyEnough()) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
}
else if (readyEnough()) {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
}
document.addEventListener("readystatechange", onChange);
contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}
}

View file

@ -1,11 +1,13 @@
var EXPORTED_SYMBOLS = ["SingleFileChild"];
let { documentIsReady } = ChromeUtils.importESModule("chrome://zotero/content/actors/actorUtils.mjs");
class SingleFileChild extends JSWindowActorChild {
async receiveMessage(message) {
let window = this.contentWindow;
await this.documentIsReady();
// Wait for 'complete'
await documentIsReady(this.document);
if (message.name !== 'snapshot') {
return null;
@ -179,34 +181,4 @@ class SingleFileChild extends JSWindowActorChild {
return true;
}
}
// From Mozilla's ScreenshotsComponentChild.jsm
documentIsReady() {
const contentWindow = this.contentWindow;
const document = this.document;
function readyEnough() {
return document.readyState === "complete";
}
if (readyEnough()) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
}
else if (readyEnough()) {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
}
document.addEventListener("readystatechange", onChange);
contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}
}

View file

@ -1,5 +1,6 @@
var EXPORTED_SYMBOLS = ["TranslationChild"];
let { documentIsReady } = ChromeUtils.importESModule("chrome://zotero/content/actors/actorUtils.mjs");
const TRANSLATE_SCRIPT_PATHS = [
'src/zotero.js',
@ -43,7 +44,8 @@ class TranslationChild extends JSWindowActorChild {
_sandbox = null;
async receiveMessage(message) {
await this.documentIsReady();
// Wait for 'complete', or 'interactive' after a 100ms delay
await documentIsReady(this.document, { allowInteractiveAfter: 100 });
let { name, data } = message;
switch (name) {
@ -302,39 +304,6 @@ class TranslationChild extends JSWindowActorChild {
return sandbox;
}
// From Mozilla's ScreenshotsComponentChild.jsm
documentIsReady() {
const contentWindow = this.contentWindow;
const document = this.document;
if (document.readyState === "complete") {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
function ready() {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
}
else if (document.readyState === "complete") {
ready();
}
else if (document.readyState === "interactive") {
setTimeout(ready, 100);
}
}
document.addEventListener("readystatechange", onChange);
contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}
didDestroy() {
if (this._sandbox) {
Cu.nukeSandbox(this._sandbox);

View file

@ -0,0 +1,42 @@
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
/**
* @param {Document} document
* @param {number | false} [allowInteractiveAfter] Delay (in milliseconds) before resolving on 'interactive'.
* If false, documentIsReady() won't resolve until 'complete'.
* @returns {Promise<void>}
*/
export async function documentIsReady(document, { allowInteractiveAfter = false } = {}) {
// Adapted from Mozilla's ScreenshotsComponentChild.jsm
function readyEnough(readyState) {
if (readyState === "interactive" && allowInteractiveAfter !== false) {
return allowInteractiveAfter > 0
? new Promise(resolve => setTimeout(() => resolve(true), allowInteractiveAfter))
: true;
}
return readyState === "complete";
}
let contentWindow = document.defaultView;
if (await readyEnough(document.readyState)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
async function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
}
else if (await readyEnough(document.readyState)) {
document.removeEventListener("readystatechange", onChange);
contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
}
document.addEventListener("readystatechange", onChange);
contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}

View file

@ -22,7 +22,6 @@ describe("HiddenBrowser", function() {
'/remote.png',
{
handle: function (request, response) {
Zotero.debug('Something loaded the image')
response.setHeader('Content-Type', 'image/png', false);
response.setStatusLine(null, 200, 'OK');
response.write('');
@ -46,7 +45,7 @@ describe("HiddenBrowser", function() {
let path = OS.Path.join(getTestDataDirectory().path, 'test-hidden.html');
let browser = new HiddenBrowser({ blockRemoteResources: true });
await browser.load(path);
await browser.getPageData(['characterSet', 'bodyText']);
await browser.waitForDocument();
browser.destroy();
assert.isFalse(pngRequested);
});
@ -55,7 +54,7 @@ describe("HiddenBrowser", function() {
let path = OS.Path.join(getTestDataDirectory().path, 'test-hidden.html');
let browser = new HiddenBrowser({ blockRemoteResources: false });
await browser.load(path);
await browser.getPageData(['characterSet', 'bodyText']);
await browser.waitForDocument();
browser.destroy();
assert.isTrue(pngRequested);
});