Migrate to block comments

This commit is contained in:
Kevin Sawicki 2016-01-11 18:03:02 -08:00
parent 630cd091a0
commit 403870a27e
44 changed files with 538 additions and 437 deletions

View file

@ -34,13 +34,13 @@ app.setAppPath = (path) ->
app.getAppPath = -> app.getAppPath = ->
appPath appPath
# Routes the events to webContents. ### Routes the events to webContents. ###
for name in ['login', 'certificate-error', 'select-client-certificate'] for name in ['login', 'certificate-error', 'select-client-certificate']
do (name) -> do (name) ->
app.on name, (event, webContents, args...) -> app.on name, (event, webContents, args...) ->
webContents.emit name, event, args... webContents.emit name, event, args...
# Deprecated. ### Deprecated. ###
app.getHomeDir = deprecate 'app.getHomeDir', 'app.getPath', -> app.getHomeDir = deprecate 'app.getHomeDir', 'app.getPath', ->
@getPath 'home' @getPath 'home'
app.getDataPath = deprecate 'app.getDataPath', 'app.getPath', -> app.getDataPath = deprecate 'app.getDataPath', 'app.getPath', ->
@ -51,22 +51,23 @@ app.resolveProxy = deprecate 'app.resolveProxy', 'session.defaultSession.resolve
session.defaultSession.resolveProxy url, callback session.defaultSession.resolveProxy url, callback
deprecate.rename app, 'terminate', 'quit' deprecate.rename app, 'terminate', 'quit'
deprecate.event app, 'finish-launching', 'ready', -> deprecate.event app, 'finish-launching', 'ready', ->
setImmediate => # give default app a chance to setup default menu. ### give default app a chance to setup default menu. ###
setImmediate =>
@emit 'finish-launching' @emit 'finish-launching'
deprecate.event app, 'activate-with-no-open-windows', 'activate', (event, hasVisibleWindows) -> deprecate.event app, 'activate-with-no-open-windows', 'activate', (event, hasVisibleWindows) ->
@emit 'activate-with-no-open-windows', event if not hasVisibleWindows @emit 'activate-with-no-open-windows', event if not hasVisibleWindows
deprecate.event app, 'select-certificate', 'select-client-certificate' deprecate.event app, 'select-certificate', 'select-client-certificate'
# Wrappers for native classes. ### Wrappers for native classes. ###
wrapDownloadItem = (downloadItem) -> wrapDownloadItem = (downloadItem) ->
# downloadItem is an EventEmitter. ### downloadItem is an EventEmitter. ###
downloadItem.__proto__ = EventEmitter.prototype downloadItem.__proto__ = EventEmitter.prototype
# Deprecated. ### Deprecated. ###
deprecate.property downloadItem, 'url', 'getURL' deprecate.property downloadItem, 'url', 'getURL'
deprecate.property downloadItem, 'filename', 'getFilename' deprecate.property downloadItem, 'filename', 'getFilename'
deprecate.property downloadItem, 'mimeType', 'getMimeType' deprecate.property downloadItem, 'mimeType', 'getMimeType'
deprecate.rename downloadItem, 'getUrl', 'getURL' deprecate.rename downloadItem, 'getUrl', 'getURL'
downloadItemBindings._setWrapDownloadItem wrapDownloadItem downloadItemBindings._setWrapDownloadItem wrapDownloadItem
# Only one App object pemitted. ### Only one App object pemitted. ###
module.exports = app module.exports = app

View file

@ -6,7 +6,7 @@ autoUpdater =
else else
require './auto-updater/auto-updater-native' require './auto-updater/auto-updater-native'
# Deprecated. ### Deprecated. ###
deprecate.rename autoUpdater, 'setFeedUrl', 'setFeedURL' deprecate.rename autoUpdater, 'setFeedUrl', 'setFeedURL'
module.exports = autoUpdater module.exports = autoUpdater

View file

@ -28,14 +28,16 @@ class AutoUpdater extends EventEmitter
return @emitError error if error? return @emitError error if error?
{releaseNotes, version} = update {releaseNotes, version} = update
# Following information is not available on Windows, so fake them. ### Following information is not available on Windows, so fake them. ###
date = new Date date = new Date
url = @updateURL url = @updateURL
@emit 'update-downloaded', {}, releaseNotes, version, date, url, => @quitAndInstall() @emit 'update-downloaded', {}, releaseNotes, version, date, url, => @quitAndInstall()
# Private: Emit both error object and message, this is to keep compatibility ###
# with Old APIs. Private: Emit both error object and message, this is to keep compatibility
with Old APIs.
###
emitError: (message) -> emitError: (message) ->
@emit 'error', new Error(message), message @emit 'error', new Error(message), message

View file

@ -2,17 +2,21 @@ fs = require 'fs'
path = require 'path' path = require 'path'
{spawn} = require 'child_process' {spawn} = require 'child_process'
appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ ### i.e. my-app/app-0.1.13/ ###
updateExe = path.resolve appFolder, '..', 'Update.exe' # i.e. my-app/Update.exe appFolder = path.dirname process.execPath
### i.e. my-app/Update.exe ###
updateExe = path.resolve appFolder, '..', 'Update.exe'
exeName = path.basename process.execPath exeName = path.basename process.execPath
# Spawn a command and invoke the callback when it completes with an error ###
# and the output from standard out. Spawn a command and invoke the callback when it completes with an error
and the output from standard out.
###
spawnUpdate = (args, detached, callback) -> spawnUpdate = (args, detached, callback) ->
try try
spawnedProcess = spawn updateExe, args, {detached} spawnedProcess = spawn updateExe, args, {detached}
catch error catch error
# Shouldn't happen, but still guard it. ### Shouldn't happen, but still guard it. ###
process.nextTick -> callback error process.nextTick -> callback error
return return
@ -26,27 +30,27 @@ spawnUpdate = (args, detached, callback) ->
errorEmitted = true errorEmitted = true
callback error callback error
spawnedProcess.on 'exit', (code, signal) -> spawnedProcess.on 'exit', (code, signal) ->
# We may have already emitted an error. ### We may have already emitted an error. ###
return if errorEmitted return if errorEmitted
# Process terminated with error. ### Process terminated with error. ###
if code isnt 0 if code isnt 0
return callback "Command failed: #{signal ? code}\n#{stderr}" return callback "Command failed: #{signal ? code}\n#{stderr}"
# Success. ### Success. ###
callback null, stdout callback null, stdout
# Start an instance of the installed app. ### Start an instance of the installed app. ###
exports.processStart = (callback) -> exports.processStart = (callback) ->
spawnUpdate ['--processStart', exeName], true, -> spawnUpdate ['--processStart', exeName], true, ->
# Download the releases specified by the URL and write new results to stdout. ### Download the releases specified by the URL and write new results to stdout. ###
exports.download = (updateURL, callback) -> exports.download = (updateURL, callback) ->
spawnUpdate ['--download', updateURL], false, (error, stdout) -> spawnUpdate ['--download', updateURL], false, (error, stdout) ->
return callback(error) if error? return callback(error) if error?
try try
# Last line of output is the JSON details about the releases ### Last line of output is the JSON details about the releases ###
json = stdout.trim().split('\n').pop() json = stdout.trim().split('\n').pop()
update = JSON.parse(json)?.releasesToApply?.pop?() update = JSON.parse(json)?.releasesToApply?.pop?()
catch catch
@ -54,11 +58,11 @@ exports.download = (updateURL, callback) ->
callback null, update callback null, update
# Update the application to the latest remote version specified by URL. ### Update the application to the latest remote version specified by URL. ###
exports.update = (updateURL, callback) -> exports.update = (updateURL, callback) ->
spawnUpdate ['--update', updateURL], false, callback spawnUpdate ['--update', updateURL], false, callback
# Is the Update.exe installed with the current application? ### Is the Update.exe installed with the current application? ###
exports.supported = -> exports.supported = ->
try try
fs.accessSync updateExe, fs.R_OK fs.accessSync updateExe, fs.R_OK

View file

@ -5,56 +5,61 @@
BrowserWindow::__proto__ = EventEmitter.prototype BrowserWindow::__proto__ = EventEmitter.prototype
BrowserWindow::_init = -> BrowserWindow::_init = ->
{app} = require 'electron' # avoid recursive require. ### avoid recursive require. ###
{app} = require 'electron'
# Simulate the application menu on platforms other than OS X. ### Simulate the application menu on platforms other than OS X. ###
if process.platform isnt 'darwin' if process.platform isnt 'darwin'
menu = app.getApplicationMenu() menu = app.getApplicationMenu()
@setMenu menu if menu? @setMenu menu if menu?
# Make new windows requested by links behave like "window.open" ### Make new windows requested by links behave like "window.open" ###
@webContents.on '-new-window', (event, url, frameName) -> @webContents.on '-new-window', (event, url, frameName) ->
options = show: true, width: 800, height: 600 options = show: true, width: 800, height: 600
ipcMain.emit 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options ipcMain.emit 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options
# window.resizeTo(...) ###
# window.moveTo(...) window.resizeTo(...)
window.moveTo(...)
###
@webContents.on 'move', (event, size) => @webContents.on 'move', (event, size) =>
@setBounds size @setBounds size
# Hide the auto-hide menu when webContents is focused. ### Hide the auto-hide menu when webContents is focused. ###
@webContents.on 'activate', => @webContents.on 'activate', =>
if process.platform isnt 'darwin' and @isMenuBarAutoHide() and @isMenuBarVisible() if process.platform isnt 'darwin' and @isMenuBarAutoHide() and @isMenuBarVisible()
@setMenuBarVisibility false @setMenuBarVisibility false
# Forward the crashed event. ### Forward the crashed event. ###
@webContents.on 'crashed', => @webContents.on 'crashed', =>
@emit 'crashed' @emit 'crashed'
# Change window title to page title. ### Change window title to page title. ###
@webContents.on 'page-title-updated', (event, title, explicitSet) => @webContents.on 'page-title-updated', (event, title, explicitSet) =>
@emit 'page-title-updated', event, title @emit 'page-title-updated', event, title
@setTitle title unless event.defaultPrevented @setTitle title unless event.defaultPrevented
# Sometimes the webContents doesn't get focus when window is shown, so we have ###
# to force focusing on webContents in this case. The safest way is to focus it Sometimes the webContents doesn't get focus when window is shown, so we have
# when we first start to load URL, if we do it earlier it won't have effect, to force focusing on webContents in this case. The safest way is to focus it
# if we do it later we might move focus in the page. when we first start to load URL, if we do it earlier it won't have effect,
# Though this hack is only needed on OS X when the app is launched from if we do it later we might move focus in the page.
# Finder, we still do it on all platforms in case of other bugs we don't know. Though this hack is only needed on OS X when the app is launched from
Finder, we still do it on all platforms in case of other bugs we don't know.
###
@webContents.once 'load-url', -> @webContents.once 'load-url', ->
@focus() @focus()
# Redirect focus/blur event to app instance too. ### Redirect focus/blur event to app instance too. ###
@on 'blur', (event) => @on 'blur', (event) =>
app.emit 'browser-window-blur', event, this app.emit 'browser-window-blur', event, this
@on 'focus', (event) => @on 'focus', (event) =>
app.emit 'browser-window-focus', event, this app.emit 'browser-window-focus', event, this
# Notify the creation of the window. ### Notify the creation of the window. ###
app.emit 'browser-window-created', {}, this app.emit 'browser-window-created', {}, this
# Be compatible with old APIs. ### Be compatible with old APIs. ###
@webContents.on 'devtools-focused', => @emit 'devtools-focused' @webContents.on 'devtools-focused', => @emit 'devtools-focused'
@webContents.on 'devtools-opened', => @emit 'devtools-opened' @webContents.on 'devtools-opened', => @emit 'devtools-opened'
@webContents.on 'devtools-closed', => @emit 'devtools-closed' @webContents.on 'devtools-closed', => @emit 'devtools-closed'
@ -76,7 +81,7 @@ BrowserWindow.fromDevToolsWebContents = (webContents) ->
windows = BrowserWindow.getAllWindows() windows = BrowserWindow.getAllWindows()
return window for window in windows when window.devToolsWebContents?.equal webContents return window for window in windows when window.devToolsWebContents?.equal webContents
# Helpers. ### Helpers. ###
BrowserWindow::loadURL = -> @webContents.loadURL.apply @webContents, arguments BrowserWindow::loadURL = -> @webContents.loadURL.apply @webContents, arguments
BrowserWindow::getURL = -> @webContents.getURL() BrowserWindow::getURL = -> @webContents.getURL()
BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments
@ -89,7 +94,7 @@ BrowserWindow::toggleDevTools = -> @webContents.toggleDevTools()
BrowserWindow::inspectElement = -> @webContents.inspectElement.apply @webContents, arguments BrowserWindow::inspectElement = -> @webContents.inspectElement.apply @webContents, arguments
BrowserWindow::inspectServiceWorker = -> @webContents.inspectServiceWorker() BrowserWindow::inspectServiceWorker = -> @webContents.inspectServiceWorker()
# Deprecated. ### Deprecated. ###
deprecate.member BrowserWindow, 'undo', 'webContents' deprecate.member BrowserWindow, 'undo', 'webContents'
deprecate.member BrowserWindow, 'redo', 'webContents' deprecate.member BrowserWindow, 'redo', 'webContents'
deprecate.member BrowserWindow, 'cut', 'webContents' deprecate.member BrowserWindow, 'cut', 'webContents'

View file

@ -16,12 +16,12 @@ messageBoxOptions =
parseArgs = (window, options, callback) -> parseArgs = (window, options, callback) ->
unless window is null or window?.constructor is BrowserWindow unless window is null or window?.constructor is BrowserWindow
# Shift. ### Shift. ###
callback = options callback = options
options = window options = window
window = null window = null
if not callback? and typeof options is 'function' if not callback? and typeof options is 'function'
# Shift. ### Shift. ###
callback = options callback = options
options = null options = null
[window, options, callback] [window, options, callback]
@ -97,7 +97,7 @@ module.exports =
options.icon ?= null options.icon ?= null
options.defaultId ?= -1 options.defaultId ?= -1
# Choose a default button to get selected when dialog is cancelled. ### Choose a default button to get selected when dialog is cancelled. ###
unless options.cancelId? unless options.cancelId?
options.cancelId = 0 options.cancelId = 0
for text, i in options.buttons for text, i in options.buttons
@ -122,6 +122,6 @@ module.exports =
showErrorBox: (args...) -> showErrorBox: (args...) ->
binding.showErrorBox args... binding.showErrorBox args...
# Mark standard asynchronous functions. ### Mark standard asynchronous functions. ###
for api in ['showMessageBox', 'showOpenDialog', 'showSaveDialog'] for api in ['showMessageBox', 'showOpenDialog', 'showSaveDialog']
v8Util.setHiddenValue module.exports[api], 'asynchronous', true v8Util.setHiddenValue module.exports[api], 'asynchronous', true

View file

@ -1,10 +1,10 @@
common = require '../../../../common/api/lib/exports/electron' common = require '../../../../common/api/lib/exports/electron'
# Import common modules. ### Import common modules. ###
common.defineProperties exports common.defineProperties exports
Object.defineProperties exports, Object.defineProperties exports,
# Browser side modules, please sort with alphabet order. ### Browser side modules, please sort with alphabet order. ###
app: app:
enumerable: true enumerable: true
get: -> require '../app' get: -> require '../app'
@ -50,7 +50,7 @@ Object.defineProperties exports,
Tray: Tray:
enumerable: true enumerable: true
get: -> require '../tray' get: -> require '../tray'
# The internal modules, invisible unless you know their names. ### The internal modules, invisible unless you know their names. ###
NavigationController: NavigationController:
get: -> require '../navigation-controller' get: -> require '../navigation-controller'
webContents: webContents:

View file

