зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1471754 - Implement the TargetList component. r=jdescottes
This component will help build and maintain the list of all the Targets. Making it easier to: * listen for all the targets: TargetList.watchTargets/unwatchTargets, * iterate over all the existing ones: TargetList.getAllTargets, * get all the TargetScoped fronts of all the targets: TargetList.getAllFronts. Differential Revision: https://phabricator.services.mozilla.com/D48857 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
a25daf5a9e
Коммит
19707475af
|
@ -54,6 +54,10 @@ class ProcessDescriptorFront extends FrontClassWithSpec(processDescriptorSpec) {
|
|||
return front;
|
||||
}
|
||||
|
||||
getCachedTarget() {
|
||||
return this._processTargetFront;
|
||||
}
|
||||
|
||||
async getTarget() {
|
||||
// Only return the cached Target if it is still alive.
|
||||
if (this._processTargetFront && this._processTargetFront.actorID) {
|
||||
|
@ -89,7 +93,10 @@ class ProcessDescriptorFront extends FrontClassWithSpec(processDescriptorSpec) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
this._processTargetFront = null;
|
||||
if (this._processTargetFront) {
|
||||
this._processTargetFront.destroy();
|
||||
this._processTargetFront = null;
|
||||
}
|
||||
this._targetFrontPromise = null;
|
||||
super.destroy();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ DIRS += [
|
|||
'platform',
|
||||
'protocol',
|
||||
'qrcode',
|
||||
'resources',
|
||||
'screenshot',
|
||||
'security',
|
||||
'sprintfjs',
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'target-list.js',
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
|
|
@ -0,0 +1,499 @@
|
|||
/* 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 Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const BROWSERTOOLBOX_FISSION_ENABLED = "devtools.browsertoolbox.fission";
|
||||
|
||||
// Intermediate components which implement the watch + unwatch
|
||||
// using existing listFoo methods and fooListChanged events.
|
||||
// The plan here is to followup to implement listen and unlisten
|
||||
// methods directly on the target fronts. This code would then
|
||||
// become the backward compatibility code which we could later remove.
|
||||
class LegacyImplementationProcesses {
|
||||
constructor(rootFront, target, onTargetAvailable, onTargetDestroyed) {
|
||||
this.rootFront = rootFront;
|
||||
this.target = target;
|
||||
this.onTargetAvailable = onTargetAvailable;
|
||||
this.onTargetDestroyed = onTargetDestroyed;
|
||||
|
||||
this.descriptors = new Set();
|
||||
this._processListChanged = this._processListChanged.bind(this);
|
||||
}
|
||||
|
||||
async _processListChanged() {
|
||||
const { processes } = await this.rootFront.listProcesses();
|
||||
// Process the new list to detect the ones being destroyed
|
||||
// Force destroyed the descriptor as well as the target
|
||||
for (const descriptor of this.descriptors) {
|
||||
if (!processes.includes(descriptor)) {
|
||||
// Manually call onTargetDestroyed listeners in order to
|
||||
// ensure calling them *before* destroying the descriptor.
|
||||
// Otherwise the descriptor will automatically destroy the target
|
||||
// and may not fire the contentProcessTarget's destroy event.
|
||||
const target = descriptor.getCachedTarget();
|
||||
if (target) {
|
||||
this.onTargetDestroyed(target);
|
||||
}
|
||||
|
||||
descriptor.destroy();
|
||||
this.descriptors.delete(descriptor);
|
||||
}
|
||||
}
|
||||
// Add the new process descriptors to the local list
|
||||
for (const descriptor of processes) {
|
||||
if (!this.descriptors.has(descriptor)) {
|
||||
this.descriptors.add(descriptor);
|
||||
const target = await descriptor.getTarget();
|
||||
if (!target) {
|
||||
console.error("Wasn't able to retrieve the target for", target);
|
||||
return;
|
||||
}
|
||||
this.onTargetAvailable(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async listen() {
|
||||
this.rootFront.on("processListChanged", this._processListChanged);
|
||||
await this._processListChanged();
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.rootFront.off("processListChanged", this._processListChanged);
|
||||
}
|
||||
}
|
||||
|
||||
class LegacyImplementationFrames {
|
||||
constructor(rootFront, target, onTargetAvailable) {
|
||||
this.rootFront = rootFront;
|
||||
this.target = target;
|
||||
this.onTargetAvailable = onTargetAvailable;
|
||||
}
|
||||
|
||||
async listen() {
|
||||
// Note that even if we are calling listRemoteFrames on `this.target`, this ends up
|
||||
// being forwarded to the RootFront. So that the Descriptors are managed
|
||||
// by RootFront.
|
||||
// TODO: support frame listening. For now, this only fetches already existing targets
|
||||
const { frames } = await this.target.listRemoteFrames();
|
||||
for (const frame of frames) {
|
||||
// As we listen for frameDescriptor's on the RootFront, we get
|
||||
// all the frames and not only the one related to the given `target`.
|
||||
// TODO: support deeply nested frames
|
||||
if (
|
||||
frame.parentID == this.target.browsingContextID ||
|
||||
frame.id == this.target.browsingContextID
|
||||
) {
|
||||
const target = await frame.getTarget();
|
||||
if (!target) {
|
||||
console.error("Wasn't able to retrieve the target for", frame);
|
||||
continue;
|
||||
}
|
||||
this.onTargetAvailable(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unlisten() {}
|
||||
}
|
||||
|
||||
// Note that in case we need to listen for all type of workers,
|
||||
// devtools/client/shared/workers-listener.js already implements such listening.
|
||||
class LegacyImplementationWorkers {
|
||||
constructor(rootFront, target, onTargetAvailable, onTargetDestroyed) {
|
||||
this.rootFront = rootFront;
|
||||
this.target = target;
|
||||
this.onTargetAvailable = onTargetAvailable;
|
||||
this.onTargetDestroyed = onTargetDestroyed;
|
||||
|
||||
this.targets = new Set();
|
||||
this._workerListChanged = this._workerListChanged.bind(this);
|
||||
}
|
||||
|
||||
async _workerListChanged() {
|
||||
const { workers } = await this.target.listWorkers();
|
||||
// Process the new list to detect the ones being destroyed
|
||||
// Force destroying the targets
|
||||
for (const target of this.targets) {
|
||||
if (!workers.includes(target)) {
|
||||
this.onTargetDestroyed(target);
|
||||
|
||||
target.destroy();
|
||||
this.targets.delete(target);
|
||||
}
|
||||
}
|
||||
// Add the new worker targets to the local list
|
||||
for (const target of workers) {
|
||||
if (!this.targets.has(target)) {
|
||||
this.targets.add(target);
|
||||
this.onTargetAvailable(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async listen() {
|
||||
this.target.on("workerListChanged", this._workerListChanged);
|
||||
await this._workerListChanged();
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.target.off("workerListChanged", this._workerListChanged);
|
||||
}
|
||||
}
|
||||
|
||||
class TargetList {
|
||||
/**
|
||||
* This class helps managing, iterating over and listening for Targets.
|
||||
*
|
||||
* It exposes:
|
||||
* - the top level target, typically the main process target for the browser toolbox
|
||||
* or the browsing context target for a regular web toolbox
|
||||
* - target of remoted iframe, in case Fission is enabled and some <iframe>
|
||||
* are running in a distinct process
|
||||
* - target switching. If the top level target changes for a new one,
|
||||
* all the targets are going to be declared as destroyed and the new ones
|
||||
* will be notified to the user of this API.
|
||||
*
|
||||
* @param {RootFront} rootFront
|
||||
* The root front.
|
||||
* @param {TargetFront} targetFront
|
||||
* The top level target to debug. Note that in case of target switching,
|
||||
* this may be replaced by a new one over time.
|
||||
*/
|
||||
constructor(rootFront, targetFront) {
|
||||
this.rootFront = rootFront;
|
||||
// Note that this is a public attribute, used outside of this class
|
||||
// and helps knowing what is the current top level target we debug.
|
||||
this.targetFront = targetFront;
|
||||
|
||||
// Reports if we have at least one listener for the given target type
|
||||
this._listenersStarted = new Set();
|
||||
|
||||
// List of all the target fronts
|
||||
this._targets = new Set();
|
||||
this._targets.add(targetFront);
|
||||
|
||||
// Listeners for target creation and destruction
|
||||
this._createListeners = new EventEmitter();
|
||||
this._destroyListeners = new EventEmitter();
|
||||
|
||||
this._onTargetAvailable = this._onTargetAvailable.bind(this);
|
||||
this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
|
||||
|
||||
this.legacyImplementation = {
|
||||
process: new LegacyImplementationProcesses(
|
||||
this.rootFront,
|
||||
this.targetFront,
|
||||
this._onTargetAvailable,
|
||||
this._onTargetDestroyed
|
||||
),
|
||||
frame: new LegacyImplementationFrames(
|
||||
this.rootFront,
|
||||
this.targetFront,
|
||||
this._onTargetAvailable,
|
||||
this._onTargetDestroyed
|
||||
),
|
||||
worker: new LegacyImplementationWorkers(
|
||||
this.rootFront,
|
||||
this.targetFront,
|
||||
this._onTargetAvailable,
|
||||
this._onTargetDestroyed
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
_fissionEnabled() {
|
||||
const fissionBrowserToolboxEnabled = Services.prefs.getBoolPref(
|
||||
BROWSERTOOLBOX_FISSION_ENABLED
|
||||
);
|
||||
const isParentProcessToolboxOrBrowserConsole =
|
||||
this.targetFront.chrome && !this.targetFront.isAddon;
|
||||
return (
|
||||
fissionBrowserToolboxEnabled && isParentProcessToolboxOrBrowserConsole
|
||||
);
|
||||
}
|
||||
|
||||
// Called whenever a new Target front is available.
|
||||
// Either because a target was already available as we started calling startListening
|
||||
// or if it has just been created
|
||||
async _onTargetAvailable(targetFront) {
|
||||
if (this._targets.has(targetFront)) {
|
||||
console.error(
|
||||
"Target is already registered in the TargetList",
|
||||
targetFront
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._targets.add(targetFront);
|
||||
|
||||
// Map the descriptor typeName to a target type.
|
||||
const targetType = this._getTargetType(targetFront);
|
||||
|
||||
// Notify the target front creation listeners
|
||||
this._createListeners.emit(
|
||||
targetType,
|
||||
targetType,
|
||||
targetFront,
|
||||
targetFront == this.targetFront
|
||||
);
|
||||
}
|
||||
|
||||
_onTargetDestroyed(targetFront) {
|
||||
const targetType = this._getTargetType(targetFront);
|
||||
this._destroyListeners.emit(
|
||||
targetType,
|
||||
targetType,
|
||||
targetFront,
|
||||
targetFront == this.targetFront
|
||||
);
|
||||
this._targets.delete(targetFront);
|
||||
}
|
||||
|
||||
_setListening(type, value) {
|
||||
if (value) {
|
||||
this._listenersStarted.add(type);
|
||||
} else {
|
||||
this._listenersStarted.delete(type);
|
||||
}
|
||||
}
|
||||
|
||||
_isListening(type) {
|
||||
return this._listenersStarted.has(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interact with the actors in order to start listening for new types of targets.
|
||||
* This will fire the _onTargetAvailable function for all already-existing targets,
|
||||
* as well as the next one to be created. It will also call _onTargetDestroyed
|
||||
* everytime a target is reported as destroyed by the actors.
|
||||
* By the time this function resolves, all the already-existing targets will be
|
||||
* reported to _onTargetAvailable.
|
||||
*/
|
||||
async startListening(types) {
|
||||
for (const type of types) {
|
||||
if (this._isListening(type)) {
|
||||
continue;
|
||||
}
|
||||
this._setListening(type, true);
|
||||
|
||||
// We only listen for additional target when the fission pref is turned on.
|
||||
if (!this._fissionEnabled()) {
|
||||
continue;
|
||||
}
|
||||
if (this.legacyImplementation[type]) {
|
||||
await this.legacyImplementation[type].listen();
|
||||
} else {
|
||||
// TO BE IMPLEMENTED via this.targetFront.watchFronts(type)
|
||||
// For now we always go throught "legacy" codepath.
|
||||
throw new Error(`Unsupported target type '${type}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopListening(types) {
|
||||
for (const type of types) {
|
||||
if (!this._isListening(type)) {
|
||||
continue;
|
||||
}
|
||||
this._setListening(type, false);
|
||||
// We only listen for additional target when the fission pref is turned on.
|
||||
if (!this._fissionEnabled()) {
|
||||
continue;
|
||||
}
|
||||
if (this.legacyImplementation[type]) {
|
||||
this.legacyImplementation[type].unlisten();
|
||||
} else {
|
||||
// TO BE IMPLEMENTED via this.targetFront.unwatchFronts(type)
|
||||
// For now we always go throught "legacy" codepath.
|
||||
throw new Error(`Unsupported target type '${type}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getTargetType(target) {
|
||||
const { typeName } = target;
|
||||
if (typeName == "browsingContextTarget") {
|
||||
return TargetList.TYPES.FRAME;
|
||||
} else if (
|
||||
typeName == "contentProcessTarget" ||
|
||||
typeName == "parentProcessTarget"
|
||||
) {
|
||||
return TargetList.TYPES.PROCESS;
|
||||
} else if (typeName == "workerTarget") {
|
||||
return TargetList.TYPES.WORKER;
|
||||
}
|
||||
throw new Error("Unsupported target typeName: " + typeName);
|
||||
}
|
||||
|
||||
_matchTargetType(type, target) {
|
||||
return type === this._getTargetType(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the creation and/or destruction of target fronts matching one of the provided types.
|
||||
*
|
||||
* @param {Array<String>} types
|
||||
* The type of target to listen for. Constant of TargetList.TYPES.
|
||||
* @param {Function} onAvailable
|
||||
* Callback fired when a target has been just created or was already available.
|
||||
* The function is called with three arguments:
|
||||
* - {String} type: The target type
|
||||
* - {TargetFront} target: The target Front
|
||||
* - {Boolean} isTopLevel: Is this target the top level one?
|
||||
* @param {Function} onDestroy
|
||||
* Callback fired in case of target front destruction.
|
||||
* The function is called with the same arguments than onAvailable.
|
||||
*/
|
||||
async watchTargets(types, onAvailable, onDestroy) {
|
||||
if (typeof onAvailable != "function") {
|
||||
throw new Error(
|
||||
"TargetList.watchTargets expects a function as second argument"
|
||||
);
|
||||
}
|
||||
|
||||
for (const type of types) {
|
||||
if (!this._isListening(type)) {
|
||||
throw new Error(
|
||||
`watchTargets was called for a target type (${type}) that isn't being listened to`
|
||||
);
|
||||
}
|
||||
|
||||
// Notify about already existing target of these types
|
||||
for (const target of this._targets) {
|
||||
if (this._matchTargetType(type, target)) {
|
||||
try {
|
||||
// Ensure waiting for eventual async create listeners
|
||||
// which may setup things regarding the existing targets
|
||||
// and listen callsite may care about the full initialization
|
||||
await onAvailable(type, target, target == this.targetFront);
|
||||
} catch (e) {
|
||||
// Prevent throwing when onAvailable handler throws on one target
|
||||
// so that it can try to register the other targets
|
||||
console.error(
|
||||
"Exception when calling onAvailable handler",
|
||||
e.message,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._createListeners.on(type, onAvailable);
|
||||
if (onDestroy) {
|
||||
this._destroyListeners.on(type, onDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening for the creation and/or destruction of a given type of target fronts.
|
||||
* See `watchTargets()` for documentation of the arguments.
|
||||
*/
|
||||
async unwatchTargets(types, onAvailable, onDestroy) {
|
||||
if (typeof onAvailable != "function") {
|
||||
throw new Error(
|
||||
"TargetList.unwatchTargets expects a function as second argument"
|
||||
);
|
||||
}
|
||||
|
||||
for (const type of types) {
|
||||
this._createListeners.off(type, onAvailable);
|
||||
if (onDestroy) {
|
||||
this._destroyListeners.off(type, onDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the current target fronts of a given type.
|
||||
*
|
||||
* @param {String} type
|
||||
* The type of target to retrieve. Constant of TargetList.TYPES.
|
||||
*/
|
||||
getAllTargets(type) {
|
||||
if (!type) {
|
||||
throw new Error("getAllTargets expects a 'type' argument");
|
||||
}
|
||||
if (!this._isListening(type)) {
|
||||
throw new Error(
|
||||
`getAllTargets was called for a target type (${type}) that isn't being listened to`
|
||||
);
|
||||
}
|
||||
|
||||
const targets = [...this._targets].filter(target =>
|
||||
this._matchTargetType(type, target)
|
||||
);
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* For all the target fronts of a given type, retrieve all the target-scoped fronts of a given type.
|
||||
*
|
||||
* @param {String} targetType
|
||||
* The type of target to iterate over. Constant of TargetList.TYPES.
|
||||
* @param {String} frontType
|
||||
* The type of target-scoped front to retrieve. It can be "inspector", "console", "thread",...
|
||||
*/
|
||||
async getAllFronts(targetType, frontType) {
|
||||
const fronts = [];
|
||||
const targets = this.getAllTargets(targetType);
|
||||
for (const target of targets) {
|
||||
const front = await target.getFront(frontType);
|
||||
fronts.push(front);
|
||||
}
|
||||
return fronts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the top level target is replaced by a new one.
|
||||
* Typically when we navigate to another domain which requires to be loaded in a distinct process.
|
||||
*
|
||||
* @param {TargetFront} newTarget
|
||||
* The new top level target to debug.
|
||||
*/
|
||||
async switchToTarget(newTarget) {
|
||||
// First report that all existing targets are destroyed
|
||||
for (const target of this._targets) {
|
||||
this._onTargetDestroyed(target);
|
||||
}
|
||||
const listenedTypes = TargetList.ALL_TYPES.filter(type =>
|
||||
this._isListening(type)
|
||||
);
|
||||
this.stopListening(listenedTypes);
|
||||
|
||||
// Clear the cached target list
|
||||
this._targets.clear();
|
||||
|
||||
// Update the reference to the top level target so that
|
||||
// creation listening can know this is about the top level target
|
||||
this.targetFront = newTarget;
|
||||
|
||||
// Notify about this new target to creation listeners
|
||||
this._onTargetAvailable(newTarget);
|
||||
|
||||
// Re-register the listeners as the top level target changed
|
||||
// and some targets are fetched from it
|
||||
await this.startListening(listenedTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All types of target:
|
||||
*/
|
||||
TargetList.TYPES = TargetList.prototype.TYPES = {
|
||||
PROCESS: "process",
|
||||
FRAME: "frame",
|
||||
WORKER: "worker",
|
||||
};
|
||||
TargetList.ALL_TYPES = TargetList.prototype.ALL_TYPES = Object.values(
|
||||
TargetList.TYPES
|
||||
);
|
||||
|
||||
module.exports = { TargetList };
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
!/devtools/client/shared/test/shared-head.js
|
||||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
head.js
|
||||
fission_document.html
|
||||
fission_iframe.html
|
||||
|
||||
[browser_target_list_frames.js]
|
||||
[browser_target_list_preffedoff.js]
|
||||
[browser_target_list_processes.js]
|
||||
[browser_target_list_switchToTarget.js]
|
||||
[browser_target_list_watchTargets.js]
|
|
@ -0,0 +1,162 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the TargetList API around frames
|
||||
|
||||
const { TargetList } = require("devtools/shared/resources/target-list");
|
||||
|
||||
const FISSION_TEST_URL = URL_ROOT + "/fission_document.html";
|
||||
|
||||
add_task(async function() {
|
||||
// Enabled fission's pref as the TargetList is almost disabled without it
|
||||
await pushPref("devtools.browsertoolbox.fission", true);
|
||||
// Disable the preloaded process as it gets created lazily and may interfere
|
||||
// with process count assertions
|
||||
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
||||
// This preference helps destroying the content process when we close the tab
|
||||
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
||||
|
||||
const client = await createLocalClient();
|
||||
const mainRoot = client.mainRoot;
|
||||
|
||||
await testBrowserFrames(mainRoot);
|
||||
await testTabFrames(mainRoot);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
async function testBrowserFrames(mainRoot) {
|
||||
info("Test TargetList against frames via the parent process target");
|
||||
|
||||
const target = await mainRoot.getMainProcess();
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
await targetList.startListening([TargetList.TYPES.FRAME]);
|
||||
|
||||
// Very naive sanity check against getAllTargets(frame)
|
||||
const frames = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
const hasBrowserDocument = frames.find(
|
||||
frameTarget => frameTarget.url == window.location.href
|
||||
);
|
||||
ok(hasBrowserDocument, "retrieve the target for the browser document");
|
||||
|
||||
// Check that calling getAllTargets(frame) return the same target instances
|
||||
const frames2 = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
is(frames2.length, frames.length, "retrieved the same number of frames");
|
||||
|
||||
function sortFronts(f1, f2) {
|
||||
return f1.actorID < f2.actorID;
|
||||
}
|
||||
frames.sort(sortFronts);
|
||||
frames2.sort(sortFronts);
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
is(frames[i], frames2[i], `frame ${i} targets are the same`);
|
||||
}
|
||||
|
||||
// Assert that watchTargets will call the create callback for all existing frames
|
||||
const targets = [];
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.FRAME,
|
||||
"We are only notified about frame targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
targets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.FRAME], onAvailable);
|
||||
is(
|
||||
targets.length,
|
||||
frames.length,
|
||||
"retrieved the same number of frames via watchTargets"
|
||||
);
|
||||
|
||||
frames.sort(sortFronts);
|
||||
targets.sort(sortFronts);
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
is(
|
||||
frames[i],
|
||||
targets[i],
|
||||
`frame ${i} targets are the same via watchTargets`
|
||||
);
|
||||
}
|
||||
targetList.unwatchTargets([TargetList.TYPES.FRAME], onAvailable);
|
||||
|
||||
/* NOT READY YET, need to implement frame listening
|
||||
// Open a new tab and see if the frame target is reported by watchTargets and getAllTargets
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
const tab = await addTab(TEST_URL);
|
||||
|
||||
is(targets.length, frames.length + 1, "Opening a tab reported a new frame");
|
||||
is(targets[targets.length - 1].url, TEST_URL, "This frame target is about the new tab");
|
||||
|
||||
const frames3 = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
const hasTabDocument = frames3.find(target => target.url == TEST_URL);
|
||||
ok(hasTabDocument, "retrieve the target for tab via getAllTargets");
|
||||
*/
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.FRAME]);
|
||||
}
|
||||
|
||||
// For now as we do not support "real fission", for tabs, this behaves as if devtools fission pref was false
|
||||
async function testTabFrames(mainRoot) {
|
||||
info("Test TargetList against frames via a tab target");
|
||||
|
||||
// Create a TargetList for a given test tab
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
const tab = await addTab(FISSION_TEST_URL);
|
||||
const target = await mainRoot.getTab({ tab });
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([TargetList.TYPES.FRAME]);
|
||||
|
||||
// Check that calling getAllTargets(frame) return the same target instances
|
||||
const frames = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
is(
|
||||
frames.length,
|
||||
1,
|
||||
"retrieved the top level document and the remoted frame"
|
||||
);
|
||||
is(
|
||||
frames[0].url,
|
||||
FISSION_TEST_URL,
|
||||
"The first frame is the top level document"
|
||||
);
|
||||
|
||||
// Assert that watchTargets will call the create callback for all existing frames
|
||||
const targets = [];
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.FRAME,
|
||||
"We are only notified about frame targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
targets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.FRAME], onAvailable);
|
||||
is(
|
||||
targets.length,
|
||||
frames.length,
|
||||
"retrieved the same number of frames via watchTargets"
|
||||
);
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
is(
|
||||
frames[i],
|
||||
targets[i],
|
||||
`frame ${i} targets are the same via watchTargets`
|
||||
);
|
||||
}
|
||||
targetList.unwatchTargets([TargetList.TYPES.FRAME], onAvailable);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.FRAME]);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the TargetList API when DevTools Fission preference is false
|
||||
|
||||
const { TargetList } = require("devtools/shared/resources/target-list");
|
||||
|
||||
const FISSION_TEST_URL = URL_ROOT + "/fission_document.html";
|
||||
|
||||
add_task(async function() {
|
||||
// Disable the preloaded process as it gets created lazily and may interfere
|
||||
// with process count assertions
|
||||
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
||||
// This preference helps destroying the content process when we close the tab
|
||||
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
||||
|
||||
const client = await createLocalClient();
|
||||
const mainRoot = client.mainRoot;
|
||||
const mainProcess = await mainRoot.getMainProcess();
|
||||
|
||||
// Assert the limited behavior of this API with fission preffed off
|
||||
await pushPref("devtools.browsertoolbox.fission", false);
|
||||
await testPreffedOffMainProcess(mainRoot, mainProcess);
|
||||
await testPreffedOffTab(mainRoot);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
async function testPreffedOffMainProcess(mainRoot, mainProcess) {
|
||||
info(
|
||||
"Test TargetList when devtools's fission pref is false, via the parent process target"
|
||||
);
|
||||
|
||||
const targetList = new TargetList(mainRoot, mainProcess);
|
||||
await targetList.startListening([
|
||||
TargetList.TYPES.PROCESS,
|
||||
TargetList.TYPES.FRAME,
|
||||
]);
|
||||
|
||||
// The API should only report the top level target,
|
||||
// i.e. the Main process target, which is considered as frame
|
||||
// and not as process.
|
||||
const processes = await targetList.getAllTargets(TargetList.TYPES.PROCESS);
|
||||
is(processes.length, 0);
|
||||
const frames = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
is(frames.length, 1, "We get only one frame when preffed-off");
|
||||
is(
|
||||
frames[0],
|
||||
mainProcess,
|
||||
"The target is the top level one via getAllTargets"
|
||||
);
|
||||
|
||||
const processTargets = [];
|
||||
const onProcessAvailable = (type, newTarget, isTopLevel) => {
|
||||
processTargets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.PROCESS], onProcessAvailable);
|
||||
is(processTargets.length, 0, "We get no process when preffed-off");
|
||||
targetList.unwatchTargets([TargetList.TYPES.PROCESS], onProcessAvailable);
|
||||
|
||||
const frameTargets = [];
|
||||
const onFrameAvailable = (type, newTarget, isTopLevel) => {
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.FRAME,
|
||||
"We are only notified about frame targets"
|
||||
);
|
||||
ok(isTopLevel, "We are only notified about the top level target");
|
||||
frameTargets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.FRAME], onFrameAvailable);
|
||||
is(
|
||||
frameTargets.length,
|
||||
1,
|
||||
"We get one frame via watchTargets when preffed-off"
|
||||
);
|
||||
is(
|
||||
frameTargets[0],
|
||||
mainProcess,
|
||||
"The target is the top level one via watchTargets"
|
||||
);
|
||||
targetList.unwatchTargets([TargetList.TYPES.FRAME], onFrameAvailable);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.PROCESS, TargetList.TYPES.FRAME]);
|
||||
}
|
||||
|
||||
async function testPreffedOffTab(mainRoot) {
|
||||
info(
|
||||
"Test TargetList when devtools's fission pref is false, via the tab target"
|
||||
);
|
||||
|
||||
// Create a TargetList for a given test tab
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
const tab = await addTab(FISSION_TEST_URL);
|
||||
const target = await mainRoot.getTab({ tab });
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([
|
||||
TargetList.TYPES.PROCESS,
|
||||
TargetList.TYPES.FRAME,
|
||||
]);
|
||||
|
||||
const processes = await targetList.getAllTargets(TargetList.TYPES.PROCESS);
|
||||
is(processes.length, 0);
|
||||
// This only reports the top level target when devtools fission preference is off
|
||||
const frames = await targetList.getAllTargets(TargetList.TYPES.FRAME);
|
||||
is(frames.length, 1, "We get only one frame when preffed-off");
|
||||
is(frames[0], target, "The target is the top level one via getAllTargets");
|
||||
|
||||
const processTargets = [];
|
||||
const onProcessAvailable = newTarget => {
|
||||
processTargets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.PROCESS], onProcessAvailable);
|
||||
is(processTargets.length, 0, "We get no process when preffed-off");
|
||||
targetList.unwatchTargets([TargetList.TYPES.PROCESS], onProcessAvailable);
|
||||
|
||||
const frameTargets = [];
|
||||
const onFrameAvailable = (type, newTarget, isTopLevel) => {
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.FRAME,
|
||||
"We are only notified about frame targets"
|
||||
);
|
||||
ok(isTopLevel, "We are only notified about the top level target");
|
||||
frameTargets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.FRAME], onFrameAvailable);
|
||||
is(
|
||||
frameTargets.length,
|
||||
1,
|
||||
"We get one frame via watchTargets when preffed-off"
|
||||
);
|
||||
is(
|
||||
frameTargets[0],
|
||||
target,
|
||||
"The target is the top level one via watchTargets"
|
||||
);
|
||||
targetList.unwatchTargets([TargetList.TYPES.FRAME], onFrameAvailable);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.PROCESS, TargetList.TYPES.FRAME]);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the TargetList API around processes
|
||||
|
||||
const { TargetList } = require("devtools/shared/resources/target-list");
|
||||
|
||||
const TEST_URL =
|
||||
"data:text/html;charset=utf-8," + encodeURIComponent(`<div id="test"></div>`);
|
||||
|
||||
add_task(async function() {
|
||||
// Enabled fission's pref as the TargetList is almost disabled without it
|
||||
await pushPref("devtools.browsertoolbox.fission", true);
|
||||
// Disable the preloaded process as it gets created lazily and may interfere
|
||||
// with process count assertions
|
||||
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
||||
// This preference helps destroying the content process when we close the tab
|
||||
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
||||
|
||||
const client = await createLocalClient();
|
||||
const mainRoot = client.mainRoot;
|
||||
const mainProcess = await mainRoot.getMainProcess();
|
||||
|
||||
const targetList = new TargetList(mainRoot, mainProcess);
|
||||
await targetList.startListening([TargetList.TYPES.PROCESS]);
|
||||
|
||||
await testProcesses(targetList, mainProcess);
|
||||
|
||||
await targetList.stopListening([TargetList.TYPES.PROCESS]);
|
||||
await client.close();
|
||||
});
|
||||
|
||||
async function testProcesses(targetList, target) {
|
||||
info("Test TargetList against processes");
|
||||
|
||||
// Note that ppmm also includes the parent process, which is considered as a frame rather than a process
|
||||
const originalProcessesCount = Services.ppmm.childCount - 1;
|
||||
const processes = await targetList.getAllTargets(TargetList.TYPES.PROCESS);
|
||||
is(
|
||||
processes.length,
|
||||
originalProcessesCount,
|
||||
"Get a target for all content processes"
|
||||
);
|
||||
|
||||
const processes2 = await targetList.getAllTargets(TargetList.TYPES.PROCESS);
|
||||
is(
|
||||
processes2.length,
|
||||
originalProcessesCount,
|
||||
"retrieved the same number of processes"
|
||||
);
|
||||
function sortFronts(f1, f2) {
|
||||
return f1.actorID < f2.actorID;
|
||||
}
|
||||
processes.sort(sortFronts);
|
||||
processes2.sort(sortFronts);
|
||||
for (let i = 0; i < processes.length; i++) {
|
||||
is(processes[i], processes2[i], `process ${i} targets are the same`);
|
||||
}
|
||||
|
||||
// Assert that watchTargets will call the create callback for all existing frames
|
||||
const targets = new Set();
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
if (targets.has(newTarget)) {
|
||||
ok(false, "The same target is notified multiple times via onAvailable");
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
targets.add(newTarget);
|
||||
};
|
||||
const onDestroyed = (type, newTarget, isTopLevel) => {
|
||||
if (!targets.has(newTarget)) {
|
||||
ok(
|
||||
false,
|
||||
"A target is declared destroyed via onDestroyed without being notified via onAvailable"
|
||||
);
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
!isTopLevel,
|
||||
"We are never notified about the top level target destruction"
|
||||
);
|
||||
targets.delete(newTarget);
|
||||
};
|
||||
await targetList.watchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable,
|
||||
onDestroyed
|
||||
);
|
||||
is(
|
||||
targets.size,
|
||||
originalProcessesCount,
|
||||
"retrieved the same number of processes via watchTargets"
|
||||
);
|
||||
for (let i = 0; i < processes.length; i++) {
|
||||
ok(
|
||||
targets.has(processes[i]),
|
||||
`process ${i} targets are the same via watchTargets`
|
||||
);
|
||||
}
|
||||
|
||||
const previousTargets = new Set(targets);
|
||||
// Assert that onAvailable is called for processes created *after* the call to watchTargets
|
||||
const onProcessCreated = new Promise(resolve => {
|
||||
const onAvailable2 = (type, newTarget, isTopLevel) => {
|
||||
if (previousTargets.has(newTarget)) {
|
||||
return;
|
||||
}
|
||||
targetList.unwatchTargets([TargetList.TYPES.PROCESS], onAvailable2);
|
||||
resolve(newTarget);
|
||||
};
|
||||
targetList.watchTargets([TargetList.TYPES.PROCESS], onAvailable2);
|
||||
});
|
||||
const tab1 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser,
|
||||
url: TEST_URL,
|
||||
forceNewProcess: true,
|
||||
});
|
||||
const createdTarget = await onProcessCreated;
|
||||
// For some reason, creating a new tab purges processes created from previous tests
|
||||
// so it is not reasonable to assert the size of `targets` as it may be lower than expected.
|
||||
ok(targets.has(createdTarget), "The new tab process is in the list");
|
||||
|
||||
const processCountAfterTabOpen = targets.size;
|
||||
|
||||
// Assert that onDestroyed is called for destroyed processes
|
||||
const onProcessDestroyed = new Promise(resolve => {
|
||||
const onAvailable3 = () => {};
|
||||
const onDestroyed3 = (type, newTarget, isTopLevel) => {
|
||||
resolve(newTarget);
|
||||
targetList.unwatchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable3,
|
||||
onDestroyed3
|
||||
);
|
||||
};
|
||||
targetList.watchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable3,
|
||||
onDestroyed3
|
||||
);
|
||||
});
|
||||
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
|
||||
const destroyedTarget = await onProcessDestroyed;
|
||||
is(
|
||||
targets.size,
|
||||
processCountAfterTabOpen - 1,
|
||||
"The closed tab's process has been reported as destroyed"
|
||||
);
|
||||
ok(
|
||||
!targets.has(destroyedTarget),
|
||||
"The destroyed target is no longer in the list"
|
||||
);
|
||||
is(
|
||||
destroyedTarget,
|
||||
createdTarget,
|
||||
"The destroyed target is the one that has been reported as created"
|
||||
);
|
||||
|
||||
await targetList.unwatchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable,
|
||||
onDestroyed
|
||||
);
|
||||
|
||||
// Ensure that getAllTargets still works after the call to unwatchTargets
|
||||
const processes3 = await targetList.getAllTargets(TargetList.TYPES.PROCESS);
|
||||
is(
|
||||
processes3.length,
|
||||
processCountAfterTabOpen - 1,
|
||||
"getAllTargets reports a new target"
|
||||
);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the TargetList API switchToTarget function
|
||||
|
||||
const { TargetList } = require("devtools/shared/resources/target-list");
|
||||
|
||||
add_task(async function() {
|
||||
// Enabled fission's pref as the TargetList is almost disabled without it
|
||||
await pushPref("devtools.browsertoolbox.fission", true);
|
||||
// Disable the preloaded process as it gets created lazily and may interfere
|
||||
// with process count assertions
|
||||
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
||||
// This preference helps destroying the content process when we close the tab
|
||||
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
||||
|
||||
const client = await createLocalClient();
|
||||
|
||||
await testSwitchToTarget(client);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
async function testSwitchToTarget(client) {
|
||||
info("Test TargetList.switchToTarget method");
|
||||
|
||||
const { mainRoot } = client;
|
||||
let target = await mainRoot.getMainProcess();
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([TargetList.TYPES.FRAME]);
|
||||
|
||||
is(
|
||||
targetList.targetFront,
|
||||
target,
|
||||
"The target list top level target is the main process one"
|
||||
);
|
||||
|
||||
// Create the new target to switch to, a new tab with an iframe
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
const tab = await addTab(
|
||||
`data:text/html,<iframe src="data:text/html,foo"></iframe>`
|
||||
);
|
||||
const secondTarget = await mainRoot.getTab({ tab: gBrowser.selectedTab });
|
||||
|
||||
const frameTargets = [];
|
||||
const onFrameAvailable = (type, newTarget, isTopLevel) => {
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.FRAME,
|
||||
"We are only notified about frame targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
frameTargets.push(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.FRAME], onFrameAvailable);
|
||||
|
||||
// Clear the recorded target list of all existing targets
|
||||
frameTargets.length = 0;
|
||||
|
||||
target = secondTarget;
|
||||
await targetList.switchToTarget(secondTarget);
|
||||
|
||||
is(
|
||||
targetList.targetFront,
|
||||
target,
|
||||
"After the switch, the top level target has been updated"
|
||||
);
|
||||
// Because JS Window Actor API isn't used yet, FrameDescriptor.getTarget returns null
|
||||
// And there is no target being created for the iframe, yet.
|
||||
// As soon as bug 1565200 is resolved, this should return two frames, including the iframe.
|
||||
is(
|
||||
frameTargets.length,
|
||||
1,
|
||||
"We get the report of two iframe when switching to the new target"
|
||||
);
|
||||
is(frameTargets[0], target);
|
||||
//is(frameTargets[1].url, "data:text/html,foo");
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.FRAME]);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the TargetList's `watchTargets` function
|
||||
|
||||
const { TargetList } = require("devtools/shared/resources/target-list");
|
||||
|
||||
const TEST_URL =
|
||||
"data:text/html;charset=utf-8," + encodeURIComponent(`<div id="test"></div>`);
|
||||
|
||||
add_task(async function() {
|
||||
// Enabled fission's pref as the TargetList is almost disabled without it
|
||||
await pushPref("devtools.browsertoolbox.fission", true);
|
||||
// Disable the preloaded process as it gets created lazily and may interfere
|
||||
// with process count assertions
|
||||
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
||||
// This preference helps destroying the content process when we close the tab
|
||||
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
||||
|
||||
const client = await createLocalClient();
|
||||
const mainRoot = client.mainRoot;
|
||||
|
||||
await testWatchTargets(mainRoot);
|
||||
await testContentProcessTarget(mainRoot);
|
||||
await testThrowingInOnAvailable(mainRoot);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
async function testWatchTargets(mainRoot) {
|
||||
info("Test TargetList watchTargets function");
|
||||
|
||||
const target = await mainRoot.getMainProcess();
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([TargetList.TYPES.PROCESS]);
|
||||
|
||||
// Note that ppmm also includes the parent process, which is considered as a frame rather than a process
|
||||
const originalProcessesCount = Services.ppmm.childCount - 1;
|
||||
|
||||
info(
|
||||
"Check that onAvailable is called for processes already created *before* the call to watchTargets"
|
||||
);
|
||||
const targets = new Set();
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
if (targets.has(newTarget)) {
|
||||
ok(false, "The same target is notified multiple times via onAvailable");
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
targets.add(newTarget);
|
||||
};
|
||||
const onDestroyed = (type, newTarget, isTopLevel) => {
|
||||
if (!targets.has(newTarget)) {
|
||||
ok(
|
||||
false,
|
||||
"A target is declared destroyed via onDestroyed without being notified via onAvailable"
|
||||
);
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
!isTopLevel,
|
||||
"We are not notified about the top level target destruction"
|
||||
);
|
||||
targets.delete(newTarget);
|
||||
};
|
||||
await targetList.watchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable,
|
||||
onDestroyed
|
||||
);
|
||||
is(
|
||||
targets.size,
|
||||
originalProcessesCount,
|
||||
"retrieved the expected number of processes via watchTargets"
|
||||
);
|
||||
// Start from 1 in order to ignore the parent process target, which is considered as a frame rather than a process
|
||||
for (let i = 1; i < Services.ppmm.childCount; i++) {
|
||||
const process = Services.ppmm.getChildAt(i);
|
||||
const hasTargetWithSamePID = [...targets].find(
|
||||
processTarget => processTarget.descriptorFront.id == process.osPid
|
||||
);
|
||||
ok(
|
||||
hasTargetWithSamePID,
|
||||
`Process with PID ${process.osPid} has been reported via onAvailable`
|
||||
);
|
||||
}
|
||||
|
||||
info(
|
||||
"Check that onAvailable is called for processes created *after* the call to watchTargets"
|
||||
);
|
||||
const previousTargets = new Set(targets);
|
||||
const onProcessCreated = new Promise(resolve => {
|
||||
const onAvailable2 = (type, newTarget, isTopLevel) => {
|
||||
if (previousTargets.has(newTarget)) {
|
||||
return;
|
||||
}
|
||||
targetList.unwatchTargets([TargetList.TYPES.PROCESS], onAvailable2);
|
||||
resolve(newTarget);
|
||||
};
|
||||
targetList.watchTargets([TargetList.TYPES.PROCESS], onAvailable2);
|
||||
});
|
||||
const tab1 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser,
|
||||
url: TEST_URL,
|
||||
forceNewProcess: true,
|
||||
});
|
||||
const createdTarget = await onProcessCreated;
|
||||
|
||||
// For some reason, creating a new tab purges processes created from previous tests
|
||||
// so it is not reasonable to assert the side of `targets` as it may be lower than expected.
|
||||
ok(targets.has(createdTarget), "The new tab process is in the list");
|
||||
|
||||
const processCountAfterTabOpen = targets.size;
|
||||
|
||||
// Assert that onDestroyed is called for destroyed processes
|
||||
const onProcessDestroyed = new Promise(resolve => {
|
||||
const onAvailable3 = () => {};
|
||||
const onDestroyed3 = (type, newTarget, isTopLevel) => {
|
||||
resolve(newTarget);
|
||||
targetList.unwatchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable3,
|
||||
onDestroyed3
|
||||
);
|
||||
};
|
||||
targetList.watchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable3,
|
||||
onDestroyed3
|
||||
);
|
||||
});
|
||||
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
|
||||
const destroyedTarget = await onProcessDestroyed;
|
||||
is(
|
||||
targets.size,
|
||||
processCountAfterTabOpen - 1,
|
||||
"The closed tab's process has been reported as destroyed"
|
||||
);
|
||||
ok(
|
||||
!targets.has(destroyedTarget),
|
||||
"The destroyed target is no longer in the list"
|
||||
);
|
||||
is(
|
||||
destroyedTarget,
|
||||
createdTarget,
|
||||
"The destroyed target is the one that has been reported as created"
|
||||
);
|
||||
|
||||
await targetList.unwatchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable,
|
||||
onDestroyed
|
||||
);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.PROCESS]);
|
||||
}
|
||||
|
||||
async function testContentProcessTarget(mainRoot) {
|
||||
info("Test TargetList watchTargets with a content process target");
|
||||
|
||||
const { processes } = await mainRoot.listProcesses();
|
||||
const target = await processes[1].getTarget();
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([TargetList.TYPES.PROCESS]);
|
||||
|
||||
// Note that ppmm also includes the parent process, which is considered as a frame rather than a process
|
||||
const originalProcessesCount = Services.ppmm.childCount - 1;
|
||||
|
||||
// Assert that watchTargets will call the create callback for all existing frames
|
||||
const targets = new Set();
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
if (targets.has(newTarget)) {
|
||||
// This may fail if the top level target is reported by LegacyImplementation
|
||||
// to TargetList and emits an available event for it.
|
||||
ok(false, "The same target is notified multiple times via onAvailable");
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
newTarget == target ? isTopLevel : !isTopLevel,
|
||||
"isTopLevel argument is correct"
|
||||
);
|
||||
targets.add(newTarget);
|
||||
};
|
||||
const onDestroyed = (type, newTarget, isTopLevel) => {
|
||||
if (!targets.has(newTarget)) {
|
||||
ok(
|
||||
false,
|
||||
"A target is declared destroyed via onDestroyed without being notified via onAvailable"
|
||||
);
|
||||
}
|
||||
is(
|
||||
type,
|
||||
TargetList.TYPES.PROCESS,
|
||||
"We are only notified about process targets"
|
||||
);
|
||||
ok(
|
||||
!isTopLevel,
|
||||
"We are not notified about the top level target destruction"
|
||||
);
|
||||
targets.delete(newTarget);
|
||||
};
|
||||
await targetList.watchTargets(
|
||||
[TargetList.TYPES.PROCESS],
|
||||
onAvailable,
|
||||
onDestroyed
|
||||
);
|
||||
|
||||
// This may fail if the top level target is reported by LegacyImplementation
|
||||
// to TargetList and registers a duplicated entry
|
||||
is(
|
||||
targets.size,
|
||||
originalProcessesCount,
|
||||
"retrieved the same number of processes via watchTargets"
|
||||
);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.PROCESS]);
|
||||
}
|
||||
|
||||
async function testThrowingInOnAvailable(mainRoot) {
|
||||
info(
|
||||
"Test TargetList watchTargets function when an exception is thrown in onAvailable callback"
|
||||
);
|
||||
|
||||
const target = await mainRoot.getMainProcess();
|
||||
const targetList = new TargetList(mainRoot, target);
|
||||
|
||||
await targetList.startListening([TargetList.TYPES.PROCESS]);
|
||||
|
||||
// Note that ppmm also includes the parent process, which is considered as a frame rather than a process
|
||||
const originalProcessesCount = Services.ppmm.childCount - 1;
|
||||
|
||||
info(
|
||||
"Check that onAvailable is called for processes already created *before* the call to watchTargets"
|
||||
);
|
||||
const targets = new Set();
|
||||
let thrown = false;
|
||||
const onAvailable = (type, newTarget, isTopLevel) => {
|
||||
if (!thrown) {
|
||||
thrown = true;
|
||||
throw new Error("Force an exception when processing the first target");
|
||||
}
|
||||
targets.add(newTarget);
|
||||
};
|
||||
await targetList.watchTargets([TargetList.TYPES.PROCESS], onAvailable);
|
||||
is(
|
||||
targets.size,
|
||||
originalProcessesCount - 1,
|
||||
"retrieved the expected number of processes via onAvailable. All but the first one where we have thrown."
|
||||
);
|
||||
|
||||
targetList.stopListening([TargetList.TYPES.PROCESS]);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test fission document</title>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>Test fission iframe</p>
|
||||
|
||||
<iframe src="https://example.com/browser/devtools/shared/resources/tests/fission_iframe.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test fission iframe document</title>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>remote iframe</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
/* 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";
|
||||
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../client/shared/test/shared-head.js */
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
|
||||
this
|
||||
);
|
||||
|
||||
const { DebuggerClient } = require("devtools/shared/client/debugger-client");
|
||||
const { DebuggerServer } = require("devtools/server/debugger-server");
|
||||
|
||||
async function createLocalClient() {
|
||||
// Instantiate a minimal server
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
if (!DebuggerServer.createRootActor) {
|
||||
DebuggerServer.registerAllActors();
|
||||
}
|
||||
const transport = DebuggerServer.connectPipe();
|
||||
const client = new DebuggerClient(transport);
|
||||
await client.connect();
|
||||
return client;
|
||||
}
|
Загрузка…
Ссылка в новой задаче