From 613ec9dda7171f5a972d3beb18c4f21078ef2ddc Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Wed, 31 Aug 2022 17:40:57 -0700 Subject: [PATCH] feat: webFrameMain.origin (#35438) --- docs/api/web-frame-main.md | 10 +++ .../api/electron_api_web_frame_main.cc | 7 ++ .../browser/api/electron_api_web_frame_main.h | 1 + spec-main/api-web-frame-main-spec.ts | 72 ++++++++++++++++--- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md index e97041668e15b..e6e7e77745f50 100644 --- a/docs/api/web-frame-main.md +++ b/docs/api/web-frame-main.md @@ -144,6 +144,16 @@ 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, 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. 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_ 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 31f27d1c4539b..fa0256476ee98 100644 --- a/shell/browser/api/electron_api_web_frame_main.cc +++ b/shell/browser/api/electron_api_web_frame_main.cc @@ -291,6 +291,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; @@ -380,6 +386,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; diff --git a/spec-main/api-web-frame-main-spec.ts b/spec-main/api-web-frame-main-spec.ts index 8890e1402f52c..9b6bbdea922d0 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, '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, '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; @@ -167,7 +217,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; @@ -200,7 +250,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 @@ -263,7 +313,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);