| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import sinon from 'sinon'; |
| |
| import * as SDK from '../core/sdk/sdk.js'; |
| import * as Protocol from '../generated/protocol.js'; |
| |
| import type {MockCDPConnection} from './MockCDPConnection.js'; |
| |
| type GetEnvironmentVariablesCallback = (params: unknown) => |
| Omit<Protocol.CSS.GetEnvironmentVariablesResponse, 'getError'>|{getError(): string}| |
| PromiseLike<Omit<Protocol.CSS.GetEnvironmentVariablesResponse, 'getError'>|{getError(): string}>; |
| |
| export function mockGetEnvironmentVariables(connection: MockCDPConnection, |
| environmentVariables: Record<string, string> = {}): void { |
| connection.setHandler('CSS.getEnvironmentVariables', null); |
| connection.setSuccessHandler('CSS.getEnvironmentVariables', () => ({environmentVariables})); |
| } |
| |
| export function getMatchedStylesWithStylesheet(payload: { |
| cssModel: SDK.CSSModel.CSSModel, |
| origin: Protocol.CSS.StyleSheetOrigin, |
| styleSheetId: Protocol.DOM.StyleSheetId, |
| connection: MockCDPConnection, |
| getEnvironmentVariablesCallback?: GetEnvironmentVariablesCallback, |
| }&Partial<Protocol.CSS.CSSStyleSheetHeader>&Partial<SDK.CSSMatchedStyles.CSSMatchedStylesPayload>): |
| Promise<SDK.CSSMatchedStyles.CSSMatchedStyles> { |
| payload.cssModel.styleSheetAdded({ |
| frameId: '' as Protocol.Page.FrameId, |
| sourceURL: '', |
| title: '', |
| disabled: false, |
| isInline: false, |
| isMutable: false, |
| isConstructed: false, |
| startLine: 0, |
| startColumn: 0, |
| length: 0, |
| endLine: 0, |
| endColumn: 0, |
| ...payload, |
| }); |
| return getMatchedStyles(payload, payload.getEnvironmentVariablesCallback, payload.connection); |
| } |
| |
| export function getMatchedStylesWithBlankRule(payload: { |
| cssModel: SDK.CSSModel.CSSModel, |
| connection: MockCDPConnection, |
| selector?: string, |
| range?: Protocol.CSS.SourceRange, |
| origin?: Protocol.CSS.StyleSheetOrigin, |
| styleSheetId?: Protocol.DOM.StyleSheetId, |
| getEnvironmentVariablesCallback?: GetEnvironmentVariablesCallback, |
| }&Partial<SDK.CSSMatchedStyles.CSSMatchedStylesPayload>): Promise<SDK.CSSMatchedStyles.CSSMatchedStyles> { |
| return getMatchedStylesWithProperties({properties: {}, ...payload}); |
| } |
| |
| export function createCSSStyle( |
| cssProperties: Protocol.CSS.CSSProperty[], |
| range?: Protocol.CSS.SourceRange, |
| styleSheetId = '0' as Protocol.DOM.StyleSheetId, |
| ): Protocol.CSS.CSSStyle { |
| return { |
| cssProperties, |
| styleSheetId, |
| range, |
| shorthandEntries: [], |
| }; |
| } |
| |
| function getSimpleList(selector: string): Protocol.CSS.SelectorList { |
| return { |
| selectors: [{text: selector}], |
| text: selector, |
| }; |
| } |
| |
| export function ruleMatch( |
| selectorOrList: string|Protocol.CSS.SelectorList, |
| properties: Protocol.CSS.CSSProperty[]|Record<string, string>, |
| options: { |
| range?: Protocol.CSS.SourceRange, |
| origin?: Protocol.CSS.StyleSheetOrigin, |
| styleSheetId?: Protocol.DOM.StyleSheetId, |
| /** Matches all selectors if undefined */ |
| matchingSelectorsIndexes?: number[], |
| nestingSelectors?: string[], |
| } = {}, |
| ): Protocol.CSS.RuleMatch { |
| const { |
| range, |
| origin = Protocol.CSS.StyleSheetOrigin.Regular, |
| styleSheetId, |
| matchingSelectorsIndexes, |
| nestingSelectors, |
| } = options; |
| |
| const cssProperties = |
| Array.isArray(properties) ? properties : Object.keys(properties).map(name => ({name, value: properties[name]})); |
| const selectorList = typeof selectorOrList === 'string' ? getSimpleList(selectorOrList) : selectorOrList; |
| const matchingSelectors = matchingSelectorsIndexes ?? selectorList.selectors.map((_, index) => index); |
| |
| return { |
| rule: { |
| nestingSelectors, |
| selectorList, |
| origin, |
| style: createCSSStyle(cssProperties, range, styleSheetId), |
| styleSheetId, |
| }, |
| matchingSelectors, |
| }; |
| } |
| |
| export function getMatchedStylesWithProperties(payload: { |
| cssModel: SDK.CSSModel.CSSModel, |
| properties: Protocol.CSS.CSSProperty[]|Record<string, string>, |
| connection: MockCDPConnection, |
| selector?: string, |
| range?: Protocol.CSS.SourceRange, |
| origin?: Protocol.CSS.StyleSheetOrigin, |
| styleSheetId?: Protocol.DOM.StyleSheetId, |
| getEnvironmentVariablesCallback?: GetEnvironmentVariablesCallback, |
| }&Partial<SDK.CSSMatchedStyles.CSSMatchedStylesPayload>): Promise<SDK.CSSMatchedStyles.CSSMatchedStyles> { |
| const styleSheetId = payload.styleSheetId ?? '0' as Protocol.DOM.StyleSheetId; |
| const range = payload.range; |
| const origin = payload.origin ?? Protocol.CSS.StyleSheetOrigin.Regular; |
| const matchedPayload = [ruleMatch(payload.selector ?? 'div', payload.properties, {range, origin, styleSheetId})]; |
| return getMatchedStylesWithStylesheet({styleSheetId, origin, matchedPayload, ...payload}); |
| } |
| |
| export function getMatchedStyles( |
| payload: Partial<SDK.CSSMatchedStyles.CSSMatchedStylesPayload>&{connection: MockCDPConnection}, |
| getEnvironmentVariablesCallback: GetEnvironmentVariablesCallback = () => ({environmentVariables: {}}), |
| connection: MockCDPConnection = payload.connection): Promise<SDK.CSSMatchedStyles.CSSMatchedStyles> { |
| connection.setHandler('CSS.getEnvironmentVariables', null); |
| connection.setHandler('CSS.getEnvironmentVariables', params => { |
| const result = getEnvironmentVariablesCallback(params); |
| if (result && 'then' in result) { |
| return Promise.resolve(result).then(res => { |
| if ('getError' in res && typeof res.getError === 'function' && res.getError()) { |
| return {error: {message: res.getError(), code: -32000}}; |
| } |
| return {result: res as Protocol.CSS.GetEnvironmentVariablesResponse}; |
| }); |
| } |
| if (result && 'getError' in result && typeof result.getError === 'function' && result.getError()) { |
| return {error: {message: result.getError(), code: -32000}}; |
| } |
| return {result: result as Protocol.CSS.GetEnvironmentVariablesResponse}; |
| }); |
| let node = payload.node; |
| if (!node) { |
| node = sinon.createStubInstance(SDK.DOMModel.DOMNode); |
| node.id = 1 as Protocol.DOM.NodeId; |
| } |
| |
| let cssModel = payload.cssModel; |
| if (!cssModel) { |
| cssModel = sinon.createStubInstance(SDK.CSSModel.CSSModel); |
| } |
| return SDK.CSSMatchedStyles.CSSMatchedStyles.create({ |
| cssModel, |
| node, |
| inlinePayload: null, |
| attributesPayload: null, |
| matchedPayload: [], |
| pseudoPayload: [], |
| inheritedPayload: [], |
| inheritedPseudoPayload: [], |
| animationsPayload: [], |
| parentLayoutNodeId: undefined, |
| positionTryRules: [], |
| propertyRules: [], |
| cssPropertyRegistrations: [], |
| atRules: [], |
| activePositionFallbackIndex: -1, |
| animationStylesPayload: [], |
| transitionsStylePayload: null, |
| inheritedAnimatedPayload: [], |
| functionRules: [], |
| ...payload, |
| }); |
| } |
| |
| /** |
| * For some unit tests we need a DOM Node but it has to have a "real" DOM |
| * Model and CSS Model attached because code calls those methods and expect |
| * to find the actual models. |
| */ |
| export function createStubbedDomNodeWithModels(opts: {nodeId: number} = { |
| nodeId: 1, |
| }): { |
| node: SDK.DOMModel.DOMNode, |
| domModel: SDK.DOMModel.DOMModel, |
| cssModel: SDK.CSSModel.CSSModel, |
| } { |
| const target = sinon.createStubInstance(SDK.Target.Target); |
| const cssModel = sinon.createStubInstance(SDK.CSSModel.CSSModel, { |
| target, |
| }); |
| const domModel = sinon.createStubInstance(SDK.DOMModel.DOMModel, { |
| cssModel, |
| }); |
| const node = sinon.createStubInstance(SDK.DOMModel.DOMNode, { |
| domModel, |
| }); |
| node.securityOrigin.callsFake(() => { |
| const docUrl = node.ownerDocument?.documentURL; |
| return docUrl ? SDK.SecurityOrigin.SecurityOrigin.create(docUrl) : null; |
| }); |
| node.id = opts.nodeId as Protocol.DOM.NodeId; |
| return {cssModel, domModel, node}; |
| } |