Bug 1599260 - Include loader id in Page.lifecycleEvent r=remote-protocol-reviewers,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D74580
This commit is contained in:
Maja Frydrychowicz 2020-05-15 20:30:27 +00:00
parent 6604ce89a1
commit f260a91bb8
6 changed files with 184 additions and 69 deletions

View File

@ -285,53 +285,39 @@ class Page extends ContentProcessDomain {
}
handleEvent({ type, target }) {
const isFrame = target.defaultView != this.content;
if (isFrame) {
if (target.defaultView != this.content) {
// Ignore iframes for now
return;
}
const timestamp = Date.now();
const timestamp = Date.now() / 1000;
const frameId = target.defaultView.docShell.browsingContext.id.toString();
const url = target.location.href;
const loaderId =
this._lastRequest?.frameId == frameId
? this._lastRequest?.loaderId
: null;
switch (type) {
case "DOMContentLoaded":
this.emit("Page.domContentEventFired", { timestamp });
if (!isFrame) {
this.emitLifecycleEvent(
frameId,
/* loaderId */ null,
"DOMContentLoaded",
timestamp
);
}
this.emitLifecycleEvent(
frameId,
loaderId,
"DOMContentLoaded",
timestamp
);
break;
case "pagehide":
// Maybe better to bound to "unload" once we can register for this event
this.emit("Page.frameStartedLoading", { frameId });
if (!isFrame) {
this.emitLifecycleEvent(
frameId,
/* loaderId */ null,
"init",
timestamp
);
}
this.emitLifecycleEvent(frameId, loaderId, "init", timestamp);
break;
case "load":
this.emit("Page.loadEventFired", { timestamp });
if (!isFrame) {
this.emitLifecycleEvent(
frameId,
/* loaderId */ null,
"load",
timestamp
);
}
this.emitLifecycleEvent(frameId, loaderId, "load", timestamp);
// XXX this should most likely be sent differently
this.emit("Page.navigatedWithinDocument", { frameId, url });
@ -340,16 +326,15 @@ class Page extends ContentProcessDomain {
case "readystatechange":
if (this.content.document.readState === "loading") {
this.emitLifecycleEvent(
frameId,
/* loaderId */ null,
"init",
timestamp
);
this.emitLifecycleEvent(frameId, loaderId, "init", timestamp);
}
}
}
_updateLoaderId(data) {
this._lastRequest = data;
}
_contentRect() {
const docEl = this.content.document.documentElement;

View File

@ -59,11 +59,13 @@ class Page extends Domain {
super(session);
this._onDialogLoaded = this._onDialogLoaded.bind(this);
this._onRequest = this._onRequest.bind(this);
this.enabled = false;
this.session.networkObserver.startTrackingBrowserNetwork(
this.session.target.browser
);
this.session.networkObserver.on("request", this._onRequest);
}
destructor() {
@ -71,6 +73,7 @@ class Page extends Domain {
this._isDestroyed = false;
this.disable();
this.session.networkObserver.off("request", this._onRequest);
this.session.networkObserver.stopTrackingBrowserNetwork(
this.session.target.browser
);
@ -108,7 +111,8 @@ class Page extends Domain {
} catch (e) {
throw new Error("Error: Cannot navigate to invalid URL");
}
if (frameId && frameId != this.session.browsingContext.id.toString()) {
const topFrameId = this.session.browsingContext.id.toString();
if (frameId && frameId != topFrameId) {
throw new UnsupportedError("frameId not supported");
}
@ -160,14 +164,10 @@ class Page extends Domain {
};
this.session.browsingContext.loadURI(url, opts);
// clients expect loaderId == requestId for a document navigation request
const {
// TODO as part of Bug 1599260
// navigationRequestId: loaderId,
errorCode,
} = await requestDone;
const { navigationRequestId: loaderId, errorCode } = await requestDone;
const result = {
frameId: this.session.browsingContext.id.toString(),
// loaderId,
frameId: topFrameId,
loaderId,
};
if (errorCode) {
result.errorText = errorCode;
@ -736,6 +736,20 @@ class Page extends Domain {
// on the actual tests relying on this API.
this.emit("Page.javascriptDialogOpening", { message, type });
}
/**
* Handles HTTP request to propagate loaderId to events emitted from
* content process
*/
_onRequest(_type, _ch, data) {
if (!data.loaderId) {
return;
}
this.executeInChild("_updateLoaderId", {
loaderId: data.loaderId,
frameId: this.session.browsingContext.id.toString(),
});
}
}
function transitionToLoadFlag(transitionType) {

View File

@ -3,7 +3,7 @@
"use strict";
// Test order and consistency of Network events as a whole.
// Test order and consistency of Network/Page events as a whole.
// Details of specific events are checked in event-specific test files.
const PAGE_URL =
@ -14,17 +14,19 @@ const JS_URL =
add_task(async function documentNavigationWithScriptResource({ client }) {
const { Page, Network } = client;
await Network.enable();
await Page.enable();
await Page.setLifecycleEventsEnabled({ enabled: true });
const { history, urlToEvents } = configureHistory(client);
const navigateDone = history.addPromise("Page.navigate");
const { frameId } = await Page.navigate({ url: PAGE_URL }).then(navigateDone);
ok(frameId, "Page.navigate returned a frameId");
info("Wait for Network events");
info("Wait for events");
const events = await history.record();
is(events.length, 5, "Expected number of events");
is(events.length, 8, "Expected number of events");
const eventNames = events.map(
item => `${item.eventName}(${item.payload.type || ""})`
item => `${item.eventName}(${item.payload.type || item.payload.name})`
);
info(`Received events: ${eventNames}`);
const documentEvents = urlToEvents.get(PAGE_URL);
@ -43,6 +45,19 @@ add_task(async function documentNavigationWithScriptResource({ client }) {
const docRequest = documentEvents[0].event;
is(docRequest.request.url, PAGE_URL, "Got the doc request");
is(docRequest.documentURL, PAGE_URL, "documenURL matches request url");
const lifeCycleEvents = history.findEvents("Page.lifecycleEvent");
const firstLifecycle = history.indexOf("Page.lifecycleEvent");
ok(
firstLifecycle > documentEvents[1].index,
"First lifecycle event is after document response"
);
for (const e of lifeCycleEvents) {
is(
e.loaderId,
docRequest.loaderId,
`${e.name} lifecycle event has same loaderId as document request`
);
}
const resourceRequest = resourceEvents[0].event;
is(resourceRequest.documentURL, PAGE_URL, "documentURL is trigger document");
@ -53,13 +68,17 @@ add_task(async function documentNavigationWithScriptResource({ client }) {
);
const navigateStep = history.indexOf("Page.navigate");
ok(
documentEvents[1].index < navigateStep,
navigateStep > documentEvents[1].index,
"Page.navigate returns after document response"
);
ok(
navigateStep < resourceEvents[0].index,
"Page.navigate returns before resource request"
);
ok(
navigateStep < firstLifecycle,
"Page.navigate returns before first lifecycle event"
);
const docResponse = documentEvents[1].event;
is(docResponse.response.url, PAGE_URL, "Got the doc response");
@ -106,9 +125,10 @@ add_task(async function documentNavigationWithScriptResource({ client }) {
function configureHistory(client) {
const REQUEST = "Network.requestWillBeSent";
const RESPONSE = "Network.responseReceived";
const LIFECYCLE = "Page.lifecycleEvent";
const { Network } = client;
const history = new RecordEvents(4);
const { Network, Page } = client;
const history = new RecordEvents(8);
const urlToEvents = new Map();
function updateUrlToEvents(kind) {
return ({ payload, index, eventName }) => {
@ -141,5 +161,14 @@ function configureHistory(client) {
},
callback: updateUrlToEvents("response"),
});
history.addRecorder({
event: Page.lifecycleEvent,
eventName: LIFECYCLE,
messageFn: payload => {
return `Received ${LIFECYCLE} ${payload.name}`;
},
});
return { history, urlToEvents };
}

View File

@ -31,7 +31,7 @@ add_task(async function noEventsAfterNetworkDomainDisabled({ client }) {
add_task(async function documentNavigationWithResource({ client }) {
const { Page, Network } = client;
await Network.enable();
const history = configureHistory(client);
const history = configureHistory(client, 2);
const { frameId: frameIdNav } = await Page.navigate({ url: PAGE_URL });
ok(frameIdNav, "Page.navigate returned a frameId");
@ -82,11 +82,11 @@ add_task(async function documentNavigationWithResource({ client }) {
);
});
function configureHistory(client) {
function configureHistory(client, total) {
const REQUEST = "Network.requestWillBeSent";
const { Network } = client;
const history = new RecordEvents(4);
const history = new RecordEvents(total);
history.addRecorder({
event: Network.requestWillBeSent,

View File

@ -6,6 +6,8 @@
// Test the Page lifecycle events
const DOC = toDataURL("default-test-page");
const PAGE_URL =
"http://example.com/browser/remote/test/browser/page/doc_empty.html";
add_task(async function noInitialEvents({ client }) {
const { Page } = client;
@ -41,7 +43,7 @@ add_task(async function noEventsAfterDisable({ client }) {
await assertNavigationLifecycleEvents({ promise, frameId, timeout: 1000 });
});
add_task(async function navigateEvents({ client }) {
add_task(async function navigateToDataURLEvents({ client }) {
const { Page } = client;
await Page.enable();
info("Page domain has been enabled");
@ -51,11 +53,32 @@ add_task(async function navigateEvents({ client }) {
info("Lifecycle events have been enabled");
let pageLoaded = Page.loadEventFired();
const { frameId } = await Page.navigate({ url: DOC });
const { frameId, loaderId } = await Page.navigate({ url: DOC });
// Bug 1632007 return a loaderId for data: url
todo(!!loaderId, "Page.navigate returns a loaderId");
await pageLoaded;
info("A new page has been loaded");
await assertNavigationLifecycleEvents({ promise, frameId });
await assertNavigationLifecycleEvents({ promise, frameId, loaderId });
});
add_task(async function navigateToPageURLEvents({ client }) {
const { Page } = client;
await Page.enable();
info("Page domain has been enabled");
await Page.setLifecycleEventsEnabled({ enabled: true });
const promise = recordPromises(Page, ["init", "DOMContentLoaded", "load"]);
info("Lifecycle events have been enabled");
let pageLoaded = Page.loadEventFired();
const { frameId, loaderId } = await Page.navigate({ url: PAGE_URL });
ok(!!loaderId, "Page.navigate returns a loaderId");
await pageLoaded;
info("A new page has been loaded");
await assertNavigationLifecycleEvents({ promise, frameId, loaderId });
});
add_task(async function navigateEventsOnReload({ client }) {
@ -118,7 +141,8 @@ function recordPromises(Page, names) {
});
}
async function assertNavigationLifecycleEvents({ promise, frameId, timeout }) {
async function assertNavigationLifecycleEvents(options = {}) {
const { promise, frameId, loaderId, timeout } = options;
// Wait for all the promises to resolve
const promises = [promise];
@ -142,8 +166,8 @@ async function assertNavigationLifecycleEvents({ promise, frameId, timeout }) {
);
// Now assert the data exposed by each of these events
const frameStartedLoading = resolutions.get("init");
is(frameStartedLoading.frameId, frameId, "init frameId is the same one");
const init = resolutions.get("init");
is(init.frameId, frameId, "init frameId is the same one");
const DOMContentLoaded = resolutions.get("DOMContentLoaded");
is(
@ -154,4 +178,25 @@ async function assertNavigationLifecycleEvents({ promise, frameId, timeout }) {
const load = resolutions.get("load");
is(load.frameId, frameId, "load frameId is the same one");
ok(init.timestamp, "init has a timestamp");
ok(
init.timestamp <= DOMContentLoaded.timestamp,
"init precedes DOMContentLoaded"
);
ok(
DOMContentLoaded.timestamp <= load.timestamp,
"DOMContentLoaded precedes load"
);
if (!loaderId) {
return;
}
is(init.loaderId, loaderId, "init has expected loaderId");
is(load.loaderId, loaderId, "load has expected loaderId");
is(
DOMContentLoaded.loaderId,
loaderId,
"DOMContentLoaded has expected loaderId"
);
}

View File

@ -7,14 +7,22 @@ const pageEmptyURL =
"http://example.com/browser/remote/test/browser/page/doc_empty.html";
add_task(async function testBasicNavigation({ client }) {
const { Page } = client;
const { Page, Network } = client;
await Page.enable();
await Network.enable();
const loadEventFired = Page.loadEventFired();
const requestEvent = Network.requestWillBeSent();
const { frameId, loaderId, errorText } = await Page.navigate({
url: pageEmptyURL,
});
const { loaderId: requestLoaderId } = await requestEvent;
todo(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId, "Page.navigate returns loaderId");
is(
loaderId,
requestLoaderId,
"Page.navigate returns same loaderId as corresponding request"
);
is(errorText, undefined, "No errorText on a successful navigation");
await loadEventFired;
@ -29,12 +37,15 @@ add_task(async function testBasicNavigation({ client }) {
});
add_task(async function testTwoNavigations({ client }) {
const { Page } = client;
const { Page, Network } = client;
await Page.enable();
await Network.enable();
let requestEvent = Network.requestWillBeSent();
let loadEventFired = Page.loadEventFired();
const { frameId, loaderId, errorText } = await Page.navigate({
url: pageEmptyURL,
});
const { loaderId: requestLoaderId } = await requestEvent;
await loadEventFired;
is(
gBrowser.selectedBrowser.currentURI.spec,
@ -43,6 +54,7 @@ add_task(async function testTwoNavigations({ client }) {
);
loadEventFired = Page.loadEventFired();
requestEvent = Network.requestWillBeSent();
const {
frameId: frameId2,
loaderId: loaderId2,
@ -50,7 +62,20 @@ add_task(async function testTwoNavigations({ client }) {
} = await Page.navigate({
url: pageEmptyURL,
});
todo(loaderId !== loaderId2, "Page.navigate returns different loaderIds");
const { loaderId: requestLoaderId2 } = await requestEvent;
ok(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId2, "Page.navigate returns loaderId");
isnot(loaderId, loaderId2, "Page.navigate returns different loaderIds");
is(
loaderId,
requestLoaderId,
"Page.navigate returns same loaderId as corresponding request"
);
is(
loaderId2,
requestLoaderId2,
"Page.navigate returns same loaderId as corresponding request"
);
is(errorText, undefined, "No errorText on a successful navigation");
is(errorText2, undefined, "No errorText on a successful navigation");
is(frameId, frameId2, "Page.navigate return same frameId");
@ -64,16 +89,25 @@ add_task(async function testTwoNavigations({ client }) {
});
add_task(async function testRedirect({ client }) {
const { Page } = client;
const { Page, Network } = client;
const sjsURL =
"http://example.com/browser/remote/test/browser/page/sjs_redirect.sjs";
const redirectURL = `${sjsURL}?${pageEmptyURL}`;
await Page.enable();
await Network.enable();
const requestEvent = Network.requestWillBeSent();
const loadEventFired = Page.loadEventFired();
const { frameId, loaderId, errorText } = await Page.navigate({
url: redirectURL,
});
todo(!!loaderId, "Page.navigate returns loaderId");
const { loaderId: requestLoaderId } = await requestEvent;
ok(!!loaderId, "Page.navigate returns loaderId");
is(
loaderId,
requestLoaderId,
"Page.navigate returns same loaderId as original request"
);
is(errorText, undefined, "No errorText on a successful navigation");
ok(!!frameId, "Page.navigate returns frameId");
@ -91,7 +125,7 @@ add_task(async function testUnknownHost({ client }) {
url: "http://example-does-not-exist.com",
});
ok(!!frameId, "Page.navigate returns frameId");
todo(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId, "Page.navigate returns loaderId");
is(errorText, "NS_ERROR_UNKNOWN_HOST", "Failed navigation returns errorText");
});
@ -101,7 +135,7 @@ add_task(async function testExpiredCertificate({ client }) {
url: "https://expired.example.com",
});
ok(!!frameId, "Page.navigate returns frameId");
todo(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId, "Page.navigate returns loaderId");
is(
errorText,
"SEC_ERROR_EXPIRED_CERTIFICATE",
@ -110,12 +144,20 @@ add_task(async function testExpiredCertificate({ client }) {
});
add_task(async function testUnknownCertificate({ client }) {
const { Page } = client;
const { Page, Network } = client;
await Network.enable();
const requestEvent = Network.requestWillBeSent();
const { frameId, loaderId, errorText } = await Page.navigate({
url: "https://self-signed.example.com",
});
const { loaderId: requestLoaderId } = await requestEvent;
ok(!!frameId, "Page.navigate returns frameId");
todo(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId, "Page.navigate returns loaderId");
is(
loaderId,
requestLoaderId,
"Page.navigate returns same loaderId as original request"
);
is(errorText, "SSL_ERROR_UNKNOWN", "Failed navigation returns errorText");
});
@ -125,7 +167,7 @@ add_task(async function testNotFound({ client }) {
url: "http://example.com/browser/remote/doesnotexist.html",
});
ok(!!frameId, "Page.navigate returns frameId");
todo(!!loaderId, "Page.navigate returns loaderId");
ok(!!loaderId, "Page.navigate returns loaderId");
is(errorText, undefined, "No errorText on a 404");
});