From 02401797b87bd6ca844e65941cfac31268e0c0c8 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Wed, 24 Aug 2022 15:31:13 -0700 Subject: [PATCH 1/4] feat: webFrameMain.origin --- docs/api/web-frame-main.md | 7 +++++++ shell/browser/api/electron_api_web_frame_main.cc | 7 +++++++ shell/browser/api/electron_api_web_frame_main.h | 1 + 3 files changed, 15 insertions(+) diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md index e97041668e15b..c78f08b2c32c0 100644 --- a/docs/api/web-frame-main.md +++ b/docs/api/web-frame-main.md @@ -144,6 +144,13 @@ ipcRenderer.on('port', (e, msg) => { A `string` representing the current URL of the frame. +#### `frame.origin` _Readonly_ + +A `string` representing the current origin of the frame. This may be different +from the URL. For instance, if the frame is a child window opened to +`about:blank`, then `frame.origin` will return the parent frame's origin, while +`frame.url` will return the empty string. + #### `frame.top` _Readonly_ A `WebFrameMain | null` representing top frame in the frame hierarchy to which `frame` diff --git a/shell/browser/api/electron_api_web_frame_main.cc b/shell/browser/api/electron_api_web_frame_main.cc index 62513979fae2c..c3f04dfe01749 100644 --- a/shell/browser/api/electron_api_web_frame_main.cc +++ b/shell/browser/api/electron_api_web_frame_main.cc @@ -298,6 +298,12 @@ GURL WebFrameMain::URL() const { return render_frame_->GetLastCommittedURL(); } +std::string WebFrameMain::Origin() const { + if (!CheckRenderFrame()) + return std::string(); + return render_frame_->GetLastCommittedOrigin().Serialize(); +} + blink::mojom::PageVisibilityState WebFrameMain::VisibilityState() const { if (!CheckRenderFrame()) return blink::mojom::PageVisibilityState::kHidden; @@ -387,6 +393,7 @@ v8::Local WebFrameMain::FillObjectTemplate( .SetProperty("processId", &WebFrameMain::ProcessID) .SetProperty("routingId", &WebFrameMain::RoutingID) .SetProperty("url", &WebFrameMain::URL) + .SetProperty("origin", &WebFrameMain::Origin) .SetProperty("visibilityState", &WebFrameMain::VisibilityState) .SetProperty("top", &WebFrameMain::Top) .SetProperty("parent", &WebFrameMain::Parent) diff --git a/shell/browser/api/electron_api_web_frame_main.h b/shell/browser/api/electron_api_web_frame_main.h index b45264db59627..4b224b464a2d4 100644 --- a/shell/browser/api/electron_api_web_frame_main.h +++ b/shell/browser/api/electron_api_web_frame_main.h @@ -108,6 +108,7 @@ class WebFrameMain : public gin::Wrappable, int ProcessID() const; int RoutingID() const; GURL URL() const; + std::string Origin() const; blink::mojom::PageVisibilityState VisibilityState() const; content::RenderFrameHost* Top() const; From 8816c6dc0b384aceaa00e063981831f2f5cc4040 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Wed, 24 Aug 2022 16:16:15 -0700 Subject: [PATCH 2/4] add tests --- spec-main/api-web-frame-main-spec.ts | 78 +++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/spec-main/api-web-frame-main-spec.ts b/spec-main/api-web-frame-main-spec.ts index 1bc812832fad5..3a294c1ecd5d3 100644 --- a/spec-main/api-web-frame-main-spec.ts +++ b/spec-main/api-web-frame-main-spec.ts @@ -2,11 +2,11 @@ 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 { BrowserWindow, WebFrameMain, webFrameMain, ipcMain, app, WebContents } from 'electron/main'; import { closeAllWindows } from './window-helpers'; import { emittedOnce, emittedNTimes } from './events-helpers'; import { AddressInfo } from 'net'; -import { ifit, waitUntil } from './spec-helpers'; +import { defer, ifit, waitUntil } from './spec-helpers'; describe('webFrameMain module', () => { const fixtures = path.resolve(__dirname, '..', 'spec-main', 'fixtures'); @@ -39,7 +39,7 @@ describe('webFrameMain module', () => { let webFrame: WebFrameMain; beforeEach(async () => { - w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html')); webFrame = w.webContents.mainFrame; }); @@ -88,8 +88,8 @@ describe('webFrameMain module', () => { }); describe('cross-origin', () => { - let serverA = null as unknown as Server; - let serverB = null as unknown as Server; + let serverA: Server; + let serverB: Server; before(async () => { serverA = await createServer(); @@ -112,7 +112,7 @@ describe('webFrameMain module', () => { describe('WebFrame.url', () => { it('should report correct address for each subframe', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html')); const webFrame = w.webContents.mainFrame; @@ -122,9 +122,59 @@ describe('webFrameMain module', () => { }); }); + describe('WebFrame.origin', () => { + it('should be null for a fresh WebContents', () => { + const w = new BrowserWindow({ show: false }); + expect(w.webContents.mainFrame.origin).to.equal('null'); + }); + + it('should be file:// for file frames', async () => { + const w = new BrowserWindow({ show: false }); + await w.loadFile(path.join(fixtures, 'pages', 'blank.html')); + expect(w.webContents.mainFrame.origin).to.equal('file://'); + }); + + it('should be http:// for an http frame', async () => { + const w = new BrowserWindow({ show: false }); + const s = await createServer(); + defer(() => s.server.close()); + await w.loadURL(s.url); + expect(w.webContents.mainFrame.origin).to.equal(s.url.replace(/\/$/, '')); + }); + + it('should show parent origin when child page is about:blank', async () => { + const w = new BrowserWindow({ show: false }); + await w.loadFile(path.join(fixtures, 'pages', 'blank.html')); + const webContentsCreated: Promise<[unknown, WebContents]> = emittedOnce(app, 'web-contents-created') as any; + expect(w.webContents.mainFrame.origin).to.equal('file://'); + await w.webContents.executeJavaScript('window.open("", null, "show=false"), null'); + const [, childWebContents] = await webContentsCreated; + expect(childWebContents.mainFrame.origin).to.equal('file://'); + }); + + it('should show parent frame\'s origin when about:blank child window opened through cross-origin subframe', async () => { + const w = new BrowserWindow({ show: false }); + const serverA = await createServer(); + const serverB = await createServer(); + defer(() => { + serverA.server.close(); + serverB.server.close(); + }); + await w.loadURL(serverA.url + '?frameSrc=' + encodeURIComponent(serverB.url)); + const { mainFrame } = w.webContents; + expect(mainFrame.origin).to.equal(serverA.url.replace(/\/$/, '')); + const [childFrame] = mainFrame.frames; + expect(childFrame.origin).to.equal(serverB.url.replace(/\/$/, '')); + const webContentsCreated: Promise<[unknown, WebContents]> = emittedOnce(app, 'web-contents-created') as any; + await childFrame.executeJavaScript('window.open("", null, "show=false"), null'); + const [, childWebContents] = await webContentsCreated; + expect(childWebContents.mainFrame.origin).to.equal(childFrame.origin); + }); + }); + describe('WebFrame IDs', () => { it('has properties for various identifiers', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame.html')); const webFrame = w.webContents.mainFrame; expect(webFrame).to.have.ownProperty('url').that.is.a('string'); @@ -154,7 +204,7 @@ describe('webFrameMain module', () => { describe('WebFrame.executeJavaScript', () => { it('can inject code into any subframe', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html')); const webFrame = w.webContents.mainFrame; @@ -165,7 +215,7 @@ describe('webFrameMain module', () => { }); it('can resolve promise', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame.html')); const webFrame = w.webContents.mainFrame; const p = () => webFrame.executeJavaScript('new Promise(resolve => setTimeout(resolve(42), 2000));'); @@ -174,7 +224,7 @@ describe('webFrameMain module', () => { }); it('can reject with error', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame.html')); const webFrame = w.webContents.mainFrame; const p = () => webFrame.executeJavaScript('new Promise((r,e) => setTimeout(e("error!"), 500));'); @@ -195,7 +245,7 @@ describe('webFrameMain module', () => { }); it('can reject when script execution fails', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame.html')); const webFrame = w.webContents.mainFrame; const p = () => webFrame.executeJavaScript('console.log(test)'); @@ -205,7 +255,7 @@ describe('webFrameMain module', () => { describe('WebFrame.reload', () => { it('reloads a frame', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); await w.loadFile(path.join(subframesPath, 'frame.html')); const webFrame = w.webContents.mainFrame; @@ -238,7 +288,7 @@ describe('webFrameMain module', () => { let w: BrowserWindow; beforeEach(async () => { - w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + w = new BrowserWindow({ show: false }); }); // TODO(jkleinsc) fix this flaky test on linux @@ -301,7 +351,7 @@ describe('webFrameMain module', () => { }); it('can find each frame from navigation events', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + const w = new BrowserWindow({ show: false }); // frame-with-frame-container.html, frame-with-frame.html, frame.html const didFrameFinishLoad = emittedNTimes(w.webContents, 'did-frame-finish-load', 3); From baa55cb1e243b55d0d84cb0da16a1c1720d208f0 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Thu, 25 Aug 2022 13:26:29 -0700 Subject: [PATCH 3/4] update docs --- docs/api/web-frame-main.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md index c78f08b2c32c0..e6e7e77745f50 100644 --- a/docs/api/web-frame-main.md +++ b/docs/api/web-frame-main.md @@ -146,10 +146,13 @@ A `string` representing the current URL of the frame. #### `frame.origin` _Readonly_ -A `string` representing the current origin of the frame. This may be different +A `string` representing the current origin of the frame, serialized according +to [RFC 6454](https://www.rfc-editor.org/rfc/rfc6454). This may be different from the URL. For instance, if the frame is a child window opened to `about:blank`, then `frame.origin` will return the parent frame's origin, while -`frame.url` will return the empty string. +`frame.url` will return the empty string. Pages without a scheme/host/port +triple origin will have the serialized origin of `"null"` (that is, the string +containing the letters n, u, l, l). #### `frame.top` _Readonly_ From a30bfb548170831c552a797d1d612488a7fc6789 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Wed, 14 Sep 2022 23:28:17 +0200 Subject: [PATCH 4/4] fix spec --- spec-main/api-web-frame-main-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec-main/api-web-frame-main-spec.ts b/spec-main/api-web-frame-main-spec.ts index 3a294c1ecd5d3..9ce8f071e2f17 100644 --- a/spec-main/api-web-frame-main-spec.ts +++ b/spec-main/api-web-frame-main-spec.ts @@ -130,7 +130,7 @@ describe('webFrameMain module', () => { it('should be file:// for file frames', async () => { const w = new BrowserWindow({ show: false }); - await w.loadFile(path.join(fixtures, 'pages', 'blank.html')); + await w.loadFile(path.join(fixtures, 'blank.html')); expect(w.webContents.mainFrame.origin).to.equal('file://'); }); @@ -144,7 +144,7 @@ describe('webFrameMain module', () => { it('should show parent origin when child page is about:blank', async () => { const w = new BrowserWindow({ show: false }); - await w.loadFile(path.join(fixtures, 'pages', 'blank.html')); + await w.loadFile(path.join(fixtures, 'blank.html')); const webContentsCreated: Promise<[unknown, WebContents]> = emittedOnce(app, 'web-contents-created') as any; expect(w.webContents.mainFrame.origin).to.equal('file://'); await w.webContents.executeJavaScript('window.open("", null, "show=false"), null');