| // 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 type {Loggable} from './Loggable.js'; |
| import {type LoggingConfig, needsLogging} from './LoggingConfig.js'; |
| |
| export interface LoggingState { |
| impressionLogged: boolean; |
| processed: boolean; |
| config: LoggingConfig; |
| veid: number; |
| parent: LoggingState|null; |
| processedForDebugging?: boolean; |
| size: DOMRect; |
| selectOpen?: boolean; |
| pendingChangeContext?: string; |
| } |
| |
| const state = new WeakMap<Loggable, LoggingState>(); |
| |
| function nextVeId(): number { |
| const result = new BigInt64Array(1); |
| crypto.getRandomValues(result); |
| return Number(result[0] >> (64n - 53n)); |
| } |
| |
| export function getOrCreateLoggingState(loggable: Loggable, config: LoggingConfig, parent?: Loggable): LoggingState { |
| if (config.parent && parentProviders.has(config.parent) && loggable instanceof Element) { |
| parent = parentProviders.get(config.parent)?.(loggable); |
| while (parent instanceof Element && !needsLogging(parent)) { |
| parent = parent.parentElementOrShadowHost() ?? undefined; |
| } |
| } |
| if (state.has(loggable)) { |
| const currentState = state.get(loggable) as LoggingState; |
| if (parent && currentState.parent !== getLoggingState(parent)) { |
| currentState.parent = getLoggingState(parent); |
| } |
| return currentState; |
| } |
| |
| const loggableState = { |
| impressionLogged: false, |
| processed: false, |
| config, |
| veid: nextVeId(), |
| parent: parent ? getLoggingState(parent) : null, |
| size: new DOMRect(0, 0, 0, 0), |
| }; |
| state.set(loggable, loggableState); |
| return loggableState; |
| } |
| |
| export function getLoggingState(loggable: Loggable): LoggingState|null { |
| return state.get(loggable) || null; |
| } |
| |
| type ParentProvider = (e: Element) => Element|undefined; |
| const parentProviders = new Map<string, ParentProvider>(); |
| |
| export function registerParentProvider(name: string, provider: ParentProvider): void { |
| if (parentProviders.has(name)) { |
| throw new Error(`Parent provider with the name '${name} is already registered'`); |
| } |
| parentProviders.set(name, provider); |
| } |
| |
| /** MUST NOT BE EXPORTED */ |
| const PARENT = Symbol('veParent'); |
| type ElementWithParent = Element&{[PARENT]?: Element}; |
| registerParentProvider('mapped', (e: ElementWithParent) => e[PARENT]); |
| |
| export function setMappedParent(element: ElementWithParent, parent: Element): void { |
| element[PARENT] = parent; |
| } |