@@ -457,98 +457,247 @@ bot.dom.getCascadedStyle_ = function(elem, styleName) {
457457 * @return {boolean } Whether or not the element is visible.
458458 * @private
459459 */
460- bot . dom . isShown_ = function ( elem , ignoreOpacity , parentsDisplayedFn ) {
461- if ( ! bot . dom . isElement ( elem ) ) {
462- throw new Error ( 'Argument to isShown must be of type Element' ) ;
460+ bot . dom . isShown_ = function ( elem , ignoreOpacity , parentsDisplayedFn ) {
461+
462+ const isMap = ( elem ) => {
463+ const getAreaRelativeRect = ( area ) => {
464+ const shape = area . shape . toLowerCase ( ) ;
465+ const coords = area . coords . split ( ',' ) ;
466+ if ( shape == 'rect' && coords . length == 4 ) {
467+ const [ x , y ] = coords ;
468+ return new DOMRect ( x , y , coords [ 2 ] - x , coords [ 3 ] - y ) ;
469+ } else if ( shape == 'circle' && coords . length == 3 ) {
470+ const [ centerX , centerY , radius ] = coords ;
471+ return new DOMRect ( centerX - radius , centerY - radius , 2 * radius , 2 * radius ) ;
472+ } else if ( shape == 'poly' && coords . length > 2 ) {
473+ let [ minX , minY ] = coords , maxX = minX , maxY = minY ;
474+ for ( var i = 2 ; i + 1 < coords . length ; i += 2 ) {
475+ minX = Math . min ( minX , coords [ i ] ) ;
476+ maxX = Math . max ( maxX , coords [ i ] ) ;
477+ minY = Math . min ( minY , coords [ i + 1 ] ) ;
478+ maxY = Math . max ( maxY , coords [ i + 1 ] ) ;
479+ }
480+ return new DOMRect ( minX , minY , maxX - minX , maxY - minY ) ;
481+ }
482+ return new DOMRect ( ) ;
483+ } ;
484+
485+ // If not a <map> or <area>, return null indicating so.
486+ const isMap = elem instanceof HTMLMapElement ;
487+ if ( ! isMap && ! ( elem instanceof HTMLAreaElement ) ) {
488+ return null ;
489+ }
490+
491+ // Get the <map> associated with this element, or null if none.
492+ const map = isMap ? elem : elem . closest ( 'map' ) ;
493+
494+ let image = null , rect = null ;
495+ if ( map && map . name ) {
496+ const mapDoc = map . ownerDocument ;
497+ image = mapDoc . querySelector ( `*[usemap="#${ map . name } "]` ) ;
498+
499+ if ( image ) {
500+ rect = image . getBoundingClientRect ( ) ;
501+ if ( ! isMap && elem . shape . toLowerCase ( ) !== 'default' ) {
502+ // Shift and crop the relative area rectangle to the map.
503+ const relRect = getAreaRelativeRect ( elem ) ;
504+ const relX = Math . min ( Math . max ( relRect . left , 0 ) , rect . width ) ;
505+ const relY = Math . min ( Math . max ( relRect . top , 0 ) , rect . height ) ;
506+ const width = Math . min ( relRect . width , rect . width - relX ) ;
507+ const height = Math . min ( relRect . height , rect . height - relY ) ;
508+ rect = new DOMRect ( relX + rect . left , relY + rect . top , width , height ) ;
509+ }
510+ }
511+ }
512+
513+ return { image : image , rect : rect || new DOMRect ( ) } ;
514+ } ;
515+
516+ const checkIsHiddenByOverflow = ( elem , style ) => {
517+ const htmlElement = elem . ownerDocument . documentElement ;
518+
519+ const getNearestOverflowAncestor = ( e , style ) => {
520+ const elementPosition = style . getPropertyValue ( 'position' ) ;
521+ const canBeOverflowed = ( container ) => {
522+ const containerStyle = getComputedStyle ( container ) ;
523+ if ( container === htmlElement ) {
524+ return true ;
525+ }
526+ const containerDisplay = containerStyle . getPropertyValue ( 'display' ) ;
527+ if ( containerDisplay . startsWith ( 'inline' ) || containerDisplay === 'contents' ) {
528+ return false ;
529+ }
530+ const containerPosition = containerStyle . getPropertyValue ( 'position' ) ;
531+ if ( elementPosition === 'absolute' && containerPosition === 'static' ) {
532+ return false ;
533+ }
534+ return true ;
535+ } ;
536+ if ( elementPosition === 'fixed' ) {
537+ return e === htmlElement ? null : htmlElement ;
538+ }
539+ let container = e . parentElement ;
540+ while ( container && ! canBeOverflowed ( container ) ) {
541+ container = container . parentElement ;
542+ }
543+ return container ;
544+ } ;
545+
546+ // Walk up the tree, examining each ancestor capable of displaying
547+ // overflow.
548+ let parentElement = getNearestOverflowAncestor ( elem , style ) ;
549+ while ( parentElement ) {
550+ const parentStyle = getComputedStyle ( parentElement ) ;
551+ const parentOverflowX = parentStyle . getPropertyValue ( 'overflow-x' ) ;
552+ const parentOverflowY = parentStyle . getPropertyValue ( 'overflow-y' ) ;
553+
554+ // If the container has overflow:visible, the element cannot be hidden in its overflow.
555+ if ( parentOverflowX !== 'visible' || parentOverflowY !== 'visible' ) {
556+ const parentRect = parentElement . getBoundingClientRect ( ) ;
557+
558+ // Zero-sized containers without overflow:visible hide all descendants.
559+ if ( parentRect . width === 0 || parentRect . height === 0 ) {
560+ return true ;
561+ }
562+
563+ const elementRect = elem . getBoundingClientRect ( ) ;
564+
565+ // Check "underflow": if an element is to the left or above the container
566+ // and overflow is "hidden" in the proper direction, the element is hidden.
567+ const isLeftOf = elementRect . x + elementRect . width < parentRect . x ;
568+ const isAbove = elementRect . y + elementRect . height < parentRect . y ;
569+ if ( ( isLeftOf && parentOverflowX === 'hidden' ) ||
570+ ( isAbove && parentOverflowY === 'hidden' ) ) {
571+ return true ;
572+ }
573+
574+ // Check "overflow": if an element is to the right or below a container
575+ // and overflow is "hidden" in the proper direction, the element is hidden.
576+ const isRightOf = elementRect . x >= parentRect . x + parentRect . width ;
577+ const isBelow = elementRect . y >= parentRect . y + parentRect . height ;
578+ if ( ( isRightOf && parentOverflowX === 'hidden' ) ||
579+ ( isBelow && parentOverflowY === 'hidden' ) ) {
580+ return true ;
581+ } else if ( ( isRightOf && parentOverflowX !== 'visible' ) ||
582+ ( isBelow && parentOverflowY !== 'visible' ) ) {
583+ // Special case for "fixed" elements: whether it is hidden by
584+ // overflow depends on the scroll position of the parent element
585+ if ( style . getPropertyValue ( 'position' ) === 'fixed' ) {
586+ const scrollPosition = parentElement . tagName === 'HTML' ?
587+ {
588+ x : parentElement . ownerDocument . scrollingElement . scrollLeft ,
589+ y : parentElement . ownerDocument . scrollingElement . scrollTop
590+ } :
591+ {
592+ x : parentElement . scrollLeft ,
593+ y : parentElement . scrollTop
594+ } ;
595+ if ( ( elementRect . x >= htmlElement . scrollWidth - scrollPosition . x ) ||
596+ ( elementRect . y >= htmlElement . scrollHeight - scrollPosition . y ) ) {
597+ return true ;
598+ }
599+ }
600+ }
601+ }
602+ parentElement = getNearestOverflowAncestor ( parentElement , parentStyle ) ;
603+ }
604+ return false ;
605+ } ;
606+
607+ if ( elem . nodeType !== Node . ELEMENT_NODE ) {
608+ throw new Error ( `Argument to isShown must be of type Element` ) ;
463609 }
464610
465- // By convention, BODY element is always shown: BODY represents the document
466- // and even if there's nothing rendered in there, user can always see there's
467- // the document.
468- if ( bot . dom . isElement ( elem , goog . dom . TagName . BODY ) ) {
611+ // The <body> element is always visible
612+ if ( elem . tagName === 'BODY' ) {
469613 return true ;
470614 }
471615
472- // Option or optgroup is shown iff enclosing select is shown (ignoring the
473- // select's opacity).
474- if ( bot . dom . isElement ( elem , goog . dom . TagName . OPTION ) ||
475- bot . dom . isElement ( elem , goog . dom . TagName . OPTGROUP ) ) {
476- var select = /**@type {Element }*/ ( goog . dom . getAncestor ( elem , function ( e ) {
477- return bot . dom . isElement ( e , goog . dom . TagName . SELECT ) ;
478- } ) ) ;
479- return ! ! select && bot . dom . isShown_ ( select , true , parentsDisplayedFn ) ;
616+ // <option> and <optgroup> elements are visible if their enclosing <select>
617+ // is visible.
618+ if ( elem instanceof HTMLOptionElement || elem instanceof HTMLOptGroupElement ) {
619+ const select = elem . closest ( 'select' ) ;
620+ if ( ! select ) {
621+ return false ;
622+ }
623+ if ( select instanceof HTMLSelectElement ) {
624+ return bot . dom . isShown_ ( select , true , parentsDisplayedFn ) ;
625+ }
480626 }
481627
482- // Image map elements are shown if image that uses it is shown, and
483- // the area of the element is positive .
484- var imageMap = bot . dom . maybeFindImageMap_ ( elem ) ;
628+ // < map> and <area> elements are visible if the images used by them are
629+ // visible .
630+ const imageMap = isMap ( elem ) ;
485631 if ( imageMap ) {
486632 return ! ! imageMap . image &&
487633 imageMap . rect . width > 0 && imageMap . rect . height > 0 &&
488634 bot . dom . isShown_ (
489635 imageMap . image , ignoreOpacity , parentsDisplayedFn ) ;
490636 }
491637
492- // Any hidden input is not shown.
493- if ( bot . dom . isElement ( elem , goog . dom . TagName . INPUT ) &&
494- elem . type . toLowerCase ( ) == 'hidden' ) {
495- return false ;
496- }
497-
498- // Any NOSCRIPT element is not shown.
499- if ( bot . dom . isElement ( elem , goog . dom . TagName . NOSCRIPT ) ) {
500- return false ;
501- }
502-
503- // Any element with hidden/collapsed visibility is not shown.
504- var visibility = bot . dom . getEffectiveStyle ( elem , 'visibility' ) ;
505- if ( visibility == 'collapse' || visibility == 'hidden' ) {
506- return false ;
507- }
638+ // Defined as a function because the Closure compiler does not understand
639+ // the checkVisibility method on Element.
640+ const checkElementVisibility = ( elem ) => {
641+ return elem . checkVisibility ( {
642+ visibilityProperty : true ,
643+ opacityProperty : ! ignoreOpacity ,
644+ contentVisibilityAuto : true
645+ } ) ;
646+ } ;
508647
509- if ( ! parentsDisplayedFn ( elem ) ) {
648+ const visibility = checkElementVisibility ( elem ) ;
649+ if ( ! visibility ) {
510650 return false ;
511651 }
512652
513- // Any transparent element is not shown.
514- if ( ! ignoreOpacity && bot . dom . getOpacity ( elem ) == 0 ) {
515- return false ;
653+ const style = getComputedStyle ( elem ) ;
654+ if ( ! style ) {
655+ return true ;
516656 }
517657
518- // Any element without positive size dimensions is not shown.
519- function positiveSize ( e ) {
520- var rect = bot . dom . getClientRect ( e ) ;
521- if ( rect . height > 0 && rect . width > 0 ) {
658+ const hasPositiveSize = ( elem , style ) => {
659+ const rect = elem . getBoundingClientRect ( ) ;
660+ if ( rect . width > 0 && rect . height > 0 ) {
522661 return true ;
523662 }
663+
524664 // A vertical or horizontal SVG Path element will report zero width or
525665 // height but is "shown" if it has a positive stroke-width.
526- if ( bot . dom . isElement ( e , 'PATH' ) && ( rect . height > 0 || rect . width > 0 ) ) {
527- var strokeWidth = bot . dom . getEffectiveStyle ( e , 'stroke-width' ) ;
666+ if ( elem . tagName . toUpperCase ( ) === 'PATH' && ( rect . height > 0 || rect . width > 0 ) ) {
667+ const strokeWidth = style . getPropertyValue ( 'stroke-width' ) ;
528668 return ! ! strokeWidth && ( parseInt ( strokeWidth , 10 ) > 0 ) ;
529669 }
670+
530671 // Zero-sized elements should still be considered to have positive size
531672 // if they have a child element or text node with positive size, unless
532673 // the element has an 'overflow' style of 'hidden'.
533- return bot . dom . getEffectiveStyle ( e , 'overflow' ) != 'hidden' &&
534- goog . array . some ( e . childNodes , function ( n ) {
535- return n . nodeType == goog . dom . NodeType . TEXT ||
536- ( bot . dom . isElement ( n ) && positiveSize ( n ) ) ;
537- } ) ;
538- }
539- if ( ! positiveSize ( elem ) ) {
674+ return ( style . getPropertyValue ( 'overflow-x' ) !== 'hidden' || style . getPropertyValue ( 'overflow-y' ) !== 'hidden' ) &&
675+ [ ...elem . childNodes ] . filter ( child => {
676+ return child . nodeType === Node . TEXT_NODE ||
677+ ( child . nodeType === Node . ELEMENT_NODE && hasPositiveSize ( child , getComputedStyle ( child ) ) ) ;
678+ } ) . length ;
679+ } ;
680+
681+ if ( ! hasPositiveSize ( elem , style ) ) {
540682 return false ;
541683 }
542684
543- // Elements that are hidden by overflow are not shown.
544- function hiddenByOverflow ( e ) {
545- return bot . dom . getOverflowState ( e ) == bot . dom . OverflowState . HIDDEN &&
546- goog . array . every ( e . childNodes , function ( n ) {
547- return ! bot . dom . isElement ( n ) || hiddenByOverflow ( n ) ||
548- ! positiveSize ( n ) ;
549- } ) ;
550- }
551- return ! hiddenByOverflow ( elem ) ;
685+ // Elements are hidden by overflow if their an ancestor container has
686+ // overflow hidden and all children are also hidden because the child node
687+ // is not an element node, zero size, or are themselves hidden by overflow.
688+ const hiddenByOverflow = ( elem , style ) => {
689+ const children = [ ...elem . childNodes ] ;
690+ return checkIsHiddenByOverflow ( elem , style ) &&
691+ children . filter ( child => {
692+ const isElement = child . nodeType === Node . ELEMENT_NODE ;
693+ const childStyle = isElement ? getComputedStyle ( child ) : null ;
694+ return ! isElement ||
695+ hiddenByOverflow ( child , childStyle ) ||
696+ ! hasPositiveSize ( child , childStyle ) ;
697+ } ) . length === children . length ;
698+ } ;
699+
700+ return ! hiddenByOverflow ( elem , style ) ;
552701} ;
553702
554703
@@ -577,16 +726,17 @@ bot.dom.isShown = function(elem, opt_ignoreOpacity) {
577726 * @return {!boolean }
578727 */
579728 function displayed ( e ) {
580- if ( bot . dom . isElement ( e ) ) {
581- var elem = /** @type {!Element } */ ( e ) ;
582- if ( bot . dom . getEffectiveStyle ( elem , 'display' ) == 'none' ) {
729+ if ( e . nodeType === Node . ELEMENT_NODE ) {
730+ const elem = /** @type {!Element } */ ( e ) ;
731+ const elemStyle = getComputedStyle ( elem ) ;
732+ if ( elemStyle && elemStyle . getPropertyValue ( 'display' ) === 'none' ) {
583733 return false ;
584734 }
585735 }
586736
587- var parent = bot . dom . getParentNodeInComposedDom ( e ) ;
737+ let parent = bot . dom . getParentNodeInComposedDom ( e ) ;
588738
589- if ( bot . dom . IS_SHADOW_DOM_ENABLED && ( parent instanceof ShadowRoot ) ) {
739+ if ( ( typeof ShadowRoot === 'function' ) && ( parent instanceof ShadowRoot ) ) {
590740 if ( parent . host . shadowRoot && parent . host . shadowRoot !== parent ) {
591741 // There is a younger shadow root, which will take precedence over
592742 // the shadow this element is in, thus this element won't be
@@ -597,15 +747,15 @@ bot.dom.isShown = function(elem, opt_ignoreOpacity) {
597747 }
598748 }
599749
600- if ( parent && ( parent . nodeType == goog . dom . NodeType . DOCUMENT ||
601- parent . nodeType == goog . dom . NodeType . DOCUMENT_FRAGMENT ) ) {
750+ if ( parent && ( parent . nodeType === Node . DOCUMENT_NODE ||
751+ parent . nodeType === Node . DOCUMENT_FRAGMENT_NODE ) ) {
602752 return true ;
603753 }
604754
605755 // Child of DETAILS element is not shown unless the DETAILS element is open
606756 // or the child is a SUMMARY element.
607- if ( parent && bot . dom . isElement ( parent , goog . dom . TagName . DETAILS ) &&
608- ! parent . open && ! bot . dom . isElement ( e , goog . dom . TagName . SUMMARY ) ) {
757+ if ( parent && parent . nodeType === Node . ELEMENT_NODE && parent . tagName . toUpperCase ( ) === 'DETAILS' &&
758+ ! parent . open && ! ( e . nodeType === Node . ELEMENT_NODE && e . tagName . toUpperCase ( ) === ' SUMMARY' ) ) {
609759 return false ;
610760 }
611761
0 commit comments