@ -1,6 +1,6 @@
{deprecate, ipcMain} = require 'electron' {deprecate, ipcMain} = require 'electron'
# This module is deprecated, we mirror everything from ipcMain. ### This module is deprecated, we mirror everything from ipcMain. ###
deprecate.warn 'ipc module', 'require("electron").ipcMain' deprecate.warn 'ipc module', 'require("electron").ipcMain'
module.exports = ipcMain module.exports = ipcMain

View file

@ -2,7 +2,7 @@ v8Util = process.atomBinding 'v8_util'
nextCommandId = 0 nextCommandId = 0
# Maps role to methods of webContents ### Maps role to methods of webContents ###
rolesMap = rolesMap =
undo: 'undo' undo: 'undo'
redo: 'redo' redo: 'redo'
@ -13,7 +13,7 @@ rolesMap =
minimize: 'minimize' minimize: 'minimize'
close: 'close' close: 'close'
# Maps methods that should be called directly on the BrowserWindow instance ### Maps methods that should be called directly on the BrowserWindow instance ###
methodInBrowserWindow = methodInBrowserWindow =
minimize: true minimize: true
close: true close: true
@ -46,7 +46,7 @@ class MenuItem
@commandId = ++nextCommandId @commandId = ++nextCommandId
@click = (focusedWindow) => @click = (focusedWindow) =>
# Manually flip the checked flags when clicked. ### Manually flip the checked flags when clicked. ###
@checked = !@checked if @type in ['checkbox', 'radio'] @checked = !@checked if @type in ['checkbox', 'radio']
if @role and rolesMap[@role] and process.platform isnt 'darwin' and focusedWindow? if @role and rolesMap[@role] and process.platform isnt 'darwin' and focusedWindow?

View file

@ -4,11 +4,11 @@
v8Util = process.atomBinding 'v8_util' v8Util = process.atomBinding 'v8_util'
bindings = process.atomBinding 'menu' bindings = process.atomBinding 'menu'
# Automatically generated radio menu item's group id. ### Automatically generated radio menu item's group id. ###
nextGroupId = 0 nextGroupId = 0
# Search between seperators to find a radio menu item and return its group id, ### Search between seperators to find a radio menu item and return its group id, ###
# otherwise generate a group id. ### otherwise generate a group id. ###
generateGroupId = (items, pos) -> generateGroupId = (items, pos) ->
if pos > 0 if pos > 0
for i in [pos - 1..0] for i in [pos - 1..0]
@ -22,12 +22,12 @@ generateGroupId = (items, pos) ->
break if item.type is 'separator' break if item.type is 'separator'
++nextGroupId ++nextGroupId
# Returns the index of item according to |id|. ### Returns the index of item according to |id|. ###
indexOfItemById = (items, id) -> indexOfItemById = (items, id) ->
return i for item, i in items when item.id is id return i for item, i in items when item.id is id
-1 -1
# Returns the index of where to insert the item according to |position|. ### Returns the index of where to insert the item according to |position|. ###
indexToInsertByPosition = (items, position) -> indexToInsertByPosition = (items, position) ->
return items.length unless position return items.length unless position
@ -41,12 +41,12 @@ indexToInsertByPosition = (items, position) ->
when 'after' when 'after'
insertIndex++ insertIndex++
when 'endof' when 'endof'
# If the |id| doesn't exist, then create a new group with the |id|. ### If the |id| doesn't exist, then create a new group with the |id|. ###
if insertIndex is -1 if insertIndex is -1
items.push id: id, type: 'separator' items.push id: id, type: 'separator'
insertIndex = items.length - 1 insertIndex = items.length - 1
# Find the end of the group. ### Find the end of the group. ###
insertIndex++ insertIndex++
while insertIndex < items.length and items[insertIndex].type isnt 'separator' while insertIndex < items.length and items[insertIndex].type isnt 'separator'
insertIndex++ insertIndex++
@ -69,7 +69,7 @@ Menu::_init = ->
executeCommand: (commandId) => executeCommand: (commandId) =>
@commandsMap[commandId]?.click BrowserWindow.getFocusedWindow() @commandsMap[commandId]?.click BrowserWindow.getFocusedWindow()
menuWillShow: => menuWillShow: =>
# Make sure radio groups have at least one menu item seleted. ### Make sure radio groups have at least one menu item seleted. ###
for id, group of @groupsMap for id, group of @groupsMap
checked = false checked = false
for radioItem in group when radioItem.checked for radioItem in group when radioItem.checked
@ -79,7 +79,7 @@ Menu::_init = ->
Menu::popup = (window, x, y) -> Menu::popup = (window, x, y) ->
unless window?.constructor is BrowserWindow unless window?.constructor is BrowserWindow
# Shift. ### Shift. ###
y = x y = x
x = window x = window
window = BrowserWindow.getFocusedWindow() window = BrowserWindow.getFocusedWindow()
@ -100,12 +100,12 @@ Menu::insert = (pos, item) ->
when 'separator' then @insertSeparator pos when 'separator' then @insertSeparator pos
when 'submenu' then @insertSubMenu pos, item.commandId, item.label, item.submenu when 'submenu' then @insertSubMenu pos, item.commandId, item.label, item.submenu
when 'radio' when 'radio'
# Grouping radio menu items. ### Grouping radio menu items. ###
item.overrideReadOnlyProperty 'groupId', generateGroupId(@items, pos) item.overrideReadOnlyProperty 'groupId', generateGroupId(@items, pos)
@groupsMap[item.groupId] ?= [] @groupsMap[item.groupId] ?= []
@groupsMap[item.groupId].push item @groupsMap[item.groupId].push item
# Setting a radio menu item should flip other items in the group. ### Setting a radio menu item should flip other items in the group. ###
v8Util.setHiddenValue item, 'checked', item.checked v8Util.setHiddenValue item, 'checked', item.checked
Object.defineProperty item, 'checked', Object.defineProperty item, 'checked',
enumerable: true enumerable: true
@ -121,14 +121,14 @@ Menu::insert = (pos, item) ->
@setIcon pos, item.icon if item.icon? @setIcon pos, item.icon if item.icon?
@setRole pos, item.role if item.role? @setRole pos, item.role if item.role?
# Make menu accessable to items. ### Make menu accessable to items. ###
item.overrideReadOnlyProperty 'menu', this item.overrideReadOnlyProperty 'menu', this
# Remember the items. ### Remember the items. ###
@items.splice pos, 0, item @items.splice pos, 0, item
@commandsMap[item.commandId] = item @commandsMap[item.commandId] = item
# Force menuWillShow to be called ### Force menuWillShow to be called ###
Menu::_callMenuWillShow = -> Menu::_callMenuWillShow = ->
@delegate?.menuWillShow() @delegate?.menuWillShow()
item.submenu._callMenuWillShow() for item in @items when item.submenu? item.submenu._callMenuWillShow() for item in @items when item.submenu?
@ -136,7 +136,8 @@ Menu::_callMenuWillShow = ->
applicationMenu = null applicationMenu = null
Menu.setApplicationMenu = (menu) -> Menu.setApplicationMenu = (menu) ->
throw new TypeError('Invalid menu') unless menu is null or menu.constructor is Menu throw new TypeError('Invalid menu') unless menu is null or menu.constructor is Menu
applicationMenu = menu # Keep a reference. ### Keep a reference. ###
applicationMenu = menu
if process.platform is 'darwin' if process.platform is 'darwin'
return if menu is null return if menu is null
@ -160,7 +161,7 @@ Menu.buildFromTemplate = (template) ->
if item.position if item.position
insertIndex = indexToInsertByPosition positionedTemplate, item.position insertIndex = indexToInsertByPosition positionedTemplate, item.position
else else
# If no |position| is specified, insert after last item. ### If no |position| is specified, insert after last item. ###
insertIndex++ insertIndex++
positionedTemplate.splice insertIndex, 0, item positionedTemplate.splice insertIndex, 0, item

View file

@ -1,42 +1,47 @@
{ipcMain} = require 'electron' {ipcMain} = require 'electron'
# The history operation in renderer is redirected to browser. ### The history operation in renderer is redirected to browser. ###
ipcMain.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> ipcMain.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) ->
event.sender[method] args... event.sender[method] args...
ipcMain.on 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', (event, method, args...) -> ipcMain.on 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', (event, method, args...) ->
event.returnValue = event.sender[method] args... event.returnValue = event.sender[method] args...
# JavaScript implementation of Chromium's NavigationController. ###
# Instead of relying on Chromium for history control, we compeletely do history JavaScript implementation of Chromium's NavigationController.
# control on user land, and only rely on WebContents.loadURL for navigation. Instead of relying on Chromium for history control, we compeletely do history
# This helps us avoid Chromium's various optimizations so we can ensure renderer control on user land, and only rely on WebContents.loadURL for navigation.
# process is restarted everytime. This helps us avoid Chromium's various optimizations so we can ensure renderer
process is restarted everytime.
###
class NavigationController class NavigationController
constructor: (@webContents) -> constructor: (@webContents) ->
@clearHistory() @clearHistory()
# webContents may have already navigated to a page. ### webContents may have already navigated to a page. ###
if @webContents._getURL() if @webContents._getURL()
@currentIndex++ @currentIndex++
@history.push @webContents._getURL() @history.push @webContents._getURL()
@webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) => @webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) =>
if @inPageIndex > -1 and not inPage if @inPageIndex > -1 and not inPage
# Navigated to a new page, clear in-page mark. ### Navigated to a new page, clear in-page mark. ###
@inPageIndex = -1 @inPageIndex = -1
else if @inPageIndex is -1 and inPage else if @inPageIndex is -1 and inPage
# Started in-page navigations. ### Started in-page navigations. ###
@inPageIndex = @currentIndex @inPageIndex = @currentIndex
if @pendingIndex >= 0 # Go to index. if @pendingIndex >= 0
### Go to index. ###
@currentIndex = @pendingIndex @currentIndex = @pendingIndex
@pendingIndex = -1 @pendingIndex = -1
@history[@currentIndex] = url @history[@currentIndex] = url
else if replaceEntry # Non-user initialized navigation. else if replaceEntry
### Non-user initialized navigation. ###
@history[@currentIndex] = url @history[@currentIndex] = url
else # Normal navigation. else
@history = @history.slice 0, @currentIndex + 1 # Clear history. ### Normal navigation. Clear history. ###
@history = @history.slice 0, @currentIndex + 1
currentEntry = @history[@currentIndex] currentEntry = @history[@currentIndex]
if currentEntry?.url isnt url if currentEntry?.url isnt url
@currentIndex++ @currentIndex++

View file

@ -4,7 +4,7 @@ throw new Error('Can not initialize protocol module before app is ready') unless
{protocol} = process.atomBinding 'protocol' {protocol} = process.atomBinding 'protocol'
# Warn about removed APIs. ### Warn about removed APIs. ###
logAndThrow = (callback, message) -> logAndThrow = (callback, message) ->
console.error message console.error message
if callback then callback(new Error(message)) else throw new Error(message) if callback then callback(new Error(message)) else throw new Error(message)

View file

@ -4,7 +4,7 @@ bindings = process.atomBinding 'session'
PERSIST_PERFIX = 'persist:' PERSIST_PERFIX = 'persist:'
# Returns the Session from |partition| string. ### Returns the Session from |partition| string. ###
exports.fromPartition = (partition='') -> exports.fromPartition = (partition='') ->
return exports.defaultSession if partition is '' return exports.defaultSession if partition is ''
if partition.startsWith PERSIST_PERFIX if partition.startsWith PERSIST_PERFIX
@ -12,13 +12,13 @@ exports.fromPartition = (partition='') ->
else else
bindings.fromPartition partition, true bindings.fromPartition partition, true
# Returns the default session. ### Returns the default session. ###
Object.defineProperty exports, 'defaultSession', Object.defineProperty exports, 'defaultSession',
enumerable: true enumerable: true
get: -> bindings.fromPartition '', false get: -> bindings.fromPartition '', false
wrapSession = (session) -> wrapSession = (session) ->
# session is an EventEmitter. ### session is an EventEmitter. ###
session.__proto__ = EventEmitter.prototype session.__proto__ = EventEmitter.prototype
bindings._setWrapSession wrapSession bindings._setWrapSession wrapSession

View file

@ -5,7 +5,7 @@
Tray::__proto__ = EventEmitter.prototype Tray::__proto__ = EventEmitter.prototype
Tray::_init = -> Tray::_init = ->
# Deprecated. ### Deprecated. ###
deprecate.rename this, 'popContextMenu', 'popUpContextMenu' deprecate.rename this, 'popContextMenu', 'popUpContextMenu'
deprecate.event this, 'clicked', 'click' deprecate.event this, 'clicked', 'click'
deprecate.event this, 'double-clicked', 'double-click' deprecate.event this, 'double-clicked', 'double-click'
@ -14,6 +14,7 @@ Tray::_init = ->
Tray::setContextMenu = (menu) -> Tray::setContextMenu = (menu) ->
@_setContextMenu menu @_setContextMenu menu
@menu = menu # Keep a strong reference of menu. ### Keep a strong reference of menu. ###
@menu = menu
module.exports = Tray module.exports = Tray

View file

@ -40,28 +40,30 @@ PDFPageSize =
custom_display_name: "Tabloid" custom_display_name: "Tabloid"
wrapWebContents = (webContents) -> wrapWebContents = (webContents) ->
# webContents is an EventEmitter. ### webContents is an EventEmitter. ###
webContents.__proto__ = EventEmitter.prototype webContents.__proto__ = EventEmitter.prototype
# WebContents::send(channel, args..) ### WebContents::send(channel, args..) ###
webContents.send = (channel, args...) -> webContents.send = (channel, args...) ->
@_send channel, [args...] @_send channel, [args...]
# Make sure webContents.executeJavaScript would run the code only when the ###
# web contents has been loaded. Make sure webContents.executeJavaScript would run the code only when the
web contents has been loaded.
###
webContents.executeJavaScript = (code, hasUserGesture=false) -> webContents.executeJavaScript = (code, hasUserGesture=false) ->
if @getURL() and not @isLoading() if @getURL() and not @isLoading()
@_executeJavaScript code, hasUserGesture @_executeJavaScript code, hasUserGesture
else else
webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture) webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture)
# The navigation controller. ### The navigation controller. ###
controller = new NavigationController(webContents) controller = new NavigationController(webContents)
for name, method of NavigationController.prototype when method instanceof Function for name, method of NavigationController.prototype when method instanceof Function
do (name, method) -> do (name, method) ->
webContents[name] = -> method.apply controller, arguments webContents[name] = -> method.apply controller, arguments
# Dispatch IPC messages to the ipc module. ### Dispatch IPC messages to the ipc module. ###
webContents.on 'ipc-message', (event, packed) -> webContents.on 'ipc-message', (event, packed) ->
[channel, args...] = packed [channel, args...] = packed
ipcMain.emit channel, event, args... ipcMain.emit channel, event, args...
@ -70,22 +72,24 @@ wrapWebContents = (webContents) ->
Object.defineProperty event, 'returnValue', set: (value) -> event.sendReply JSON.stringify(value) Object.defineProperty event, 'returnValue', set: (value) -> event.sendReply JSON.stringify(value)
ipcMain.emit channel, event, args... ipcMain.emit channel, event, args...
# Handle context menu action request from pepper plugin. ### Handle context menu action request from pepper plugin. ###
webContents.on 'pepper-context-menu', (event, params) -> webContents.on 'pepper-context-menu', (event, params) ->
menu = Menu.buildFromTemplate params.menu menu = Menu.buildFromTemplate params.menu
menu.popup params.x, params.y menu.popup params.x, params.y
# This error occurs when host could not be found. ### This error occurs when host could not be found. ###
webContents.on 'did-fail-provisional-load', (args...) -> webContents.on 'did-fail-provisional-load', (args...) ->
# Calling loadURL during this event might cause crash, so delay the event ###
# until next tick. Calling loadURL during this event might cause crash, so delay the event
until next tick.
###
setImmediate => @emit 'did-fail-load', args... setImmediate => @emit 'did-fail-load', args...
# Delays the page-title-updated event to next tick. ### Delays the page-title-updated event to next tick. ###
webContents.on '-page-title-updated', (args...) -> webContents.on '-page-title-updated', (args...) ->
setImmediate => @emit 'page-title-updated', args... setImmediate => @emit 'page-title-updated', args...
# Deprecated. ### Deprecated. ###
deprecate.rename webContents, 'loadUrl', 'loadURL' deprecate.rename webContents, 'loadUrl', 'loadURL'
deprecate.rename webContents, 'getUrl', 'getURL' deprecate.rename webContents, 'getUrl', 'getURL'
deprecate.event webContents, 'page-title-set', 'page-title-updated', (args...) -> deprecate.event webContents, 'page-title-set', 'page-title-updated', (args...) ->

