UNPKG

chrome-devtools-frontend

Version:
204 lines (186 loc) • 8.19 kB
// Copyright 2023 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import {assertNotNullOrUndefined} from '../../core/platform/platform.js'; import {processEventForDebugging, processImpressionsForDebugging} from './Debugging.js'; import type {Loggable} from './Loggable.js'; import {getLoggingState, type LoggingState} from './LoggingState.js'; export async function logImpressions(loggables: Loggable[]): Promise<void> { const impressions = await Promise.all(loggables.map(async loggable => { const loggingState = getLoggingState(loggable); assertNotNullOrUndefined(loggingState); const impression: Host.InspectorFrontendHostAPI.VisualElementImpression = {id: loggingState.veid, type: loggingState.config.ve}; if (typeof loggingState.config.context !== 'undefined') { impression.context = await contextAsNumber(loggingState.config.context); } if (loggingState.parent) { impression.parent = loggingState.parent.veid; } if (loggingState.size) { impression.width = Math.round(loggingState.size.width); impression.height = Math.round(loggingState.size.height); } return impression; })); if (impressions.length) { Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordImpression({impressions}); processImpressionsForDebugging(loggables.map(l => getLoggingState(l) as LoggingState)); } } export const logResize = (loggable: Loggable, size: DOMRect): void => { const loggingState = getLoggingState(loggable); if (!loggingState) { return; } loggingState.size = size; const resizeEvent: Host.InspectorFrontendHostAPI .ResizeEvent = {veid: loggingState.veid, width: loggingState.size.width, height: loggingState.size.height}; Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordResize(resizeEvent); processEventForDebugging('Resize', loggingState, {width: Math.round(size.width), height: Math.round(size.height)}); }; export const logClick = (throttler: Common.Throttler.Throttler) => ( loggable: Loggable, event: Event, options?: {doubleClick?: boolean}) => { const loggingState = getLoggingState(loggable); if (!loggingState) { return; } const clickEvent: Host.InspectorFrontendHostAPI.ClickEvent = {veid: loggingState.veid, doubleClick: Boolean(options?.doubleClick)}; if (event instanceof MouseEvent && 'sourceCapabilities' in event && event.sourceCapabilities) { clickEvent.mouseButton = event.button; } void throttler.schedule(async () => { Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordClick(clickEvent); processEventForDebugging( 'Click', loggingState, {mouseButton: clickEvent.mouseButton, doubleClick: clickEvent.doubleClick}); }); }; export const logHover = (throttler: Common.Throttler.Throttler) => async (event: Event) => { const loggingState = getLoggingState(event.currentTarget as Element); assertNotNullOrUndefined(loggingState); const hoverEvent: Host.InspectorFrontendHostAPI.HoverEvent = {veid: loggingState.veid}; void throttler.schedule(async () => { Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordHover(hoverEvent); processEventForDebugging('Hover', loggingState); }, Common.Throttler.Scheduling.DELAYED); }; export const logDrag = (throttler: Common.Throttler.Throttler) => async (event: Event) => { const loggingState = getLoggingState(event.currentTarget as Element); assertNotNullOrUndefined(loggingState); const dragEvent: Host.InspectorFrontendHostAPI.DragEvent = {veid: loggingState.veid}; void throttler.schedule(async () => { Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordDrag(dragEvent); processEventForDebugging('Drag', loggingState); }, Common.Throttler.Scheduling.DELAYED); }; export async function logChange(loggable: Loggable): Promise<void> { const loggingState = getLoggingState(loggable); assertNotNullOrUndefined(loggingState); const changeEvent: Host.InspectorFrontendHostAPI.ChangeEvent = {veid: loggingState.veid}; const context = loggingState.pendingChangeContext; if (context) { changeEvent.context = await contextAsNumber(context); } Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordChange(changeEvent); processEventForDebugging('Change', loggingState, {context}); } let pendingKeyDownContext: string|null = null; export const logKeyDown = (throttler: Common.Throttler.Throttler) => async (loggable: Loggable|null, event: Event|null, context?: string) => { if (!(event instanceof KeyboardEvent)) { return; } const loggingState = loggable ? getLoggingState(loggable) : null; const codes = (typeof loggingState?.config.track?.keydown === 'string') ? loggingState.config.track.keydown : ''; if (codes.length && !codes.split('|').includes(event.code) && !codes.split('|').includes(event.key)) { return; } const keyDownEvent: Host.InspectorFrontendHostAPI.KeyDownEvent = {veid: loggingState?.veid}; if (!context && codes?.length) { context = contextFromKeyCodes(event); } if (pendingKeyDownContext && context && pendingKeyDownContext !== context) { void throttler.process?.(); } pendingKeyDownContext = context || null; void throttler.schedule(async () => { if (context) { keyDownEvent.context = await contextAsNumber(context); } Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordKeyDown(keyDownEvent); processEventForDebugging('KeyDown', loggingState, {context}); pendingKeyDownContext = null; }); }; function contextFromKeyCodes(event: Event): string|undefined { if (!(event instanceof KeyboardEvent)) { return undefined; } const key = event.key; const lowerCaseKey = key.toLowerCase(); const components = []; if (event.shiftKey && key !== lowerCaseKey) { components.push('shift'); } if (event.ctrlKey) { components.push('ctrl'); } if (event.altKey) { components.push('alt'); } if (event.metaKey) { components.push('meta'); } components.push(lowerCaseKey); return components.join('-'); } export async function contextAsNumber(context: string|undefined): Promise<number|undefined> { if (typeof context === 'undefined') { return undefined; } const number = parseInt(context, 10); if (!isNaN(number)) { return number; } if (!crypto.subtle) { // Layout tests run in an insecure context where crypto.subtle is not available. return 0xDEADBEEF; } const encoder = new TextEncoder(); const data = encoder.encode(context); const digest = await crypto.subtle.digest('SHA-1', data); return new DataView(digest).getInt32(0, true); } export async function logSettingAccess(name: string, value: number|string|boolean): Promise<void> { let numericValue: number|undefined = undefined; let stringValue: string|undefined = undefined; if (typeof value === 'string') { stringValue = value; } else if (typeof value === 'number' || typeof value === 'boolean') { numericValue = Number(value); } const nameHash = await contextAsNumber(name); if (!nameHash) { return; } const settingAccessEvent: Host.InspectorFrontendHostAPI.SettingAccessEvent = { name: nameHash, numeric_value: numericValue, string_value: await contextAsNumber(stringValue), }; Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordSettingAccess(settingAccessEvent); processEventForDebugging('SettingAccess', null, {name, numericValue, stringValue}); } export async function logFunctionCall(name: string, context?: string): Promise<void> { const nameHash = await contextAsNumber(name); if (typeof nameHash === 'undefined') { return; } const functionCallEvent: Host.InspectorFrontendHostAPI.FunctionCallEvent = {name: nameHash, context: await contextAsNumber(context)}; Host.InspectorFrontendHost.InspectorFrontendHostInstance.recordFunctionCall(functionCallEvent); processEventForDebugging('FunctionCall', null, {name, context}); }