| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {needsLogging} from './LoggingConfig.js'; |
| |
| interface ElementWithParent { |
| element: Element; |
| parent?: Element; |
| slot?: Element; |
| } |
| |
| export function getDomState(documents: Document[]): {loggables: ElementWithParent[], shadowRoots: ShadowRoot[]} { |
| const loggables: ElementWithParent[] = []; |
| const shadowRoots: ShadowRoot[] = []; |
| const queue: ElementWithParent[] = []; |
| const enqueue = (children: HTMLCollection|Element[], parent?: Element): void => { |
| for (const child of children) { |
| queue.push({element: child, parent}); |
| } |
| }; |
| for (const document of documents) { |
| enqueue(document.body.children); |
| } |
| |
| let head = 0; |
| const dequeue = (): ElementWithParent => queue[head++]; |
| while (true) { |
| const top = dequeue(); |
| if (!top) { |
| break; |
| } |
| const {element} = top; |
| if (element.localName === 'template') { |
| continue; |
| } |
| let {parent} = top; |
| if (needsLogging(element)) { |
| loggables.push({element, parent}); |
| parent = element; |
| } |
| if (element.localName === 'slot' && (element as HTMLSlotElement).assignedElements().length) { |
| enqueue((element as HTMLSlotElement).assignedElements(), parent); |
| } else if (element.shadowRoot) { |
| shadowRoots.push(element.shadowRoot); |
| enqueue(element.shadowRoot.children, parent); |
| } else { |
| enqueue(element.children, parent); |
| } |
| } |
| return {loggables, shadowRoots}; |
| } |
| |
| const MIN_ELEMENT_SIZE_FOR_IMPRESSIONS = 10; |
| |
| export function visibleOverlap(element: Element, viewportRect: DOMRect): DOMRect|null { |
| const elementRect = element.getBoundingClientRect(); |
| const overlap = intersection(viewportRect, elementRect); |
| |
| const sizeThreshold = Math.max(Math.min(MIN_ELEMENT_SIZE_FOR_IMPRESSIONS, elementRect.width, elementRect.height), 1); |
| |
| if (!overlap || overlap.width < sizeThreshold || overlap.height < sizeThreshold) { |
| return null; |
| } |
| return overlap; |
| } |
| |
| function intersection(a: DOMRect, b: DOMRect): DOMRect|null { |
| const x0 = Math.max(a.left, b.left); |
| const x1 = Math.min(a.left + a.width, b.left + b.width); |
| |
| if (x0 <= x1) { |
| const y0 = Math.max(a.top, b.top); |
| const y1 = Math.min(a.top + a.height, b.top + b.height); |
| |
| if (y0 <= y1) { |
| return new DOMRect(x0, y0, x1 - x0, y1 - y0); |
| } |
| } |
| return null; |
| } |