View file

@ -3,7 +3,7 @@ fs = require 'fs'
path = require 'path' path = require 'path'
url = require 'url' url = require 'url'
# Mapping between hostname and file path. ### Mapping between hostname and file path. ###
hostPathMap = {} hostPathMap = {}
hostPathMapNextKey = 0 hostPathMapNextKey = 0
@ -15,14 +15,16 @@ getHostForPath = (path) ->
getPathForHost = (host) -> getPathForHost = (host) ->
hostPathMap[host] hostPathMap[host]
# Cache extensionInfo. ### Cache extensionInfo. ###
extensionInfoMap = {} extensionInfoMap = {}
getExtensionInfoFromPath = (srcDirectory) -> getExtensionInfoFromPath = (srcDirectory) ->
manifest = JSON.parse fs.readFileSync(path.join(srcDirectory, 'manifest.json')) manifest = JSON.parse fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
unless extensionInfoMap[manifest.name]? unless extensionInfoMap[manifest.name]?
# We can not use 'file://' directly because all resources in the extension ###
# will be treated as relative to the root in Chrome. We can not use 'file://' directly because all resources in the extension
will be treated as relative to the root in Chrome.
###
page = url.format page = url.format
protocol: 'chrome-extension' protocol: 'chrome-extension'
slashes: true slashes: true
@ -35,11 +37,11 @@ getExtensionInfoFromPath = (srcDirectory) ->
exposeExperimentalAPIs: true exposeExperimentalAPIs: true
extensionInfoMap[manifest.name] extensionInfoMap[manifest.name]
# The loaded extensions cache and its persistent path. ### The loaded extensions cache and its persistent path. ###
loadedExtensions = null loadedExtensions = null
loadedExtensionsPath = null loadedExtensionsPath = null
# Persistent loaded extensions. ### Persistent loaded extensions. ###
{app} = electron {app} = electron
app.on 'will-quit', -> app.on 'will-quit', ->
try try
@ -50,21 +52,21 @@ app.on 'will-quit', ->
fs.writeFileSync loadedExtensionsPath, JSON.stringify(loadedExtensions) fs.writeFileSync loadedExtensionsPath, JSON.stringify(loadedExtensions)
catch e catch e
# We can not use protocol or BrowserWindow until app is ready. ### We can not use protocol or BrowserWindow until app is ready. ###
app.once 'ready', -> app.once 'ready', ->
{protocol, BrowserWindow} = electron {protocol, BrowserWindow} = electron
# Load persistented extensions. ### Load persistented extensions. ###
loadedExtensionsPath = path.join app.getPath('userData'), 'DevTools Extensions' loadedExtensionsPath = path.join app.getPath('userData'), 'DevTools Extensions'
try try
loadedExtensions = JSON.parse fs.readFileSync(loadedExtensionsPath) loadedExtensions = JSON.parse fs.readFileSync(loadedExtensionsPath)
loadedExtensions = [] unless Array.isArray loadedExtensions loadedExtensions = [] unless Array.isArray loadedExtensions
# Preheat the extensionInfo cache. ### Preheat the extensionInfo cache. ###
getExtensionInfoFromPath srcDirectory for srcDirectory in loadedExtensions getExtensionInfoFromPath srcDirectory for srcDirectory in loadedExtensions
catch e catch e
# The chrome-extension: can map a extension URL request to real file path. ### The chrome-extension: can map a extension URL request to real file path. ###
chromeExtensionHandler = (request, callback) -> chromeExtensionHandler = (request, callback) ->
parsed = url.parse request.url parsed = url.parse request.url
return callback() unless parsed.hostname and parsed.path? return callback() unless parsed.hostname and parsed.path?
@ -88,7 +90,7 @@ app.once 'ready', ->
BrowserWindow.removeDevToolsExtension = (name) -> BrowserWindow.removeDevToolsExtension = (name) ->
delete extensionInfoMap[name] delete extensionInfoMap[name]
# Load persistented extensions when devtools is opened. ### Load persistented extensions when devtools is opened. ###
init = BrowserWindow::_init init = BrowserWindow::_init
BrowserWindow::_init = -> BrowserWindow::_init = ->
init.call this init.call this

View file

