Bug 1776250 - [devtools] Show a badge in the markupview when children are unavailable r=nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D150189
This commit is contained in:
Julian Descottes 2022-08-01 16:52:55 +00:00
Родитель 1d8c226620
Коммит 4e1c8d6774
8 изменённых файлов: 290 добавлений и 3 удалений

Просмотреть файл

@ -41,4 +41,5 @@ skip-if =
skip-if =
os == "mac" && fission # high frequency intermittent
[browser_browser_toolbox_shouldprocessupdates.js]
[browser_browser_toolbox_unavailable_children.js]
[browser_browser_toolbox_watchedByDevTools.js]

Просмотреть файл

@ -0,0 +1,146 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.import(
"resource://testing-common/PromiseTestUtils.jsm"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/);
/* import-globals-from ../../../inspector/test/shared-head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
this
);
// On debug test machine, it takes about 50s to run the test.
requestLongerTimeout(4);
// This test is used to test a badge displayed in the markup view under content
// browser elements when switching from Multi Process mode to Parent Process
// mode.
add_task(async function() {
// Forces the Browser Toolbox to open on the inspector by default
await pushPref("devtools.browsertoolbox.panel", "inspector");
const tab = await addTab(
"https://example.com/document-builder.sjs?html=<div id=pick-me>Pickme"
);
tab.linkedBrowser.setAttribute("test-tab", "true");
const ToolboxTask = await initBrowserToolboxTask({
enableBrowserToolboxFission: true,
});
await ToolboxTask.importFunctions({
waitUntil,
getNodeFront,
selectNode,
});
const tabProcessID =
tab.linkedBrowser.browsingContext.currentWindowGlobal.osPid;
const decodedTabURI = decodeURI(tab.linkedBrowser.currentURI.spec);
await ToolboxTask.spawn(
[tabProcessID, isFissionEnabled(), decodedTabURI],
async (processID, _isFissionEnabled, tabURI) => {
/* global gToolbox */
const inspector = gToolbox.getPanel("inspector");
info("Select the test browser element.");
await selectNode('browser[remote="true"][test-tab]', inspector);
info("Retrieve the node front for selected node.");
const browserNodeFront = inspector.selection.nodeFront;
ok(!!browserNodeFront, "Retrieved a node front for the browser");
is(browserNodeFront.displayName, "browser");
// Small helper to expand containers and return the child container
// matching the provided display name.
async function expandContainer(container, expectedChildName) {
info(`Expand the node expected to contain a ${expectedChildName}`);
await inspector.markup.expandNode(container.node);
await waitUntil(() => container.getChildContainers().length > 0);
const children = container
.getChildContainers()
.filter(child => child.node.displayName === expectedChildName);
is(children.length, 1);
return children[0];
}
info("Check that the corresponding markup view container has children");
const browserContainer = inspector.markup.getContainer(browserNodeFront);
ok(browserContainer.hasChildren);
ok(
!browserContainer.node.childrenUnavailable,
"childrenUnavailable un-set"
);
ok(
!browserContainer.elt.querySelector(".unavailable-children"),
"The unavailable badge is not displayed"
);
// Store the asserts as a helper to reuse it later in the test.
async function assertMarkupView() {
info("Check that the children are #document > html > body > div");
let container = await expandContainer(browserContainer, "#document");
container = await expandContainer(container, "html");
container = await expandContainer(container, "body");
container = await expandContainer(container, "div");
info("Select the #pick-me div");
await selectNode(container.node, inspector);
is(inspector.selection.nodeFront.id, "pick-me");
}
await assertMarkupView();
const parentProcessScope = gToolbox.doc.querySelector(
'input[name="chrome-debug-mode"][value="parent-process"]'
);
info("Switch to parent process only scope");
const onInspectorUpdated = inspector.once("inspector-updated");
parentProcessScope.click();
await onInspectorUpdated;
// Note: `getChildContainers` returns null when the container has no
// children, instead of an empty array.
await waitUntil(() => browserContainer.getChildContainers() === null);
ok(!browserContainer.hasChildren, "browser container has no children");
ok(browserContainer.node.childrenUnavailable, "childrenUnavailable set");
ok(
!!browserContainer.elt.querySelector(".unavailable-children"),
"The unavailable badge is displayed"
);
const everythingScope = gToolbox.doc.querySelector(
'input[name="chrome-debug-mode"][value="everything"]'
);
info("Switch to multi process scope");
everythingScope.click();
info("Wait until browserContainer has children");
await waitUntil(() => browserContainer.hasChildren);
ok(browserContainer.hasChildren, "browser container has children");
ok(
!browserContainer.node.childrenUnavailable,
"childrenUnavailable un-set"
);
ok(
!browserContainer.elt.querySelector(".unavailable-children"),
"The unavailable badge is no longer displayed"
);
await assertMarkupView();
}
);
await ToolboxTask.destroy();
});

