Toggle show ignore-listed call frames

This changes the way ignore-listing is disabled in the call stack.
In place of the link that previously allowed ignore-listing to be
turned off (but not back on), we now have a checkbox allowing the
ignored frames to be unhidden and then hidden again.

Bug: 1336604
Change-Id: Ie42dbe4884c571bf0a696c83c2695610f0ff53e8
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3769849
Reviewed-by: Victor Porof <victorporof@chromium.org>
Commit-Queue: Victor Porof <victorporof@chromium.org>
Reviewed-by: Danil Somsikov <dsv@chromium.org>
This commit is contained in:
Eric Leese
2022-07-19 17:12:04 +00:00
committed by Devtools-frontend LUCI CQ
parent ad851c2381
commit a6533f0ac1
12 changed files with 249 additions and 31 deletions
@@ -107,6 +107,7 @@ let callstackSidebarPaneInstance: CallStackSidebarPane;
export class CallStackSidebarPane extends UI.View.SimpleView implements UI.ContextFlavorListener.ContextFlavorListener,
UI.ListControl.ListDelegate<Item> {
private readonly ignoreListMessageElement: Element;
private readonly ignoreListCheckboxElement: HTMLInputElement;
private readonly notPausedMessageElement: HTMLElement;
private readonly callFrameWarningsElement: HTMLElement;
private readonly items: UI.ListModel.ListModel<Item>;
@@ -124,14 +125,15 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
private constructor() {
super(i18nString(UIStrings.callStack), true);
this.ignoreListMessageElement = this.createIgnoreListMessageElement();
({element: this.ignoreListMessageElement, checkbox: this.ignoreListCheckboxElement} =
this.createIgnoreListMessageElementAndCheckbox());
this.contentElement.appendChild(this.ignoreListMessageElement);
this.notPausedMessageElement = this.contentElement.createChild('div', 'gray-info-message');
this.notPausedMessageElement.textContent = i18nString(UIStrings.notPaused);
this.notPausedMessageElement.tabIndex = -1;
this.callFrameWarningsElement = this.contentElement.createChild('div', 'ignore-listed-message');
this.callFrameWarningsElement = this.contentElement.createChild('div', 'call-frame-warnings-message');
const icon = UI.Icon.Icon.create('smallicon-warning', 'call-frame-warning-icon');
this.callFrameWarningsElement.appendChild(icon);
this.callFrameWarningsElement.appendChild(document.createTextNode(i18nString(UIStrings.callFrameWarnings)));
@@ -180,6 +182,7 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
flavorChanged(_object: Object|null): void {
this.showIgnoreListed = false;
this.ignoreListCheckboxElement.checked = false;
this.maxAsyncStackChainDepth = defaultMaxAsyncStackChainDepth;
this.update();
}
@@ -278,6 +281,9 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
}
this.showMoreMessageElement.classList.toggle('hidden', !asyncStackTrace);
this.items.replaceAll(items);
for (const item of this.items) {
this.refreshItem(item);
}
if (this.maxAsyncStackChainDepth === defaultMaxAsyncStackChainDepth) {
this.list.selectNextItem(true /* canWrap */, false /* center */);
const selectedItem = this.list.selectedItem();
@@ -305,6 +311,7 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
}
this.ignoreListMessageElement.classList.toggle('hidden', true);
} else {
this.showIgnoreListed = this.ignoreListCheckboxElement.checked;
const itemsSet = new Set<Item>(items);
let hasIgnoreListed = false;
for (let i = 0; i < this.items.length; ++i) {
@@ -314,7 +321,7 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
}
hasIgnoreListed = hasIgnoreListed || item.isIgnoreListed;
}
this.ignoreListMessageElement.classList.toggle('hidden', this.showIgnoreListed || !hasIgnoreListed);
this.ignoreListMessageElement.classList.toggle('hidden', !hasIgnoreListed);
}
delete this.muteActivateItem;
});
@@ -387,24 +394,24 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
return true;
}
private createIgnoreListMessageElement(): Element {
private createIgnoreListMessageElementAndCheckbox(): {element: Element, checkbox: HTMLInputElement} {
const element = document.createElement('div');
element.classList.add('ignore-listed-message');
element.createChild('span');
const showAllLink = element.createChild('span', 'link');
showAllLink.textContent = i18nString(UIStrings.showIgnorelistedFrames);
UI.ARIAUtils.markAsLink(showAllLink);
showAllLink.tabIndex = 0;
const label = element.createChild('label');
label.classList.add('ignore-listed-message-label');
const checkbox = label.createChild('input') as HTMLInputElement;
checkbox.tabIndex = 0;
checkbox.type = 'checkbox';
checkbox.classList.add('ignore-listed-checkbox');
label.append(i18nString(UIStrings.showIgnorelistedFrames));
const showAll = (): void => {
this.showIgnoreListed = true;
this.showIgnoreListed = checkbox.checked;
for (const item of this.items) {
this.refreshItem(item);
}
this.ignoreListMessageElement.classList.toggle('hidden', true);
};
showAllLink.addEventListener('click', showAll);
showAllLink.addEventListener('keydown', event => event.key === 'Enter' && showAll());
return element;
checkbox.addEventListener('click', showAll);
return {element, checkbox};
}
private createShowMoreMessageElement(): Element {
@@ -4,7 +4,7 @@
* found in the LICENSE file.
*/
.ignore-listed-message {
.call-frame-warnings-message {
--override-ignore-message-background-color: #ffffc2;
text-align: center;
@@ -14,6 +14,16 @@
background-color: var(--override-ignore-message-background-color);
}
.ignore-listed-message {
padding: 1px;
}
.ignore-listed-message-label {
color: var(--color-text-secondary);
align-items: center;
display: flex;
}
.-theme-with-dark-background .ignore-listed-message,
:host-context(.-theme-with-dark-background) .ignore-listed-message {
--override-ignore-message-background-color: rgb(72 72 0);
@@ -23,14 +33,6 @@
margin-left: 5px;
}
.ignore-listed-message > .link {
margin-left: 5px;
}
.ignore-listed-message > .link:focus {
outline-width: unset;
}
.show-more-message {
text-align: center;
font-style: italic;
@@ -80,6 +82,10 @@
background-color: var(--legacy-focus-bg-color);
}
.ignore-listed-checkbox:focus-visible {
outline-width: unset;
}
.call-frame-item:not(.async-header):hover {
background-color: var(--color-background-elevation-1);
}
@@ -499,7 +499,7 @@ describe('The Debugger Language Plugins', async () => {
const text = await details.evaluate(e => e.textContent);
assert.deepEqual(text, 'Failed to load debug file "test.wasm".');
const banners = await $$('.ignore-listed-message');
const banners = await $$('.call-frame-warnings-message');
const bannerTexts = await Promise.all(banners.map(e => e.evaluate(e => e.textContent)));
assert.include(bannerTexts, 'Some call frames have warnings');
@@ -569,7 +569,7 @@ describe('The Debugger Language Plugins', async () => {
const text = await details.evaluate(e => e.textContent);
assert.deepEqual(text, 'Failed to load debug file "test.dwo".');
const banners = await $$('.ignore-listed-message');
const banners = await $$('.call-frame-warnings-message');
const bannerTexts = await Promise.all(banners.map(e => e.evaluate(e => e.textContent)));
assert.include(bannerTexts, 'Some call frames have warnings');
+16 -1
View File
@@ -39,6 +39,10 @@ export const openSettingsTab = async (tabTitle: string) => {
await waitFor(panelSelector);
};
export const closeSettings = async () => {
await click('.dialog-close-button');
};
export const togglePreferenceInSettingsTab = async (label: string) => {
await openSettingsTab('Preferences');
@@ -55,5 +59,16 @@ export const togglePreferenceInSettingsTab = async (label: string) => {
return newValue !== value;
});
await click('.dialog-close-button');
await closeSettings();
};
export const setIgnoreListPattern = async (pattern: string) => {
await openSettingsTab('Ignore List');
await click('[aria-label="Add filename pattern"]');
const textBox = await waitFor('[aria-label="Pattern"]');
await click(textBox);
await textBox.type(pattern);
await textBox.type('\n');
await waitFor(`[title="Ignore scripts whose names match '${pattern}'"]`);
await closeSettings();
};
+32 -5
View File
@@ -280,8 +280,9 @@ export async function executionLineHighlighted() {
}
export async function getCallFrameNames() {
await waitFor('.call-frame-item-title');
const items = await $$('.call-frame-item-title');
const selector = '.call-frame-item:not(.hidden) .call-frame-item-title';
await waitFor(selector);
const items = await $$(selector);
const promises = items.map(handle => handle.evaluate(el => el.textContent as string));
const results = [];
for (const promise of promises) {
@@ -291,8 +292,9 @@ export async function getCallFrameNames() {
}
export async function getCallFrameLocations() {
await waitFor('.call-frame-location');
const items = await $$('.call-frame-location');
const selector = '.call-frame-item:not(.hidden) .call-frame-location';
await waitFor(selector);
const items = await $$(selector);
const promises = items.map(handle => handle.evaluate(el => el.textContent as string));
const results = [];
for (const promise of promises) {
@@ -484,10 +486,35 @@ export async function readSourcesTreeView(): Promise<string[]> {
return results.map(item => item.replace(/localhost:[0-9]+/, 'localhost:XXXX'));
}
async function hasPausedEvents(frontend: puppeteer.Page): Promise<boolean> {
const events = await getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT);
return Boolean(events && events.length);
}
export async function stepThroughTheCode() {
const {frontend} = getBrowserAndPages();
await getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT);
await frontend.keyboard.press('F9');
await waitForFunction(() => getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT));
await waitForFunction(() => hasPausedEvents(frontend));
await waitFor(PAUSE_INDICATOR_SELECTOR);
}
export async function stepIn() {
const {frontend} = getBrowserAndPages();
await getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT);
await frontend.keyboard.press('F11');
await waitForFunction(() => hasPausedEvents(frontend));
await waitFor(PAUSE_INDICATOR_SELECTOR);
}
export async function stepOut() {
const {frontend} = getBrowserAndPages();
await getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT);
await frontend.keyboard.down('Shift');
await frontend.keyboard.press('F11');
await frontend.keyboard.up('Shift');
await waitForFunction(() => hasPausedEvents(frontend));
await waitFor(PAUSE_INDICATOR_SELECTOR);
}
+3
View File
@@ -33,6 +33,9 @@ copy_to_gen("sources") {
"memory-workers.rawresponse",
"minified-sourcecode.html",
"minified-sourcecode.js",
"multi-files-mycode.js",
"multi-files-thirdparty.js",
"multi-files.html",
"multi-workers-sourcemap.html",
"multi-workers.html",
"multi-workers.js",
@@ -0,0 +1,9 @@
import {callfunc} from './multi-files-thirdparty.js';
function inner() {
console.log('Hello');
}
export function outer() {
callfunc(inner);
}
@@ -0,0 +1,17 @@
/**
* @param {() => void} func
* @returns void
*/
function innercall(func) {
func();
}
/**
* @param {() => void} func
* @returns void
*/
export function callfunc(func) {
innercall(func);
}
@@ -0,0 +1,6 @@
<script type="module">
import {outer} from './multi-files-mycode.js';
globalThis.f = outer;
</script>
+1
View File
@@ -20,6 +20,7 @@ node_ts_library("sources") {
"debug-raw-wasm_test.ts",
"header-overrides_test.ts",
"icon-row-bucket_test.ts",
"ignore-list_test.ts",
"inline-variable_test.ts",
"link-to-correct-source-line_test.ts",
"live-edit-moving-breakpoint_test.ts",
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import {
click,
getBrowserAndPages,
getPendingEvents,
installEventListener,
step,
waitFor,
waitForFunction,
waitForNone,
} from '../../shared/helper.js';
import {setIgnoreListPattern} from '../helpers/settings-helpers.js';
import {
addBreakpointForLine,
DEBUGGER_PAUSED_EVENT,
getCallFrameNames,
openSourceCodeEditorForFile,
PAUSE_INDICATOR_SELECTOR,
RESUME_BUTTON,
stepIn,
stepOut,
} from '../helpers/sources-helpers.js';
describe('Ignore list', async function() {
it('can be toggled on and off in call stack', async function() {
await setIgnoreListPattern('thirdparty');
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('multi-files-mycode.js', 'multi-files.html');
await addBreakpointForLine(frontend, 4);
const scriptEvaluation = target.evaluate('f();');
await waitFor(RESUME_BUTTON);
await waitFor('.ignore-listed-call-frame.hidden');
assert.deepEqual(await getCallFrameNames(), ['inner', 'outer', '(anonymous)']);
// Toggle to show ignore-listed frames
await click('.ignore-listed-message-label');
await waitFor('.ignore-listed-call-frame:not(.hidden)');
assert.deepEqual(await getCallFrameNames(), ['inner', 'innercall', 'callfunc', 'outer', '(anonymous)']);
// Toggle back off
await click('.ignore-listed-message-label');
await waitFor('.ignore-listed-call-frame.hidden');
assert.deepEqual(await getCallFrameNames(), ['inner', 'outer', '(anonymous)']);
await click(RESUME_BUTTON);
await scriptEvaluation;
});
it('shows no toggle when everything is ignore-listed', async function() {
await setIgnoreListPattern('multi|puppeteer');
const {target, frontend} = getBrowserAndPages();
await openSourceCodeEditorForFile('multi-files-mycode.js', 'multi-files.html');
await addBreakpointForLine(frontend, 4);
const scriptEvaluation = target.evaluate('f();');
await waitFor(RESUME_BUTTON);
// Toggle is not shown
await waitFor('.ignore-listed-message.hidden');
// There are visible ignore-listed frames
await waitFor('.ignore-listed-call-frame:not(.hidden)');
// All frames are shown
assert.deepEqual(await getCallFrameNames(), ['inner', 'innercall', 'callfunc', 'outer', '(anonymous)']);
await click(RESUME_BUTTON);
await scriptEvaluation;
});
it('skips frames when stepping in and out', async function() {
await setIgnoreListPattern('thirdparty');
const {target, frontend} = getBrowserAndPages();
installEventListener(frontend, DEBUGGER_PAUSED_EVENT);
await openSourceCodeEditorForFile('multi-files-mycode.js', 'multi-files.html');
await addBreakpointForLine(frontend, 8);
const scriptEvaluation = target.evaluate('f();');
await waitFor(RESUME_BUTTON);
await waitFor(PAUSE_INDICATOR_SELECTOR);
await waitForFunction(async () => await getPendingEvents(frontend, DEBUGGER_PAUSED_EVENT));
assert.deepEqual(await getCallFrameNames(), ['outer', '(anonymous)']);
await step('Step in', stepIn);
await waitFor('.call-frame-title-text[title="inner"]');
await waitFor('.ignore-listed-call-frame.hidden');
assert.deepEqual(await getCallFrameNames(), ['inner', 'outer', '(anonymous)']);
await step('Step out', stepOut);
await waitForNone('.call-frame-title-text[title="inner"]');
assert.deepEqual(await getCallFrameNames(), ['outer', '(anonymous)']);
await click(RESUME_BUTTON);
await scriptEvaluation;
});
});
+24
View File
@@ -552,6 +552,30 @@ export const enableCDPLogging = async () => {
});
};
export const enableCDPTracking = async () => {
const {frontend} = getBrowserAndPages();
await frontend.evaluate(() => {
globalThis.__messageMapForTest = new Map();
globalThis.ProtocolClient.test.onMessageSent = (message: {method: string, id: number}) => {
globalThis.__messageMapForTest.set(message.id, message.method);
};
globalThis.ProtocolClient.test.onMessageReceived = (message: {id?: number}) => {
if (message.id) {
globalThis.__messageMapForTest.delete(message.id);
}
};
});
};
export const logOutstandingCDP = async () => {
const {frontend} = getBrowserAndPages();
await frontend.evaluate(() => {
for (const entry of globalThis.__messageMapForTest) {
console.error(entry);
}
});
};
export const selectOption = async (select: puppeteer.ElementHandle<HTMLSelectElement>, value: string) => {
await select.evaluate(async (node: HTMLSelectElement, _value: string) => {
node.value = _value;