@ -4,26 +4,30 @@
deepEqual = (opt1, opt2) -> deepEqual = (opt1, opt2) ->
return JSON.stringify(opt1) is JSON.stringify(opt2) return JSON.stringify(opt1) is JSON.stringify(opt2)
# A queue for holding all requests from renderer process. ### A queue for holding all requests from renderer process. ###
requestsQueue = [] requestsQueue = []
ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) -> ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) ->
request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender
requestsQueue.push request requestsQueue.push request
desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1 desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1
# If the WebContents is destroyed before receiving result, just remove the ###
# reference from requestsQueue to make the module not send the result to it. If the WebContents is destroyed before receiving result, just remove the
reference from requestsQueue to make the module not send the result to it.
###
event.sender.once 'destroyed', -> event.sender.once 'destroyed', ->
request.webContents = null request.webContents = null
desktopCapturer.emit = (event, name, sources) -> desktopCapturer.emit = (event, name, sources) ->
# Receiving sources result from main process, now send them back to renderer. ### Receiving sources result from main process, now send them back to renderer. ###
handledRequest = requestsQueue.shift 0 handledRequest = requestsQueue.shift 0
result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources)
handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result
# Check the queue to see whether there is other same request. If has, handle ###
# it for reducing redunplicated `desktopCaptuer.startHandling` calls. Check the queue to see whether there is other same request. If has, handle
it for reducing redunplicated `desktopCaptuer.startHandling` calls.
###
unhandledRequestsQueue = [] unhandledRequestsQueue = []
for request in requestsQueue for request in requestsQueue
if deepEqual handledRequest.options, request.options if deepEqual handledRequest.options, request.options
@ -31,7 +35,7 @@ desktopCapturer.emit = (event, name, sources) ->
else else
unhandledRequestsQueue.push request unhandledRequestsQueue.push request
requestsQueue = unhandledRequestsQueue requestsQueue = unhandledRequestsQueue
# If the requestsQueue is not empty, start a new request handling. ### If the requestsQueue is not empty, start a new request handling. ###
if requestsQueue.length > 0 if requestsQueue.length > 0
{captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options
desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize

View file

@ -1,6 +1,7 @@
{ipcMain, webContents} = require 'electron' {ipcMain, webContents} = require 'electron'
webViewManager = null # Doesn't exist in early initialization. ### Doesn't exist in early initialization. ###
webViewManager = null
supportedWebViewEvents = [ supportedWebViewEvents = [
'load-commit' 'load-commit'
@ -40,15 +41,15 @@ guestInstances = {}
embedderElementsMap = {} embedderElementsMap = {}
reverseEmbedderElementsMap = {} reverseEmbedderElementsMap = {}
# Moves the last element of array to the first one. ### Moves the last element of array to the first one. ###
moveLastToFirst = (list) -> moveLastToFirst = (list) ->
list.unshift list.pop() list.unshift list.pop()
# Generate guestInstanceId. ### Generate guestInstanceId. ###
getNextInstanceId = (webContents) -> getNextInstanceId = (webContents) ->
++nextInstanceId ++nextInstanceId
# Create a new guest instance. ### Create a new guest instance. ###
createGuest = (embedder, params) -> createGuest = (embedder, params) ->
webViewManager ?= process.atomBinding 'web_view_manager' webViewManager ?= process.atomBinding 'web_view_manager'
@ -56,21 +57,23 @@ createGuest = (embedder, params) ->
guest = webContents.create {isGuest: true, partition: params.partition, embedder} guest = webContents.create {isGuest: true, partition: params.partition, embedder}
guestInstances[id] = {guest, embedder} guestInstances[id] = {guest, embedder}
# Destroy guest when the embedder is gone or navigated. ### Destroy guest when the embedder is gone or navigated. ###
destroyEvents = ['destroyed', 'crashed', 'did-navigate'] destroyEvents = ['destroyed', 'crashed', 'did-navigate']
destroy = -> destroy = ->
destroyGuest embedder, id if guestInstances[id]? destroyGuest embedder, id if guestInstances[id]?
for event in destroyEvents for event in destroyEvents
embedder.once event, destroy embedder.once event, destroy
# Users might also listen to the crashed event, so We must ensure the guest ###
# is destroyed before users' listener gets called. It is done by moving our Users might also listen to the crashed event, so We must ensure the guest
# listener to the first one in queue. is destroyed before users' listener gets called. It is done by moving our
listener to the first one in queue.
###
listeners = embedder._events[event] listeners = embedder._events[event]
moveLastToFirst listeners if Array.isArray listeners moveLastToFirst listeners if Array.isArray listeners
guest.once 'destroyed', -> guest.once 'destroyed', ->
embedder.removeListener event, destroy for event in destroyEvents embedder.removeListener event, destroy for event in destroyEvents
# Init guest web view after attached. ### Init guest web view after attached. ###
guest.once 'did-attach', -> guest.once 'did-attach', ->
params = @attachParams params = @attachParams
delete @attachParams delete @attachParams
@ -96,32 +99,32 @@ createGuest = (embedder, params) ->
guest.allowPopups = params.allowpopups guest.allowPopups = params.allowpopups
# Dispatch events to embedder. ### Dispatch events to embedder. ###
for event in supportedWebViewEvents for event in supportedWebViewEvents
do (event) -> do (event) ->
guest.on event, (_, args...) -> guest.on event, (_, args...) ->
embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{guest.viewInstanceId}", event, args... embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{guest.viewInstanceId}", event, args...
# Dispatch guest's IPC messages to embedder. ### Dispatch guest's IPC messages to embedder. ###
guest.on 'ipc-message-host', (_, packed) -> guest.on 'ipc-message-host', (_, packed) ->
[channel, args...] = packed [channel, args...] = packed
embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{guest.viewInstanceId}", channel, args... embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{guest.viewInstanceId}", channel, args...
# Autosize. ### Autosize. ###
guest.on 'size-changed', (_, args...) -> guest.on 'size-changed', (_, args...) ->
embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{guest.viewInstanceId}", args... embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{guest.viewInstanceId}", args...
id id
# Attach the guest to an element of embedder. ### Attach the guest to an element of embedder. ###
attachGuest = (embedder, elementInstanceId, guestInstanceId, params) -> attachGuest = (embedder, elementInstanceId, guestInstanceId, params) ->
guest = guestInstances[guestInstanceId].guest guest = guestInstances[guestInstanceId].guest
# Destroy the old guest when attaching. ### Destroy the old guest when attaching. ###
key = "#{embedder.getId()}-#{elementInstanceId}" key = "#{embedder.getId()}-#{elementInstanceId}"
oldGuestInstanceId = embedderElementsMap[key] oldGuestInstanceId = embedderElementsMap[key]
if oldGuestInstanceId? if oldGuestInstanceId?
# Reattachment to the same guest is not currently supported. ### Reattachment to the same guest is not currently supported. ###
return unless oldGuestInstanceId != guestInstanceId return unless oldGuestInstanceId != guestInstanceId
return unless guestInstances[oldGuestInstanceId]? return unless guestInstances[oldGuestInstanceId]?
@ -139,7 +142,7 @@ attachGuest = (embedder, elementInstanceId, guestInstanceId, params) ->
embedderElementsMap[key] = guestInstanceId embedderElementsMap[key] = guestInstanceId
reverseEmbedderElementsMap[guestInstanceId] = key reverseEmbedderElementsMap[guestInstanceId] = key
# Destroy an existing guest instance. ### Destroy an existing guest instance. ###
destroyGuest = (embedder, id) -> destroyGuest = (embedder, id) ->
webViewManager.removeGuest embedder, id webViewManager.removeGuest embedder, id
guestInstances[id].guest.destroy() guestInstances[id].guest.destroy()
@ -165,10 +168,10 @@ ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', (event, id, params) ->
ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', (event, id, allowtransparency) -> ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', (event, id, allowtransparency) ->
guestInstances[id]?.guest.setAllowTransparency allowtransparency guestInstances[id]?.guest.setAllowTransparency allowtransparency
# Returns WebContents from its guest id. ### Returns WebContents from its guest id. ###
exports.getGuest = (id) -> exports.getGuest = (id) ->
guestInstances[id]?.guest guestInstances[id]?.guest
# Returns the embedder of the guest. ### Returns the embedder of the guest. ###
exports.getEmbedder = (id) -> exports.getEmbedder = (id) ->
guestInstances[id]?.embedder guestInstances[id]?.embedder

View file

@ -3,7 +3,7 @@ v8Util = process.atomBinding 'v8_util'
frameToGuest = {} frameToGuest = {}
# Copy attribute of |parent| to |child| if it is not defined in |child|. ### Copy attribute of |parent| to |child| if it is not defined in |child|. ###
mergeOptions = (child, parent) -> mergeOptions = (child, parent) ->
for own key, value of parent when key not of child for own key, value of parent when key not of child
if typeof value is 'object' if typeof value is 'object'
@ -12,34 +12,36 @@ mergeOptions = (child, parent) ->
child[key] = value child[key] = value
child child
# Merge |options| with the |embedder|'s window's options. ### Merge |options| with the |embedder|'s window's options. ###
mergeBrowserWindowOptions = (embedder, options) -> mergeBrowserWindowOptions = (embedder, options) ->
if embedder.browserWindowOptions? if embedder.browserWindowOptions?
# Inherit the original options if it is a BrowserWindow. ### Inherit the original options if it is a BrowserWindow. ###
mergeOptions options, embedder.browserWindowOptions mergeOptions options, embedder.browserWindowOptions
else else
# Or only inherit web-preferences if it is a webview. ### Or only inherit web-preferences if it is a webview. ###
options.webPreferences ?= {} options.webPreferences ?= {}
mergeOptions options.webPreferences, embedder.getWebPreferences() mergeOptions options.webPreferences, embedder.getWebPreferences()
options options
# Create a new guest created by |embedder| with |options|. ### Create a new guest created by |embedder| with |options|. ###
createGuest = (embedder, url, frameName, options) -> createGuest = (embedder, url, frameName, options) ->
guest = frameToGuest[frameName] guest = frameToGuest[frameName]
if frameName and guest? if frameName and guest?
guest.loadURL url guest.loadURL url
return guest.id return guest.id
# Remember the embedder window's id. ### Remember the embedder window's id. ###
options.webPreferences ?= {} options.webPreferences ?= {}
options.webPreferences.openerId = BrowserWindow.fromWebContents(embedder)?.id options.webPreferences.openerId = BrowserWindow.fromWebContents(embedder)?.id
guest = new BrowserWindow(options) guest = new BrowserWindow(options)
guest.loadURL url 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 When |embedder| is destroyed we should also destroy attached guest, and if
# closing guest. guest is closed by user then we should prevent |embedder| from double
closing guest.
###
guestId = guest.id guestId = guest.id
closedByEmbedder = -> closedByEmbedder = ->
guest.removeListener 'closed', closedByUser guest.removeListener 'closed', closedByUser
@ -58,7 +60,7 @@ createGuest = (embedder, url, frameName, options) ->
guest.id guest.id
# Routed window.open messages. ### Routed window.open messages. ###
ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) ->
[url, frameName, options] = args [url, frameName, options] = args
options = mergeBrowserWindowOptions event.sender, options options = mergeBrowserWindowOptions event.sender, options

View file

@ -3,26 +3,28 @@ path = require 'path'
util = require 'util' util = require 'util'
Module = require 'module' Module = require 'module'
# We modified the original process.argv to let node.js load the atom.js, ### We modified the original process.argv to let node.js load the atom.js, ###
# we need to restore it here. ### we need to restore it here. ###
process.argv.splice 1, 1 process.argv.splice 1, 1
# Clear search paths. ### Clear search paths. ###
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')
# Import common settings. ### Import common settings. ###
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')
globalPaths = Module.globalPaths globalPaths = Module.globalPaths
unless process.env.ELECTRON_HIDE_INTERNAL_MODULES unless process.env.ELECTRON_HIDE_INTERNAL_MODULES
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') globalPaths.push path.resolve(__dirname, '..', 'api', 'lib')
# Expose public APIs. ### Expose public APIs. ###
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports')
if process.platform is 'win32' if process.platform is 'win32'
# Redirect node's console to use our own implementations, since node can not ###
# handle console output when running as GUI program. Redirect node's console to use our own implementations, since node can not
handle console output when running as GUI program.
###
consoleLog = (args...) -> consoleLog = (args...) ->
process.log util.format(args...) + "\n" process.log util.format(args...) + "\n"
streamWrite = (chunk, encoding, callback) -> streamWrite = (chunk, encoding, callback) ->
@ -33,40 +35,40 @@ if process.platform is 'win32'
console.log = console.error = console.warn = consoleLog console.log = console.error = console.warn = consoleLog
process.stdout.write = process.stderr.write = streamWrite process.stdout.write = process.stderr.write = streamWrite
# Always returns EOF for stdin stream. ### Always returns EOF for stdin stream. ###
Readable = require('stream').Readable Readable = require('stream').Readable
stdin = new Readable stdin = new Readable
stdin.push null stdin.push null
process.__defineGetter__ 'stdin', -> stdin process.__defineGetter__ 'stdin', -> stdin
# Don't quit on fatal error. ### Don't quit on fatal error. ###
process.on 'uncaughtException', (error) -> process.on 'uncaughtException', (error) ->
# Do nothing if the user has a custom uncaught exception handler. ### Do nothing if the user has a custom uncaught exception handler. ###
if process.listeners('uncaughtException').length > 1 if process.listeners('uncaughtException').length > 1
return return
# Show error in GUI. ### Show error in GUI. ###
{dialog} = require 'electron' {dialog} = require 'electron'
stack = error.stack ? "#{error.name}: #{error.message}" stack = error.stack ? "#{error.name}: #{error.message}"
message = "Uncaught Exception:\n#{stack}" message = "Uncaught Exception:\n#{stack}"
dialog.showErrorBox 'A JavaScript error occurred in the main process', message dialog.showErrorBox 'A JavaScript error occurred in the main process', message
# Emit 'exit' event on quit. ### Emit 'exit' event on quit. ###
{app} = require 'electron' {app} = require 'electron'
app.on 'quit', (event, exitCode) -> app.on 'quit', (event, exitCode) ->
process.emit 'exit', exitCode process.emit 'exit', exitCode
# Map process.exit to app.exit, which quits gracefully. ### Map process.exit to app.exit, which quits gracefully. ###
process.exit = app.exit process.exit = app.exit
# Load the RPC server. ### Load the RPC server. ###
require './rpc-server' require './rpc-server'
# Load the guest view manager. ### Load the guest view manager. ###
require './guest-view-manager' require './guest-view-manager'
require './guest-window-manager' require './guest-window-manager'
# Now we try to load app's package.json. ### Now we try to load app's package.json. ###
packageJson = null packageJson = null
searchPaths = [ 'app', 'app.asar', 'default_app' ] searchPaths = [ 'app', 'app.asar', 'default_app' ]
@ -82,37 +84,37 @@ unless packageJson?
process.nextTick -> process.exit 1 process.nextTick -> process.exit 1
throw new Error("Unable to find a valid app") throw new Error("Unable to find a valid app")
# Set application's version. ### Set application's version. ###
app.setVersion packageJson.version if packageJson.version? app.setVersion packageJson.version if packageJson.version?
# Set application's name. ### Set application's name. ###
if packageJson.productName? if packageJson.productName?
app.setName packageJson.productName app.setName packageJson.productName
else if packageJson.name? else if packageJson.name?
app.setName packageJson.name app.setName packageJson.name
# Set application's desktop name. ### Set application's desktop name. ###
if packageJson.desktopName? if packageJson.desktopName?
app.setDesktopName packageJson.desktopName app.setDesktopName packageJson.desktopName
else else
app.setDesktopName "#{app.getName()}.desktop" app.setDesktopName "#{app.getName()}.desktop"
# Chrome 42 disables NPAPI plugins by default, reenable them here ### Chrome 42 disables NPAPI plugins by default, reenable them here ###
app.commandLine.appendSwitch 'enable-npapi' app.commandLine.appendSwitch 'enable-npapi'
# Set the user path according to application's name. ### Set the user path according to application's name. ###
app.setPath 'userData', path.join(app.getPath('appData'), app.getName()) app.setPath 'userData', path.join(app.getPath('appData'), app.getName())
app.setPath 'userCache', path.join(app.getPath('cache'), app.getName()) app.setPath 'userCache', path.join(app.getPath('cache'), app.getName())
app.setAppPath packagePath app.setAppPath packagePath
# Load the chrome extension support. ### Load the chrome extension support. ###
require './chrome-extension' require './chrome-extension'
# Load internal desktop-capturer module. ### Load internal desktop-capturer module. ###
require './desktop-capturer' require './desktop-capturer'
# Set main startup script of the app. ### Set main startup script of the app. ###
mainStartupScript = packageJson.main or 'index.js' mainStartupScript = packageJson.main or 'index.js'
# Finally load app's main.js and transfer control to C++. ### Finally load app's main.js and transfer control to C++. ###
Module._load path.join(packagePath, mainStartupScript), Module, true Module._load path.join(packagePath, mainStartupScript), Module, true

View file

@ -6,46 +6,52 @@ class ObjectsRegistry extends EventEmitter
@setMaxListeners Number.MAX_VALUE @setMaxListeners Number.MAX_VALUE
@nextId = 0 @nextId = 0
# Stores all objects by ref-counting. ###
# (id) => {object, count} Stores all objects by ref-counting.
(id) => {object, count}
###
@storage = {} @storage = {}
# Stores the IDs of objects referenced by WebContents. ###
# (webContentsId) => {(id) => (count)} Stores the IDs of objects referenced by WebContents.
(webContentsId) => {(id) => (count)}
###
@owners = {} @owners = {}
# Register a new object, the object would be kept referenced until you release ###
# it explicitly. Register a new object, the object would be kept referenced until you release
it explicitly.
###
add: (webContentsId, obj) -> add: (webContentsId, obj) ->
id = @saveToStorage obj id = @saveToStorage obj
# Remember the owner. ### Remember the owner. ###
@owners[webContentsId] ?= {} @owners[webContentsId] ?= {}
@owners[webContentsId][id] ?= 0 @owners[webContentsId][id] ?= 0
@owners[webContentsId][id]++ @owners[webContentsId][id]++
# Returns object's id ### Returns object's id ###
id id
# Get an object according to its ID. ### Get an object according to its ID. ###
get: (id) -> get: (id) ->
@storage[id]?.object @storage[id]?.object
# Dereference an object according to its ID. ### Dereference an object according to its ID. ###
remove: (webContentsId, id) -> remove: (webContentsId, id) ->
@dereference id, 1 @dereference id, 1
# Also reduce the count in owner. ### Also reduce the count in owner. ###
pointer = @owners[webContentsId] pointer = @owners[webContentsId]
return unless pointer? return unless pointer?
--pointer[id] --pointer[id]
delete pointer[id] if pointer[id] is 0 delete pointer[id] if pointer[id] is 0
# Clear all references to objects refrenced by the WebContents. ### Clear all references to objects refrenced by the WebContents. ###
clear: (webContentsId) -> clear: (webContentsId) ->
@emit "clear-#{webContentsId}" @emit "clear-#{webContentsId}"
return unless @owners[webContentsId]? return unless @owners[webContentsId]?
@dereference id, count for id, count of @owners[webContentsId] @dereference id, count for id, count of @owners[webContentsId]
delete @owners[webContentsId] delete @owners[webContentsId]
# Private: Saves the object into storage and assigns an ID for it. ### Private: Saves the object into storage and assigns an ID for it. ###
saveToStorage: (object) -> saveToStorage: (object) ->
id = v8Util.getHiddenValue object, 'atomId' id = v8Util.getHiddenValue object, 'atomId'
unless id unless id
@ -55,7 +61,7 @@ class ObjectsRegistry extends EventEmitter
++@storage[id].count ++@storage[id].count
id id
# Private: Dereference the object from store. ### Private: Dereference the object from store. ###
dereference: (id, count) -> dereference: (id, count) ->
pointer = @storage[id] pointer = @storage[id]
return unless pointer? return unless pointer?

View file

@ -7,7 +7,7 @@ objectsRegistry = require './objects-registry'
v8Util = process.atomBinding 'v8_util' v8Util = process.atomBinding 'v8_util'
{IDWeakMap} = process.atomBinding 'id_weak_map' {IDWeakMap} = process.atomBinding 'id_weak_map'
# Convert a real value into meta data. ### Convert a real value into meta data. ###
valueToMeta = (sender, value, optimizeSimpleObject=false) -> valueToMeta = (sender, value, optimizeSimpleObject=false) ->
meta = type: typeof value meta = type: typeof value
@ -18,11 +18,11 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) ->
meta.type = 'date' if value instanceof Date meta.type = 'date' if value instanceof Date
meta.type = 'promise' if value?.constructor.name is 'Promise' meta.type = 'promise' if value?.constructor.name is 'Promise'
# Treat simple objects as value. ### Treat simple objects as value. ###
if optimizeSimpleObject and meta.type is 'object' and v8Util.getHiddenValue value, 'simple' if optimizeSimpleObject and meta.type is 'object' and v8Util.getHiddenValue value, 'simple'
meta.type = 'value' meta.type = 'value'
# Treat the arguments object as array. ### Treat the arguments object as array. ###
meta.type = 'array' if meta.type is 'object' and value.callee? and value.length? meta.type = 'array' if meta.type is 'object' and value.callee? and value.length?
if meta.type is 'array' if meta.type is 'array'
@ -31,9 +31,11 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) ->
else if meta.type is 'object' or meta.type is 'function' else if meta.type is 'object' or meta.type is 'function'
meta.name = value.constructor.name meta.name = value.constructor.name
# Reference the original value if it's an object, because when it's ###
# passed to renderer we would assume the renderer keeps a reference of Reference the original value if it's an object, because when it's
# it. passed to renderer we would assume the renderer keeps a reference of
it.
###
meta.id = objectsRegistry.add sender.getId(), value meta.id = objectsRegistry.add sender.getId(), value
meta.members = ({name, type: typeof field} for name, field of value) meta.members = ({name, type: typeof field} for name, field of value)
@ -43,7 +45,7 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) ->
meta.then = valueToMeta sender, value.then.bind(value) meta.then = valueToMeta sender, value.then.bind(value)
else if meta.type is 'error' else if meta.type is 'error'
meta.members = plainObjectToMeta value meta.members = plainObjectToMeta value
# Error.name is not part of own properties. ### Error.name is not part of own properties. ###
meta.members.push {name: 'name', value: value.name} meta.members.push {name: 'name', value: value.name}
else if meta.type is 'date' else if meta.type is 'date'
meta.value = value.getTime() meta.value = value.getTime()
@ -53,15 +55,15 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) ->
meta meta
# Convert object to meta by value. ### Convert object to meta by value. ###
plainObjectToMeta = (obj) -> plainObjectToMeta = (obj) ->
Object.getOwnPropertyNames(obj).map (name) -> {name, value: obj[name]} Object.getOwnPropertyNames(obj).map (name) -> {name, value: obj[name]}
# Convert Error into meta data. ### Convert Error into meta data. ###
exceptionToMeta = (error) -> exceptionToMeta = (error) ->
type: 'exception', message: error.message, stack: (error.stack || error) type: 'exception', message: error.message, stack: (error.stack || error)
# Convert array of meta data from renderer into array of real values. ### Convert array of meta data from renderer into array of real values. ###
unwrapArgs = (sender, args) -> unwrapArgs = (sender, args) ->
metaToValue = (meta) -> metaToValue = (meta) ->
switch meta.type switch meta.type
@ -80,7 +82,7 @@ unwrapArgs = (sender, args) ->
returnValue = metaToValue meta.value returnValue = metaToValue meta.value
-> returnValue -> returnValue
when 'function' when 'function'
# Cache the callbacks in renderer. ### Cache the callbacks in renderer. ###
unless sender.callbacks unless sender.callbacks
sender.callbacks = new IDWeakMap sender.callbacks = new IDWeakMap
sender.on 'render-view-deleted', -> sender.on 'render-view-deleted', ->
@ -106,8 +108,10 @@ unwrapArgs = (sender, args) ->
args.map metaToValue args.map metaToValue
# Call a function and send reply asynchronously if it's a an asynchronous ###
# style function and the caller didn't pass a callback. Call a function and send reply asynchronously if it's a an asynchronous
style function and the caller didn't pass a callback.
###
callFunction = (event, func, caller, args) -> callFunction = (event, func, caller, args) ->
funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
funcPassedCallback = typeof args[args.length - 1] is 'function' funcPassedCallback = typeof args[args.length - 1] is 'function'
@ -121,15 +125,17 @@ callFunction = (event, func, caller, args) ->
ret = func.apply caller, args ret = func.apply caller, args
event.returnValue = valueToMeta event.sender, ret, true event.returnValue = valueToMeta event.sender, ret, true
catch e catch e
# Catch functions thrown further down in function invocation and wrap ###
# them with the function name so it's easier to trace things like Catch functions thrown further down in function invocation and wrap
# `Error processing argument -1.` them with the function name so it's easier to trace things like
`Error processing argument -1.`
###
funcName = func.name ? "anonymous" funcName = func.name ? "anonymous"
throw new Error("Could not call remote function `#{funcName}`. throw new Error("Could not call remote function `#{funcName}`.
Check that the function signature is correct. Check that the function signature is correct.
Underlying error: #{e.message}") Underlying error: #{e.message}")
# Send by BrowserWindow when its render view is deleted. ### Send by BrowserWindow when its render view is deleted. ###
process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) -> process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) ->
objectsRegistry.clear id objectsRegistry.clear id
@ -164,8 +170,10 @@ ipcMain.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) ->
try try
args = unwrapArgs event.sender, args args = unwrapArgs event.sender, args
constructor = objectsRegistry.get id constructor = objectsRegistry.get id
# Call new with array of arguments. ###
# http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible Call new with array of arguments.
http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
###
obj = new (Function::bind.apply(constructor, [null].concat(args))) obj = new (Function::bind.apply(constructor, [null].concat(args)))
event.returnValue = valueToMeta event.sender, obj event.returnValue = valueToMeta event.sender, obj
catch e catch e
@ -183,7 +191,7 @@ ipcMain.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) ->
try try
args = unwrapArgs event.sender, args args = unwrapArgs event.sender, args
constructor = objectsRegistry.get(id)[method] constructor = objectsRegistry.get(id)[method]
# Call new with array of arguments. ### Call new with array of arguments. ###
obj = new (Function::bind.apply(constructor, [null].concat(args))) obj = new (Function::bind.apply(constructor, [null].concat(args)))
event.returnValue = valueToMeta event.sender, obj event.returnValue = valueToMeta event.sender, obj
catch e catch e

View file

@ -7,14 +7,16 @@ class CallbacksRegistry
@callbacks = {} @callbacks = {}
add: (callback) -> add: (callback) ->
# The callback is already added. ### The callback is already added. ###
id = v8Util.getHiddenValue callback, 'callbackId' id = v8Util.getHiddenValue callback, 'callbackId'
return id if id? return id if id?
id = ++@nextId id = ++@nextId
# Capture the location of the function and put it in the ID string, ###
# so that release errors can be tracked down easily. Capture the location of the function and put it in the ID string,
so that release errors can be tracked down easily.
###
regexp = /at (.*)/gi regexp = /at (.*)/gi
stackString = (new Error).stack stackString = (new Error).stack

