import { Cache } from "@siteimprove/alfa-cache" ; import { Device } from "@siteimprove/alfa-device" ; import { Graph } from "@siteimprove/alfa-graph" ; import { Serializable } from "@siteimprove/alfa-json" ; import { Lazy } from "@siteimprove/alfa-lazy" ; import { Map } from "@siteimprove/alfa-map" ; import { None , Option } from "@siteimprove/alfa-option" ; import { Predicate } from "@siteimprove/alfa-predicate" ; import { Refinement } from "@siteimprove/alfa-refinement" ; import { Sequence } from "@siteimprove/alfa-sequence" ; import { Set } from "@siteimprove/alfa-set" ; import { Style } from "@siteimprove/alfa-style" ; import * as dom from "@siteimprove/alfa-dom" ; import * as json from "@siteimprove/alfa-json" ; import { Attribute } from "./attribute" ; import { Name } from "./name" ; import { Role } from "./role" ; import { Feature } from "./feature" ; import { Container , Element , Inert , Text } from "." ; import * as predicate from "./node/predicate" ; const { equals } = Predicate ; /**
 *  https public export abstract class Node implements Serializable < Node . JSON > { protected readonly _node : dom . Node ; protected readonly _children : Array < Node > ; protected _parent : Option < Node > = None ; remarks protected _frozen : boolean = false ; protected constructor ( owner , children ) { this . _node = owner ; this . _children = children . map ( ( child ) => ( child . _frozen ? child . clone ( ) : child ) ) . filter ( ( child ) => child . _attachParent ( this ) ) ; } public get node ( ) : dom . Node { return this . _node ; } public get name ( ) : Option < Name > { return None ; } public get role ( ) : Option < Role > { return None ; } public get frozen ( ) : boolean { return this . _frozen ; } /**
     * Freeze the node. This prevents further expansion of the node hierarchy,
     * meaning that the node can no longer be passed as a child to a parent node.
     */ public freeze ( ) { this . _frozen = true ; return this ; } public attribute < N extends Attribute . Name > ( refinement ) ; public attribute ( predicate ) ; public attribute < N extends Attribute . Name > ( name ) ; public attribute ( predicate ) { return None ; } /**
     *  https public parent ( options = { } ) { const parent = this . _parent ; if ( options . ignored === true ) { return parent ; } return parent . flatMap ( ( parent ) => parent . isIgnored ( ) ? parent . parent ( options ) : Option . of ( parent ) ) ; } /**
     *  https public root ( options = { } ) { for ( const parent of this . parent ( options ) ) { return parent . root ( options ) ; } return this ; } /**
     *  https public children ( options = { } ) { const children = Sequence . from ( this . _children ) ; if ( options . ignored === true ) { return children ; } return children . flatMap ( ( child ) => child . isIgnored ( ) ? child . children ( options ) : Sequence . of ( child ) ) ; } /**
     *  https public descendants ( options = { } ) { return this . children ( options ) . flatMap ( ( child ) => Sequence . of ( child , Lazy . of ( ( ) => child . descendants ( options ) ) ) ) ; } /**
     *  https public inclusiveDescendants ( options = { } ) { return Sequence . of ( this , Lazy . of ( ( ) => this . descendants ( options ) ) ) ; } /**
     *  https public ancestors ( options = { } ) { return this . parent ( options ) . map ( ( parent ) => Sequence . of ( parent , Lazy . of ( ( ) => parent . ancestors ( options ) ) ) ) . getOrElse ( ( ) => Sequence . empty ( ) ) ; } /**
     *  https public inclusiveAncestors ( options = { } ) { return Sequence . of ( this , Lazy . of ( ( ) => this . ancestors ( options ) ) ) ; } public abstract clone ( parent ? ) ; public abstract isIgnored ( ) ; public abstract toJSON ( ) ; internal public _attachParent ( parent ) { if ( this . _frozen || this . _parent . isSome ( ) ) { return false ; } this . _parent = Option . of ( parent ) ; this . _frozen = true ; return true ; } } public export namespace Node { export interface JSON { [ key ] : json . JSON ; type : string ; node : string ; children : Array < JSON > ; } export interface Traversal { /**
         * When `true`, traverse both exposed and ignored nodes.
         */ readonly ignored ? : boolean ; } const cache = Cache . empty < Device , Cache < dom . Node , Node > > ( ) ; export function from ( node , device ) { const _cache = cache . get ( device , Cache . empty ) ; if ( _cache . has ( node ) ) { return _cache . get ( node ) . get ( ) ; } const root = node . root ( { flattened : true } ) ; if ( _cache . has ( root ) ) { return _cache . get ( node , ( ) => Inert . of ( node ) ) ; } const elements = root . inclusiveDescendants ( ) . filter ( dom . Element . isElement ) ; const ids = Map . from ( elements . collect ( ( element ) => element . id . map ( ( id ) => [ id , element ] as const ) ) . reverse ( ) ) ; const references = elements . collect ( ( element ) => element . attribute ( "aria-owns" ) . map ( ( attribute ) => [ element , attribute . tokens ( ) . collect ( ( id ) => ids . get ( id ) ) . reject ( ( reference ) => element === reference || element . ancestors ( ) . includes ( reference ) ) , ] as const ) ) ; const [ claimed , owned ] = references . reduce ( ( [ claimed , owned , graph ] , [ element , references ] ) => { references = references . reject ( ( reference ) => claimed . has ( reference ) || graph . hasPath ( reference , element ) ) ; if ( references . isEmpty ( ) ) { return [ claimed , owned , graph ] ; } claimed = references . reduce ( ( claimed , reference ) => claimed . add ( reference ) , claimed ) ; graph = references . reduce ( ( graph , reference ) => graph . connect ( element , reference ) , graph ) ; return [ claimed , owned . set ( element , references ) , graph ] ; } , [ Set . empty < dom . Element > ( ) , Map . empty < dom . Element , Sequence < dom . Element > > ( ) , Graph . empty < dom . Element > ( ) , ] ) ; fromNode ( root , device , claimed , owned , State . empty ( ) ) ; return _cache . get ( node , ( ) => Inert . of ( node ) ) ; } class State { private static _empty = new State ( false , true ) ; public static empty ( ) { return this . _empty ; } private readonly _isPresentational : boolean ; private readonly _isVisible : boolean ; private constructor ( isPresentational , isVisible ) { this . _isPresentational = isPresentational ; this . _isVisible = isVisible ; } public get isPresentational ( ) : boolean { return this . _isPresentational ; } public get isVisible ( ) : boolean { return this . _isVisible ; } public presentational ( isPresentational ) { if ( this . _isPresentational === isPresentational ) { return this ; } return new State ( isPresentational , this . _isVisible ) ; } public visible ( isVisible ) { if ( this . _isVisible === isVisible ) { return this ; } return new State ( this . _isPresentational , isVisible ) ; } } function fromNode ( node , device , claimed , owned , state ) { return cache . get ( device , Cache . empty ) . get ( node , ( ) => { if ( dom . Element . isElement ( node ) ) { if ( node . attribute ( "aria-hidden" ) . some ( ( attribute ) => attribute . enumerate ( "true" , "false" ) . some ( equals ( "true" ) ) ) ) { return Inert . of ( node ) ; } const style = Style . from ( node , device ) ; if ( style . computed ( "display" ) . some ( ( { values : [ outside ] } ) => outside . value === "none" ) ) { return Inert . of ( node ) ; } let children ; if ( node . name === "iframe" ) { children = ( ) => [ ] ; } else { const explicit = owned . get ( node ) . getOrElse ( ( ) => Sequence . empty < dom . Node > ( ) ) ; const implicit = node . children ( { flattened : true , } ) . reject ( ( child ) => claimed . has ( child ) || explicit . includes ( child ) ) ; children = ( state ) => implicit . concat ( explicit ) . map ( ( child ) => fromNode ( child , device , claimed , owned , state ) ) ; } if ( style . computed ( "visibility" ) . value . value !== "visible" ) { return Container . of ( node , children ( state . visible ( false ) ) ) ; } state = state . visible ( true ) ; const role = Role . fromExplicit ( node ) . orElse ( ( ) => state . isPresentational ? Option . of ( Role . of ( "presentation" ) ) : Role . fromImplicit ( node ) ) ; if ( role . some ( ( role ) => role . isPresentational ( ) ) ) { return Container . of ( node , children ( state . presentational ( Role . fromImplicit ( node ) . some ( ( role ) => role . hasRequiredChildren ( ) ) ) ) ) ; } let attributes = Map . empty < Attribute . Name , Attribute > ( ) ; if ( role . isSome ( ) ) { for ( const attribute of role . get ( ) . attributes ) { for ( const value of role . get ( ) . implicitAttributeValue ( attribute ) ) { attributes = attributes . set ( attribute , Attribute . of ( attribute , value ) ) ; } } } for ( const namespace of node . namespace ) { for ( const feature of Feature . from ( namespace , node . name ) ) { for ( const attribute of feature . attributes ( node ) ) { attributes = attributes . set ( attribute . name , attribute ) ; } } } for ( const { name , value } of node . attributes ) { if ( Attribute . isName ( name ) ) { const attribute = Attribute . of ( name , value ) ; if ( attribute . isGlobal ( ) || role . some ( ( role ) => role . isAttributeSupported ( attribute . name ) ) ) { attributes = attributes . set ( name , attribute ) ; } } } if ( attributes . isEmpty ( ) && role . isNone ( ) && node . tabIndex ( ) . isNone ( ) ) { return Container . of ( node , children ( state ) ) ; } if ( role . some ( ( role ) => role . hasPresentationalChildren ( ) ) ) { state = state . presentational ( true ) ; } return Element . of ( node , role , Name . from ( node , device ) , attributes . values ( ) , children ( state ) ) ; } if ( dom . Text . isText ( node ) ) { if ( ! state . isVisible ) { return Inert . of ( node ) ; } return Text . of ( node , Name . from ( node , device ) ) ; } return Container . of ( node , node . children ( { flattened : true , } ) . reject ( ( child ) => claimed . has ( child ) ) . map ( ( child ) => fromNode ( child , device , claimed , owned , state ) ) ) ; } ) ; } export const { hasName , hasRole } = predicate ; }