electron/spec-main/api-web-frame-main-spec.ts
electron-roller[bot] 3da598015b
chore: bump chromium to 100.0.4894.0 (main) (#32852)
* chore: bump chromium in DEPS to 100.0.4880.0

* resolve conflicts

* chore: update patches

* fix patch

* PIP20: add a new DocumentOverlayWindowViews subtype

https://chromium-review.googlesource.com/c/chromium/src/+/3252789

* Clean up PictureInPictureWindowManager::EnterPictureInPicture()

https://chromium-review.googlesource.com/c/chromium/src/+/3424145

* Remove StoragePartitionId.

https://chromium-review.googlesource.com/c/chromium/src/+/2811120

* Remove FLoC code

https://chromium-review.googlesource.com/c/chromium/src/+/3424359

* media: Make AddSupportedKeySystems() Async

https://chromium-review.googlesource.com/c/chromium/src/+/3430502

* [Extensions] Move some l10n file util methods to //extensions/browser

https://chromium-review.googlesource.com/c/chromium/src/+/3408192

* chore: IWYU

* Reland "webhid: Grant permissions for policy-allowed devices"

https://chromium-review.googlesource.com/c/chromium/src/+/3444147

* Migrate base::Value::GetList() to base::Value::GetListDeprecated(): 2/N.

https://chromium-review.googlesource.com/c/chromium/src/+/3435727
https://chromium-review.googlesource.com/c/chromium/src/+/3440910
https://chromium-review.googlesource.com/c/chromium/src/+/3440088

* [text blink period] Cache blink period instead of fetching from defaults

https://chromium-review.googlesource.com/c/chromium/src/+/3419059

* chore: update picture-in-picture.patch

https://chromium-review.googlesource.com/c/chromium/src/+/3252789

* ci: update to Xcode 13.2.1

https://chromium-review.googlesource.com/c/chromium/src/+/3437552

* chore: bump chromium in DEPS to 100.0.4882.1

* chore: update patches

* chore: bump chromium in DEPS to 100.0.4884.0

* chore: update patches

* chore: bump chromium in DEPS to 100.0.4886.0

* chore: update patches

* Refactor DownloadManager to use StoragePartitionConfig

https://chromium-review.googlesource.com/c/chromium/src/+/3222011

* Remove ToWebInputElement() in favor of new WebNode::DynamicTo<> helpers.

https://chromium-review.googlesource.com/c/chromium/src/+/3433852

* refactor: autofill to use the color pipeline

https://bugs.chromium.org/p/chromium/issues/detail?id=1249558
https://bugs.chromium.org/p/chromium/issues/detail?id=1003612

* [ProcessSingleton] Add many more trace events to cover all scenarios

https://chromium-review.googlesource.com/c/chromium/src/+/3429325

* fixup! PIP20: add a new DocumentOverlayWindowViews subtype

* chore: bump chromium in DEPS to 100.0.4888.0

* chore: update patches

* chore: update picture-in-picture.patch

* fixup! refactor: autofill to use the color pipeline

* ci: fixup fix sync

(cherry picked from commit c1e3e395465739bce5ca8e1c5ec1f5bd72b99ebd)

* chore: bump chromium in DEPS to 100.0.4889.0

* chore: update patches

* chore: fix feat_add_data_transfer_to_requestsingleinstancelock.patch

* fixup! PIP20: add a new DocumentOverlayWindowViews subtype

* Remove remaining NativeTheme::GetSystemColor() machinery.

https://chromium-review.googlesource.com/c/chromium/src/+/3421719

* ci: fetch proper esbuild for macos

* ci: fixup fetch proper esbuild for macos

* fix: failing Node.js test on outdated CurrentValueSerializerFormatVersion

* chore: bump chromium in DEPS to 100.0.4892.0

* 3460365: Set V8 fatal error callbacks during Isolate initialization

https://chromium-review.googlesource.com/c/chromium/src/+/3460365

* 3454343: PIP20: use permanent top controls

https://chromium-review.googlesource.com/c/chromium/src/+/3454343

* 3465574: Move most of GTK color mixers to ui/color/.

https://chromium-review.googlesource.com/c/chromium/src/+/3465574

* chore: fixup patch indices

* 3445327: [locales] Remove locales reference

https://chromium-review.googlesource.com/c/chromium/src/+/3445327

* 3456548: [DBB][#7] Blue border falls back to all tab if cropped-to zero pixels

https://chromium-review.googlesource.com/c/chromium/src/+/3456548

* 3441196: Convert GuestView's remaining legacy IPC messages to Mojo

https://chromium-review.googlesource.com/c/chromium/src/+/3441196

* 3455491: Don't include run_loop.h in thread_task_runner_handle.h

https://chromium-review.googlesource.com/c/chromium/src/+/3455491

* fixup! 3454343: PIP20: use permanent top controls

* 3442501: Add missing includes of //base/observer_list.h

https://chromium-review.googlesource.com/c/chromium/src/+/3442501

* 3437552: mac: Deploy a new hermetic build of Xcode 13.2.1 13C100

https://chromium-review.googlesource.com/c/chromium/src/+/3437552

* chore: bump chromium in DEPS to 100.0.4894.0

* fixup! 3460365: Set V8 fatal error callbacks during Isolate initialization

* chore: update patches

* 3425231: Use DnsOverHttpsConfig where appropriate

https://chromium-review.googlesource.com/c/chromium/src/+/3425231

* test: disable test-heapsnapshot-near-heap-limit-worker.js

As a result of CLs linked in https://bugs.chromium.org/p/v8/issues/detail?id=12503,
heap snapshotting near the heap limit DCHECKS in Node.js specs. This will
likely require a larger refactor in Node.js so i've disabled the test for
now and opened an upstream issue on node-v8 issue
at https://github.com/nodejs/node-v8/issues/218.

* Port all usage of NativeTheme color IDs to color pipeline

https://bugs.chromium.org/p/chromium/issues/detail?id=1249558

* chore: update patches after rebase

* ci: use gen2 machine for more disk space

* ci: don't try to make root volume writeable

* ci: use older xcode/macos for tests

* fix: html fullscreen transitions stacking

(cherry picked from commit 5e10965cdd7b2a024def5fc568912cefd0f05b44)

* ci: speed up woa testing

(cherry picked from commit 75c33c48b032137794f5734348a9ee3daa60d9de)
(cherry picked from commit e81996234029669663bf0daaababd34684dcbb17)

* ci: disable flaky tests on WOA

* ci: run remote tests separately to isolate issue there

* tests: disable node test parallel/test-worker-debug for now

* revert: fix: html fullscreen transitions stacking

* tests: disable flaky test on macOS arm64

* fixup circleci config so build tools can find xcode version

* make sure the workspace is clean before job runs

(cherry picked from commit 75f713c9748ac1a356846c39f268886130554fd6)

* tests: disable flaky test on Linux

* ci: debug why windows i32 is crashing

* Revert "ci: debug why windows i32 is crashing"

This reverts commit 4c4bba87ea76f16ef3b304dadff59ad4d366f60f.

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-02-25 13:17:35 -05:00

312 lines
12 KiB
TypeScript

import { expect } from 'chai';
import * as http from 'http';
import * as path from 'path';
import * as url from 'url';
import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain } from 'electron/main';
import { closeAllWindows } from './window-helpers';
import { emittedOnce, emittedNTimes } from './events-helpers';
import { AddressInfo } from 'net';
import { ifit, waitUntil } from './spec-helpers';
describe('webFrameMain module', () => {
const fixtures = path.resolve(__dirname, '..', 'spec-main', 'fixtures');
const subframesPath = path.join(fixtures, 'sub-frames');
const fileUrl = (filename: string) => url.pathToFileURL(path.join(subframesPath, filename)).href;
type Server = { server: http.Server, url: string }
/** Creates an HTTP server whose handler embeds the given iframe src. */
const createServer = () => new Promise<Server>(resolve => {
const server = http.createServer((req, res) => {
const params = new URLSearchParams(url.parse(req.url || '').search || '');
if (params.has('frameSrc')) {
res.end(`<iframe src="${params.get('frameSrc')}"></iframe>`);
} else {
res.end('');
}
});
server.listen(0, '127.0.0.1', () => {
const url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`;
resolve({ server, url });
});
});
afterEach(closeAllWindows);
describe('WebFrame traversal APIs', () => {
let w: BrowserWindow;
let webFrame: WebFrameMain;
beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
webFrame = w.webContents.mainFrame;
});
it('can access top frame', () => {
expect(webFrame.top).to.equal(webFrame);
});
it('has no parent on top frame', () => {
expect(webFrame.parent).to.be.null();
});
it('can access immediate frame descendents', () => {
const { frames } = webFrame;
expect(frames).to.have.lengthOf(1);
const subframe = frames[0];
expect(subframe).not.to.equal(webFrame);
expect(subframe.parent).to.equal(webFrame);
});
it('can access deeply nested frames', () => {
const subframe = webFrame.frames[0];
expect(subframe).not.to.equal(webFrame);
expect(subframe.parent).to.equal(webFrame);
const nestedSubframe = subframe.frames[0];
expect(nestedSubframe).not.to.equal(webFrame);
expect(nestedSubframe).not.to.equal(subframe);
expect(nestedSubframe.parent).to.equal(subframe);
});
it('can traverse all frames in root', () => {
const urls = webFrame.framesInSubtree.map(frame => frame.url);
expect(urls).to.deep.equal([
fileUrl('frame-with-frame-container.html'),
fileUrl('frame-with-frame.html'),
fileUrl('frame.html')
]);
});
it('can traverse all frames in subtree', () => {
const urls = webFrame.frames[0].framesInSubtree.map(frame => frame.url);
expect(urls).to.deep.equal([
fileUrl('frame-with-frame.html'),
fileUrl('frame.html')
]);
});
describe('cross-origin', () => {
let serverA = null as unknown as Server;
let serverB = null as unknown as Server;
before(async () => {
serverA = await createServer();
serverB = await createServer();
});
after(() => {
serverA.server.close();
serverB.server.close();
});
it('can access cross-origin frames', async () => {
await w.loadURL(`${serverA.url}?frameSrc=${serverB.url}`);
webFrame = w.webContents.mainFrame;
expect(webFrame.url.startsWith(serverA.url)).to.be.true();
expect(webFrame.frames[0].url).to.equal(serverB.url);
});
});
});
describe('WebFrame.url', () => {
it('should report correct address for each subframe', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
const webFrame = w.webContents.mainFrame;
expect(webFrame.url).to.equal(fileUrl('frame-with-frame-container.html'));
expect(webFrame.frames[0].url).to.equal(fileUrl('frame-with-frame.html'));
expect(webFrame.frames[0].frames[0].url).to.equal(fileUrl('frame.html'));
});
});
describe('WebFrame IDs', () => {
it('has properties for various identifiers', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
await w.loadFile(path.join(subframesPath, 'frame.html'));
const webFrame = w.webContents.mainFrame;
expect(webFrame).to.have.ownProperty('url').that.is.a('string');
expect(webFrame).to.have.ownProperty('frameTreeNodeId').that.is.a('number');
expect(webFrame).to.have.ownProperty('name').that.is.a('string');
expect(webFrame).to.have.ownProperty('osProcessId').that.is.a('number');
expect(webFrame).to.have.ownProperty('processId').that.is.a('number');
expect(webFrame).to.have.ownProperty('routingId').that.is.a('number');
});
});
describe('WebFrame.visibilityState', () => {
it('should match window state', async () => {
const w = new BrowserWindow({ show: true });
await w.loadURL('about:blank');
const webFrame = w.webContents.mainFrame;
expect(webFrame.visibilityState).to.equal('visible');
w.hide();
await expect(
waitUntil(() => webFrame.visibilityState === 'hidden')
).to.eventually.be.fulfilled();
});
});
describe('WebFrame.executeJavaScript', () => {
it('can inject code into any subframe', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
const webFrame = w.webContents.mainFrame;
const getUrl = (frame: WebFrameMain) => frame.executeJavaScript('location.href');
expect(await getUrl(webFrame)).to.equal(fileUrl('frame-with-frame-container.html'));
expect(await getUrl(webFrame.frames[0])).to.equal(fileUrl('frame-with-frame.html'));
expect(await getUrl(webFrame.frames[0].frames[0])).to.equal(fileUrl('frame.html'));
});
});
describe('WebFrame.reload', () => {
it('reloads a frame', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
await w.loadFile(path.join(subframesPath, 'frame.html'));
const webFrame = w.webContents.mainFrame;
await webFrame.executeJavaScript('window.TEMP = 1', false);
expect(webFrame.reload()).to.be.true();
await emittedOnce(w.webContents, 'dom-ready');
expect(await webFrame.executeJavaScript('window.TEMP', false)).to.be.null();
});
});
describe('WebFrame.send', () => {
it('works', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(subframesPath, 'preload.js'),
nodeIntegrationInSubFrames: true
}
});
await w.loadURL('about:blank');
const webFrame = w.webContents.mainFrame;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
webFrame.send('preload-ping');
const [, routingId] = await pongPromise;
expect(routingId).to.equal(webFrame.routingId);
});
});
describe('RenderFrame lifespan', () => {
let w: BrowserWindow;
beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
});
// TODO(jkleinsc) fix this flaky test on linux
ifit(process.platform !== 'linux')('throws upon accessing properties when disposed', async () => {
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
const { mainFrame } = w.webContents;
w.destroy();
// Wait for WebContents, and thus RenderFrameHost, to be destroyed.
await new Promise(resolve => setTimeout(resolve, 0));
expect(() => mainFrame.url).to.throw();
});
it('persists through cross-origin navigation', async () => {
const server = await createServer();
// 'localhost' is treated as a separate origin.
const crossOriginUrl = server.url.replace('127.0.0.1', 'localhost');
await w.loadURL(server.url);
const { mainFrame } = w.webContents;
expect(mainFrame.url).to.equal(server.url);
await w.loadURL(crossOriginUrl);
expect(w.webContents.mainFrame).to.equal(mainFrame);
expect(mainFrame.url).to.equal(crossOriginUrl);
});
});
describe('webFrameMain.fromId', () => {
it('returns undefined for unknown IDs', () => {
expect(webFrameMain.fromId(0, 0)).to.be.undefined();
});
it('can find each frame from navigation events', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
// frame-with-frame-container.html, frame-with-frame.html, frame.html
const didFrameFinishLoad = emittedNTimes(w.webContents, 'did-frame-finish-load', 3);
w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
for (const [, isMainFrame, frameProcessId, frameRoutingId] of await didFrameFinishLoad) {
const frame = webFrameMain.fromId(frameProcessId, frameRoutingId);
expect(frame).not.to.be.null();
expect(frame?.processId).to.be.equal(frameProcessId);
expect(frame?.routingId).to.be.equal(frameRoutingId);
expect(frame?.top === frame).to.be.equal(isMainFrame);
}
});
});
describe('"frame-created" event', () => {
it('emits when the main frame is created', async () => {
const w = new BrowserWindow({ show: false });
const promise = emittedOnce(w.webContents, 'frame-created');
w.webContents.loadFile(path.join(subframesPath, 'frame.html'));
const [, details] = await promise;
expect(details.frame).to.equal(w.webContents.mainFrame);
});
it('emits when nested frames are created', async () => {
const w = new BrowserWindow({ show: false });
const promise = emittedNTimes(w.webContents, 'frame-created', 2);
w.webContents.loadFile(path.join(subframesPath, 'frame-container.html'));
const [[, mainDetails], [, nestedDetails]] = await promise;
expect(mainDetails.frame).to.equal(w.webContents.mainFrame);
expect(nestedDetails.frame).to.equal(w.webContents.mainFrame.frames[0]);
});
it('is not emitted upon cross-origin navigation', async () => {
const server = await createServer();
// HACK: Use 'localhost' instead of '127.0.0.1' so Chromium treats it as
// a separate origin because differing ports aren't enough 🤔
const secondUrl = `http://localhost:${new URL(server.url).port}`;
const w = new BrowserWindow({ show: false });
await w.webContents.loadURL(server.url);
let frameCreatedEmitted = false;
w.webContents.once('frame-created', () => {
frameCreatedEmitted = true;
});
await w.webContents.loadURL(secondUrl);
expect(frameCreatedEmitted).to.be.false();
});
});
describe('"dom-ready" event', () => {
it('emits for top-level frame', async () => {
const w = new BrowserWindow({ show: false });
const promise = emittedOnce(w.webContents.mainFrame, 'dom-ready');
w.webContents.loadURL('about:blank');
await promise;
});
it('emits for sub frame', async () => {
const w = new BrowserWindow({ show: false });
const promise = new Promise<void>(resolve => {
w.webContents.on('frame-created', (e, { frame }) => {
frame.on('dom-ready', () => {
if (frame.name === 'frameA') {
resolve();
}
});
});
});
w.webContents.loadFile(path.join(subframesPath, 'frame-with-frame.html'));
await promise;
});
});
});