View file

@ -1,5 +1,5 @@
if process.platform is 'linux' and process.type is 'renderer' if process.platform is 'linux' and process.type is 'renderer'
# On Linux we could not access clipboard in renderer process. ### On Linux we could not access clipboard in renderer process. ###
module.exports = require('electron').remote.clipboard module.exports = require('electron').remote.clipboard
else else
module.exports = process.atomBinding 'clipboard' module.exports = process.atomBinding 'clipboard'

View file

@ -10,7 +10,7 @@ class CrashReporter
start: (options={}) -> start: (options={}) ->
{@productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra} = options {@productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra} = options
# Deprecated. ### Deprecated. ###
{deprecate} = electron {deprecate} = electron
if options.submitUrl if options.submitUrl
submitURL ?= options.submitUrl submitURL ?= options.submitUrl

View file

@ -1,4 +1,4 @@
# Deprecate a method. ### Deprecate a method. ###
deprecate = (oldName, newName, fn) -> deprecate = (oldName, newName, fn) ->
warned = false warned = false
-> ->
@ -7,7 +7,7 @@ deprecate = (oldName, newName, fn) ->
deprecate.warn oldName, newName deprecate.warn oldName, newName
fn.apply this, arguments fn.apply this, arguments
# The method is renamed. ### The method is renamed. ###
deprecate.rename = (object, oldName, newName) -> deprecate.rename = (object, oldName, newName) ->
warned = false warned = false
newMethod = -> newMethod = ->
@ -20,7 +20,7 @@ deprecate.rename = (object, oldName, newName) ->
else else
object[oldName] = newMethod object[oldName] = newMethod
# Forward the method to member. ### Forward the method to member. ###
deprecate.member = (object, method, member) -> deprecate.member = (object, method, member) ->
warned = false warned = false
object.prototype[method] = -> object.prototype[method] = ->
@ -29,7 +29,7 @@ deprecate.member = (object, method, member) ->
deprecate.warn method, "#{member}.#{method}" deprecate.warn method, "#{member}.#{method}"
this[member][method].apply this[member], arguments this[member][method].apply this[member], arguments
# Deprecate a property. ### Deprecate a property. ###
deprecate.property = (object, property, method) -> deprecate.property = (object, property, method) ->
Object.defineProperty object, property, Object.defineProperty object, property,
get: -> get: ->
@ -39,11 +39,12 @@ deprecate.property = (object, property, method) ->
deprecate.warn "#{property} property", "#{method} method" deprecate.warn "#{property} property", "#{method} method"
this[method]() this[method]()
# Deprecate an event. ### Deprecate an event. ###
deprecate.event = (emitter, oldName, newName, fn) -> deprecate.event = (emitter, oldName, newName, fn) ->
warned = false warned = false
emitter.on newName, (args...) -> emitter.on newName, (args...) ->
if @listenerCount(oldName) > 0 # there is listeners for old API. ### there is listeners for old API. ###
if @listenerCount(oldName) > 0
unless warned or process.noDeprecation unless warned or process.noDeprecation
warned = true warned = true
deprecate.warn "'#{oldName}' event", "'#{newName}' event" deprecate.warn "'#{oldName}' event", "'#{newName}' event"
@ -52,11 +53,11 @@ deprecate.event = (emitter, oldName, newName, fn) ->
else else
@emit oldName, args... @emit oldName, args...
# Print deprecation warning. ### Print deprecation warning. ###
deprecate.warn = (oldName, newName) -> deprecate.warn = (oldName, newName) ->
deprecate.log "#{oldName} is deprecated. Use #{newName} instead." deprecate.log "#{oldName} is deprecated. Use #{newName} instead."
# Print deprecation message. ### Print deprecation message. ###
deprecate.log = (message) -> deprecate.log = (message) ->
if process.throwDeprecation if process.throwDeprecation
throw new Error(message) throw new Error(message)

View file

@ -1,16 +1,16 @@
# Do not expose the internal modules to `require`. ### Do not expose the internal modules to `require`. ###
exports.hideInternalModules = -> exports.hideInternalModules = ->
{globalPaths} = require 'module' {globalPaths} = require 'module'
if globalPaths.length is 3 if globalPaths.length is 3
# Remove the "common/api/lib" and "browser-or-renderer/api/lib". ### Remove the "common/api/lib" and "browser-or-renderer/api/lib". ###
globalPaths.splice 0, 2 globalPaths.splice 0, 2
# Attaches properties to |exports|. ### Attaches properties to |exports|. ###
exports.defineProperties = (exports) -> exports.defineProperties = (exports) ->
Object.defineProperties exports, Object.defineProperties exports,
# Common modules, please sort with alphabet order. ### Common modules, please sort with alphabet order. ###
clipboard: clipboard:
# Must be enumerable, otherwise it woulde be invisible to remote module. ### Must be enumerable, otherwise it woulde be invisible to remote module. ###
enumerable: true enumerable: true
get: -> require '../clipboard' get: -> require '../clipboard'
crashReporter: crashReporter:
@ -22,7 +22,7 @@ exports.defineProperties = (exports) ->
shell: shell:
enumerable: true enumerable: true
get: -> require '../shell' get: -> require '../shell'
# The internal modules, invisible unless you know their names. ### The internal modules, invisible unless you know their names. ###
CallbacksRegistry: CallbacksRegistry:
get: -> require '../callbacks-registry' get: -> require '../callbacks-registry'
deprecate: deprecate:

View file

@ -1,7 +1,7 @@
{deprecate} = require 'electron' {deprecate} = require 'electron'
nativeImage = process.atomBinding 'native_image' nativeImage = process.atomBinding 'native_image'
# Deprecated. ### Deprecated. ###
deprecate.rename nativeImage, 'createFromDataUrl', 'createFromDataURL' deprecate.rename nativeImage, 'createFromDataUrl', 'createFromDataURL'
module.exports = nativeImage module.exports = nativeImage

View file

@ -3,7 +3,7 @@ child_process = require 'child_process'
path = require 'path' path = require 'path'
util = require 'util' util = require 'util'
# Cache asar archive objects. ### Cache asar archive objects. ###
cachedArchives = {} cachedArchives = {}
getOrCreateArchive = (p) -> getOrCreateArchive = (p) ->
archive = cachedArchives[p] archive = cachedArchives[p]
@ -12,13 +12,15 @@ getOrCreateArchive = (p) ->
return false unless archive return false unless archive
cachedArchives[p] = archive cachedArchives[p] = archive
# Clean cache on quit. ### Clean cache on quit. ###
process.on 'exit', -> process.on 'exit', ->
archive.destroy() for own p, archive of cachedArchives archive.destroy() for own p, archive of cachedArchives
# Separate asar package's path from full path. ### Separate asar package's path from full path. ###
splitPath = (p) -> splitPath = (p) ->
return [false] if process.noAsar # shortcut to disable asar. ### shortcut to disable asar. ###
return [false] if process.noAsar
return [false] if typeof p isnt 'string' return [false] if typeof p isnt 'string'
return [true, p, ''] if p.substr(-5) is '.asar' return [true, p, ''] if p.substr(-5) is '.asar'
p = path.normalize p p = path.normalize p
@ -26,7 +28,7 @@ splitPath = (p) ->
return [false] if index is -1 return [false] if index is -1
[true, p.substr(0, index + 5), p.substr(index + 6)] [true, p.substr(0, index + 5), p.substr(index + 6)]
# Convert asar archive's Stats object to fs's Stats object. ### Convert asar archive's Stats object to fs's Stats object. ###
nextInode = 0 nextInode = 0
uid = if process.getuid? then process.getuid() else 0 uid = if process.getuid? then process.getuid() else 0
gid = if process.getgid? then process.getgid() else 0 gid = if process.getgid? then process.getgid() else 0
@ -54,7 +56,7 @@ asarStatsToFsStats = (stats) ->
isSocket: -> false isSocket: -> false
} }
# Create a ENOENT error. ### Create a ENOENT error. ###
notFoundError = (asarPath, filePath, callback) -> notFoundError = (asarPath, filePath, callback) ->
error = new Error("ENOENT, #{filePath} not found in #{asarPath}") error = new Error("ENOENT, #{filePath} not found in #{asarPath}")
error.code = "ENOENT" error.code = "ENOENT"
@ -63,7 +65,7 @@ notFoundError = (asarPath, filePath, callback) ->
throw error throw error
process.nextTick -> callback error process.nextTick -> callback error
# Create a ENOTDIR error. ### Create a ENOTDIR error. ###
notDirError = (callback) -> notDirError = (callback) ->
error = new Error('ENOTDIR, not a directory') error = new Error('ENOTDIR, not a directory')
error.code = 'ENOTDIR' error.code = 'ENOTDIR'
@ -72,14 +74,14 @@ notDirError = (callback) ->
throw error throw error
process.nextTick -> callback error process.nextTick -> callback error
# Create invalid archive error. ### Create invalid archive error. ###
invalidArchiveError = (asarPath, callback) -> invalidArchiveError = (asarPath, callback) ->
error = new Error("Invalid package #{asarPath}") error = new Error("Invalid package #{asarPath}")
unless typeof callback is 'function' unless typeof callback is 'function'
throw error throw error
process.nextTick -> callback error process.nextTick -> callback error
# Override APIs that rely on passing file path instead of content to C++. ### Override APIs that rely on passing file path instead of content to C++. ###
overrideAPISync = (module, name, arg = 0) -> overrideAPISync = (module, name, arg = 0) ->
old = module[name] old = module[name]
module[name] = -> module[name] = ->
@ -115,7 +117,7 @@ overrideAPI = (module, name, arg = 0) ->
arguments[arg] = newPath arguments[arg] = newPath
old.apply this, arguments old.apply this, arguments
# Override fs APIs. ### Override fs APIs. ###
exports.wrapFsWithAsar = (fs) -> exports.wrapFsWithAsar = (fs) ->
lstatSync = fs.lstatSync lstatSync = fs.lstatSync
fs.lstatSync = (p) -> fs.lstatSync = (p) ->
@ -148,7 +150,7 @@ exports.wrapFsWithAsar = (fs) ->
[isAsar, asarPath, filePath] = splitPath p [isAsar, asarPath, filePath] = splitPath p
return statSync p unless isAsar return statSync p unless isAsar
# Do not distinguish links for now. ### Do not distinguish links for now. ###
fs.lstatSync p fs.lstatSync p
stat = fs.stat stat = fs.stat
@ -156,7 +158,7 @@ exports.wrapFsWithAsar = (fs) ->
[isAsar, asarPath, filePath] = splitPath p [isAsar, asarPath, filePath] = splitPath p
return stat p, callback unless isAsar return stat p, callback unless isAsar
# Do not distinguish links for now. ### Do not distinguish links for now. ###
process.nextTick -> fs.lstat p, callback process.nextTick -> fs.lstat p, callback
statSyncNoException = fs.statSyncNoException statSyncNoException = fs.statSyncNoException
@ -265,7 +267,9 @@ exports.wrapFsWithAsar = (fs) ->
openSync = fs.openSync openSync = fs.openSync
readFileSync = fs.readFileSync readFileSync = fs.readFileSync
fs.readFileSync = (p, opts) -> fs.readFileSync = (p, opts) ->
options = opts # this allows v8 to optimize this function ### this allows v8 to optimize this function ###
options = opts
[isAsar, asarPath, filePath] = splitPath p [isAsar, asarPath, filePath] = splitPath p
return readFileSync.apply this, arguments unless isAsar return readFileSync.apply this, arguments unless isAsar
@ -353,17 +357,21 @@ exports.wrapFsWithAsar = (fs) ->
return internalModuleStat p unless isAsar return internalModuleStat p unless isAsar
archive = getOrCreateArchive asarPath archive = getOrCreateArchive asarPath
return -34 unless archive # -ENOENT ### -ENOENT ###
return -34 unless archive
stats = archive.stat filePath stats = archive.stat filePath
return -34 unless stats # -ENOENT ### -ENOENT ###
return -34 unless stats
if stats.isDirectory then return 1 else return 0 if stats.isDirectory then return 1 else return 0
# Calling mkdir for directory inside asar archive should throw ENOTDIR ###
# error, but on Windows it throws ENOENT. Calling mkdir for directory inside asar archive should throw ENOTDIR
# This is to work around the recursive looping bug of mkdirp since it is error, but on Windows it throws ENOENT.
# widely used. This is to work around the recursive looping bug of mkdirp since it is
widely used.
###
if process.platform is 'win32' if process.platform is 'win32'
mkdir = fs.mkdir mkdir = fs.mkdir
fs.mkdir = (p, mode, callback) -> fs.mkdir = (p, mode, callback) ->

View file

@ -1,13 +1,13 @@
return (process, require, asarSource) -> return (process, require, asarSource) ->
{createArchive} = process.binding 'atom_common_asar' {createArchive} = process.binding 'atom_common_asar'
# Make asar.coffee accessible via "require". ### Make asar.coffee accessible via "require". ###
process.binding('natives').ATOM_SHELL_ASAR = asarSource process.binding('natives').ATOM_SHELL_ASAR = asarSource
# Monkey-patch the fs module. ### Monkey-patch the fs module. ###
require('ATOM_SHELL_ASAR').wrapFsWithAsar require('fs') require('ATOM_SHELL_ASAR').wrapFsWithAsar require('fs')
# Make graceful-fs work with asar. ### Make graceful-fs work with asar. ###
source = process.binding 'natives' source = process.binding 'natives'
source['original-fs'] = source.fs source['original-fs'] = source.fs
source['fs'] = """ source['fs'] = """

View file

@ -10,15 +10,17 @@ process.atomBinding = (name) ->
process.binding "atom_common_#{name}" if /No such module/.test e.message process.binding "atom_common_#{name}" if /No such module/.test e.message
unless process.env.ELECTRON_HIDE_INTERNAL_MODULES unless process.env.ELECTRON_HIDE_INTERNAL_MODULES
# Add common/api/lib to module search paths. ### Add common/api/lib to module search paths. ###
Module.globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') Module.globalPaths.push path.resolve(__dirname, '..', 'api', 'lib')
# setImmediate and process.nextTick makes use of uv_check and uv_prepare to ###
# run the callbacks, however since we only run uv loop on requests, the setImmediate and process.nextTick makes use of uv_check and uv_prepare to
# callbacks wouldn't be called until something else activated the uv loop, run the callbacks, however since we only run uv loop on requests, the
# which would delay the callbacks for arbitrary long time. So we should callbacks wouldn't be called until something else activated the uv loop,
# initiatively activate the uv loop once setImmediate and process.nextTick is which would delay the callbacks for arbitrary long time. So we should
# called. initiatively activate the uv loop once setImmediate and process.nextTick is
called.
###
wrapWithActivateUvLoop = (func) -> wrapWithActivateUvLoop = (func) ->
-> ->
process.activateUvLoop() process.activateUvLoop()
@ -28,9 +30,11 @@ global.setImmediate = wrapWithActivateUvLoop timers.setImmediate
global.clearImmediate = timers.clearImmediate global.clearImmediate = timers.clearImmediate
if process.type is 'browser' if process.type is 'browser'
# setTimeout needs to update the polling timeout of the event loop, when ###
# called under Chromium's event loop the node's event loop won't get a chance setTimeout needs to update the polling timeout of the event loop, when
# to update the timeout, so we have to force the node's event loop to called under Chromium's event loop the node's event loop won't get a chance
# recalculate the timeout in browser process. to update the timeout, so we have to force the node's event loop to
recalculate the timeout in browser process.
###
global.setTimeout = wrapWithActivateUvLoop timers.setTimeout global.setTimeout = wrapWithActivateUvLoop timers.setTimeout
global.setInterval = wrapWithActivateUvLoop timers.setInterval global.setInterval = wrapWithActivateUvLoop timers.setInterval

