зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
1d8c226620
Коммит
4e1c8d6774
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче