@@ -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
359369export 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
369384export interface ViewOutput {
@@ -388,7 +403,9 @@ function adornerRef(input: ViewInput): DirectiveResult<typeof Lit.Directives.Ref
388403export 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 ;
0 commit comments