chrome-devtools-frontend
Version:
Chrome DevTools UI
188 lines (156 loc) • 6.08 kB
text/typescript
// Copyright 2026 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 * as Protocol from '../../generated/protocol.js';
/**
* Functions in this file are serialized via `.toString()` and evaluated in the
* inspected page context over CDP (e.g. `Runtime.callFunctionOn`). Because they
* execute in the inspected browser environment, they use standard DOM types.
*/
export function scrollListenerInPage(this: HTMLElement|Document, id: number, reportScrollPositionBindingName: string,
scrollListenerNameInPage: string): void {
if ('scrollingElement' in this && !this.scrollingElement) {
return;
}
const scrollingElement = ('scrollingElement' in this ? this.scrollingElement : this) as HTMLElement;
// @ts-expect-error We're setting a custom field on `Element` or `Document` for retaining the function on the page.
this[scrollListenerNameInPage] = () => {
// @ts-expect-error `reportScrollPosition` binding is injected to the page before calling the function.
globalThis[reportScrollPositionBindingName](
JSON.stringify({scrollTop: scrollingElement.scrollTop, scrollLeft: scrollingElement.scrollLeft, id}));
};
// @ts-expect-error We've already defined the function used below.
this.addEventListener('scroll', this[scrollListenerNameInPage], true);
}
export function removeScrollListenerInPage(this: HTMLElement|Document, scrollListenerNameInPage: string): void {
// @ts-expect-error We've already set this custom field while adding scroll listener.
this.removeEventListener('scroll', this[scrollListenerNameInPage]);
// @ts-expect-error We've already set this custom field while adding scroll listener.
delete this[scrollListenerNameInPage];
}
export function scrollTopInPage(this: Element|Document): number {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return 0;
}
return this.scrollingElement.scrollTop;
}
return this.scrollTop;
}
export function scrollLeftInPage(this: Element|Document): number {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return 0;
}
return this.scrollingElement.scrollLeft;
}
return this.scrollLeft;
}
export function setScrollTopInPage(this: Element|Document, offsetInPage: number): void {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return;
}
this.scrollingElement.scrollTop = offsetInPage;
} else {
this.scrollTop = offsetInPage;
}
}
export function setScrollLeftInPage(this: Element|Document, offsetInPage: number): void {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return;
}
this.scrollingElement.scrollLeft = offsetInPage;
} else {
this.scrollLeft = offsetInPage;
}
}
export function verticalScrollRangeInPage(this: Element|Document): number {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return 0;
}
return this.scrollingElement.scrollHeight - this.scrollingElement.clientHeight;
}
return this.scrollHeight - this.clientHeight;
}
export function horizontalScrollRangeInPage(this: Element|Document): number {
if ('scrollingElement' in this) {
if (!this.scrollingElement) {
return 0;
}
return this.scrollingElement.scrollWidth - this.scrollingElement.clientWidth;
}
return this.scrollWidth - this.clientWidth;
}
export function toggleClassAndInjectStyleRule(this: Element, pseudoElementName: string|null, hidden: boolean): void {
const classNamePrefix = '__web-inspector-hide';
const classNameSuffix = '-shortcut__';
const styleTagId = '__web-inspector-hide-shortcut-style__';
const pseudoElementNameEscaped = pseudoElementName ? pseudoElementName.replace(/[\(\)\:]/g, '_') : '';
const className = classNamePrefix + pseudoElementNameEscaped + classNameSuffix;
this.classList.toggle(className, hidden);
let localRoot: Element|HTMLHeadElement = this;
while (localRoot.parentNode) {
localRoot = (localRoot.parentNode as Element);
}
if (localRoot.nodeType === Node.DOCUMENT_NODE) {
localRoot = document.head;
}
let style = localRoot.querySelector('style#' + styleTagId);
if (!style) {
const selectors = [];
selectors.push('.__web-inspector-hide-shortcut__');
selectors.push('.__web-inspector-hide-shortcut__ *');
const selector = selectors.join(', ');
const ruleBody = ' visibility: hidden !important;';
const rule = '\n' + selector + '\n{\n' + ruleBody + '\n}\n';
style = document.createElement('style');
style.id = styleTagId;
style.textContent = rule;
localRoot.appendChild(style);
}
// In addition to putting them on the element we want to hide, we will
// also add pseudo element classes to the style element to keep track of
// which pseudo elements we have style rules for.
if (pseudoElementName && !style.classList.contains(className)) {
style.classList.add(className);
style.textContent = `.${className}${pseudoElementName}, ${style.textContent}`;
}
}
export function scrollIntoViewInPage(this: Element): void {
this.scrollIntoViewIfNeeded(true);
}
export function focusInPage(this: HTMLElement): void {
this.focus();
}
export function toStringForClipboard(this: Object, data: {
subtype: string|undefined,
indent: string,
}): string|undefined {
const subtype = data.subtype;
const indent = data.indent;
if (subtype === 'node') {
return this instanceof Element ? this.outerHTML : undefined;
}
if (subtype && typeof this === 'undefined') {
return String(subtype);
}
try {
return JSON.stringify(this, null, indent);
} catch {
return String(this);
}
}
export function saveVariable(this: Window, value: Protocol.Runtime.CallArgument): string {
const prefix = 'temp';
let index = 1;
while ((prefix + index) in this) {
++index;
}
const name = prefix + index;
// @ts-expect-error Assignment to global object
this[name] = value;
return name;
}