blob: 835664d1e9455b67aefb7f568b73a5c141b5e200 [file] [edit]
// 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};
}