Browse Source

feat: webFrameMain.origin (#35535)

* feat: webFrameMain.origin

* add tests

* update docs

* fix spec

Co-authored-by: Jeremy Rose <[email protected]>
Co-authored-by: Milan Burda <[email protected]>
trop[bot] 2 years ago
parent
commit
cb5b28f833

+ 10 - 0
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`

+ 7 - 0
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<v8::ObjectTemplate> 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)

+ 1 - 0
shell/browser/api/electron_api_web_frame_main.h

@@ -108,6 +108,7 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
   int ProcessID() const;
   int RoutingID() const;
   GURL URL() const;
+  std::string Origin() const;
   blink::mojom::PageVisibilityState VisibilityState() const;
 
   content::RenderFrameHost* Top() const;

+ 64 - 14
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;
 
@@ -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);