Skip to content

Commit 7044223

Browse files
OrKoNDevtools-frontend LUCI CQ
authored andcommitted
Migrate container adorner
Bug: 407751414 Change-Id: I0929b3412ce2a9bc9922d1313f3d333eab70de32 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7156735 Reviewed-by: Philip Pfaffe <pfaffe@chromium.org> Commit-Queue: Alex Rudenko <alexrudenko@chromium.org> Reviewed-by: Danil Somsikov <dsv@chromium.org>
1 parent 85c4dce commit 7044223

File tree

3 files changed

+115
-56
lines changed

3 files changed

+115
-56
lines changed

front_end/panels/elements/ElementsTreeElement.ts

Lines changed: 91 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ const UIStrings = {
229229
* the overlay showing CSS scroll snapping for the current element.
230230
*/
231231
disableScrollSnap: 'Disable scroll-snap overlay',
232+
/**
233+
* @description Label of an adorner in the Elements panel. When clicked, it enables
234+
* the overlay showing the container overlay for the current element.
235+
*/
236+
enableContainer: 'Enable container overlay',
237+
/**
238+
* @description Label of an adorner in the Elements panel. When clicked, it disables
239+
* the overlay showing container for the current element.
240+
*/
241+
disableContainer: 'Disable container overlay',
232242
/**
233243
* @description Label of an adorner in the Elements panel. When clicked, it forces
234244
* the element into applying its starting-style rules.
@@ -357,13 +367,18 @@ export function isOpeningTag(context: TagTypeContext): context is OpeningTagCont
357367
}
358368

359369
export interface ViewInput {
370+
containerAdornerActive: boolean;
371+
360372
showAdAdorner: boolean;
373+
showContainerAdorner: boolean;
374+
361375
adorners?: Set<Adorners.Adorner.Adorner>;
362376
nodeInfo?: DocumentFragment;
363377

364378
onGutterClick: (e: Event) => void;
365379
onAdornerAdded: (adorner: Adorners.Adorner.Adorner) => void;
366380
onAdornerRemoved: (adorner: Adorners.Adorner.Adorner) => void;
381+
onContainerAdornerClick: (e: Event) => void;
367382
}
368383

369384
export interface ViewOutput {
@@ -388,7 +403,9 @@ function adornerRef(input: ViewInput): DirectiveResult<typeof Lit.Directives.Ref
388403
export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
389404
const adAdornerConfig =
390405
ElementsComponents.AdornerManager.getRegisteredAdorner(ElementsComponents.AdornerManager.RegisteredAdorners.AD);
391-
const hasAdorners = input.adorners || input.showAdAdorner;
406+
const containerAdornerConfig = ElementsComponents.AdornerManager.getRegisteredAdorner(
407+
ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER);
408+
const hasAdorners = input.adorners?.size || input.showAdAdorner || input.showContainerAdorner;
392409
// clang-format off
393410
render(html`
394411
<div ${ref(el => { output.contentElement = el as HTMLElement; })}>
@@ -399,11 +416,30 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
399416
</div>
400417
${hasAdorners? html`<div class="adorner-container ${!hasAdorners ? 'hidden': ''}">
401418
${input.showAdAdorner ? html`<devtools-adorner
402-
.data=${{name: adAdornerConfig.name, jslogContext: adAdornerConfig.name}}
403419
aria-label=${i18nString(UIStrings.thisFrameWasIdentifiedAsAnAd)}
420+
.data=${{name: adAdornerConfig.name, jslogContext: adAdornerConfig.name}}
404421
${adornerRef(input)}>
405422
<span>${adAdornerConfig.name}</span>
406423
</devtools-adorner>` : nothing}
424+
${input.showContainerAdorner ? html`<devtools-adorner
425+
class=clickable
426+
role=button
427+
toggleable=true
428+
tabindex=0
429+
.data=${{name: containerAdornerConfig.name, jslogContext: containerAdornerConfig.name}}
430+
jslog=${VisualLogging.adorner(containerAdornerConfig.name).track({click: true})}
431+
active=${input.containerAdornerActive}
432+
aria-label=${input.containerAdornerActive ? i18nString(UIStrings.enableContainer) : i18nString(UIStrings.disableContainer)}
433+
@click=${input.onContainerAdornerClick}
434+
@keydown=${(event: KeyboardEvent) => {
435+
if (event.code === 'Enter' || event.code === 'Space') {
436+
input.onContainerAdornerClick(event);
437+
event.stopPropagation();
438+
}
439+
}}
440+
${adornerRef(input)}>
441+
<span>${containerAdornerConfig.name}</span>
442+
</devtools-adorner>`: nothing}
407443
${repeat(Array.from((input.adorners ?? new Set()).values()).sort(adornerComparator), adorner => {
408444
return adorner;
409445
})}
@@ -442,6 +478,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
442478
#adornersThrottler = new Common.Throttler.Throttler(100);
443479
#adorners = new Set<Adorners.Adorner.Adorner>();
444480
#nodeInfo?: DocumentFragment;
481+
#containerAdornerActive = false;
482+
#layout: SDK.CSSModel.LayoutProperties|null = null;
445483

446484
constructor(node: SDK.DOMModel.DOMNode, isClosingTag?: boolean) {
447485
// The title will be updated in onattach.
@@ -474,9 +512,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
474512
void this.updateStyleAdorners();
475513

476514
void this.updateScrollAdorner();
515+
516+
void this.#updateAdorners();
477517
}
478518
this.expandAllButtonElement = null;
479-
480519
this.performUpdate();
481520

482521
if (this.nodeInternal.retained && !this.isClosingTag()) {
@@ -494,6 +533,16 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
494533
if (this.nodeInternal.detached && !this.isClosingTag()) {
495534
this.listItemNode.setAttribute('title', 'Detached Tree Node');
496535
}
536+
537+
node.domModel().overlayModel().addEventListener(
538+
SDK.OverlayModel.Events.PERSISTENT_CONTAINER_QUERY_OVERLAY_STATE_CHANGED, event => {
539+
const {nodeId: eventNodeId, enabled} = event.data;
540+
if (eventNodeId !== node.id) {
541+
return;
542+
}
543+
this.#containerAdornerActive = enabled;
544+
this.performUpdate();
545+
});
497546
}
498547

499548
static animateOnDOMUpdate(treeElement: ElementsTreeElement): void {
@@ -556,10 +605,14 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
556605
performUpdate(): void {
557606
DEFAULT_VIEW(
558607
{
608+
containerAdornerActive: this.#containerAdornerActive,
559609
adorners: !this.isClosingTag() ? this.#adorners : undefined,
560610
showAdAdorner:
561611
ElementsPanel.instance().isAdornerEnabled(ElementsComponents.AdornerManager.RegisteredAdorners.AD) &&
562612
this.nodeInternal.isAdFrameNode(),
613+
showContainerAdorner: ElementsPanel.instance().isAdornerEnabled(
614+
ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER) &&
615+
Boolean(this.#layout?.isContainer) && !this.isClosingTag(),
563616
nodeInfo: this.#nodeInfo,
564617
onGutterClick: this.showContextMenu.bind(this),
565618
onAdornerAdded: adorner => {
@@ -568,10 +621,30 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
568621
onAdornerRemoved: adorner => {
569622
ElementsPanel.instance().deregisterAdorner(adorner);
570623
},
624+
onContainerAdornerClick: (event: Event) => this.#onContainerAdornerClick(event),
571625
},
572626
this, this.listItemElement);
573627
}
574628

629+
#onContainerAdornerClick(event: Event): void {
630+
event.stopPropagation();
631+
const node = this.node();
632+
const nodeId = node.id;
633+
if (!nodeId) {
634+
return;
635+
}
636+
const model = node.domModel().overlayModel();
637+
if (model.isHighlightedContainerQueryInPersistentOverlay(nodeId)) {
638+
model.hideContainerQueryInPersistentOverlay(nodeId);
639+
this.#containerAdornerActive = false;
640+
} else {
641+
model.highlightContainerQueryInPersistentOverlay(nodeId);
642+
this.#containerAdornerActive = true;
643+
Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
644+
}
645+
void this.updateAdorners();
646+
}
647+
575648
highlightAttribute(attributeName: string): void {
576649
// If the attribute is not found, we highlight the tag name instead.
577650
let animationElement = this.listItemElement.querySelector('.webkit-html-tag-name') ?? this.listItemElement;
@@ -2601,17 +2674,28 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
26012674
}
26022675
}
26032676

2604-
private updateAdorners(): void {
2677+
updateAdorners(): void {
26052678
// TODO: remove adornersThrottler in favour of throttled updated (requestUpdate/performUpdate).
26062679
void this.#adornersThrottler.schedule(this.#updateAdorners.bind(this));
26072680
}
26082681

2609-
#updateAdorners(): Promise<void> {
2610-
// TODO: remove in favour of throttled updated (requestUpdate/performUpdate).
2682+
async #updateAdorners(): Promise<void> {
2683+
if (this.isClosingTag()) {
2684+
this.performUpdate();
2685+
return;
2686+
}
2687+
const node = this.node();
2688+
const nodeId = node.id;
2689+
if (node.nodeType() !== Node.COMMENT_NODE && node.nodeType() !== Node.DOCUMENT_FRAGMENT_NODE &&
2690+
node.nodeType() !== Node.TEXT_NODE && nodeId !== undefined) {
2691+
this.#layout = await node.domModel().cssModel().getLayoutPropertiesFromComputedStyle(nodeId);
2692+
} else {
2693+
this.#layout = null;
2694+
}
26112695
this.performUpdate();
2612-
return Promise.resolve();
26132696
}
26142697

2698+
// TODO: remove in favour of updateAdorners.
26152699
async updateStyleAdorners(): Promise<void> {
26162700
if (!isOpeningTag(this.tagTypeContext)) {
26172701
return;
@@ -2623,15 +2707,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
26232707
node.nodeType() === Node.TEXT_NODE || nodeId === undefined) {
26242708
return;
26252709
}
2626-
26272710
const layout = await node.domModel().cssModel().getLayoutPropertiesFromComputedStyle(nodeId);
26282711
// TODO: move this to the template.
26292712
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.SUBGRID);
26302713
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.GRID);
26312714
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.GRID_LANES);
26322715
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.FLEX);
26332716
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL_SNAP);
2634-
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER);
26352717
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.MEDIA);
26362718
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.STARTING_STYLE);
26372719
this.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.POPOVER);
@@ -2648,9 +2730,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
26482730
if (layout.hasScroll) {
26492731
this.pushScrollSnapAdorner();
26502732
}
2651-
if (layout.isContainer) {
2652-
this.pushContainerAdorner();
2653-
}
26542733
}
26552734

26562735
if (node.isMediaNode()) {
@@ -2908,49 +2987,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
29082987
}
29092988
}
29102989

2911-
pushContainerAdorner(): void {
2912-
const node = this.node();
2913-
const nodeId = node.id;
2914-
if (!nodeId) {
2915-
return;
2916-
}
2917-
const config = ElementsComponents.AdornerManager.getRegisteredAdorner(
2918-
ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER);
2919-
const adorner = this.adorn(config);
2920-
adorner.classList.add('container');
2921-
2922-
const onClick = ((() => {
2923-
const model = node.domModel().overlayModel();
2924-
if (adorner.isActive()) {
2925-
model.highlightContainerQueryInPersistentOverlay(nodeId);
2926-
Badges.UserBadges.instance().recordAction(Badges.BadgeAction.MODERN_DOM_BADGE_CLICKED);
2927-
} else {
2928-
model.hideContainerQueryInPersistentOverlay(nodeId);
2929-
}
2930-
}) as EventListener);
2931-
2932-
adorner.addInteraction(onClick, {
2933-
isToggle: true,
2934-
shouldPropagateOnKeydown: false,
2935-
ariaLabelDefault: i18nString(UIStrings.enableScrollSnap),
2936-
ariaLabelActive: i18nString(UIStrings.disableScrollSnap),
2937-
});
2938-
2939-
node.domModel().overlayModel().addEventListener(
2940-
SDK.OverlayModel.Events.PERSISTENT_CONTAINER_QUERY_OVERLAY_STATE_CHANGED, event => {
2941-
const {nodeId: eventNodeId, enabled} = event.data;
2942-
if (eventNodeId !== nodeId) {
2943-
return;
2944-
}
2945-
adorner.toggle(enabled);
2946-
});
2947-
2948-
this.#adorners.add(adorner);
2949-
if (node.domModel().overlayModel().isHighlightedContainerQueryInPersistentOverlay(nodeId)) {
2950-
adorner.toggle(true);
2951-
}
2952-
}
2953-
29542990
pushMediaAdorner(): void {
29552991
const node = this.node();
29562992
const nodeId = node.id;

front_end/panels/elements/ElementsTreeOutline.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
362362
* FIXME: adorners should be part of the view input.
363363
*/
364364
updateNodeAdorners(node: SDK.DOMModel.DOMNode): void {
365-
void this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.updateStyleAdorners();
365+
const element = this.#viewOutput.elementsTreeOutline?.findTreeElement(node);
366+
void element?.updateStyleAdorners();
367+
void element?.updateAdorners();
366368
}
367369

368370
highlightMatch(node: SDK.DOMModel.DOMNode, query?: string): void {
@@ -2011,6 +2013,7 @@ export class ElementsTreeOutline extends
20112013
const treeElement = this.treeElementByNode.get(node);
20122014
if (treeElement && isOpeningTag(treeElement.tagTypeContext)) {
20132015
void treeElement.updateStyleAdorners();
2016+
void treeElement.updateAdorners();
20142017
}
20152018
}
20162019
}

front_end/ui/components/adorners/Adorner.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ export interface AdornerData {
1616
jslogContext?: string;
1717
}
1818

19+
/**
20+
* @deprecated Do not add new usages. The custom component will be removed an
21+
* embedded into the corresponding views.
22+
*/
1923
export class Adorner extends HTMLElement {
24+
static readonly observedAttributes = ['active', 'toggleable'];
2025
name = '';
2126

2227
readonly #shadow = this.attachShadow({mode: 'open'});
@@ -53,6 +58,21 @@ export class Adorner extends HTMLElement {
5358
this.#render();
5459
}
5560

61+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
62+
if (oldValue === newValue) {
63+
return;
64+
}
65+
66+
switch (name) {
67+
case 'active':
68+
this.toggle(newValue === 'true');
69+
break;
70+
case 'toggleable':
71+
this.#isToggle = newValue === 'true';
72+
break;
73+
}
74+
}
75+
5676
isActive(): boolean {
5777
return this.getAttribute('aria-pressed') === 'true';
5878
}

0 commit comments

Comments
 (0)