fune/devtools/shared/commands/resource/tests/browser_resources_document_events.js
Alexandre Poirot 8e9b448440 Bug 1766485 - [devtools] Consider server side target switching always enabled for tab toolboxes. r=devtools-reviewers,nchevobbe
This removes the devtools.target-switching.server.enabled preference,
which was only having an impact on tab debugging.
Toolboxes other than tabs aren't yet using "server side target switching",
which will probably happen once we enable EFT in these and
we would probably not use a preference for enabling this.

This remove all assertions in tests covering client side target switching.
And it removed a couple of tests that became irrelevant.

Differential Revision: https://phabricator.services.mozilla.com/D166921
2023-01-30 12:49:54 +00:00

725 lines
21 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test the ResourceCommand API around DOCUMENT_EVENT
add_task(async function() {
await testDocumentEventResources();
await testDocumentEventResourcesWithIgnoreExistingResources();
await testDomCompleteWithOverloadedConsole();
await testIframeNavigation();
await testBfCacheNavigation();
await testDomCompleteWithWindowStop();
await testCrossOriginNavigation();
});
async function testDocumentEventResources() {
info("Test ResourceCommand for DOCUMENT_EVENT");
// Open a test tab
const title = "DocumentEventsTitle";
const url = `data:text/html,<title>${title}</title>Document Events`;
const tab = await addTab(url);
const listener = new ResourceListener();
const { commands } = await initResourceCommand(tab);
info(
"Check whether the document events are fired correctly even when the document was already loaded"
);
const onLoadingAtInit = listener.once("dom-loading");
const onInteractiveAtInit = listener.once("dom-interactive");
const onCompleteAtInit = listener.once("dom-complete");
await commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.DOCUMENT_EVENT],
{
onAvailable: parameters => listener.dispatch(parameters),
}
);
await assertPromises(
commands,
// targetBeforeNavigation is only used when there is a will-navigate and a navigate, but there is none here
null,
// As we started watching on an already loaded document, and no navigation happened since we called watchResources,
// we don't have any will-navigate event
null,
onLoadingAtInit,
onInteractiveAtInit,
onCompleteAtInit
);
ok(
true,
"Document events are fired even when the document was already loaded"
);
let domLoadingResource = await onLoadingAtInit;
is(
domLoadingResource.url,
url,
`resource ${domLoadingResource.name} has expected url`
);
is(
domLoadingResource.title,
undefined,
`resource ${domLoadingResource.name} does not have a title property`
);
let domInteractiveResource = await onInteractiveAtInit;
is(
domInteractiveResource.url,
url,
`resource ${domInteractiveResource.name} has expected url`
);
is(
domInteractiveResource.title,
title,
`resource ${domInteractiveResource.name} has expected title`
);
let domCompleteResource = await onCompleteAtInit;
is(
domCompleteResource.url,
undefined,
`resource ${domCompleteResource.name} does not have a url property`
);
is(
domCompleteResource.title,
undefined,
`resource ${domCompleteResource.name} does not have a title property`
);
info("Check whether the document events are fired correctly when reloading");
const onWillNavigate = listener.once("will-navigate");
const onLoadingAtReloaded = listener.once("dom-loading");
const onInteractiveAtReloaded = listener.once("dom-interactive");
const onCompleteAtReloaded = listener.once("dom-complete");
const targetBeforeNavigation = commands.targetCommand.targetFront;
gBrowser.reloadTab(tab);
await assertPromises(
commands,
targetBeforeNavigation,
onWillNavigate,
onLoadingAtReloaded,
onInteractiveAtReloaded,
onCompleteAtReloaded
);
ok(true, "Document events are fired after reloading");
domLoadingResource = await onLoadingAtReloaded;
is(
domLoadingResource.url,
url,
`resource ${domLoadingResource.name} has expected url after reloading`
);
is(
domLoadingResource.title,
undefined,
`resource ${domLoadingResource.name} does not have a title property after reloading`
);
domInteractiveResource = await onInteractiveAtInit;
is(
domInteractiveResource.url,
url,
`resource ${domInteractiveResource.name} has url property after reloading`
);
is(
domInteractiveResource.title,
title,
`resource ${domInteractiveResource.name} has expected title after reloading`
);
domCompleteResource = await onCompleteAtInit;
is(
domCompleteResource.url,
undefined,
`resource ${domCompleteResource.name} does not have a url property after reloading`
);
is(
domCompleteResource.title,
undefined,
`resource ${domCompleteResource.name} does not have a title property after reloading`
);
await commands.destroy();
}
async function testDocumentEventResourcesWithIgnoreExistingResources() {
info("Test ignoreExistingResources option for DOCUMENT_EVENT");
const tab = await addTab("data:text/html,Document Events");
const { commands } = await initResourceCommand(tab);
info("Check whether the existing document events will not be fired");
const documentEvents = [];
await commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.DOCUMENT_EVENT],
{
onAvailable: resources => documentEvents.push(...resources),
ignoreExistingResources: true,
}
);
is(documentEvents.length, 0, "Existing document events are not fired");
info("Check whether the future document events are fired");
const targetBeforeNavigation = commands.targetCommand.targetFront;
gBrowser.reloadTab(tab);
info(
"Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
);
await waitFor(() => documentEvents.length === 4);
assertEvents({ commands, targetBeforeNavigation, documentEvents });
await commands.destroy();
}
async function testIframeNavigation() {
info("Test iframe navigations for DOCUMENT_EVENT");
const tab = await addTab(
'https://example.com/document-builder.sjs?html=<iframe src="https://example.net/document-builder.sjs?html=net"></iframe>'
);
const secondPageUrl = "https://example.org/document-builder.sjs?html=org";
const { commands } = await initResourceCommand(tab);
let documentEvents = [];
await commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.DOCUMENT_EVENT],
{
onAvailable: resources => documentEvents.push(...resources),
}
);
let iframeTarget;
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
is(
documentEvents.length,
6,
"With fission/EFT, we get two targets and two sets of events: dom-loading, dom-interactive, dom-complete"
);
[, iframeTarget] = await commands.targetCommand.getAllTargets([
commands.targetCommand.TYPES.FRAME,
]);
// Filter out each target events as their order to be random between the two targets
const topTargetEvents = documentEvents.filter(
r => r.targetFront == commands.targetCommand.targetFront
);
const iframeTargetEvents = documentEvents.filter(
r => r.targetFront != commands.targetCommand.targetFront
);
assertEvents({
commands,
documentEvents: [null /* no will-navigate */, ...topTargetEvents],
});
assertEvents({
commands,
documentEvents: [null /* no will-navigate */, ...iframeTargetEvents],
expectedTargetFront: iframeTarget,
});
} else {
assertEvents({
commands,
documentEvents: [null /* no will-navigate */, ...documentEvents],
});
}
info("Navigate the iframe to another process (if fission is enabled)");
documentEvents = [];
await SpecialPowers.spawn(gBrowser.selectedBrowser, [secondPageUrl], function(
url
) {
const iframe = content.document.querySelector("iframe");
iframe.src = url;
});
// We are switching to a new target only when fission is enabled...
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
await waitFor(() => documentEvents.length >= 3);
is(
documentEvents.length,
3,
"With fission/EFT, we switch to a new target and get: dom-loading, dom-interactive, dom-complete (but no will-navigate as that's only for the top BrowsingContext)"
);
const [, newIframeTarget] = await commands.targetCommand.getAllTargets([
commands.targetCommand.TYPES.FRAME,
]);
assertEvents({
commands,
targetBeforeNavigation: iframeTarget,
documentEvents: [null /* no will-navigate */, ...documentEvents],
expectedTargetFront: newIframeTarget,
expectedNewURI: secondPageUrl,
});
} else {
// Wait for some time in order to let a chance to receive some unexpected events
await wait(250);
is(
documentEvents.length,
0,
"If fission is disabled, we navigate within the same process, we get no new target and no new resource"
);
}
await commands.destroy();
}
function isBfCacheInParentEnabled() {
return (
Services.appinfo.sessionHistoryInParent &&
Services.prefs.getBoolPref("fission.bfcacheInParent", false)
);
}
async function testBfCacheNavigation() {
info("Test bfcache navigations for DOCUMENT_EVENT");
info("Open a first document and navigate to a second one");
const firstLocation = "data:text/html,<title>first</title>first page";
const secondLocation = "data:text/html,<title>second</title>second page";
const tab = await addTab(firstLocation);
const onLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, secondLocation);
await onLoaded;
const { commands } = await initResourceCommand(tab);
const documentEvents = [];
await commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.DOCUMENT_EVENT],
{
onAvailable: resources => {
documentEvents.push(...resources);
},
ignoreExistingResources: true,
}
);
// Wait for some time for extra safety
await wait(250);
is(documentEvents.length, 0, "Existing document events are not fired");
info("Navigate back to the first page");
const onSwitched = commands.targetCommand.once("switched-target");
const targetBeforeNavigation = commands.targetCommand.targetFront;
gBrowser.goBack();
// We are switching to a new target only when fission/EFT is enabled...
if (
(isFissionEnabled() || isEveryFrameTargetEnabled()) &&
isBfCacheInParentEnabled()
) {
await onSwitched;
}
info(
"Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
);
await waitFor(() => documentEvents.length >= 4);
/* Ignore will-navigate timestamp as all other DOCUMENT_EVENTS will be set at the original load date,
which is when we loaded from the network, and not when we loaded from bfcache */
assertEvents({
commands,
targetBeforeNavigation,
documentEvents,
ignoreWillNavigateTimestamp: true,
});
// Wait for some time in order to let a chance to have duplicated dom-loading events
await wait(250);
is(
documentEvents.length,
4,
"There is no duplicated event and only the 4 expected DOCUMENT_EVENT states"
);
const [
willNavigateEvent,
loadingEvent,
interactiveEvent,
completeEvent,
] = documentEvents;
is(
willNavigateEvent.name,
"will-navigate",
"The first DOCUMENT_EVENT is will-navigate"
);
is(
loadingEvent.name,
"dom-loading",
"The second DOCUMENT_EVENT is dom-loading"
);
is(
interactiveEvent.name,
"dom-interactive",
"The third DOCUMENT_EVENT is dom-interactive"
);
is(
completeEvent.name,
"dom-complete",
"The fourth DOCUMENT_EVENT is dom-complete"
);
is(
loadingEvent.url,
firstLocation,
`resource ${loadingEvent.name} has expected url after navigation back`
);
is(
loadingEvent.title,
undefined,
`resource ${loadingEvent.name} does not have a title property after navigating back`
);
is(
interactiveEvent.url,
firstLocation,
`resource ${interactiveEvent.name} has expected url property after navigating back`
);
is(
interactiveEvent.title,
"first",
`resource ${interactiveEvent.name} has expected title after navigating back`
);
is(
completeEvent.url,
undefined,
`resource ${completeEvent.name} does not have a url property after navigating back`
);
is(
completeEvent.title,
undefined,
`resource ${completeEvent.name} does not have a title property after navigating back`
);
await commands.destroy();
}
async function testCrossOriginNavigation() {
info("Test cross origin navigations for DOCUMENT_EVENT");
const tab = await addTab("https://example.com/document-builder.sjs?html=com");
const { commands } = await initResourceCommand(tab);
const documentEvents = [];
await commands.resourceCommand.watchResources(
[commands.resourceCommand.TYPES.DOCUMENT_EVENT],
{
onAvailable: resources => documentEvents.push(...resources),
ignoreExistingResources: true,
}
);
// Wait for some time for extra safety
await wait(250);
is(documentEvents.length, 0, "Existing document events are not fired");
info("Navigate to another process");
const onSwitched = commands.targetCommand.once("switched-target");
const netUrl =
"https://example.net/document-builder.sjs?html=<head><title>titleNet</title></head>net";
const onLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
const targetBeforeNavigation = commands.targetCommand.targetFront;
BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, netUrl);
await onLoaded;
// We are switching to a new target only when fission is enabled...
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
await onSwitched;
}
info(
"Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
);
await waitFor(() => documentEvents.length >= 4);
assertEvents({ commands, targetBeforeNavigation, documentEvents });
// Wait for some time in order to let a chance to have duplicated dom-loading events
await wait(250);
is(
documentEvents.length,
4,
"There is no duplicated event and only the 4 expected DOCUMENT_EVENT states"
);
const [
willNavigateEvent,
loadingEvent,
interactiveEvent,
completeEvent,
] = documentEvents;
is(
willNavigateEvent.name,
"will-navigate",
"The first DOCUMENT_EVENT is will-navigate"
);
is(
loadingEvent.name,
"dom-loading",
"The second DOCUMENT_EVENT is dom-loading"
);
is(
interactiveEvent.name,
"dom-interactive",
"The third DOCUMENT_EVENT is dom-interactive"
);
is(
completeEvent.name,
"dom-complete",
"The fourth DOCUMENT_EVENT is dom-complete"
);
is(
loadingEvent.url,
encodeURI(netUrl),
`resource ${loadingEvent.name} has expected url after reloading`
);
is(
loadingEvent.title,
undefined,
`resource ${loadingEvent.name} does not have a title property after reloading`
);
is(
interactiveEvent.url,
encodeURI(netUrl),
`resource ${interactiveEvent.name} has expected url property after reloading`
);
is(
interactiveEvent.title,
"titleNet",
`resource ${interactiveEvent.name} has expected title after reloading`
);
is(
completeEvent.url,
undefined,
`resource ${completeEvent.name} does not have a url property after reloading`
);
is(
completeEvent.title,
undefined,
`resource ${completeEvent.name} does not have a title property after reloading`
);
await commands.destroy();
}
async function testDomCompleteWithOverloadedConsole() {
info("Test dom-complete with an overloaded console object");
const tab = await addTab(
"data:text/html,<script>window.console = {};</script>"
);
const { client, resourceCommand, targetCommand } = await initResourceCommand(
tab
);
info("Check that all DOCUMENT_EVENTS are fired for the already loaded page");
const documentEvents = [];
await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], {
onAvailable: resources => documentEvents.push(...resources),
});
is(documentEvents.length, 3, "Existing document events are fired");
const domComplete = documentEvents[2];
is(domComplete.name, "dom-complete", "the last resource is the dom-complete");
is(
domComplete.hasNativeConsoleAPI,
false,
"the console object is reported to be overloaded"
);
targetCommand.destroy();
await client.close();
}
async function testDomCompleteWithWindowStop() {
info("Test dom-complete with a page calling window.stop()");
const tab = await addTab("data:text/html,foo");
const {
commands,
client,
resourceCommand,
targetCommand,
} = await initResourceCommand(tab);
info("Check that all DOCUMENT_EVENTS are fired for the already loaded page");
let documentEvents = [];
await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], {
onAvailable: resources => documentEvents.push(...resources),
});
is(documentEvents.length, 3, "Existing document events are fired");
documentEvents = [];
const html = `<!DOCTYPE html><html>
<head>
<title>stopped page</title>
<script>window.stop();</script>
</head>
<body>Page content that shouldn't be displayed</body>
</html>`;
const secondLocation = "data:text/html," + encodeURIComponent(html);
const targetBeforeNavigation = commands.targetCommand.targetFront;
BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, secondLocation);
info(
"Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
);
await waitFor(() => documentEvents.length === 4);
assertEvents({ commands, targetBeforeNavigation, documentEvents });
targetCommand.destroy();
await client.close();
}
async function assertPromises(
commands,
targetBeforeNavigation,
onWillNavigate,
onLoading,
onInteractive,
onComplete
) {
const willNavigateEvent = await onWillNavigate;
const loadingEvent = await onLoading;
const interactiveEvent = await onInteractive;
const completeEvent = await onComplete;
assertEvents({
commands,
targetBeforeNavigation,
documentEvents: [
willNavigateEvent,
loadingEvent,
interactiveEvent,
completeEvent,
],
});
}
function assertEvents({
commands,
targetBeforeNavigation,
documentEvents,
expectedTargetFront = commands.targetCommand.targetFront,
expectedNewURI = gBrowser.selectedBrowser.currentURI.spec,
ignoreWillNavigateTimestamp = false,
}) {
const [
willNavigateEvent,
loadingEvent,
interactiveEvent,
completeEvent,
] = documentEvents;
if (willNavigateEvent) {
is(willNavigateEvent.name, "will-navigate", "Received the will-navigate");
is(
willNavigateEvent.newURI,
expectedNewURI,
"will-navigate newURI is set to the current tab new location"
);
}
is(
loadingEvent.name,
"dom-loading",
"loading received in the exepected order"
);
is(
interactiveEvent.name,
"dom-interactive",
"interactive received in the expected order"
);
is(completeEvent.name, "dom-complete", "complete received last");
if (willNavigateEvent) {
is(
typeof willNavigateEvent.time,
"number",
`Type of time attribute for will-navigate event is correct (${willNavigateEvent.time})`
);
}
is(
typeof loadingEvent.time,
"number",
`Type of time attribute for loading event is correct (${loadingEvent.time})`
);
is(
typeof interactiveEvent.time,
"number",
`Type of time attribute for interactive event is correct (${interactiveEvent.time})`
);
is(
typeof completeEvent.time,
"number",
`Type of time attribute for complete event is correct (${completeEvent.time})`
);
if (willNavigateEvent && !ignoreWillNavigateTimestamp) {
ok(
willNavigateEvent.time <= loadingEvent.time,
`Timestamp for dom-loading event is greater than will-navigate event (${willNavigateEvent.time} <= ${loadingEvent.time})`
);
}
ok(
loadingEvent.time <= interactiveEvent.time,
`Timestamp for interactive event is greater than loading event (${loadingEvent.time} <= ${interactiveEvent.time})`
);
ok(
interactiveEvent.time <= completeEvent.time,
`Timestamp for complete event is greater than interactive event (${interactiveEvent.time} <= ${completeEvent.time}).`
);
if (willNavigateEvent) {
// If we switched to a new target, this target will be different from currentTargetFront.
// This only happen if we navigate to another process or if server target switching is enabled.
is(
willNavigateEvent.targetFront,
targetBeforeNavigation,
"will-navigate target was the one before the navigation"
);
}
is(
loadingEvent.targetFront,
expectedTargetFront,
"loading target is the expected one"
);
is(
interactiveEvent.targetFront,
expectedTargetFront,
"interactive target is the expected one"
);
is(
completeEvent.targetFront,
expectedTargetFront,
"complete target is the expected one"
);
is(
completeEvent.hasNativeConsoleAPI,
true,
"None of the tests (except the dedicated one) overload the console object"
);
}
class ResourceListener {
_listeners = new Map();
dispatch(resources) {
for (const resource of resources) {
const resolve = this._listeners.get(resource.name);
if (resolve) {
resolve(resource);
this._listeners.delete(resource.name);
}
}
}
once(resourceName) {
return new Promise(r => this._listeners.set(resourceName, r));
}
}