Bug 1780156 - [devtools] Add EvaluationContextSelector to BrowserConsole. r=ochameau.

Differential Revision: https://phabricator.services.mozilla.com/D150090
This commit is contained in:
Nicolas Chevobbe 2022-07-19 14:02:58 +00:00
Родитель 49704926df
Коммит a6b5d5b4dc
8 изменённых файлов: 257 добавлений и 60 удалений

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

@ -55,6 +55,12 @@ loader.lazyRequireGetter(
"devtools/client/webconsole/utils/messages",
true
);
loader.lazyRequireGetter(
this,
"getSelectedTarget",
"devtools/shared/commands/target/selectors/targets",
true
);
const HELP_URL =
"https://firefox-source-docs.mozilla.org/devtools-user/web_console/helpers/";
@ -113,7 +119,9 @@ function evaluateExpression(expression, from = "input") {
.execute(expression, {
frameActor: hud.getSelectedFrameActorID(),
selectedNodeActor: webConsoleUI.getSelectedNodeActorID(),
selectedTargetFront: toolbox && toolbox.getSelectedTargetFront(),
selectedTargetFront: getSelectedTarget(
webConsoleUI.hud.commands.targetCommand.store.getState()
),
mapped,
})
.then(onSettled, onSettled);
@ -244,7 +252,8 @@ function handleHelperResult(response) {
case "screenshotOutput":
const { args, value } = helperResult;
const targetFront =
toolbox?.getSelectedTargetFront() || hud.currentTarget;
getSelectedTarget(hud.commands.targetCommand.store.getState()) ||
hud.currentTarget;
let screenshotMessages;
// @backward-compat { version 87 } The screenshot-content actor isn't available
@ -371,14 +380,7 @@ function setInputValue(value) {
* the previous evaluation.
*/
function terminalInputChanged(expression, force = false) {
return async ({
dispatch,
webConsoleUI,
hud,
toolbox,
commands,
getState,
}) => {
return async ({ dispatch, webConsoleUI, hud, commands, getState }) => {
const prefs = getAllPrefs(getState());
if (!prefs.eagerEvaluation) {
return null;
@ -417,7 +419,9 @@ function terminalInputChanged(expression, force = false) {
const response = await commands.scriptCommand.execute(expression, {
frameActor: hud.getSelectedFrameActorID(),
selectedNodeActor: webConsoleUI.getSelectedNodeActorID(),
selectedTargetFront: toolbox && toolbox.getSelectedTargetFront(),
selectedTargetFront: getSelectedTarget(
hud.commands.targetCommand.store.getState()
),
mapped,
eager: true,
});

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

@ -60,10 +60,7 @@ class EditorToolbar extends Component {
}
renderEvaluationContextSelector() {
if (
!this.props.webConsoleUI.wrapper.toolbox ||
!this.props.showEvaluationContextSelector
) {
if (!this.props.showEvaluationContextSelector) {
return null;
}

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

@ -232,13 +232,17 @@ class EvaluationContextSelector extends Component {
render() {
const { webConsoleUI, targets, selectedTarget } = this.props;
const doc = webConsoleUI.document;
const { toolbox } = webConsoleUI.wrapper;
if (targets.length <= 1) {
// Don't render if there's only one target.
// Also bail out if the console is being destroyed (where WebConsoleUI.wrapper gets
// nullified).
if (targets.length <= 1 || !webConsoleUI.wrapper) {
return null;
}
const doc = webConsoleUI.document;
const { toolbox } = webConsoleUI.wrapper;
return MenuButton(
{
menuId: "webconsole-input-evaluationsButton",

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

@ -1489,11 +1489,7 @@ class JSTerm extends Component {
}
renderEvaluationContextSelector() {
if (
!this.props.webConsoleUI.wrapper.toolbox ||
this.props.editorMode ||
!this.props.showEvaluationContextSelector
) {
if (this.props.editorMode || !this.props.showEvaluationContextSelector) {
return null;
}

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

@ -40,6 +40,7 @@ skip-if = (os == "linux") || (os == "win") || (os == "mac" && !debug) # Bug 1440
[browser_console_enable_network_monitoring.js]
skip-if = verify
[browser_console_error_source_click.js]
[browser_console_evaluation_context_selector.js]
[browser_console_filters.js]
[browser_console_ignore_debugger_statement.js]
[browser_console_jsterm_await.js]

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

@ -0,0 +1,189 @@
/* 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 https://mozilla.org/MPL/2.0/. */
"use strict";
add_task(async function() {
await pushPref("devtools.chrome.enabled", true);
const hud = await BrowserConsoleManager.toggleBrowserConsole();
const evaluationContextSelectorButton = await waitFor(() =>
hud.ui.outputNode.querySelector(".webconsole-evaluation-selector-button")
);
if (!isFissionEnabled() && !isEveryFrameTargetEnabled()) {
is(
evaluationContextSelectorButton,
null,
"context selector is only displayed when Fission or EFT is enabled"
);
return;
}
ok(
evaluationContextSelectorButton,
"The evaluation context selector is visible"
);
is(
evaluationContextSelectorButton.innerText,
"Top",
"The button has the expected 'Top' text"
);
is(
evaluationContextSelectorButton.classList.contains(
"webconsole-evaluation-selector-button-non-top"
),
false,
"The non-top class isn't applied"
);
await executeAndWaitForResultMessage(
hud,
"document.location",
"chrome://browser/content/browser.xhtml"
);
ok(true, "The evaluation was done in the top context");
setInputValue(hud, "document.location.host");
await waitForEagerEvaluationResult(hud, `"browser"`);
info("Check the context selector menu");
checkContextSelectorMenuItemAt(hud, 0, {
label: "Top",
tooltip: "chrome://browser/content/browser.xhtml",
checked: true,
});
checkContextSelectorMenuItemAt(hud, 1, {
separator: true,
});
info(
"Add a tab with a worker and check both the document and the worker are displayed in the context selector"
);
const documentFile = "test-evaluate-worker.html";
const documentWithWorkerUrl =
"https://example.com/browser/devtools/client/webconsole/test/browser/" +
documentFile;
const tab = await addTab(documentWithWorkerUrl);
const documentIndex = await waitFor(() => {
const i = getContextSelectorItems(hud).findIndex(el =>
el.querySelector(".label")?.innerText?.endsWith(documentFile)
);
return i == -1 ? null : i;
});
info("Select the document target");
selectTargetInContextSelector(hud, documentWithWorkerUrl);
await waitFor(() =>
evaluationContextSelectorButton.innerText.includes(documentFile)
);
ok(true, "The context was set to the selected document");
is(
evaluationContextSelectorButton.classList.contains(
"webconsole-evaluation-selector-button-non-top"
),
true,
"The non-top class is applied"
);
checkContextSelectorMenuItemAt(hud, documentIndex, {
label: documentWithWorkerUrl,
tooltip: documentWithWorkerUrl,
checked: true,
});
await waitForEagerEvaluationResult(hud, `"example.com"`);
ok(true, "The instant evaluation result is updated in the document context");
await executeAndWaitForResultMessage(
hud,
"document.location",
documentWithWorkerUrl
);
ok(true, "The evaluation is done in the document context");
// set input text so we can watch for instant evaluation result update
setInputValue(hud, "globalThis.location.href");
await waitForEagerEvaluationResult(hud, `"${documentWithWorkerUrl}"`);
info("Select the worker target");
const workerFile = "test-evaluate-worker.js";
const workerUrl =
"https://example.com/browser/devtools/client/webconsole/test/browser/" +
workerFile;
const workerIndex = await waitFor(() => {
const i = getContextSelectorItems(hud).findIndex(el =>
el.querySelector(".label")?.innerText?.endsWith(workerFile)
);
return i == -1 ? null : i;
});
selectTargetInContextSelector(hud, workerFile);
await waitFor(() =>
evaluationContextSelectorButton.innerText.includes(workerFile)
);
ok(true, "The context was set to the selected worker");
await waitForEagerEvaluationResult(hud, `"${workerUrl}"`);
ok(true, "The instant evaluation result is updated in the worker context");
checkContextSelectorMenuItemAt(hud, workerIndex, {
label: workerFile,
tooltip: workerFile,
checked: true,
});
await executeAndWaitForResultMessage(
hud,
"globalThis.location",
`WorkerLocation`
);
ok(true, "The evaluation is done in the worker context");
// set input text so we can watch for instant evaluation result update
setInputValue(hud, "document.location.host");
await waitForEagerEvaluationResult(
hud,
`ReferenceError: document is not defined`
);
info(
"Remove the tab and make sure both the document and worker target are removed from the context selector"
);
await removeTab(tab);
await waitFor(() => evaluationContextSelectorButton.innerText == "Top");
ok(true, "The context is set back to Top");
checkContextSelectorMenuItemAt(hud, 0, {
label: "Top",
tooltip: "chrome://browser/content/browser.xhtml",
checked: true,
});
is(
getContextSelectorItems(hud).every(el => {
const label = el.querySelector(".label")?.innerText;
return (
!label ||
(label !== "test-evaluate-worker.html" && label !== workerFile)
);
}),
true,
"the document and worker targets were removed"
);
await waitForEagerEvaluationResult(hud, `"browser"`);
ok(true, "The instant evaluation was done in the top context");
await executeAndWaitForResultMessage(
hud,
"document.location",
"chrome://browser/content/browser.xhtml"
);
ok(true, "The evaluation was done in the top context");
});

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

@ -1837,11 +1837,8 @@ function getContextSelectorItems(hud) {
* state.
*
* @param {WebConsole} hud
* @param {Array<Object>} expected: An array of object which can have the following shape:
* - {String} label: The label of the target
* - {String} tooltip: The tooltip of the target element in the menu
* - {Boolean} checked: if the target should be selected or not
* - {Boolean} separator: if the element is a simple separator
* @param {Array<Object>} expected: An array of object (see checkContextSelectorMenuItemAt
* for expected properties)
*/
function checkContextSelectorMenu(hud, expected) {
const items = getContextSelectorItems(hud);
@ -1852,32 +1849,43 @@ function checkContextSelectorMenu(hud, expected) {
"The context selector menu has the expected number of items"
);
expected.forEach(({ label, tooltip, checked, separator }, i) => {
const el = items[i];
if (separator === true) {
is(
el.getAttribute("role"),
"menuseparator",
"The element is a separator"
);
return;
}
const elChecked = el.getAttribute("aria-checked") === "true";
const elTooltip = el.getAttribute("title");
const elLabel = el.querySelector(".label").innerText;
is(elLabel, label, `The item has the expected label`);
is(elTooltip, tooltip, `Item "${label}" has the expected tooltip`);
is(
elChecked,
checked,
`Item "${label}" is ${checked ? "checked" : "unchecked"}`
);
expected.forEach((expectedItem, i) => {
checkContextSelectorMenuItemAt(hud, i, expectedItem);
});
}
/**
* Check that the evaluation context selector menu has the expected item at the specified index.
*
* @param {WebConsole} hud
* @param {Number} index
* @param {Object} expected
* @param {String} expected.label: The label of the target
* @param {String} expected.tooltip: The tooltip of the target element in the menu
* @param {Boolean} expected.checked: if the target should be selected or not
* @param {Boolean} expected.separator: if the element is a simple separator
*/
function checkContextSelectorMenuItemAt(hud, index, expected) {
const el = getContextSelectorItems(hud).at(index);
if (expected.separator === true) {
is(el.getAttribute("role"), "menuseparator", "The element is a separator");
return;
}
const elChecked = el.getAttribute("aria-checked") === "true";
const elTooltip = el.getAttribute("title");
const elLabel = el.querySelector(".label").innerText;
is(elLabel, expected.label, `The item has the expected label`);
is(elTooltip, expected.tooltip, `Item "${elLabel}" has the expected tooltip`);
is(
elChecked,
expected.checked,
`Item "${elLabel}" is ${expected.checked ? "checked" : "unchecked"}`
);
}
/**
* Select a target in the context selector.
*

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

@ -50,18 +50,16 @@ loader.lazyGetter(this, "L10N", function() {
return new LocalizationHelper("devtools/client/locales/startup.properties");
});
function renderApp({ app, store, toolbox, root }) {
function renderApp({ app, store, commands, root }) {
return ReactDOM.render(
createElement(
Provider,
{ store },
toolbox
? createElement(
createProvider(toolbox.commands.targetCommand.storeId),
{ store: toolbox.commands.targetCommand.store },
app
)
: app
createElement(
createProvider(commands.targetCommand.storeId),
{ store: commands.targetCommand.store },
app
)
),
root
);
@ -135,7 +133,7 @@ class WebConsoleWrapper {
app,
store,
root: this.parentNode,
toolbox: this.toolbox,
commands: this.hud.commands,
});
} else {
// If there's no parentNode, we are in a test. So we can resolve immediately.