Просмотреть файл

@ -19,10 +19,22 @@ loader.lazyRequireGetter(
"devtools/shared/dom-node-constants"
);
const ChromeUtils = require("ChromeUtils");
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"browserToolboxScope",
"devtools.browsertoolbox.scope"
);
const BROWSER_TOOLBOX_FISSION_ENABLED = Services.prefs.getBoolPref(
"devtools.browsertoolbox.fission",
false
);
const BROWSER_TOOLBOX_SCOPE_EVERYTHING = "everything";
const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
@ -301,14 +313,33 @@ class NodeFront extends FrontClassWithSpec(nodeSpec) {
return this.getAttribute("class") || "";
}
// Check if the node has children but the current DevTools session is unable
// to retrieve them.
// Typically: a <frame> or <browser> element which loads a document in another
// process, but the toolbox' configuration prevents to inspect it (eg the
// parent-process only Browser Toolbox).
get childrenUnavailable() {
return (
this._form.useChildTargetToFetchChildren &&
!this.useChildTargetToFetchChildren
);
}
get hasChildren() {
return this._form.numChildren > 0;
return this.numChildren > 0;
}
get numChildren() {
if (this.childrenUnavailable) {
return 0;
}
return this._form.numChildren;
}
get useChildTargetToFetchChildren() {
if (!BROWSER_TOOLBOX_FISSION_ENABLED && this._hasParentProcessTarget) {
if (
this._hasParentProcessTarget &&
(!BROWSER_TOOLBOX_FISSION_ENABLED ||
browserToolboxScope != BROWSER_TOOLBOX_SCOPE_EVERYTHING)
) {
return false;
}
@ -571,6 +602,12 @@ class NodeFront extends FrontClassWithSpec(nodeSpec) {
this._childBrowsingContextTarget = await this.targetFront.getWindowGlobalTarget(
this._form.browsingContextID
);
// Bug 1776250: When the target is destroyed, we need to easily find the
// parent node front so that we can update its frontend container in the
// markup-view.
this._childBrowsingContextTarget.setParentNodeFront(this);
return this._childBrowsingContextTarget;
}
}

Просмотреть файл

@ -27,6 +27,10 @@ class WindowGlobalTargetFront extends TargetMixin(
// used anywhere else.
this._javascriptEnabled = null;
// If this target was retrieved via NodeFront connectToFrame, keep a
// reference to the parent NodeFront.
this._parentNodeFront = null;
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onFrameUpdate = this._onFrameUpdate.bind(this);
@ -92,6 +96,14 @@ class WindowGlobalTargetFront extends TargetMixin(
}
}
getParentNodeFront() {
return this._parentNodeFront;
}
setParentNodeFront(nodeFront) {
this._parentNodeFront = nodeFront;
}
/**
* Set the targetFront url.
*
@ -143,6 +155,7 @@ class WindowGlobalTargetFront extends TargetMixin(
destroy() {
const promise = super.destroy();
this._parentNodeFront = null;
// As detach isn't necessarily called on target's destroy
// (it isn't for local tabs), ensure removing listeners set in constructor.

Просмотреть файл

@ -296,6 +296,8 @@ function MarkupView(inspector, frame, controllerWindow) {
this._onWalkerNodeStatesChanged = this._onWalkerNodeStatesChanged.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onResourceAvailable = this._onResourceAvailable.bind(this);
this._onTargetAvailable = this._onTargetAvailable.bind(this);
this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
this._onMouseClick = this._onMouseClick.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseOut = this._onMouseOut.bind(this);
@ -368,6 +370,13 @@ function MarkupView(inspector, frame, controllerWindow) {
this.resourceCommand.watchResources([this.resourceCommand.TYPES.ROOT_NODE], {
onAvailable: this._onResourceAvailable,
});
this.targetCommand = this.inspector.commands.targetCommand;
this.targetCommand.watchTargets({
types: [this.targetCommand.TYPES.FRAME],
onAvailable: this._onTargetAvailable,
onDestroyed: this._onTargetDestroyed,
});
}
MarkupView.prototype = {
@ -1474,6 +1483,22 @@ MarkupView.prototype = {
}
},
_onTargetAvailable: function({ targetFront }) {},
_onTargetDestroyed: function({ targetFront, isModeSwitching }) {
// Bug 1776250: We only watch targets in order to update containers which
// might no longer be able to display children hosted in remote processes,
// which corresponds to a Browser Toolbox mode switch.
if (isModeSwitching) {
const container = this.getContainer(targetFront.getParentNodeFront());
if (container) {
this._forceUpdateChildren(container, {
updateLevel: true,
});
}
}
},
/**
* Mutation observer used for included nodes.
*/
@ -2171,6 +2196,16 @@ MarkupView.prototype = {
return Promise.resolve(container);
}
// Before bailing out for other conditions, check if the unavailable
// children badge needs updating (Bug 1776250).
if (
typeof container?.editor?.hasUnavailableChildren == "function" &&
container.editor.hasUnavailableChildren() !=
container.node.childrenUnavailable
) {
container.update();
}
if (
container.inlineTextChild &&
container.inlineTextChild != container.node.inlineTextChild
@ -2410,6 +2445,11 @@ MarkupView.prototype = {
[this.resourceCommand.TYPES.ROOT_NODE],
{ onAvailable: this._onResourceAvailable }
);
this.targetCommand.unwatchTargets({
types: [this.targetCommand.TYPES.FRAME],
onAvailable: this._onTargetAvailable,
onDestroyed: this._onTargetDestroyed,
});
this.inspector.toolbox.nodePicker.off(
"picker-node-hovered",
this._onToolboxPickerHover

Просмотреть файл

@ -339,6 +339,7 @@ ElementEditor.prototype = {
this.updateCustomBadge();
this.updateScrollableBadge();
this.updateTextEditor();
this.updateUnavailableChildren();
this.updateOverflowBadge();
this.updateOverflowHighlight();
},
@ -577,6 +578,39 @@ ElementEditor.prototype = {
}
},
hasUnavailableChildren() {
return !!this.childrenUnavailableElt;
},
/**
* Update a special badge displayed for nodes which have children that can't
* be inspected by the current session (eg a parent-process only toolbox
* inspecting a content browser).
*/
updateUnavailableChildren: function() {
const childrenUnavailable = this.node.childrenUnavailable;
if (this.childrenUnavailableElt) {
this.elt.removeChild(this.childrenUnavailableElt);
this.childrenUnavailableElt = null;
}
if (childrenUnavailable) {
this.childrenUnavailableElt = this.doc.createElement("div");
this.childrenUnavailableElt.className = "unavailable-children";
this.childrenUnavailableElt.dataset.label = INSPECTOR_L10N.getStr(
"markupView.unavailableChildren.label"
);
this.childrenUnavailableElt.title = INSPECTOR_L10N.getStr(
"markupView.unavailableChildren.title"
);
this.elt.insertBefore(
this.childrenUnavailableElt,
this.elt.querySelector(".close")
);
}
},
_startModifyingAttributes: function() {
return this.node.startModifyingAttributes();
},

Просмотреть файл

@ -27,6 +27,16 @@ markupView.more.showAll2=Show one more node;Show all #1 nodes
# inspector.
markupView.whitespaceOnly.label=whitespace
# LOCALIZATION NOTE (markupView.unavailableChildren.label)
# Used in the badge that appears when the Browser Toolbox is in "parent-process"
# mode and the markup view cannot display the children from a content browser.
markupView.unavailableChildren.label=unavailable
# LOCALIZATION NOTE (markupView.unavailableChildren.title)
# Title for the badge that appears when the Browser Toolbox is in "parent-process"
# mode and the markup view cannot display the children from a content browser.
markupView.unavailableChildren.title=Children of this element are unavailable with the current Browser Toolbox mode
# LOCALIZATION NOTE (markupView.whitespaceOnly)
# Used in a tooltip that appears when the user hovers over whitespace-only text nodes in
# the inspector. %S in the content will be replaced by the whitespace characters used in

Просмотреть файл

@ -31,7 +31,8 @@
/* Inspector badge */
.inspector-badge,
.editor.text .whitespace::before {
.editor.text .whitespace::before,
.unavailable-children::before {
display: inline-block;
/* 9px text is too blurry on low-resolution screens */
font-size: 10px;
@ -55,6 +56,11 @@
margin-inline-start: 0;
}
.unavailable-children::before {
content: attr(data-label);
height: auto;
}
@media (min-resolution: 1.1dppx) {
.inspector-badge,
.editor.text .whitespace::before {