review-ui-widgets
GitHub提供Chrome DevTools前端UI和组件变更的代码审查指导,确保符合现代UI规范、类型安全和MVP架构原则。
Trigger Scenarios
Install
npx skills add ChromeDevTools/devtools-frontend --skill review-ui-widgets -g -y
SKILL.md
Frontmatter
{
"name": "review-ui-widgets",
"description": "Reviews Chrome DevTools frontend changes in path group ui-and-widgets (^front_end\/(ui|panels)\/.*\\.(ts|css)$), strictly reflecting review standards, architectural invariants, and security boundaries derived from historical code reviews."
}
Chrome DevTools frontend reviewer skill: ui-and-widgets
This skill provides review guidance for Chrome DevTools frontend changes matching the ui-and-widgets path group (^front_end/(ui|panels)/.*.(ts|css)$). It focuses specifically on code review verification for UI and widget changes, complementing authoring guidance in ui-widgets and docs/ui_engineering.md, and integrating with the general review checklist in code-review. Every rule below recurred in at least 5 CLs from 2 distinct reviewers in historical reviews; cite the rule id in findings.
Path group ui-and-widgets (17 rules)
UI_MODERN_COMPONENT_AND_ENCAPSULATION_STANDARDS: Use modern DevTools UI primitives (devtools-button, devtools-data-grid, devtools-icon), #private fields, and module entrypoint imports
- Severity: WARNING
- Why: Legacy
UI.Fragment,DataGridImpl,LitElement,new Icon(), and TypeScriptprivatefields are superseded by standard<devtools-*>custom elements (imported via module entrypoints such asimport * as Buttons from '../../ui/components/buttons/buttons.js'so custom tags register at runtime) and ECMAScript#privatefields. - Look for:
UI.Fragment.build, legacyDataGridImpl,extends LitElement,privatekeyword instead of#in new UI classes, or<devtools-*>tags in Lit templates without importing the corresponding component module entrypoint (e.g.import * as Buttons from '../../ui/components/buttons/buttons.js'). - Do not flag: Overriding inherited
protectedlifecycle methods on framework base classes (e.g. extendingUI.Widget.Widget).
UI_TYPE_SAFETY_AND_EXHAUSTIVENESS: Model multi-state UI workflows with discriminated unions, Platform.assertNever exhaustiveness checks, and strict domain types
- Severity: WARNING
- Why: Representing mutually exclusive UI states with loose optional boolean flags or
anytypes permits impossible states and silently misses new enum variants. Discriminated unions paired withPlatform.assertNeverindefault:branches enforce compile-time exhaustiveness. - Look for:
switchstatements over union/enum types missingPlatform.assertNever(x)indefault:,anyordeclarefield modifiers in UI classes, or multiple boolean flags (isLoading,isError,isEmpty) representing mutually exclusive states. - Do not flag: Switch statements that intentionally handle only a small subset of a large external protocol enum.
UI_PURE_VIEW_AND_PRESENTER_SEPARATION: Keep UI.Widget view functions pure and stateless by computing presentation data in presenters and passing decoupled ViewInput data structures
- Severity: WARNING
- Why: In DevTools' MVP architecture,
DEFAULT_VIEWfunctions must be pure projections ofViewInputto Lit templates. Querying SDK models, reading global settings, mutating presenter state, or calling host/clipboard APIs inside the view couples rendering to global state and breaks isolated view testing. - Look for:
DEFAULT_VIEWor view helper functions callingCommon.Settings.Settings.instance(),SDK.*,Host.*, mutatinginput.*arrays in place, or accepting rawSDK/Protocoldomain objects instead of decoupled presentation interfaces onViewInput. - Do not flag: Legacy
UI.Widgetsubclasses that have not yet migrated to aDEFAULT_VIEWfunction.
UI_LIT_DECLARATIVE_TEMPLATES_AND_DIRECTIVES: Use Lit.nothing, repeat with keys, classMap/styleMap, and ifDefined in single-pass declarative templates instead of imperative DOM mutations
- Severity: WARNING
- Why: Mutating DOM elements via
appendChild,classList.toggle, orinnerHTMLalongside Lit templates, or returning''/nullinstead ofLit.nothingin conditional branches, corrupts Lit's part tracking and creates unnecessary text nodes or wrapper<div>s. - Look for: Ternary branches returning
''orundefinedinhtmltemplates instead ofLit.nothing, unkeyed.map()on dynamic stateful lists instead ofrepeat(),ifDefinedomissions on optional attributes, or multipleLit.render()calls mutating the same container. - Do not flag: Static arrays of primitive labels where item identity never reorders.
UI_WIDGET_DOM_AND_LISTENER_LIFECYCLE: Attach listeners to external models and managers in wasShown and detach them symmetrically in willHide
- Severity: ERROR
- Why: Subscribing to long-lived SDK
TargetManager,Settings, or model events in a widget's constructor without unsubscribing inwillHide()(oronDetach()/onunbind()) causes hidden panels to process background updates and leaks detached widget instances. - Look for:
addEventListener,addModelListener, oraddChangeListeneron singletons, models, or settings insideUI.Widgetconstructors orwasShown()without a matchingremove*call inwillHide()using the same function reference. - Do not flag: Listeners attached to the widget's own internal DOM elements (
this.contentElement), which are garbage-collected together with the widget.
UI_ACCESSIBILITY_AND_SEMANTIC_HTML: Use native semantic HTML controls with accurate ARIA attributes, keyboard handlers, and !event.isComposing guards
- Severity: WARNING
- Why: Clickable
<div>/<span>elements and nested interactive controls (<button>inside<a>or<button>) break keyboard navigation and screen readers. Interactive elements require semantic tags (<button>,<details>/<summary>,<thead>/<tbody>), synchronized ARIA state (aria-expanded,aria-detailsfor rich tooltips vsaria-describedbyfor plain text), and!event.isComposingchecks onEnterkeydown handlers. - Look for:
@clickon<div>/<span>without button semantics,<button>nested inside<button>/<x-link>,Enterkeydown handlers missing!event.isComposing, oraria-describedbypointing to interactive<devtools-tooltip>elements. - Do not flag: Delegating click listeners on container elements that handle bubbled events from child buttons.
UI_PERFORMANCE_AND_FLAMECHART_RENDERING: Precompute expensive metrics outside render loops, use Set/Map for O(1) lookups, and debounce high-frequency UI updates
- Severity: WARNING
- Why: Performing linear
Array.includes/findscans, sorting, or regex parsing inside FlameChart canvas draw loops,ElementsTreeOutlinemutation callbacks, or Lit render passes causes frame drops and UI jank on large traces and DOM trees. - Look for:
.includes()/.find()/.sort()inside canvas draw or event iteration loops, repeatedResizeObserverregistrations on individual child items instead of the parent container, or un-debounced filter/mutation handlers. - Do not flag: One-time initialization passes over bounded small arrays (less than 20 items).
UI_LOCALIZATION_AND_UISTRINGS_HYGIENE: Define user-facing strings in sentence-case UIStrings using complete sentences and i18nTemplate for embedded elements, and prune unused entries
- Severity: WARNING
- Why: Concatenating translated fragments breaks word order in non-English locales, title-Cased strings violate Chromium UX standards, translating English-only experimental strings wastes localization quota, and unused
UIStringsentries bloat locale bundles. - Look for: String concatenation around
i18nString(...), HTML/DOM elements interpolated withouti18nTemplate, Title Case labels inUIStrings, unlocalized English literals in templates, or unused keys inUIStrings. - Do not flag: Technical protocol identifiers, MIME types, or strings in
UIStringsNotTranslate/i18n.i18n.lockedString(...).
UI_REACTIVE_WIDGET_UPDATE_LIFECYCLE: Schedule widget re-renders asynchronously via this.requestUpdate() and guard property setters with equality checks
- Severity: WARNING
- Why: Calling
this.performUpdate()or invokingthis.#view(...)synchronously inside property setters causes redundant synchronous DOM renders when multiple properties change in the same tick. Setters should short-circuit if the value is unchanged and callthis.requestUpdate(). - Look for: Widget property setters calling
this.performUpdate()orthis.#view(...)directly, or setters callingthis.requestUpdate()without checkingif (this.#prop === value) return;. - Do not flag: Internal test helpers or synchronous initialization paths explicitly documented as requiring immediate flush.
UI_VISUAL_LOGGING_INSTRUMENTATION: Instrument interactive UI elements and containers with kebab-case VisualLogging contexts registered in KnownContextValues
- Severity: WARNING
- Why: DevTools relies on Visual Elements (
jslog) telemetry to measure feature usage. Omittingjslogon new buttons/toggles/menus, using localized or camelCase context strings, adding redundant panel prefixes inside already-logged containers, or droppingjslogduring Lit refactors breaks analytics. - Look for: Interactive elements (
<button>,<devtools-button>,<input>, menus, drawers) missingjslogorjslogContext, non-kebab-case context names, or new context values missing fromKnownContextValues.ts. - Do not flag: Purely decorative icons or internal wrapper
<div>s that represent neither a distinct visual section nor an interactive control.
UI_CSS_SCOPING_AND_FLEX_LAYOUT: Scope light-DOM stylesheets and align elements using flexbox or grid rather than competing margin and position offsets
- Severity: INFO
- Why: Unscoped class or tag selectors in light-DOM widget stylesheets leak across the DevTools document, while combining
vertical-align, negative margins, andposition: relativeoffsets creates brittle layouts that break under font scaling or zoom. - Look for: Unscoped generic selectors in light-DOM
.cssfiles (or:hostinside@scopeblocks instead of:scope), or CSS rules combiningvertical-align, negativemargin-top, and pixel offsets to center icons and buttons. - Do not flag: Shadow DOM component stylesheets where
:hostscoping is native.
UI_VIEW_OUTPUT_AND_CALLBACK_CONTRACTS: Encapsulate imperative DOM actions in ViewOutput callbacks and pass user actions via semantic ViewInput callbacks instead of raw DOM references
- Severity: WARNING
- Why: Exposing raw
HTMLElementreferences onViewOutputor passing rawMouseEvent/KeyboardEventobjects to presenter callbacks leaks DOM details into presenters. Views should populate narrow functional closures onViewOutput(e.g.output.focusInput = () => ...) and unpack event values before callinginput.onSelect(value). - Look for:
ViewOutputinterfaces containingHTMLElementorElementproperties, orViewInputcallbacks typed as(event: Event) => voidinstead of semantic domain values. - Do not flag: Third-party editor or canvas host containers that require a raw mount
HTMLElement.
UI_PANEL_AND_META_REGISTRATION: Follow DevTools panel directory conventions and register settings and views in the appropriate *-meta.ts scope
- Severity: WARNING
- Why: Panel-level
UI.Widgetviews belong directly underfront_end/panels/<panel>/(reservingcomponents/for reusable leaf components), internal non-user-facing settings must useSettingCategory.NONEor model descriptors rather than appearing in Settings UI, and shared SDK settings belong insdk-meta.ts. - Look for: Panel-level
UI.Widgetviews placed insidepanels/<panel>/components/, internal state flags registered with user-facing titles in*-meta.ts, orloadViewcallbacks calling.instance()instead of using the passedUniversecontext. - Do not flag: Standalone reusable leaf components inside
components/or genuine user-configurable preferences.
UI_WEAK_DOM_REFERENCES_AND_OBSERVER_CLEANUP: Store DOM node metadata in WeakMap or WeakRef collections and clean up observers when elements detach
- Severity: WARNING
- Why: Retaining DOM
ElementorSDK.Targetreferences in strongMap/Setcollections (or inWeakMapvalues that hold strong references back to their keys) prevents detached DOM trees from being garbage-collected, while leavingResizeObserver,MutationObserver, orLit.Directives.refcallbacks active after unmount leaks memory. - Look for: Strong
Map<Element, ...>/Set<Element>collections holding DOM/model associations,WeakMapvalues retaining strong references back to their DOM keys,ResizeObserver/IntersectionObserverwithout.unobserve()/.disconnect()on teardown, orref(el => ...)callbacks that do not handleel === undefined. - Do not flag: Module-level
WeakMap<Element, ...>globals whose values do not strongly reference the key, or short-lived local maps created and discarded within a single synchronous function call.
UI_SECURITY_AND_OUTPUT_SANITIZATION: Sanitize untrusted strings, URLs, CSS selectors, and Markdown exports before rendering or exporting
- Severity: ERROR
- Why: Interpolating untrusted network URLs, DOM attributes, or user strings into raw HTML snippets,
CSS.escape-free attribute selectors, Markdown table exports, orhrefattributes without protocol validation risks injection, XSS, broken layout formatting, or text spoofing. - Look for: Raw string interpolation in
querySelector('[attr="' + val + '"]')withoutCSS.escape,hrefattribute bindings with unvalidatedjavascript:URLs, Markdown table cell serialization without escaping|*_<>\, or raw HTML snippet generation without HTML escaping. - Do not flag: Standard Lit
htmltext bindings (${text}), which escape HTML entities automatically.
UI_DECLARATIVE_CHILD_WIDGET_COMPOSITION: Compose child UI.Widget instances declaratively in Lit templates using the widget() directive instead of imperative show() calls
- Severity: WARNING
- Why: Imperatively instantiating child widgets and calling
child.show(container)inside presenters or views bypasses Lit reconciliation, complicates lifecycle management, and recreates widgets unnecessarily across renders. - Look for:
new ChildWidget()followed by.show(...)or.appendChild(child.element)inside Lit view functions or migratedperformUpdate()methods instead of<devtools-widget ${widget(ChildWidget, {...})}></devtools-widget>(or${widget(ChildWidget, {...})}). - Do not flag: Top-level legacy split widgets or tabbed panes that have not migrated to Lit views.
UI_AWAIT_UPDATE_COMPLETE_POST_RENDER: Await this.updateComplete before executing post-render DOM measurements, focus changes, or scroll adjustments
- Severity: ERROR
- Why: Because
this.requestUpdate()schedules rendering asynchronously in a microtask, querying DOM geometry, focusing newly rendered controls, or anchoring popovers immediately afterrequestUpdate()operates on stale pre-render DOM state. - Look for:
this.requestUpdate()followed synchronously by.focus(),.getBoundingClientRect(),.scrollIntoView(), or child widget measurement withoutawait this.updateComplete. - Do not flag: State updates that do not interact with rendered DOM nodes after scheduling the update.
Version History
- 6527f72 Current 2026-10-20 02:26


