blob: 4563499edb85257d6c1a5d92018780e803bc66b9 [file]
// 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 {assert} from 'chai';
import {describe, it} from 'mocha';
import sinon from 'sinon';
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as Platform from '../../core/platform/platform.js';
import * as TextUtils from '../../core/text_utils/text_utils.js';
import {assertScreenshot, doubleRaf, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
import type * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Network from './network.js';
const {urlString} = Platform.DevToolsPath;
async function waitForViewToSettle(view: Network.BinaryResourceView.BinaryResourceView): Promise<void> {
await doubleRaf();
const viewer = view.element.querySelector('.viewer-widget');
if (viewer) {
let prevHeight = -1;
while (viewer.clientHeight !== prevHeight) {
prevHeight = viewer.clientHeight;
await doubleRaf();
}
}
}
describeWithEnvironment('BinaryResourceView', () => {
it('renders the hex view correctly', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view, {width: 400, height: 400, includeCommonStyles: true});
await waitForViewToSettle(view);
await assertScreenshot('network/binary_resource_view_hex.png');
view.detach();
});
it('renders the base64 view correctly', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view, {width: 400, height: 400, includeCommonStyles: true});
await waitForViewToSettle(view);
const combobox = view.element.querySelector('select');
assert.isOk(combobox);
combobox.value = 'base64';
combobox.dispatchEvent(new Event('change'));
await doubleRaf();
await assertScreenshot('network/binary_resource_view_base64.png');
view.detach();
});
it('renders the utf8 view correctly', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view, {width: 400, height: 400, includeCommonStyles: true});
await waitForViewToSettle(view);
const combobox = view.element.querySelector('select');
assert.isOk(combobox);
combobox.value = 'utf8';
combobox.dispatchEvent(new Event('change'));
await doubleRaf();
await assertScreenshot('network/binary_resource_view_utf8.png');
view.detach();
});
it('renders the default hex view', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view);
await waitForViewToSettle(view);
const combobox = view.element.querySelector('select');
assert.isNotNull(combobox);
assert.strictEqual(combobox?.value, 'hex');
view.detach();
});
it('updates state when dropdown changes', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view);
await waitForViewToSettle(view);
const combobox = view.element.querySelector('select');
assert.isOk(combobox);
combobox.value = 'utf8';
combobox.dispatchEvent(new Event('change'));
assert.strictEqual(Common.Settings.Settings.instance().createSetting('binary-view-type', 'hex').get(), 'utf8');
view.detach();
});
it('copies to clipboard on copy button click', async () => {
const base64content = btoa('hello world');
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
renderElementIntoDOM(view);
await waitForViewToSettle(view);
let copiedText = '';
const copyTextStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'copyText')
.callsFake((text?: string|null) => {
copiedText = text || '';
});
const copyButton = view.element.querySelector('devtools-button[title="Copy to clipboard"]') as HTMLElement;
assert.isNotNull(copyButton);
copyButton.click();
sinon.assert.calledOnce(copyTextStub);
// hex string of 'hello world'
const expectedHex = '68656c6c6f20776f726c64';
assert.strictEqual(copiedText, expectedHex);
view.detach();
});
describe('with a stubbed view', () => {
async function createView(viewOutput?: Network.BinaryResourceView.ViewOutput) {
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(btoa('hello world'), true, 'application/octet-stream'));
const view = createViewFunctionStub(Network.BinaryResourceView.BinaryResourceView, viewOutput);
const widget = new Network.BinaryResourceView.BinaryResourceView(
contentData, urlString`http://example.com`, Common.ResourceType.resourceTypes.XHR, undefined, view);
widget.markAsRoot();
renderElementIntoDOM(widget);
await view.nextInput;
return {view, widget};
}
it('shows the copied message and fades it out after a delay', async () => {
const clock = sinon.useFakeTimers({toFake: ['setTimeout', 'clearTimeout']});
try {
const {view, widget} = await createView();
sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'copyText');
assert.strictEqual(view.input.copiedText, '');
view.input.copySelectedViewToClipboard();
let input = await view.nextInput;
assert.strictEqual(input.copiedText, 'Copied as Hex');
assert.isFalse(input.copiedTextFadeout);
clock.tick(2000);
input = await view.nextInput;
assert.strictEqual(input.copiedText, 'Copied as Hex');
assert.isTrue(input.copiedTextFadeout);
widget.detach();
} finally {
clock.restore();
}
});
it('carries the reading position of the active view over to the new view type', async () => {
const activeView = {getPositionPercentage: () => 0.42} as unknown as
SourceFrame.StreamingContentHexView.StreamingContentHexView;
const {view, widget} = await createView({activeView});
assert.strictEqual(view.input.binaryViewType, 'hex');
assert.strictEqual(view.input.activePositionPercentage, 0);
view.input.binaryViewTypeChanged({target: {value: 'base64'}} as unknown as Event);
const input = await view.nextInput;
assert.strictEqual(input.binaryViewType, 'base64');
assert.strictEqual(input.activePositionPercentage, 0.42);
widget.detach();
});
});
});
describeWithEnvironment('BinaryResourceView Position Syncing', () => {
it('synchronizes cursor position percentage between views', async () => {
// We need more content so that a 50% scroll is meaningful and document length > 0
const base64content = btoa('hello world'.repeat(100));
const contentData = TextUtils.StreamingContentData.StreamingContentData.from(
new TextUtils.ContentData.ContentData(base64content, true, 'application/octet-stream'));
const view = new Network.BinaryResourceView.BinaryResourceView(
contentData,
urlString`http://example.com`,
Common.ResourceType.resourceTypes.XHR,
);
// Needed to instantiate CodeMirror for the base64 view
renderElementIntoDOM(view, {width: 800, height: 600});
await waitForViewToSettle(view);
const currentViewWidget = view.element.querySelector('devtools-widget');
assert.isOk(currentViewWidget);
const oldWidget = UI.Widget.Widget.get(currentViewWidget) as unknown as {setPositionPercentage(p: number): void};
// Fake the position percentage
oldWidget.setPositionPercentage(0.42);
const combobox = view.element.querySelector('select');
assert.isOk(combobox);
combobox.value = 'base64';
combobox.dispatchEvent(new Event('change'));
await doubleRaf();
const newViewWidget = view.element.querySelector('devtools-widget');
assert.isOk(newViewWidget);
assert.notStrictEqual(currentViewWidget, newViewWidget, 'A new devtools-widget should have been rendered');
const newWidget = UI.Widget.Widget.get(newViewWidget) as unknown as {getPositionPercentage(): number};
const newPercentage = newWidget.getPositionPercentage();
// Due to character rounding on lines, it might not be exactly 0.42. Just check if it's close.
assert.closeTo(newPercentage, 0.42, 0.05);
view.detach();
});
});