View file

@ -1,21 +1,21 @@
path = require 'path' path = require 'path'
Module = require 'module' Module = require 'module'
# Clear Node's global search paths. ### Clear Node's global search paths. ###
Module.globalPaths.length = 0 Module.globalPaths.length = 0
# Clear current and parent(init.coffee)'s search paths. ### Clear current and parent(init.coffee)'s search paths. ###
module.paths = [] module.paths = []
module.parent.paths = [] module.parent.paths = []
# Prevent Node from adding paths outside this app to search paths. ### Prevent Node from adding paths outside this app to search paths. ###
Module._nodeModulePaths = (from) -> Module._nodeModulePaths = (from) ->
from = path.resolve from from = path.resolve from
# If "from" is outside the app then we do nothing. ### If "from" is outside the app then we do nothing. ###
skipOutsidePaths = from.startsWith process.resourcesPath skipOutsidePaths = from.startsWith process.resourcesPath
# Following logoic is copied from module.js. ### Following logoic is copied from module.js. ###
splitRe = if process.platform is 'win32' then /[\/\\]/ else /\// splitRe = if process.platform is 'win32' then /[\/\\]/ else /\//
paths = [] paths = []

View file

@ -3,7 +3,7 @@
nextId = 0 nextId = 0
getNextId = -> ++nextId getNextId = -> ++nextId
# |options.type| can not be empty and has to include 'window' or 'screen'. ### |options.type| can not be empty and has to include 'window' or 'screen'. ###
isValid = (options) -> isValid = (options) ->
return options?.types? and Array.isArray options.types return options?.types? and Array.isArray options.types

View file

@ -1,10 +1,10 @@
common = require '../../../../common/api/lib/exports/electron' common = require '../../../../common/api/lib/exports/electron'
# Import common modules. ### Import common modules. ###
common.defineProperties exports common.defineProperties exports
Object.defineProperties exports, Object.defineProperties exports,
# Renderer side modules, please sort with alphabet order. ### Renderer side modules, please sort with alphabet order. ###
desktopCapturer: desktopCapturer:
enumerable: true enumerable: true
get: -> require '../desktop-capturer' get: -> require '../desktop-capturer'

View file

@ -3,7 +3,7 @@
binding = process.atomBinding 'ipc' binding = process.atomBinding 'ipc'
v8Util = process.atomBinding 'v8_util' v8Util = process.atomBinding 'v8_util'
# Created by init.coffee. ### Created by init.coffee. ###
ipcRenderer = v8Util.getHiddenValue global, 'ipc' ipcRenderer = v8Util.getHiddenValue global, 'ipc'
ipcRenderer.send = (args...) -> ipcRenderer.send = (args...) ->

View file

@ -1,16 +1,16 @@
{ipcRenderer, deprecate} = require 'electron' {ipcRenderer, deprecate} = require 'electron'
{EventEmitter} = require 'events' {EventEmitter} = require 'events'
# This module is deprecated, we mirror everything from ipcRenderer. ### This module is deprecated, we mirror everything from ipcRenderer. ###
deprecate.warn 'ipc module', 'require("electron").ipcRenderer' deprecate.warn 'ipc module', 'require("electron").ipcRenderer'
# Routes events of ipcRenderer. ### Routes events of ipcRenderer. ###
ipc = new EventEmitter ipc = new EventEmitter
ipcRenderer.emit = (channel, event, args...) -> ipcRenderer.emit = (channel, event, args...) ->
ipc.emit channel, args... ipc.emit channel, args...
EventEmitter::emit.apply ipcRenderer, arguments EventEmitter::emit.apply ipcRenderer, arguments
# Deprecated. ### Deprecated. ###
for method of ipcRenderer when method.startsWith 'send' for method of ipcRenderer when method.startsWith 'send'
ipc[method] = ipcRenderer[method] ipc[method] = ipcRenderer[method]
deprecate.rename ipc, 'sendChannel', 'send' deprecate.rename ipc, 'sendChannel', 'send'

View file

@ -3,7 +3,7 @@ v8Util = process.atomBinding 'v8_util'
callbacksRegistry = new CallbacksRegistry callbacksRegistry = new CallbacksRegistry
# Check for circular reference. ### Check for circular reference. ###
isCircular = (field, visited) -> isCircular = (field, visited) ->
if typeof field is 'object' if typeof field is 'object'
if field in visited if field in visited
@ -11,7 +11,7 @@ isCircular = (field, visited) ->
visited.push field visited.push field
return false return false
# Convert the arguments object into an array of meta data. ### Convert the arguments object into an array of meta data. ###
wrapArgs = (args, visited=[]) -> wrapArgs = (args, visited=[]) ->
valueToMeta = (value) -> valueToMeta = (value) ->
if Array.isArray value if Array.isArray value
@ -40,7 +40,7 @@ wrapArgs = (args, visited=[]) ->
Array::slice.call(args).map valueToMeta Array::slice.call(args).map valueToMeta
# Convert meta data from browser into real value. ### Convert meta data from browser into real value. ###
metaToValue = (meta) -> metaToValue = (meta) ->
switch meta.type switch meta.type
when 'value' then meta.value when 'value' then meta.value
@ -53,43 +53,47 @@ metaToValue = (meta) ->
throw new Error("#{meta.message}\n#{meta.stack}") throw new Error("#{meta.message}\n#{meta.stack}")
else else
if meta.type is 'function' if meta.type is 'function'
# A shadow class to represent the remote function object. ### A shadow class to represent the remote function object. ###
ret = ret =
class RemoteFunction class RemoteFunction
constructor: -> constructor: ->
if @constructor == RemoteFunction if @constructor == RemoteFunction
# Constructor call. ### Constructor call. ###
obj = ipcRenderer.sendSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments) obj = ipcRenderer.sendSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)
# Returning object in constructor will replace constructed object ###
# with the returned object. Returning object in constructor will replace constructed object
# http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this with the returned object.
http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this
###
return metaToValue obj return metaToValue obj
else else
# Function call. ### Function call. ###
obj = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) obj = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)
return metaToValue obj return metaToValue obj
else else
ret = v8Util.createObjectWithName meta.name ret = v8Util.createObjectWithName meta.name
# Polulate delegate members. ### Polulate delegate members. ###
for member in meta.members for member in meta.members
if member.type is 'function' if member.type is 'function'
ret[member.name] = createRemoteMemberFunction meta.id, member.name ret[member.name] = createRemoteMemberFunction meta.id, member.name
else else
Object.defineProperty ret, member.name, createRemoteMemberProperty(meta.id, member.name) Object.defineProperty ret, member.name, createRemoteMemberProperty(meta.id, member.name)
# Track delegate object's life time, and tell the browser to clean up ###
# when the object is GCed. Track delegate object's life time, and tell the browser to clean up
when the object is GCed.
###
v8Util.setDestructor ret, -> v8Util.setDestructor ret, ->
ipcRenderer.send 'ATOM_BROWSER_DEREFERENCE', meta.id ipcRenderer.send 'ATOM_BROWSER_DEREFERENCE', meta.id
# Remember object's id. ### Remember object's id. ###
v8Util.setHiddenValue ret, 'atomId', meta.id v8Util.setHiddenValue ret, 'atomId', meta.id
ret ret
# Construct a plain object from the meta. ### Construct a plain object from the meta. ###
metaToPlainObject = (meta) -> metaToPlainObject = (meta) ->
obj = switch meta.type obj = switch meta.type
when 'error' then new Error when 'error' then new Error
@ -97,53 +101,59 @@ metaToPlainObject = (meta) ->
obj[name] = value for {name, value} in meta.members obj[name] = value for {name, value} in meta.members
obj obj
# Create a RemoteMemberFunction instance. ###
# This function's content should not be inlined into metaToValue, otherwise V8 Create a RemoteMemberFunction instance.
# may consider it circular reference. This function's content should not be inlined into metaToValue, otherwise V8
may consider it circular reference.
###
createRemoteMemberFunction = (metaId, name) -> createRemoteMemberFunction = (metaId, name) ->
class RemoteMemberFunction class RemoteMemberFunction
constructor: -> constructor: ->
if @constructor is RemoteMemberFunction if @constructor is RemoteMemberFunction
# Constructor call. ### Constructor call. ###
ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments) ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments)
return metaToValue ret return metaToValue ret
else else
# Call member function. ### Call member function. ###
ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments) ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments)
return metaToValue ret return metaToValue ret
# Create configuration for defineProperty. ###
# This function's content should not be inlined into metaToValue, otherwise V8 Create configuration for defineProperty.
# may consider it circular reference. This function's content should not be inlined into metaToValue, otherwise V8
may consider it circular reference.
###
createRemoteMemberProperty = (metaId, name) -> createRemoteMemberProperty = (metaId, name) ->
enumerable: true, enumerable: true,
configurable: false, configurable: false,
set: (value) -> set: (value) ->
# Set member data. ### Set member data. ###
ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', metaId, name, value ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', metaId, name, value
value value
get: -> get: ->
# Get member data. ### Get member data. ###
metaToValue ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name) metaToValue ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name)
# Browser calls a callback in renderer. ### Browser calls a callback in renderer. ###
ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) ->
callbacksRegistry.apply id, metaToValue(args) callbacksRegistry.apply id, metaToValue(args)
# A callback in browser is released. ### A callback in browser is released. ###
ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) ->
callbacksRegistry.remove id callbacksRegistry.remove id
# List all built-in modules in browser process. ### List all built-in modules in browser process. ###
browserModules = require '../../../browser/api/lib/exports/electron' browserModules = require '../../../browser/api/lib/exports/electron'
# And add a helper receiver for each one. ### And add a helper receiver for each one. ###
for name of browserModules for name of browserModules
do (name) -> do (name) ->
Object.defineProperty exports, name, get: -> exports.getBuiltin name Object.defineProperty exports, name, get: -> exports.getBuiltin name
# Get remote module. ###
# (Just like node's require, the modules are cached permanently, note that this Get remote module.
# is safe leak since the object is not expected to get freed in browser) (Just like node's require, the modules are cached permanently, note that this
is safe leak since the object is not expected to get freed in browser)
###
moduleCache = {} moduleCache = {}
exports.require = (module) -> exports.require = (module) ->
return moduleCache[module] if moduleCache[module]? return moduleCache[module] if moduleCache[module]?
@ -151,10 +161,10 @@ exports.require = (module) ->
meta = ipcRenderer.sendSync 'ATOM_BROWSER_REQUIRE', module meta = ipcRenderer.sendSync 'ATOM_BROWSER_REQUIRE', module
moduleCache[module] = metaToValue meta moduleCache[module] = metaToValue meta
# Optimize require('electron'). ### Optimize require('electron'). ###
moduleCache.electron = exports moduleCache.electron = exports
# Alias to remote.require('electron').xxx. ### Alias to remote.require('electron').xxx. ###
builtinCache = {} builtinCache = {}
exports.getBuiltin = (module) -> exports.getBuiltin = (module) ->
return builtinCache[module] if builtinCache[module]? return builtinCache[module] if builtinCache[module]?
@ -162,38 +172,38 @@ exports.getBuiltin = (module) ->
meta = ipcRenderer.sendSync 'ATOM_BROWSER_GET_BUILTIN', module meta = ipcRenderer.sendSync 'ATOM_BROWSER_GET_BUILTIN', module
builtinCache[module] = metaToValue meta builtinCache[module] = metaToValue meta
# Get current BrowserWindow object. ### Get current BrowserWindow object. ###
windowCache = null windowCache = null
exports.getCurrentWindow = -> exports.getCurrentWindow = ->
return windowCache if windowCache? return windowCache if windowCache?
meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WINDOW' meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WINDOW'
windowCache = metaToValue meta windowCache = metaToValue meta
# Get current WebContents object. ### Get current WebContents object. ###
webContentsCache = null webContentsCache = null
exports.getCurrentWebContents = -> exports.getCurrentWebContents = ->
return webContentsCache if webContentsCache? return webContentsCache if webContentsCache?
meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WEB_CONTENTS' meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WEB_CONTENTS'
webContentsCache = metaToValue meta webContentsCache = metaToValue meta
# Get a global object in browser. ### Get a global object in browser. ###
exports.getGlobal = (name) -> exports.getGlobal = (name) ->
meta = ipcRenderer.sendSync 'ATOM_BROWSER_GLOBAL', name meta = ipcRenderer.sendSync 'ATOM_BROWSER_GLOBAL', name
metaToValue meta metaToValue meta
# Get the process object in browser. ### Get the process object in browser. ###
processCache = null processCache = null
exports.__defineGetter__ 'process', -> exports.__defineGetter__ 'process', ->
processCache = exports.getGlobal('process') unless processCache? processCache = exports.getGlobal('process') unless processCache?
processCache processCache
# Create a funtion that will return the specifed value when called in browser. ### Create a funtion that will return the specifed value when called in browser. ###
exports.createFunctionWithReturnValue = (returnValue) -> exports.createFunctionWithReturnValue = (returnValue) ->
func = -> returnValue func = -> returnValue
v8Util.setHiddenValue func, 'returnValue', true v8Util.setHiddenValue func, 'returnValue', true
func func
# Get the guest WebContents from guestInstanceId. ### Get the guest WebContents from guestInstanceId. ###
exports.getGuestWebContents = (guestInstanceId) -> exports.getGuestWebContents = (guestInstanceId) ->
meta = ipcRenderer.sendSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId meta = ipcRenderer.sendSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId
metaToValue meta metaToValue meta

View file

@ -1,7 +1,7 @@
{deprecate} = require 'electron' {deprecate} = require 'electron'
{webFrame} = process.atomBinding 'web_frame' {webFrame} = process.atomBinding 'web_frame'
# Deprecated. ### Deprecated. ###
deprecate.rename webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure' deprecate.rename webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure'
deprecate.rename webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP' deprecate.rename webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP'
deprecate.rename webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged' deprecate.rename webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged'

View file

