Bug 1716284 - [devtools] Add a reflow resource. r=ochameau.

This is meant to replace usage of the ReflowActor.

Differential Revision: https://phabricator.services.mozilla.com/D117899
This commit is contained in:
Nicolas Chevobbe 2021-06-16 11:43:19 +00:00
parent bf9d68cfb7
commit a2652bf4e9
11 changed files with 208 additions and 1 deletions

View File

@ -305,7 +305,7 @@ LayoutChangesObserver.prototype = {
_startEventLoop: function() {
// Avoid emitting events if the targetActor has been detached (may happen
// during shutdown)
if (!this.targetActor || !this.targetActor.attached) {
if (!this.targetActor || this.targetActor.isDestroyed()) {
return;
}

View File

@ -16,6 +16,7 @@ const TYPES = {
NETWORK_EVENT: "network-event",
STYLESHEET: "stylesheet",
NETWORK_EVENT_STACKTRACE: "network-event-stacktrace",
REFLOW: "reflow",
SOURCE: "source",
THREAD_STATE: "thread-state",
SERVER_SENT_EVENT: "server-sent-event",
@ -75,6 +76,9 @@ const FrameTargetResources = augmentResourceDictionary({
[TYPES.NETWORK_EVENT_STACKTRACE]: {
path: "devtools/server/actors/resources/network-events-stacktraces",
},
[TYPES.REFLOW]: {
path: "devtools/server/actors/resources/reflow",
},
[TYPES.SOURCE]: {
path: "devtools/server/actors/resources/sources",
},

View File

@ -19,6 +19,7 @@ DevToolsModules(
"network-events-stacktraces.js",
"network-events.js",
"platform-messages.js",
"reflow.js",
"server-sent-events.js",
"sources.js",
"storage-cache.js",

View File

@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
TYPES: { REFLOW },
} = require("devtools/server/actors/resources/index");
const Targets = require("devtools/server/actors/targets/index");
const {
getLayoutChangesObserver,
releaseLayoutChangesObserver,
} = require("devtools/server/actors/reflow");
class ReflowWatcher {
/**
* Start watching for reflows related to a given Target Actor.
*
* @param TargetActor targetActor
* The target actor from which we should observe reflows
* @param Object options
* Dictionary object with following attributes:
* - onAvailable: mandatory function
* This will be called for each resource.
*/
async watch(targetActor, { onAvailable }) {
// Only track reflow for non-ParentProcess FRAME targets
if (
targetActor.targetType !== Targets.TYPES.FRAME ||
targetActor.typeName === "parentProcessTarget"
) {
return;
}
this._targetActor = targetActor;
const onReflows = reflows => {
onAvailable([
{
resourceType: REFLOW,
reflows,
},
]);
};
this._observer = getLayoutChangesObserver(targetActor);
this._offReflows = this._observer.on("reflows", onReflows);
this._observer.start();
}
destroy() {
releaseLayoutChangesObserver(this._targetActor);
if (this._offReflows) {
this._offReflows();
this._offReflows = null;
}
}
}
module.exports = ReflowWatcher;

View File

@ -156,6 +156,7 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
[Resources.TYPES.PLATFORM_MESSAGE]: true,
[Resources.TYPES.NETWORK_EVENT]: hasBrowserElement,
[Resources.TYPES.NETWORK_EVENT_STACKTRACE]: hasBrowserElement,
[Resources.TYPES.REFLOW]: true,
[Resources.TYPES.STYLESHEET]: hasBrowserElement,
[Resources.TYPES.SOURCE]: hasBrowserElement,
[Resources.TYPES.THREAD_STATE]: hasBrowserElement,

View File

@ -35,6 +35,10 @@ class MockTargetActor extends EventEmitter {
get chromeEventHandler() {
return this.docShell.chromeEventHandler;
}
isDestroyed() {
return false;
}
}
function MockWindow(docShell) {

View File

@ -16,6 +16,7 @@ DevToolsModules(
"network-event-stacktraces.js",
"network-events.js",
"platform-messages.js",
"reflow.js",
"root-node.js",
"server-sent-events.js",
"session-storage.js",

View File

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const ResourceCommand = require("devtools/shared/commands/resource/resource-command");
module.exports = async function({ targetFront, onAvailable }) {
const reflowFront = await targetFront.getFront("reflow");
reflowFront.on("reflows", reflows =>
onAvailable([
{
resourceType: ResourceCommand.TYPES.REFLOW,
reflows,
},
])
);
await reflowFront.start();
};

View File

@ -1033,6 +1033,7 @@ ResourceCommand.TYPES = ResourceCommand.prototype.TYPES = {
EXTENSION_STORAGE: "extension-storage",
INDEXED_DB: "indexed-db",
NETWORK_EVENT_STACKTRACE: "network-event-stacktrace",
REFLOW: "reflow",
SOURCE: "source",
THREAD_STATE: "thread-state",
SERVER_SENT_EVENT: "server-sent-event",
@ -1100,6 +1101,8 @@ const LegacyListeners = {
.THREAD_STATE]: require("devtools/shared/commands/resource/legacy-listeners/thread-states"),
[ResourceCommand.TYPES
.SERVER_SENT_EVENT]: require("devtools/shared/commands/resource/legacy-listeners/server-sent-events"),
[ResourceCommand.TYPES
.REFLOW]: require("devtools/shared/commands/resource/legacy-listeners/reflow"),
};
// Optional transformers for each type of resource.

View File

@ -43,6 +43,7 @@ support-files =
[browser_resources_network_event_stacktraces.js]
[browser_resources_network_events.js]
[browser_resources_platform_messages.js]
[browser_resources_reflows.js]
[browser_resources_root_node.js]
[browser_resources_server_sent_events.js]
[browser_resources_several_resources.js]

View File

@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test the ResourceCommand API for reflows
const { TYPES } = require("devtools/shared/commands/resource/resource-command");
add_task(async function() {
const tab = await addTab(
"http://example.com/document-builder.sjs?html=<h1>Test reflow resources</h1>"
);
const { client, resourceCommand, targetCommand } = await initResourceCommand(
tab
);
const resources = [];
const onAvailable = _resources => {
resources.push(..._resources);
};
await resourceCommand.watchResources([TYPES.REFLOW], {
onAvailable,
});
is(resources.length, 0, "No reflow resource were sent initially");
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
const el = content.document.createElement("div");
el.textContent = "1";
content.document.body.appendChild(el);
});
await waitFor(() => resources.length === 1);
checkReflowResource(resources[0]);
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
const el = content.document.querySelector("div");
el.style.display = "inline-grid";
});
await waitFor(() => resources.length === 2);
ok(
true,
"A reflow resource is sent when the display property of an element is modified"
);
checkReflowResource(resources.at(-1));
info("Check that adding an iframe does emit a reflow");
const iframeBC = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async () => {
const el = content.document.createElement("iframe");
const onIframeLoaded = new Promise(resolve =>
el.addEventListener("load", resolve, { once: true })
);
content.document.body.appendChild(el);
el.src =
"http://example.org/document-builder.sjs?html=<h2>remote iframe</h2>";
await onIframeLoaded;
return el.browsingContext;
}
);
await waitFor(() => resources.length === 3);
ok(true, "A reflow resource was received when adding a remote iframe");
checkReflowResource(resources.at(-1));
info("Check that we receive reflow resources for the remote iframe");
await SpecialPowers.spawn(iframeBC, [], () => {
const el = content.document.createElement("section");
el.textContent = "remote org iframe";
el.style.display = "grid";
content.document.body.appendChild(el);
});
await waitFor(() => resources.length === 4);
if (isFissionEnabled()) {
ok(
resources.at(-1).targetFront.url.includes("example.org"),
"The reflow resource is linked to the remote target"
);
}
checkReflowResource(resources.at(-1));
targetCommand.destroy();
await client.close();
});
function checkReflowResource(resource) {
is(
resource.resourceType,
TYPES.REFLOW,
"The resource has the expected resourceType"
);
ok(Array.isArray(resource.reflows), "the `reflows` property is an array");
for (const reflow of resource.reflows) {
is(
Number.isFinite(reflow.start),
true,
"reflow start property is a number"
);
is(Number.isFinite(reflow.end), true, "reflow end property is a number");
ok(reflow.end >= reflow.start, "end is greater than start");
}
}