blob: e3e9a8a80f90d85084bc61004c0a0b082c5cbf80 [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 {it} from 'mocha';
import sinon from 'sinon';
import * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {
getCleanTextContentFromElements,
renderElementIntoDOM,
} from '../../testing/DOMHelpers.js';
import {createTarget, describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
import {html, render} from '../../ui/lit/lit.js';
import * as Application from './application.js';
describeWithEnvironment('OpenedWindowDetailsView', () => {
const targetInfo: Protocol.Target.TargetInfo = {
targetId: 'target-id' as Protocol.Target.TargetID,
type: 'page',
title: 'Example Window',
url: 'https://example.com/popup.html',
attached: true,
canAccessOpener: true,
openerFrameId: 'opener-frame-id' as Protocol.Page.FrameId,
};
it('passes window details and opener element to the view', async () => {
const domNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
domNode.nodeName.returns('IFRAME');
const openerFrame = sinon.createStubInstance(SDK.ResourceTreeModel.ResourceTreeFrame);
openerFrame.getOwnerDOMNodeOrDocument.resolves(domNode);
sinon.stub(SDK.FrameManager.FrameManager.instance(), 'getFrame')
.withArgs(targetInfo.openerFrameId!)
.returns(openerFrame);
const view = createViewFunctionStub(Application.OpenedWindowDetailsView.OpenedWindowDetailsView);
const widget = new Application.OpenedWindowDetailsView.OpenedWindowDetailsView(targetInfo, false, view);
renderElementIntoDOM(widget);
const input = await view.nextInput;
assert.strictEqual(input.title, 'Example Window');
assert.strictEqual(input.url, 'https://example.com/popup.html');
assert.isTrue(input.canAccessOpener);
assert.isNotNull(input.openerElement);
});
it('reveals opener DOM node in Elements panel on click', async () => {
const domNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
domNode.nodeName.returns('IFRAME');
const openerFrame = sinon.createStubInstance(SDK.ResourceTreeModel.ResourceTreeFrame);
openerFrame.getOwnerDOMNodeOrDocument.resolves(domNode);
sinon.stub(SDK.FrameManager.FrameManager.instance(), 'getFrame')
.withArgs(targetInfo.openerFrameId!)
.returns(openerFrame);
const revealStub = sinon.stub(Common.Revealer.RevealerRegistry.instance(), 'reveal').resolves();
const view = createViewFunctionStub(Application.OpenedWindowDetailsView.OpenedWindowDetailsView);
const widget = new Application.OpenedWindowDetailsView.OpenedWindowDetailsView(targetInfo, false, view);
renderElementIntoDOM(widget);
const input = await view.nextInput;
widget.detach();
assert.exists(input.openerElement);
const container = document.createElement('div');
renderElementIntoDOM(container);
render(input.openerElement, container);
const button = container.querySelector('button');
assert.exists(button);
button.click();
sinon.assert.calledOnceWithExactly(revealStub, domNode, false);
});
it('highlights and clears overlay highlight on opener hover', async () => {
const domNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
domNode.nodeName.returns('IFRAME');
const openerFrame = sinon.createStubInstance(SDK.ResourceTreeModel.ResourceTreeFrame);
openerFrame.getOwnerDOMNodeOrDocument.resolves(domNode);
sinon.stub(SDK.FrameManager.FrameManager.instance(), 'getFrame')
.withArgs(targetInfo.openerFrameId!)
.returns(openerFrame);
const hideHighlightStub = sinon.stub(SDK.OverlayModel.OverlayModel, 'hideDOMNodeHighlight');
const view = createViewFunctionStub(Application.OpenedWindowDetailsView.OpenedWindowDetailsView);
const widget = new Application.OpenedWindowDetailsView.OpenedWindowDetailsView(targetInfo, false, view);
renderElementIntoDOM(widget);
const input = await view.nextInput;
widget.detach();
assert.exists(input.openerElement);
const container = document.createElement('div');
renderElementIntoDOM(container);
render(input.openerElement, container);
const openerSpan = container.querySelector('span');
assert.exists(openerSpan);
openerSpan.dispatchEvent(new MouseEvent('mouseenter'));
sinon.assert.calledOnce(openerFrame.highlight);
openerSpan.dispatchEvent(new MouseEvent('mouseleave'));
sinon.assert.calledOnceWithExactly(hideHighlightStub, SDK.TargetManager.TargetManager.instance());
});
it('updates title when window is closed', async () => {
const view = createViewFunctionStub(Application.OpenedWindowDetailsView.OpenedWindowDetailsView);
const widget = new Application.OpenedWindowDetailsView.OpenedWindowDetailsView(targetInfo, false, view);
renderElementIntoDOM(widget);
await view.nextInput;
widget.setIsWindowClosed(true);
const closedInput = await view.nextInput;
assert.strictEqual(closedInput.title, 'Example Window (closed)');
});
it('renders report content in DEFAULT_OPENED_WINDOW_VIEW', () => {
const container = document.createElement('div');
renderElementIntoDOM(container);
Application.OpenedWindowDetailsView.DEFAULT_OPENED_WINDOW_VIEW({
title: 'Example Window (closed)',
url: 'https://example.com/popup.html',
canAccessOpener: false,
openerElement: html`<span>${'<#document>'}</span>`,
},
undefined, container);
const headers = getCleanTextContentFromElements(container, 'devtools-report-section-header');
assert.deepEqual(headers, ['Document', 'Security']);
const keys = getCleanTextContentFromElements(container, 'devtools-report-key');
assert.deepEqual(keys, ['URL', 'Opener frame', 'Access to opener']);
const values = getCleanTextContentFromElements(container, 'devtools-report-value');
assert.deepEqual(values, ['https://example.com/popup.html', '<#document>', 'No']);
});
});
describeWithEnvironment('WorkerDetailsView', () => {
it('passes worker details and COEP status to the view', async () => {
const target = createTarget({id: 'worker-id' as Protocol.Target.TargetID});
const networkManager = target.model(SDK.NetworkManager.NetworkManager);
assert.exists(networkManager);
const coepStatus: Protocol.Network.CrossOriginEmbedderPolicyStatus = {
value: Protocol.Network.CrossOriginEmbedderPolicyValue.None,
reportOnlyValue: Protocol.Network.CrossOriginEmbedderPolicyValue.RequireCorp,
reportingEndpoint: '',
reportOnlyReportingEndpoint: 'https://example.com/rep',
};
sinon.stub(networkManager, 'getSecurityIsolationStatus').resolves({
coep: coepStatus,
coop: {
value: Protocol.Network.CrossOriginOpenerPolicyValue.UnsafeNone,
reportOnlyValue: Protocol.Network.CrossOriginOpenerPolicyValue.UnsafeNone,
},
csp: [],
});
const targetInfo: Protocol.Target.TargetInfo = {
targetId: target.id() as Protocol.Target.TargetID,
type: 'worker',
title: '',
url: 'https://example.com/worker.js',
attached: true,
canAccessOpener: false,
};
const view = createViewFunctionStub(Application.OpenedWindowDetailsView.WorkerDetailsView);
const widget = new Application.OpenedWindowDetailsView.WorkerDetailsView(targetInfo, view);
renderElementIntoDOM(widget);
const input = await view.nextInput;
assert.strictEqual(input.title, 'https://example.com/worker.js');
assert.strictEqual(input.url, 'https://example.com/worker.js');
assert.strictEqual(input.workerType, 'Web Worker');
assert.deepEqual(input.coepPolicy, coepStatus);
});
it('renders report content in DEFAULT_WORKER_VIEW', () => {
const container = document.createElement('div');
renderElementIntoDOM(container);
Application.OpenedWindowDetailsView.DEFAULT_WORKER_VIEW({
title: 'https://example.com/worker.js',
url: 'https://example.com/worker.js',
workerType: 'Service Worker',
coepPolicy: {
value: Protocol.Network.CrossOriginEmbedderPolicyValue.None,
reportOnlyValue: Protocol.Network.CrossOriginEmbedderPolicyValue.RequireCorp,
reportOnlyReportingEndpoint: 'https://example.com/rep',
},
},
undefined, container);
const keys = getCleanTextContentFromElements(container, 'devtools-report-key');
assert.deepEqual(keys, ['URL', 'Type', 'Cross-Origin Embedder Policy']);
const values = getCleanTextContentFromElements(container, 'devtools-report-value');
assert.deepEqual(values, [
'https://example.com/worker.js',
'Service Worker',
'RequireCorpreport-onlyreporting tohttps://example.com/rep',
]);
});
});