UNPKG

chrome-devtools-frontend

Version:
387 lines (361 loc) • 15.9 kB
// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Sources from '../../panels/sources/sources.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as Lit from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import categorizedBreakpointsSidebarPaneStyles from './categorizedBreakpointsSidebarPane.css.js'; const UIStrings = { /** * @description Category of breakpoints */ auctionWorklet: 'Ad Auction Worklet', /** * @description Text that refers to the animation of the web page */ animation: 'Animation', /** * @description Screen reader description of a hit breakpoint in the Sources panel */ breakpointHit: 'breakpoint hit', /** * @description Text in DOMDebugger Model */ canvas: 'Canvas', /** * @description Text in DOMDebugger Model */ clipboard: 'Clipboard', /** * @description Noun. Describes a group of DOM events (such as 'select' and 'submit') in this context. */ control: 'Control', /** * @description Text that refers to device such as a phone */ device: 'Device', /** * @description Text in DOMDebugger Model */ domMutation: 'DOM Mutation', /** * @description Text in DOMDebugger Model */ dragDrop: 'Drag / drop', /** * @description Title for a group of cities */ geolocation: 'Geolocation', /** * @description Text in DOMDebugger Model */ keyboard: 'Keyboard', /** * @description Text to load something */ load: 'Load', /** * @description Text that appears on a button for the media resource type filter. */ media: 'Media', /** * @description Text in DOMDebugger Model */ mouse: 'Mouse', /** * @description Text in DOMDebugger Model */ notification: 'Notification', /** * @description Text to parse something */ parse: 'Parse', /** * @description Text in DOMDebugger Model */ pictureinpicture: 'Picture-in-Picture', /** * @description Text in DOMDebugger Model */ pointer: 'Pointer', /** * @description Label for a group of JavaScript files */ script: 'Script', /** * @description Category of breakpoints */ sharedStorageWorklet: 'Shared Storage Worklet', /** * @description Text in DOMDebugger Model */ timer: 'Timer', /** * @description Text for the touch type to simulate on a device */ touch: 'Touch', /** * @description Title for a category of breakpoints on Trusted Type violations */ trustedTypeViolations: 'Trusted Type Violations', /** * @description Title of the WebAudio tool */ webaudio: 'WebAudio', /** * @description Text in DOMDebugger Model */ window: 'Window', /** * @description Text for the service worker type. */ worker: 'Worker', /** * @description Text that appears on a button for the xhr resource type filter. */ xhr: 'XHR', } as const; const str_ = i18n.i18n.registerUIStrings('panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); const {html, render, Directives: {ref}} = Lit; interface ViewOutput { defaultFocus: Element|undefined; userExpandedCategories: Set<SDK.CategorizedBreakpoint.Category>; } interface ViewInput { onFilterChanged: (filterText: string|null) => void; onBreakpointChange: (breakpoint: SDK.CategorizedBreakpoint.CategorizedBreakpoint, enabled: boolean) => void; filterText: string|null; userExpandedCategories: Set<SDK.CategorizedBreakpoint.Category>; highlightedItem: SDK.CategorizedBreakpoint.CategorizedBreakpoint|null; categories: Map<SDK.CategorizedBreakpoint.Category, SDK.CategorizedBreakpoint.CategorizedBreakpoint[]>; sortedCategoryNames: SDK.CategorizedBreakpoint.Category[]; } export type View = typeof DEFAULT_VIEW; export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => { const shouldExpandCategory = (breakpoints: SDK.CategorizedBreakpoint.CategorizedBreakpoint[]): boolean => Boolean(input.filterText) || (input.highlightedItem && breakpoints.includes(input.highlightedItem)) || breakpoints.some(breakpoint => breakpoint.enabled()); const filter = (breakpoint: SDK.CategorizedBreakpoint.CategorizedBreakpoint): boolean => !input.filterText || Boolean(Sources.CategorizedBreakpointL10n.getLocalizedBreakpointName(breakpoint.name).match(input.filterText)) || breakpoint === input.highlightedItem; const filteredCategories = input.sortedCategoryNames.values() .map(category => [category, input.categories.get(category)?.filter(filter)]) .filter( (filteredCategory): filteredCategory is [SDK.CategorizedBreakpoint.Category, SDK.CategorizedBreakpoint.CategorizedBreakpoint[]] => Boolean(filteredCategory[1]?.length)) .toArray(); const onCheckboxClicked = (event: Event, target: SDK.CategorizedBreakpoint.Category|SDK.CategorizedBreakpoint.CategorizedBreakpoint): void => { const eventTarget = event.target; if (!(eventTarget instanceof UI.UIUtils.CheckboxLabel)) { return; } const enabled = eventTarget.checked; if (target instanceof SDK.CategorizedBreakpoint.CategorizedBreakpoint) { input.onBreakpointChange(target, enabled); } else { input.categories.get(target)?.forEach(breakpoint => input.onBreakpointChange(breakpoint, enabled)); } }; const classes = (breakpoint: SDK.CategorizedBreakpoint.CategorizedBreakpoint): ReturnType<typeof Lit.Directives.classMap> => Lit.Directives.classMap({ small: true, 'source-code': true, 'breakpoint-hit': input.highlightedItem === breakpoint, }); const onExpand = (category: SDK.CategorizedBreakpoint.Category, {detail: {expanded}}: UI.TreeOutline.TreeViewElement.ExpandEvent): void => { const breakpoints = category && input.categories.get(category); if (!breakpoints) { return; } if (shouldExpandCategory(breakpoints)) { // Basically ignore expand/collapse when the category is expanded by default. return; } if (expanded) { output.userExpandedCategories.add(category); } else { output.userExpandedCategories.delete(category); } }; render( // clang-format off html` <devtools-toolbar jslog=${VisualLogging.toolbar()}> <devtools-toolbar-input type="filter" @change=${(e: CustomEvent<string>) => input.onFilterChanged(e.detail)} style="flex: 1;" ></devtools-toolbar-input> </devtools-toolbar> <devtools-tree ${ref(e => { output.defaultFocus = e; })} .template=${html` <ul role="tree"> ${filteredCategories.map(([category, breakpoints]) => html` <li @expand=${(e: UI.TreeOutline.TreeViewElement.ExpandEvent) => onExpand(category, e)} role="treeitem" jslog-context=${category} aria-checked=${breakpoints.some(breakpoint => breakpoint.enabled()) ? breakpoints.some(breakpoint => !breakpoint.enabled()) ? 'mixed' : true : false}> <style>${categorizedBreakpointsSidebarPaneStyles}</style> <devtools-checkbox class="small" tabIndex=-1 title=${getLocalizedCategory(category)} ?indeterminate=${breakpoints.some(breakpoint => !breakpoint.enabled()) && breakpoints.some(breakpoint => breakpoint.enabled())} ?checked=${!breakpoints.some(breakpoint => !breakpoint.enabled())} @change=${(e: Event) => onCheckboxClicked(e, category)} >${getLocalizedCategory(category)}</devtools-checkbox> <ul role="group" ?hidden=${!shouldExpandCategory(breakpoints) && !input.userExpandedCategories.has(category)}> ${breakpoints.map(breakpoint => html` <li role="treeitem" aria-checked=${breakpoint.enabled()} jslog-context=${Platform.StringUtilities.toKebabCase(breakpoint.name)}> <div ?hidden=${breakpoint !== input.highlightedItem} class="breakpoint-hit-marker"></div> <devtools-checkbox class=${classes(breakpoint)} tabIndex=-1 title=${Sources.CategorizedBreakpointL10n.getLocalizedBreakpointName(breakpoint.name)} ?checked=${breakpoint.enabled()} aria-description=${breakpoint === input.highlightedItem ? i18nString(UIStrings.breakpointHit) : Lit.nothing} @change=${(e: Event) => onCheckboxClicked(e, breakpoint)} >${Sources.CategorizedBreakpointL10n.getLocalizedBreakpointName(breakpoint.name)}</devtools-checkbox> </li>`)} </ul> </li>`)} </ul> `}> </devtools-tree>`, // clang-format on target); }; export abstract class CategorizedBreakpointsSidebarPane extends UI.Widget.VBox { readonly #viewId: string; // A layout test reaches into this private readonly categories = new Map<SDK.CategorizedBreakpoint.Category, SDK.CategorizedBreakpoint.CategorizedBreakpoint[]>(); #sortedCategories: SDK.CategorizedBreakpoint.Category[]; #highlightedItem: SDK.CategorizedBreakpoint.CategorizedBreakpoint|null = null; #filterText: string|null = null; #view: View; #userExpandedCategories = new Set<SDK.CategorizedBreakpoint.Category>(); constructor( breakpoints: SDK.CategorizedBreakpoint.CategorizedBreakpoint[], jslog: string, viewId: string, view = DEFAULT_VIEW) { super({useShadowDom: true, jslog}); this.#view = view; this.#viewId = viewId; for (const breakpoint of breakpoints) { let categorizedBreakpoints = this.categories.get(breakpoint.category()); if (!categorizedBreakpoints) { categorizedBreakpoints = []; this.categories.set(breakpoint.category(), categorizedBreakpoints); } categorizedBreakpoints.push(breakpoint); } this.#sortedCategories = [...this.categories.keys()].sort((a, b) => { const categoryA = getLocalizedCategory(a); const categoryB = getLocalizedCategory(b); return categoryA.localeCompare(categoryB, i18n.DevToolsLocale.DevToolsLocale.instance().locale); }); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, this.update, this); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this.update, this); UI.Context.Context.instance().addFlavorChangeListener(SDK.Target.Target, this.update, this); this.requestUpdate(); } protected getBreakpointFromPausedDetails(_details: SDK.DebuggerModel.DebuggerPausedDetails): SDK.CategorizedBreakpoint.CategorizedBreakpoint|null { return null; } update(): void { const target = UI.Context.Context.instance().flavor(SDK.Target.Target); const debuggerModel = target ? target.model(SDK.DebuggerModel.DebuggerModel) : null; const details = debuggerModel ? debuggerModel.debuggerPausedDetails() : null; const breakpoint = details && this.getBreakpointFromPausedDetails(details); this.#highlightedItem = breakpoint; if (!breakpoint) { return; } void UI.ViewManager.ViewManager.instance().showView(this.#viewId); this.requestUpdate(); } #onFilterChanged(filterText: string|null): void { this.#filterText = filterText; this.requestUpdate(); } protected onBreakpointChanged(breakpoint: SDK.CategorizedBreakpoint.CategorizedBreakpoint, enabled: boolean): void { breakpoint.setEnabled(enabled); this.requestUpdate(); } override performUpdate(): void { const input: ViewInput = { filterText: this.#filterText, onFilterChanged: this.#onFilterChanged.bind(this), onBreakpointChange: this.onBreakpointChanged.bind(this), sortedCategoryNames: this.#sortedCategories, categories: this.categories, highlightedItem: this.#highlightedItem, userExpandedCategories: this.#userExpandedCategories, }; const that = this; const output: ViewOutput = { set defaultFocus(e: Element|undefined) { that.setDefaultFocusedElement(e ?? null); }, userExpandedCategories: this.#userExpandedCategories, }; this.#view(input, output, this.contentElement); } } const LOCALIZED_CATEGORIES: Record<SDK.CategorizedBreakpoint.Category, () => Platform.UIString.LocalizedString> = { [SDK.CategorizedBreakpoint.Category.ANIMATION]: i18nLazyString(UIStrings.animation), [SDK.CategorizedBreakpoint.Category.AUCTION_WORKLET]: i18nLazyString(UIStrings.auctionWorklet), [SDK.CategorizedBreakpoint.Category.CANVAS]: i18nLazyString(UIStrings.canvas), [SDK.CategorizedBreakpoint.Category.CLIPBOARD]: i18nLazyString(UIStrings.clipboard), [SDK.CategorizedBreakpoint.Category.CONTROL]: i18nLazyString(UIStrings.control), [SDK.CategorizedBreakpoint.Category.DEVICE]: i18nLazyString(UIStrings.device), [SDK.CategorizedBreakpoint.Category.DOM_MUTATION]: i18nLazyString(UIStrings.domMutation), [SDK.CategorizedBreakpoint.Category.DRAG_DROP]: i18nLazyString(UIStrings.dragDrop), [SDK.CategorizedBreakpoint.Category.GEOLOCATION]: i18nLazyString(UIStrings.geolocation), [SDK.CategorizedBreakpoint.Category.KEYBOARD]: i18nLazyString(UIStrings.keyboard), [SDK.CategorizedBreakpoint.Category.LOAD]: i18nLazyString(UIStrings.load), [SDK.CategorizedBreakpoint.Category.MEDIA]: i18nLazyString(UIStrings.media), [SDK.CategorizedBreakpoint.Category.MOUSE]: i18nLazyString(UIStrings.mouse), [SDK.CategorizedBreakpoint.Category.NOTIFICATION]: i18nLazyString(UIStrings.notification), [SDK.CategorizedBreakpoint.Category.PARSE]: i18nLazyString(UIStrings.parse), [SDK.CategorizedBreakpoint.Category.PICTURE_IN_PICTURE]: i18nLazyString(UIStrings.pictureinpicture), [SDK.CategorizedBreakpoint.Category.POINTER]: i18nLazyString(UIStrings.pointer), [SDK.CategorizedBreakpoint.Category.SCRIPT]: i18nLazyString(UIStrings.script), [SDK.CategorizedBreakpoint.Category.SHARED_STORAGE_WORKLET]: i18nLazyString(UIStrings.sharedStorageWorklet), [SDK.CategorizedBreakpoint.Category.TIMER]: i18nLazyString(UIStrings.timer), [SDK.CategorizedBreakpoint.Category.TOUCH]: i18nLazyString(UIStrings.touch), [SDK.CategorizedBreakpoint.Category.TRUSTED_TYPE_VIOLATION]: i18nLazyString(UIStrings.trustedTypeViolations), [SDK.CategorizedBreakpoint.Category.WEB_AUDIO]: i18nLazyString(UIStrings.webaudio), [SDK.CategorizedBreakpoint.Category.WINDOW]: i18nLazyString(UIStrings.window), [SDK.CategorizedBreakpoint.Category.WORKER]: i18nLazyString(UIStrings.worker), [SDK.CategorizedBreakpoint.Category.XHR]: i18nLazyString(UIStrings.xhr), }; function getLocalizedCategory(category: SDK.CategorizedBreakpoint.Category): Platform.UIString.LocalizedString { return LOCALIZED_CATEGORIES[category](); }