@ -3,35 +3,37 @@ path = require 'path'
url = require 'url' url = require 'url'
Module = require 'module' Module = require 'module'
# We modified the original process.argv to let node.js load the ###
# atom-renderer.js, we need to restore it here. We modified the original process.argv to let node.js load the
atom-renderer.js, we need to restore it here.
###
process.argv.splice 1, 1 process.argv.splice 1, 1
# Clear search paths. ### Clear search paths. ###
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')
# Import common settings. ### Import common settings. ###
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')
globalPaths = Module.globalPaths globalPaths = Module.globalPaths
unless process.env.ELECTRON_HIDE_INTERNAL_MODULES unless process.env.ELECTRON_HIDE_INTERNAL_MODULES
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') globalPaths.push path.resolve(__dirname, '..', 'api', 'lib')
# Expose public APIs. ### Expose public APIs. ###
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports')
# The global variable will be used by ipc for event dispatching ### The global variable will be used by ipc for event dispatching ###
v8Util = process.atomBinding 'v8_util' v8Util = process.atomBinding 'v8_util'
v8Util.setHiddenValue global, 'ipc', new events.EventEmitter v8Util.setHiddenValue global, 'ipc', new events.EventEmitter
# Process command line arguments. ### Process command line arguments. ###
nodeIntegration = 'false' nodeIntegration = 'false'
for arg in process.argv for arg in process.argv
if arg.indexOf('--guest-instance-id=') == 0 if arg.indexOf('--guest-instance-id=') == 0
# This is a guest web view. ### This is a guest web view. ###
process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1) process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1)
else if arg.indexOf('--opener-id=') == 0 else if arg.indexOf('--opener-id=') == 0
# This is a guest BrowserWindow. ### This is a guest BrowserWindow. ###
process.openerId = parseInt arg.substr(arg.indexOf('=') + 1) process.openerId = parseInt arg.substr(arg.indexOf('=') + 1)
else if arg.indexOf('--node-integration=') == 0 else if arg.indexOf('--node-integration=') == 0
nodeIntegration = arg.substr arg.indexOf('=') + 1 nodeIntegration = arg.substr arg.indexOf('=') + 1
@ -39,27 +41,27 @@ for arg in process.argv
preloadScript = arg.substr arg.indexOf('=') + 1 preloadScript = arg.substr arg.indexOf('=') + 1
if location.protocol is 'chrome-devtools:' if location.protocol is 'chrome-devtools:'
# Override some inspector APIs. ### Override some inspector APIs. ###
require './inspector' require './inspector'
nodeIntegration = 'true' nodeIntegration = 'true'
else if location.protocol is 'chrome-extension:' else if location.protocol is 'chrome-extension:'
# Add implementations of chrome API. ### Add implementations of chrome API. ###
require './chrome-api' require './chrome-api'
nodeIntegration = 'true' nodeIntegration = 'true'
else else
# Override default web functions. ### Override default web functions. ###
require './override' require './override'
# Load webview tag implementation. ### Load webview tag implementation. ###
unless process.guestInstanceId? unless process.guestInstanceId?
require './web-view/web-view' require './web-view/web-view'
require './web-view/web-view-attributes' require './web-view/web-view-attributes'
if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe'] if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe']
# Export node bindings to global. ### Export node bindings to global. ###
global.require = require global.require = require
global.module = module global.module = module
# Set the __filename to the path of html file if it is file: protocol. ### Set the __filename to the path of html file if it is file: protocol. ###
if window.location.protocol is 'file:' if window.location.protocol is 'file:'
pathname = pathname =
if process.platform is 'win32' and window.location.pathname[0] is '/' if process.platform is 'win32' and window.location.pathname[0] is '/'
@ -69,16 +71,16 @@ if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe']
global.__filename = path.normalize decodeURIComponent(pathname) global.__filename = path.normalize decodeURIComponent(pathname)
global.__dirname = path.dirname global.__filename global.__dirname = path.dirname global.__filename
# Set module's filename so relative require can work as expected. ### Set module's filename so relative require can work as expected. ###
module.filename = global.__filename module.filename = global.__filename
# Also search for module under the html file. ### Also search for module under the html file. ###
module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname) module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
else else
global.__filename = __filename global.__filename = __filename
global.__dirname = __dirname global.__dirname = __dirname
# Redirect window.onerror to uncaughtException. ### Redirect window.onerror to uncaughtException. ###
window.onerror = (message, filename, lineno, colno, error) -> window.onerror = (message, filename, lineno, colno, error) ->
if global.process.listeners('uncaughtException').length > 0 if global.process.listeners('uncaughtException').length > 0
global.process.emit 'uncaughtException', error global.process.emit 'uncaughtException', error
@ -86,18 +88,18 @@ if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe']
else else
false false
# Emit the 'exit' event when page is unloading. ### Emit the 'exit' event when page is unloading. ###
window.addEventListener 'unload', -> window.addEventListener 'unload', ->
process.emit 'exit' process.emit 'exit'
else else
# Delete Node's symbols after the Environment has been loaded. ### Delete Node's symbols after the Environment has been loaded. ###
process.once 'loaded', -> process.once 'loaded', ->
delete global.process delete global.process
delete global.setImmediate delete global.setImmediate
delete global.clearImmediate delete global.clearImmediate
delete global.global delete global.global
# Load the script specfied by the "preload" attribute. ### Load the script specfied by the "preload" attribute. ###
if preloadScript if preloadScript
try try
require preloadScript require preloadScript

View file

@ -1,8 +1,8 @@
window.onload = -> window.onload = ->
# Use menu API to show context menu. ### Use menu API to show context menu. ###
InspectorFrontendHost.showContextMenuAtPoint = createMenu InspectorFrontendHost.showContextMenuAtPoint = createMenu
# Use dialog API to override file chooser dialog. ### Use dialog API to override file chooser dialog. ###
WebInspector.createFileSelectorElement = createFileSelectorElement WebInspector.createFileSelectorElement = createFileSelectorElement
convertToMenuTemplate = (items) -> convertToMenuTemplate = (items) ->
@ -38,7 +38,7 @@ createMenu = (x, y, items, document) ->
{Menu} = remote {Menu} = remote
menu = Menu.buildFromTemplate convertToMenuTemplate(items) menu = Menu.buildFromTemplate convertToMenuTemplate(items)
# The menu is expected to show asynchronously. ### The menu is expected to show asynchronously. ###
setTimeout -> menu.popup remote.getCurrentWindow() setTimeout -> menu.popup remote.getCurrentWindow()
showFileChooserDialog = (callback) -> showFileChooserDialog = (callback) ->

View file

@ -1,12 +1,12 @@
{ipcRenderer, remote} = require 'electron' {ipcRenderer, remote} = require 'electron'
# Helper function to resolve relative url. ### Helper function to resolve relative url. ###
a = window.top.document.createElement 'a' a = window.top.document.createElement 'a'
resolveURL = (url) -> resolveURL = (url) ->
a.href = url a.href = url
a.href a.href
# Window object returned by "window.open". ### Window object returned by "window.open". ###
class BrowserWindowProxy class BrowserWindowProxy
@proxies: {} @proxies: {}
@ -38,15 +38,15 @@ class BrowserWindowProxy
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args...
unless process.guestInstanceId? unless process.guestInstanceId?
# Override default window.close. ### Override default window.close. ###
window.close = -> window.close = ->
remote.getCurrentWindow().close() remote.getCurrentWindow().close()
# Make the browser window or guest view emit "new-window" event. ### Make the browser window or guest view emit "new-window" event. ###
window.open = (url, frameName='', features='') -> window.open = (url, frameName='', features='') ->
options = {} options = {}
ints = [ 'x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor' ] ints = [ 'x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor' ]
# Make sure to get rid of excessive whitespace in the property name ### Make sure to get rid of excessive whitespace in the property name ###
for feature in features.split /,\s*/ for feature in features.split /,\s*/
[name, value] = feature.split /\s*=/ [name, value] = feature.split /\s*=/
options[name] = options[name] =
@ -62,7 +62,7 @@ window.open = (url, frameName='', features='') ->
options.width ?= 800 options.width ?= 800
options.height ?= 600 options.height ?= 600
# Resolve relative urls. ### Resolve relative urls. ###
url = resolveURL url url = resolveURL url
(options[name] = parseInt(options[name], 10) if options[name]?) for name in ints (options[name] = parseInt(options[name], 10) if options[name]?) for name in ints
@ -73,21 +73,21 @@ window.open = (url, frameName='', features='') ->
else else
null null
# Use the dialog API to implement alert(). ### Use the dialog API to implement alert(). ###
window.alert = (message, title='') -> window.alert = (message, title='') ->
buttons = ['OK'] buttons = ['OK']
message = message.toString() message = message.toString()
remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons} remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons}
# Alert should always return undefined. ### Alert should always return undefined. ###
return return
# And the confirm(). ### And the confirm(). ###
window.confirm = (message, title='') -> window.confirm = (message, title='') ->
buttons = ['OK', 'Cancel'] buttons = ['OK', 'Cancel']
cancelId = 1 cancelId = 1
not remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId} not remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId}
# But we do not support prompt(). ### But we do not support prompt(). ###
window.prompt = -> window.prompt = ->
throw new Error('prompt() is and will not be supported.') throw new Error('prompt() is and will not be supported.')
@ -95,8 +95,8 @@ if process.openerId?
window.opener = BrowserWindowProxy.getOrCreate process.openerId window.opener = BrowserWindowProxy.getOrCreate process.openerId
ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) -> ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) ->
# Manually dispatch event instead of using postMessage because we also need to ### Manually dispatch event instead of using postMessage because we also need to ###
# set event.source. ### set event.source. ###
event = document.createEvent 'Event' event = document.createEvent 'Event'
event.initEvent 'message', false, false event.initEvent 'message', false, false
event.data = message event.data = message
@ -104,7 +104,7 @@ ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message,
event.source = BrowserWindowProxy.getOrCreate(sourceId) event.source = BrowserWindowProxy.getOrCreate(sourceId)
window.dispatchEvent event window.dispatchEvent event
# Forward history operations to browser. ### Forward history operations to browser. ###
sendHistoryOperation = (args...) -> sendHistoryOperation = (args...) ->
ipcRenderer.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args... ipcRenderer.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args...
@ -118,7 +118,7 @@ Object.defineProperty window.history, 'length',
get: -> get: ->
getHistoryOperation 'length' getHistoryOperation 'length'
# Make document.hidden and document.visibilityState return the correct value. ### Make document.hidden and document.visibilityState return the correct value. ###
Object.defineProperty document, 'hidden', Object.defineProperty document, 'hidden',
get: -> get: ->
currentWindow = remote.getCurrentWindow() currentWindow = remote.getCurrentWindow()

View file

@ -4,14 +4,16 @@ webViewConstants = require './web-view-constants'
{remote} = require 'electron' {remote} = require 'electron'
# Helper function to resolve url set in attribute. ### Helper function to resolve url set in attribute. ###
a = document.createElement 'a' a = document.createElement 'a'
resolveURL = (url) -> resolveURL = (url) ->
a.href = url a.href = url
a.href a.href
# Attribute objects. ###
# Default implementation of a WebView attribute. Attribute objects.
Default implementation of a WebView attribute.
###
class WebViewAttribute class WebViewAttribute
constructor: (name, webViewImpl) -> constructor: (name, webViewImpl) ->
@name = name @name = name
@ -20,29 +22,29 @@ class WebViewAttribute
@defineProperty() @defineProperty()
# Retrieves and returns the attribute's value. ### Retrieves and returns the attribute's value. ###
getValue: -> @webViewImpl.webviewNode.getAttribute(@name) || '' getValue: -> @webViewImpl.webviewNode.getAttribute(@name) || ''
# Sets the attribute's value. ### Sets the attribute's value. ###
setValue: (value) -> @webViewImpl.webviewNode.setAttribute(@name, value || '') setValue: (value) -> @webViewImpl.webviewNode.setAttribute(@name, value || '')
# Changes the attribute's value without triggering its mutation handler. ### Changes the attribute's value without triggering its mutation handler. ###
setValueIgnoreMutation: (value) -> setValueIgnoreMutation: (value) ->
@ignoreMutation = true @ignoreMutation = true
@setValue value @setValue value
@ignoreMutation = false @ignoreMutation = false
# Defines this attribute as a property on the webview node. ### Defines this attribute as a property on the webview node. ###
defineProperty: -> defineProperty: ->
Object.defineProperty @webViewImpl.webviewNode, @name, Object.defineProperty @webViewImpl.webviewNode, @name,
get: => @getValue() get: => @getValue()
set: (value) => @setValue value set: (value) => @setValue value
enumerable: true enumerable: true
# Called when the attribute's value changes. ### Called when the attribute's value changes. ###
handleMutation: -> handleMutation: ->
# An attribute that is treated as a Boolean. ### An attribute that is treated as a Boolean. ###
class BooleanAttribute extends WebViewAttribute class BooleanAttribute extends WebViewAttribute
constructor: (name, webViewImpl) -> constructor: (name, webViewImpl) ->
super name, webViewImpl super name, webViewImpl
@ -55,7 +57,7 @@ class BooleanAttribute extends WebViewAttribute
else else
@webViewImpl.webviewNode.setAttribute @name, '' @webViewImpl.webviewNode.setAttribute @name, ''
# Attribute that specifies whether transparency is allowed in the webview. ### Attribute that specifies whether transparency is allowed in the webview. ###
class AllowTransparencyAttribute extends BooleanAttribute class AllowTransparencyAttribute extends BooleanAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl super webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl
@ -64,7 +66,7 @@ class AllowTransparencyAttribute extends BooleanAttribute
return unless @webViewImpl.guestInstanceId return unless @webViewImpl.guestInstanceId
guestViewInternal.setAllowTransparency @webViewImpl.guestInstanceId, @getValue() guestViewInternal.setAllowTransparency @webViewImpl.guestInstanceId, @getValue()
# Attribute used to define the demension limits of autosizing. ### Attribute used to define the demension limits of autosizing. ###
class AutosizeDimensionAttribute extends WebViewAttribute class AutosizeDimensionAttribute extends WebViewAttribute
constructor: (name, webViewImpl) -> constructor: (name, webViewImpl) ->
super name, webViewImpl super name, webViewImpl
@ -82,14 +84,14 @@ class AutosizeDimensionAttribute extends WebViewAttribute
width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0 width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0
height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0 height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0
# Attribute that specifies whether the webview should be autosized. ### Attribute that specifies whether the webview should be autosized. ###
class AutosizeAttribute extends BooleanAttribute class AutosizeAttribute extends BooleanAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl super webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl
handleMutation: AutosizeDimensionAttribute::handleMutation handleMutation: AutosizeDimensionAttribute::handleMutation
# Attribute representing the state of the storage partition. ### Attribute representing the state of the storage partition. ###
class PartitionAttribute extends WebViewAttribute class PartitionAttribute extends WebViewAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_PARTITION, webViewImpl super webViewConstants.ATTRIBUTE_PARTITION, webViewImpl
@ -98,7 +100,7 @@ class PartitionAttribute extends WebViewAttribute
handleMutation: (oldValue, newValue) -> handleMutation: (oldValue, newValue) ->
newValue = newValue || '' newValue = newValue || ''
# The partition cannot change if the webview has already navigated. ### The partition cannot change if the webview has already navigated. ###
unless @webViewImpl.beforeFirstNavigation unless @webViewImpl.beforeFirstNavigation
window.console.error webViewConstants.ERROR_MSG_ALREADY_NAVIGATED window.console.error webViewConstants.ERROR_MSG_ALREADY_NAVIGATED
@setValueIgnoreMutation oldValue @setValueIgnoreMutation oldValue
@ -108,7 +110,7 @@ class PartitionAttribute extends WebViewAttribute
@validPartitionId = false @validPartitionId = false
window.console.error webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE window.console.error webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE
# Attribute that handles the location and navigation of the webview. ### Attribute that handles the location and navigation of the webview. ###
class SrcAttribute extends WebViewAttribute class SrcAttribute extends WebViewAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_SRC, webViewImpl super webViewConstants.ATTRIBUTE_SRC, webViewImpl
@ -122,28 +124,36 @@ class SrcAttribute extends WebViewAttribute
setValueIgnoreMutation: (value) -> setValueIgnoreMutation: (value) ->
WebViewAttribute::setValueIgnoreMutation.call(this, value) WebViewAttribute::setValueIgnoreMutation.call(this, value)
# takeRecords() is needed to clear queued up src mutations. Without it, it ###
# is possible for this change to get picked up asyncronously by src's takeRecords() is needed to clear queued up src mutations. Without it, it
# mutation observer |observer|, and then get handled even though we do not is possible for this change to get picked up asyncronously by src's
# want to handle this mutation. mutation observer |observer|, and then get handled even though we do not
want to handle this mutation.
###
@observer.takeRecords() @observer.takeRecords()
handleMutation: (oldValue, newValue) -> handleMutation: (oldValue, newValue) ->
# Once we have navigated, we don't allow clearing the src attribute. ###
# Once <webview> enters a navigated state, it cannot return to a Once we have navigated, we don't allow clearing the src attribute.
# placeholder state. Once <webview> enters a navigated state, it cannot return to a
placeholder state.
###
if not newValue and oldValue if not newValue and oldValue
# src attribute changes normally initiate a navigation. We suppress ###
# the next src attribute handler call to avoid reloading the page src attribute changes normally initiate a navigation. We suppress
# on every guest-initiated navigation. the next src attribute handler call to avoid reloading the page
on every guest-initiated navigation.
###
@setValueIgnoreMutation oldValue @setValueIgnoreMutation oldValue
return return
@parse() @parse()
# The purpose of this mutation observer is to catch assignment to the src ###
# attribute without any changes to its value. This is useful in the case The purpose of this mutation observer is to catch assignment to the src
# where the webview guest has crashed and navigating to the same address attribute without any changes to its value. This is useful in the case
# spawns off a new process. where the webview guest has crashed and navigating to the same address
spawns off a new process.
###
setupMutationObserver: -> setupMutationObserver: ->
@observer = new MutationObserver (mutations) => @observer = new MutationObserver (mutations) =>
for mutation in mutations for mutation in mutations
@ -169,7 +179,7 @@ class SrcAttribute extends WebViewAttribute
@webViewImpl.createGuest() @webViewImpl.createGuest()
return return
# Navigate to |this.src|. ### Navigate to |this.src|. ###
opts = {} opts = {}
httpreferrer = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() httpreferrer = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()
if httpreferrer then opts.httpReferrer = httpreferrer if httpreferrer then opts.httpReferrer = httpreferrer
@ -180,17 +190,17 @@ class SrcAttribute extends WebViewAttribute
guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId) guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId)
guestContents.loadURL @getValue(), opts guestContents.loadURL @getValue(), opts
# Attribute specifies HTTP referrer. ### Attribute specifies HTTP referrer. ###
class HttpReferrerAttribute extends WebViewAttribute class HttpReferrerAttribute extends WebViewAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl
# Attribute specifies user agent ### Attribute specifies user agent ###
class UserAgentAttribute extends WebViewAttribute class UserAgentAttribute extends WebViewAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl super webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl
# Attribute that set preload script. ### Attribute that set preload script. ###
class PreloadAttribute extends WebViewAttribute class PreloadAttribute extends WebViewAttribute
constructor: (webViewImpl) -> constructor: (webViewImpl) ->
super webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl super webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl
@ -204,7 +214,7 @@ class PreloadAttribute extends WebViewAttribute
preload = '' preload = ''
preload preload
# Sets up all of the webview attributes. ### Sets up all of the webview attributes. ###
WebViewImpl::setupWebViewAttributes = -> WebViewImpl::setupWebViewAttributes = ->
@attributes = {} @attributes = {}

