123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- 'use strict'
- const {BrowserWindow, ipcMain, webContents} = require('electron')
- const {isSameOrigin} = process.atomBinding('v8_util')
- const hasProp = {}.hasOwnProperty
- const frameToGuest = {}
- // Copy attribute of |parent| to |child| if it is not defined in |child|.
- const mergeOptions = function (child, parent) {
- let key, value
- for (key in parent) {
- if (!hasProp.call(parent, key)) continue
- value = parent[key]
- if (!(key in child)) {
- if (typeof value === 'object') {
- child[key] = mergeOptions({}, value)
- } else {
- child[key] = value
- }
- }
- }
- return child
- }
- // Merge |options| with the |embedder|'s window's options.
- const mergeBrowserWindowOptions = function (embedder, options) {
- if (embedder.browserWindowOptions != null) {
- // Inherit the original options if it is a BrowserWindow.
- mergeOptions(options, embedder.browserWindowOptions)
- } else {
- // Or only inherit web-preferences if it is a webview.
- if (options.webPreferences == null) {
- options.webPreferences = {}
- }
- mergeOptions(options.webPreferences, embedder.getWebPreferences())
- }
- // Disable node integration on child window if disabled on parent window
- if (embedder.getWebPreferences().nodeIntegration === false) {
- options.webPreferences.nodeIntegration = false
- }
- return options
- }
- // Create a new guest created by |embedder| with |options|.
- const createGuest = function (embedder, url, frameName, options) {
- let guest = frameToGuest[frameName]
- if (frameName && (guest != null)) {
- guest.loadURL(url)
- return guest.id
- }
- // Remember the embedder window's id.
- if (options.webPreferences == null) {
- options.webPreferences = {}
- }
- options.webPreferences.openerId = embedder.id
- guest = new BrowserWindow(options)
- guest.loadURL(url)
- // When |embedder| is destroyed we should also destroy attached guest, and if
- // guest is closed by user then we should prevent |embedder| from double
- // closing guest.
- const guestId = guest.webContents.id
- const closedByEmbedder = function () {
- guest.removeListener('closed', closedByUser)
- guest.destroy()
- }
- const closedByUser = function () {
- embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
- embedder.removeListener('render-view-deleted', closedByEmbedder)
- }
- embedder.once('render-view-deleted', closedByEmbedder)
- guest.once('closed', closedByUser)
- if (frameName) {
- frameToGuest[frameName] = guest
- guest.frameName = frameName
- guest.once('closed', function () {
- delete frameToGuest[frameName]
- })
- }
- return guestId
- }
- const getGuestWindow = function (guestContents) {
- let guestWindow = BrowserWindow.fromWebContents(guestContents)
- if (guestWindow == null) {
- const hostContents = guestContents.hostWebContents
- if (hostContents != null) {
- guestWindow = BrowserWindow.fromWebContents(hostContents)
- }
- }
- return guestWindow
- }
- // Checks whether |sender| can access the |target|:
- // 1. Check whether |sender| is the parent of |target|.
- // 2. Check whether |sender| has node integration, if so it is allowed to
- // do anything it wants.
- // 3. Check whether the origins match.
- //
- // However it allows a child window without node integration but with same
- // origin to do anything it wants, when its opener window has node integration.
- // The W3C does not have anything on this, but from my understanding of the
- // security model of |window.opener|, this should be fine.
- const canAccessWindow = function (sender, target) {
- return (target.getWebPreferences().openerId === sender.id) ||
- (sender.getWebPreferences().nodeIntegration === true) ||
- isSameOrigin(sender.getURL(), target.getURL())
- }
- // Routed window.open messages.
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName, disposition, options) {
- options = mergeBrowserWindowOptions(event.sender, options)
- event.sender.emit('new-window', event, url, frameName, disposition, options)
- if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
- event.returnValue = null
- } else {
- event.returnValue = createGuest(event.sender, url, frameName, options)
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- if (!canAccessWindow(event.sender, guestContents)) {
- console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
- return
- }
- const guestWindow = getGuestWindow(guestContents)
- if (guestWindow != null) guestWindow.destroy()
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) {
- event.returnValue = null
- return
- }
- if (!canAccessWindow(event.sender, guestContents)) {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- event.returnValue = null
- return
- }
- const guestWindow = getGuestWindow(guestContents)
- if (guestWindow != null) {
- event.returnValue = guestWindow[method](...args)
- } else {
- event.returnValue = null
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- // The W3C does not seem to have word on how postMessage should work when the
- // origins do not match, so we do not do |canAccessWindow| check here since
- // postMessage across origins is useful and not harmful.
- if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
- const sourceId = event.sender.id
- guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- if (canAccessWindow(event.sender, guestContents)) {
- guestContents[method](...args)
- } else {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) {
- event.returnValue = null
- return
- }
- if (canAccessWindow(event.sender, guestContents)) {
- event.returnValue = guestContents[method](...args)
- } else {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- event.returnValue = null
- }
- })
|