diff --git a/remote/domains/content/Page.jsm b/remote/domains/content/Page.jsm index 31a26053c134..80057967625a 100644 --- a/remote/domains/content/Page.jsm +++ b/remote/domains/content/Page.jsm @@ -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; diff --git a/remote/domains/parent/Page.jsm b/remote/domains/parent/Page.jsm index 7831fb3e8edd..6af5d387b51f 100644 --- a/remote/domains/parent/Page.jsm +++ b/remote/domains/parent/Page.jsm @@ -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) { diff --git a/remote/test/browser/network/browser_navigationEvents.js b/remote/test/browser/network/browser_navigationEvents.js index 22cb4e4a317e..202e164c8b86 100644 --- a/remote/test/browser/network/browser_navigationEvents.js +++ b/remote/test/browser/network/browser_navigationEvents.js @@ -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 }; } diff --git a/remote/test/browser/network/browser_requestWillBeSent.js b/remote/test/browser/network/browser_requestWillBeSent.js index ad7de26853a3..71014d69d08b 100644 --- a/remote/test/browser/network/browser_requestWillBeSent.js +++ b/remote/test/browser/network/browser_requestWillBeSent.js @@ -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, diff --git a/remote/test/browser/page/browser_lifecycleEvent.js b/remote/test/browser/page/browser_lifecycleEvent.js index a126d7d33bef..b46948960825 100644 --- a/remote/test/browser/page/browser_lifecycleEvent.js +++ b/remote/test/browser/page/browser_lifecycleEvent.js @@ -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" + ); } diff --git a/remote/test/browser/page/browser_navigate.js b/remote/test/browser/page/browser_navigate.js index 5bb24eefc11a..fbbf085a14f4 100644 --- a/remote/test/browser/page/browser_navigate.js +++ b/remote/test/browser/page/browser_navigate.js @@ -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"); });