View file

@ -1,5 +1,5 @@
module.exports = module.exports =
# Attributes. ### Attributes. ###
ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency' ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency'
ATTRIBUTE_AUTOSIZE: 'autosize' ATTRIBUTE_AUTOSIZE: 'autosize'
ATTRIBUTE_MAXHEIGHT: 'maxheight' ATTRIBUTE_MAXHEIGHT: 'maxheight'
@ -17,10 +17,10 @@ module.exports =
ATTRIBUTE_PRELOAD: 'preload' ATTRIBUTE_PRELOAD: 'preload'
ATTRIBUTE_USERAGENT: 'useragent' ATTRIBUTE_USERAGENT: 'useragent'
# Internal attribute. ### Internal attribute. ###
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid' ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid'
# Error messages. ### Error messages. ###
ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.' ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.'
ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' + ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' +
'Script cannot be injected into content until the page has loaded.' 'Script cannot be injected into content until the page has loaded.'

View file

@ -4,11 +4,11 @@ v8Util = process.atomBinding 'v8_util'
guestViewInternal = require './guest-view-internal' guestViewInternal = require './guest-view-internal'
webViewConstants = require './web-view-constants' webViewConstants = require './web-view-constants'
# ID generator. ### ID generator. ###
nextId = 0 nextId = 0
getNextId = -> ++nextId getNextId = -> ++nextId
# Represents the internal state of the WebView node. ### Represents the internal state of the WebView node. ###
class WebViewImpl class WebViewImpl
constructor: (@webviewNode) -> constructor: (@webviewNode) ->
v8Util.setHiddenValue @webviewNode, 'internal', this v8Util.setHiddenValue @webviewNode, 'internal', this
@ -17,7 +17,7 @@ class WebViewImpl
@beforeFirstNavigation = true @beforeFirstNavigation = true
# on* Event handlers. ### on* Event handlers. ###
@on = {} @on = {}
@browserPluginNode = @createBrowserPluginNode() @browserPluginNode = @createBrowserPluginNode()
@ -30,20 +30,24 @@ class WebViewImpl
shadowRoot.appendChild @browserPluginNode shadowRoot.appendChild @browserPluginNode
createBrowserPluginNode: -> createBrowserPluginNode: ->
# We create BrowserPlugin as a custom element in order to observe changes ###
# to attributes synchronously. We create BrowserPlugin as a custom element in order to observe changes
to attributes synchronously.
###
browserPluginNode = new WebViewImpl.BrowserPlugin() browserPluginNode = new WebViewImpl.BrowserPlugin()
v8Util.setHiddenValue browserPluginNode, 'internal', this v8Util.setHiddenValue browserPluginNode, 'internal', this
browserPluginNode browserPluginNode
# Resets some state upon reattaching <webview> element to the DOM. ### Resets some state upon reattaching <webview> element to the DOM. ###
reset: -> reset: ->
# If guestInstanceId is defined then the <webview> has navigated and has ###
# already picked up a partition ID. Thus, we need to reset the initialization If guestInstanceId is defined then the <webview> has navigated and has
# state. However, it may be the case that beforeFirstNavigation is false BUT already picked up a partition ID. Thus, we need to reset the initialization
# guestInstanceId has yet to be initialized. This means that we have not state. However, it may be the case that beforeFirstNavigation is false BUT
# heard back from createGuest yet. We will not reset the flag in this case so guestInstanceId has yet to be initialized. This means that we have not
# that we don't end up allocating a second guest. heard back from createGuest yet. We will not reset the flag in this case so
that we don't end up allocating a second guest.
###
if @guestInstanceId if @guestInstanceId
guestViewInternal.destroyGuest @guestInstanceId guestViewInternal.destroyGuest @guestInstanceId
@webContents = null @webContents = null
@ -52,34 +56,38 @@ class WebViewImpl
@attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true @attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
@internalInstanceId = 0 @internalInstanceId = 0
# Sets the <webview>.request property. ### Sets the <webview>.request property. ###
setRequestPropertyOnWebViewNode: (request) -> setRequestPropertyOnWebViewNode: (request) ->
Object.defineProperty @webviewNode, 'request', value: request, enumerable: true Object.defineProperty @webviewNode, 'request', value: request, enumerable: true
setupFocusPropagation: -> setupFocusPropagation: ->
unless @webviewNode.hasAttribute 'tabIndex' unless @webviewNode.hasAttribute 'tabIndex'
# <webview> needs a tabIndex in order to be focusable. ###
# TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute <webview> needs a tabIndex in order to be focusable.
# to allow <webview> to be focusable. TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
# See http://crbug.com/231664. to allow <webview> to be focusable.
See http://crbug.com/231664.
###
@webviewNode.setAttribute 'tabIndex', -1 @webviewNode.setAttribute 'tabIndex', -1
@webviewNode.addEventListener 'focus', (e) => @webviewNode.addEventListener 'focus', (e) =>
# Focus the BrowserPlugin when the <webview> takes focus. ### Focus the BrowserPlugin when the <webview> takes focus. ###
@browserPluginNode.focus() @browserPluginNode.focus()
@webviewNode.addEventListener 'blur', (e) => @webviewNode.addEventListener 'blur', (e) =>
# Blur the BrowserPlugin when the <webview> loses focus. ### Blur the BrowserPlugin when the <webview> loses focus. ###
@browserPluginNode.blur() @browserPluginNode.blur()
# This observer monitors mutations to attributes of the <webview> and ###
# updates the BrowserPlugin properties accordingly. In turn, updating This observer monitors mutations to attributes of the <webview> and
# a BrowserPlugin property will update the corresponding BrowserPlugin updates the BrowserPlugin properties accordingly. In turn, updating
# attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more a BrowserPlugin property will update the corresponding BrowserPlugin
# details. attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
details.
###
handleWebviewAttributeMutation: (attributeName, oldValue, newValue) -> handleWebviewAttributeMutation: (attributeName, oldValue, newValue) ->
if not @attributes[attributeName] or @attributes[attributeName].ignoreMutation if not @attributes[attributeName] or @attributes[attributeName].ignoreMutation
return return
# Let the changed attribute handle its own mutation; ### Let the changed attribute handle its own mutation; ###
@attributes[attributeName].handleMutation oldValue, newValue @attributes[attributeName].handleMutation oldValue, newValue
handleBrowserPluginAttributeMutation: (attributeName, oldValue, newValue) -> handleBrowserPluginAttributeMutation: (attributeName, oldValue, newValue) ->
@ -87,7 +95,7 @@ class WebViewImpl
@browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID @browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID
@internalInstanceId = parseInt newValue @internalInstanceId = parseInt newValue
# Track when the element resizes using the element resize callback. ### Track when the element resizes using the element resize callback. ###
webFrame.registerElementResizeCallback @internalInstanceId, @onElementResize.bind(this) webFrame.registerElementResizeCallback @internalInstanceId, @onElementResize.bind(this)
return unless @guestInstanceId return unless @guestInstanceId
@ -103,8 +111,8 @@ class WebViewImpl
width = node.offsetWidth width = node.offsetWidth
height = node.offsetHeight height = node.offsetHeight
# Check the current bounds to make sure we do not resize <webview> ### Check the current bounds to make sure we do not resize <webview> ###
# outside of current constraints. ### outside of current constraints. ###
maxWidth = @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width maxWidth = @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width
maxHeight = @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width maxHeight = @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width
minWidth = @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width minWidth = @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width
@ -120,12 +128,14 @@ class WebViewImpl
newHeight <= maxHeight) newHeight <= maxHeight)
node.style.width = newWidth + 'px' node.style.width = newWidth + 'px'
node.style.height = newHeight + 'px' node.style.height = newHeight + 'px'
# Only fire the DOM event if the size of the <webview> has actually ###
# changed. Only fire the DOM event if the size of the <webview> has actually
changed.
###
@dispatchEvent webViewEvent @dispatchEvent webViewEvent
onElementResize: (newSize) -> onElementResize: (newSize) ->
# Dispatch the 'resize' event. ### Dispatch the 'resize' event. ###
resizeEvent = new Event('resize', bubbles: true) resizeEvent = new Event('resize', bubbles: true)
resizeEvent.newWidth = newSize.width resizeEvent.newWidth = newSize.width
resizeEvent.newHeight = newSize.height resizeEvent.newHeight = newSize.height
@ -141,8 +151,8 @@ class WebViewImpl
dispatchEvent: (webViewEvent) -> dispatchEvent: (webViewEvent) ->
@webviewNode.dispatchEvent webViewEvent @webviewNode.dispatchEvent webViewEvent
# Adds an 'on<event>' property on the webview, which can be used to set/unset ### Adds an 'on<event>' property on the webview, which can be used to set/unset ###
# an event handler. ### an event handler. ###
setupEventProperty: (eventName) -> setupEventProperty: (eventName) ->
propertyName = 'on' + eventName.toLowerCase() propertyName = 'on' + eventName.toLowerCase()
Object.defineProperty @webviewNode, propertyName, Object.defineProperty @webviewNode, propertyName,
@ -155,14 +165,16 @@ class WebViewImpl
@webviewNode.addEventListener eventName, value @webviewNode.addEventListener eventName, value
enumerable: true enumerable: true
# Updates state upon loadcommit. ### Updates state upon loadcommit. ###
onLoadCommit: (webViewEvent) -> onLoadCommit: (webViewEvent) ->
oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC
newValue = webViewEvent.url newValue = webViewEvent.url
if webViewEvent.isMainFrame and (oldValue != newValue) if webViewEvent.isMainFrame and (oldValue != newValue)
# Touching the src attribute triggers a navigation. To avoid ###
# triggering a page reload on every guest-initiated navigation, Touching the src attribute triggers a navigation. To avoid
# we do not handle this mutation triggering a page reload on every guest-initiated navigation,
we do not handle this mutation.
###
@attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation newValue @attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation newValue
onAttach: (storagePartitionId) -> onAttach: (storagePartitionId) ->
@ -174,11 +186,13 @@ class WebViewImpl
userAgentOverride: @userAgentOverride userAgentOverride: @userAgentOverride
for own attributeName, attribute of @attributes for own attributeName, attribute of @attributes
params[attributeName] = attribute.getValue() params[attributeName] = attribute.getValue()
# When the WebView is not participating in layout (display:none) ###
# then getBoundingClientRect() would report a width and height of 0. When the WebView is not participating in layout (display:none)
# However, in the case where the WebView has a fixed size we can then getBoundingClientRect() would report a width and height of 0.
# use that value to initially size the guest so as to avoid a relayout of However, in the case where the WebView has a fixed size we can
# the on display:block. use that value to initially size the guest so as to avoid a relayout of
the on display:block.
###
css = window.getComputedStyle @webviewNode, null css = window.getComputedStyle @webviewNode, null
elementRect = @webviewNode.getBoundingClientRect() elementRect = @webviewNode.getBoundingClientRect()
params.elementWidth = parseInt(elementRect.width) || params.elementWidth = parseInt(elementRect.width) ||
@ -194,14 +208,14 @@ class WebViewImpl
guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams()
# Registers browser plugin <object> custom element. ### Registers browser plugin <object> custom element. ###
registerBrowserPluginElement = -> registerBrowserPluginElement = ->
proto = Object.create HTMLObjectElement.prototype proto = Object.create HTMLObjectElement.prototype
proto.createdCallback = -> proto.createdCallback = ->
@setAttribute 'type', 'application/browser-plugin' @setAttribute 'type', 'application/browser-plugin'
@setAttribute 'id', 'browser-plugin-' + getNextId() @setAttribute 'id', 'browser-plugin-' + getNextId()
# The <object> node fills in the <webview> container. ### The <object> node fills in the <webview> container. ###
@style.display = 'block' @style.display = 'block'
@style.width = '100%' @style.width = '100%'
@style.height = '100%' @style.height = '100%'
@ -212,7 +226,7 @@ registerBrowserPluginElement = ->
internal.handleBrowserPluginAttributeMutation name, oldValue, newValue internal.handleBrowserPluginAttributeMutation name, oldValue, newValue
proto.attachedCallback = -> proto.attachedCallback = ->
# Load the plugin immediately. ### Load the plugin immediately. ###
unused = this.nonExistentAttribute unused = this.nonExistentAttribute
WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement 'browserplugin', WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement 'browserplugin',
@ -223,7 +237,7 @@ registerBrowserPluginElement = ->
delete proto.detachedCallback delete proto.detachedCallback
delete proto.attributeChangedCallback delete proto.attributeChangedCallback
# Registers <webview> custom element. ### Registers <webview> custom element. ###
registerWebViewElement = -> registerWebViewElement = ->
proto = Object.create HTMLObjectElement.prototype proto = Object.create HTMLObjectElement.prototype
@ -250,7 +264,7 @@ registerWebViewElement = ->
internal.elementAttached = true internal.elementAttached = true
internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
# Public-facing API methods. ### Public-facing API methods. ###
methods = [ methods = [
'getURL' 'getURL'
'getTitle' 'getTitle'
@ -304,7 +318,7 @@ registerWebViewElement = ->
'insertCSS' 'insertCSS'
] ]
# Forward proto.foo* method calls to WebViewImpl.foo*. ### Forward proto.foo* method calls to WebViewImpl.foo*. ###
createBlockHandler = (m) -> createBlockHandler = (m) ->
(args...) -> (args...) ->
internal = v8Util.getHiddenValue this, 'internal' internal = v8Util.getHiddenValue this, 'internal'
@ -318,14 +332,14 @@ registerWebViewElement = ->
proto[m] = createNonBlockHandler m for m in nonblockMethods proto[m] = createNonBlockHandler m for m in nonblockMethods
# Deprecated. ### Deprecated. ###
deprecate.rename proto, 'getUrl', 'getURL' deprecate.rename proto, 'getUrl', 'getURL'
window.WebView = webFrame.registerEmbedderCustomElement 'webview', window.WebView = webFrame.registerEmbedderCustomElement 'webview',
prototype: proto prototype: proto
# Delete the callbacks so developers cannot call them and produce unexpected ### Delete the callbacks so developers cannot call them and produce unexpected ###
# behavior. ### behavior. ###
delete proto.createdCallback delete proto.createdCallback
delete proto.attachedCallback delete proto.attachedCallback
delete proto.detachedCallback delete proto.detachedCallback