Convert all source files to JavaScript
This commit is contained in:
parent
403870a27e
commit
1f9691ae13
144 changed files with 11211 additions and 7301 deletions
|
@ -1,10 +0,0 @@
|
|||
url = require 'url'
|
||||
|
||||
chrome = window.chrome = window.chrome || {}
|
||||
chrome.extension =
|
||||
getURL: (path) ->
|
||||
url.format
|
||||
protocol: location.protocol
|
||||
slashes: true
|
||||
hostname: location.hostname
|
||||
pathname: path
|
16
atom/renderer/lib/chrome-api.js
Normal file
16
atom/renderer/lib/chrome-api.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
var chrome, url;
|
||||
|
||||
url = require('url');
|
||||
|
||||
chrome = window.chrome = window.chrome || {};
|
||||
|
||||
chrome.extension = {
|
||||
getURL: function(path) {
|
||||
return url.format({
|
||||
protocol: location.protocol,
|
||||
slashes: true,
|
||||
hostname: location.hostname,
|
||||
pathname: path
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,111 +0,0 @@
|
|||
events = require 'events'
|
||||
path = require 'path'
|
||||
url = require 'url'
|
||||
Module = require 'module'
|
||||
|
||||
###
|
||||
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
|
||||
|
||||
### Clear search paths. ###
|
||||
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')
|
||||
|
||||
### Import common settings. ###
|
||||
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')
|
||||
|
||||
globalPaths = Module.globalPaths
|
||||
unless process.env.ELECTRON_HIDE_INTERNAL_MODULES
|
||||
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib')
|
||||
|
||||
### Expose public APIs. ###
|
||||
globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports')
|
||||
|
||||
### The global variable will be used by ipc for event dispatching ###
|
||||
v8Util = process.atomBinding 'v8_util'
|
||||
v8Util.setHiddenValue global, 'ipc', new events.EventEmitter
|
||||
|
||||
### Process command line arguments. ###
|
||||
nodeIntegration = 'false'
|
||||
for arg in process.argv
|
||||
if arg.indexOf('--guest-instance-id=') == 0
|
||||
### This is a guest web view. ###
|
||||
process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1)
|
||||
else if arg.indexOf('--opener-id=') == 0
|
||||
### This is a guest BrowserWindow. ###
|
||||
process.openerId = parseInt arg.substr(arg.indexOf('=') + 1)
|
||||
else if arg.indexOf('--node-integration=') == 0
|
||||
nodeIntegration = arg.substr arg.indexOf('=') + 1
|
||||
else if arg.indexOf('--preload=') == 0
|
||||
preloadScript = arg.substr arg.indexOf('=') + 1
|
||||
|
||||
if location.protocol is 'chrome-devtools:'
|
||||
### Override some inspector APIs. ###
|
||||
require './inspector'
|
||||
nodeIntegration = 'true'
|
||||
else if location.protocol is 'chrome-extension:'
|
||||
### Add implementations of chrome API. ###
|
||||
require './chrome-api'
|
||||
nodeIntegration = 'true'
|
||||
else
|
||||
### Override default web functions. ###
|
||||
require './override'
|
||||
### Load webview tag implementation. ###
|
||||
unless process.guestInstanceId?
|
||||
require './web-view/web-view'
|
||||
require './web-view/web-view-attributes'
|
||||
|
||||
if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe']
|
||||
### Export node bindings to global. ###
|
||||
global.require = require
|
||||
global.module = module
|
||||
|
||||
### Set the __filename to the path of html file if it is file: protocol. ###
|
||||
if window.location.protocol is 'file:'
|
||||
pathname =
|
||||
if process.platform is 'win32' and window.location.pathname[0] is '/'
|
||||
window.location.pathname.substr 1
|
||||
else
|
||||
window.location.pathname
|
||||
global.__filename = path.normalize decodeURIComponent(pathname)
|
||||
global.__dirname = path.dirname global.__filename
|
||||
|
||||
### Set module's filename so relative require can work as expected. ###
|
||||
module.filename = global.__filename
|
||||
|
||||
### Also search for module under the html file. ###
|
||||
module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
|
||||
else
|
||||
global.__filename = __filename
|
||||
global.__dirname = __dirname
|
||||
|
||||
### Redirect window.onerror to uncaughtException. ###
|
||||
window.onerror = (message, filename, lineno, colno, error) ->
|
||||
if global.process.listeners('uncaughtException').length > 0
|
||||
global.process.emit 'uncaughtException', error
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
### Emit the 'exit' event when page is unloading. ###
|
||||
window.addEventListener 'unload', ->
|
||||
process.emit 'exit'
|
||||
else
|
||||
### Delete Node's symbols after the Environment has been loaded. ###
|
||||
process.once 'loaded', ->
|
||||
delete global.process
|
||||
delete global.setImmediate
|
||||
delete global.clearImmediate
|
||||
delete global.global
|
||||
|
||||
### Load the script specfied by the "preload" attribute. ###
|
||||
if preloadScript
|
||||
try
|
||||
require preloadScript
|
||||
catch error
|
||||
if error.code is 'MODULE_NOT_FOUND'
|
||||
console.error "Unable to load preload script #{preloadScript}"
|
||||
else
|
||||
console.error(error)
|
||||
console.error(error.stack)
|
154
atom/renderer/lib/init.js
Normal file
154
atom/renderer/lib/init.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
var Module, arg, error, error1, events, globalPaths, i, len, nodeIntegration, path, pathname, preloadScript, ref, url, v8Util;
|
||||
|
||||
events = require('events');
|
||||
|
||||
path = require('path');
|
||||
|
||||
url = require('url');
|
||||
|
||||
Module = require('module');
|
||||
|
||||
|
||||
/*
|
||||
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);
|
||||
|
||||
|
||||
/* Clear search paths. */
|
||||
|
||||
require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths'));
|
||||
|
||||
|
||||
/* Import common settings. */
|
||||
|
||||
require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init'));
|
||||
|
||||
globalPaths = Module.globalPaths;
|
||||
|
||||
if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) {
|
||||
globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib'));
|
||||
}
|
||||
|
||||
|
||||
/* Expose public APIs. */
|
||||
|
||||
globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports'));
|
||||
|
||||
|
||||
/* The global variable will be used by ipc for event dispatching */
|
||||
|
||||
v8Util = process.atomBinding('v8_util');
|
||||
|
||||
v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter);
|
||||
|
||||
|
||||
/* Process command line arguments. */
|
||||
|
||||
nodeIntegration = 'false';
|
||||
|
||||
ref = process.argv;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
arg = ref[i];
|
||||
if (arg.indexOf('--guest-instance-id=') === 0) {
|
||||
|
||||
/* This is a guest web view. */
|
||||
process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1));
|
||||
} else if (arg.indexOf('--opener-id=') === 0) {
|
||||
|
||||
/* This is a guest BrowserWindow. */
|
||||
process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1));
|
||||
} else if (arg.indexOf('--node-integration=') === 0) {
|
||||
nodeIntegration = arg.substr(arg.indexOf('=') + 1);
|
||||
} else if (arg.indexOf('--preload=') === 0) {
|
||||
preloadScript = arg.substr(arg.indexOf('=') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (location.protocol === 'chrome-devtools:') {
|
||||
|
||||
/* Override some inspector APIs. */
|
||||
require('./inspector');
|
||||
nodeIntegration = 'true';
|
||||
} else if (location.protocol === 'chrome-extension:') {
|
||||
|
||||
/* Add implementations of chrome API. */
|
||||
require('./chrome-api');
|
||||
nodeIntegration = 'true';
|
||||
} else {
|
||||
|
||||
/* Override default web functions. */
|
||||
require('./override');
|
||||
|
||||
/* Load webview tag implementation. */
|
||||
if (process.guestInstanceId == null) {
|
||||
require('./web-view/web-view');
|
||||
require('./web-view/web-view-attributes');
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeIntegration === 'true' || nodeIntegration === 'all' || nodeIntegration === 'except-iframe' || nodeIntegration === 'manual-enable-iframe') {
|
||||
|
||||
/* Export node bindings to global. */
|
||||
global.require = require;
|
||||
global.module = module;
|
||||
|
||||
/* Set the __filename to the path of html file if it is file: protocol. */
|
||||
if (window.location.protocol === 'file:') {
|
||||
pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname;
|
||||
global.__filename = path.normalize(decodeURIComponent(pathname));
|
||||
global.__dirname = path.dirname(global.__filename);
|
||||
|
||||
/* Set module's filename so relative require can work as expected. */
|
||||
module.filename = global.__filename;
|
||||
|
||||
/* Also search for module under the html file. */
|
||||
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname));
|
||||
} else {
|
||||
global.__filename = __filename;
|
||||
global.__dirname = __dirname;
|
||||
}
|
||||
|
||||
/* Redirect window.onerror to uncaughtException. */
|
||||
window.onerror = function(message, filename, lineno, colno, error) {
|
||||
if (global.process.listeners('uncaughtException').length > 0) {
|
||||
global.process.emit('uncaughtException', error);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/* Emit the 'exit' event when page is unloading. */
|
||||
window.addEventListener('unload', function() {
|
||||
return process.emit('exit');
|
||||
});
|
||||
} else {
|
||||
|
||||
/* Delete Node's symbols after the Environment has been loaded. */
|
||||
process.once('loaded', function() {
|
||||
delete global.process;
|
||||
delete global.setImmediate;
|
||||
delete global.clearImmediate;
|
||||
return delete global.global;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Load the script specfied by the "preload" attribute. */
|
||||
|
||||
if (preloadScript) {
|
||||
try {
|
||||
require(preloadScript);
|
||||
} catch (error1) {
|
||||
error = error1;
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
console.error("Unable to load preload script " + preloadScript);
|
||||
} else {
|
||||
console.error(error);
|
||||
console.error(error.stack);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
window.onload = ->
|
||||
### Use menu API to show context menu. ###
|
||||
InspectorFrontendHost.showContextMenuAtPoint = createMenu
|
||||
|
||||
### Use dialog API to override file chooser dialog. ###
|
||||
WebInspector.createFileSelectorElement = createFileSelectorElement
|
||||
|
||||
convertToMenuTemplate = (items) ->
|
||||
template = []
|
||||
for item in items
|
||||
do (item) ->
|
||||
transformed =
|
||||
if item.type is 'subMenu'
|
||||
type: 'submenu'
|
||||
label: item.label
|
||||
enabled: item.enabled
|
||||
submenu: convertToMenuTemplate item.subItems
|
||||
else if item.type is 'separator'
|
||||
type: 'separator'
|
||||
else if item.type is 'checkbox'
|
||||
type: 'checkbox'
|
||||
label: item.label
|
||||
enabled: item.enabled
|
||||
checked: item.checked
|
||||
else
|
||||
type: 'normal'
|
||||
label: item.label
|
||||
enabled: item.enabled
|
||||
if item.id?
|
||||
transformed.click = ->
|
||||
DevToolsAPI.contextMenuItemSelected item.id
|
||||
DevToolsAPI.contextMenuCleared()
|
||||
template.push transformed
|
||||
template
|
||||
|
||||
createMenu = (x, y, items, document) ->
|
||||
{remote} = require 'electron'
|
||||
{Menu} = remote
|
||||
|
||||
menu = Menu.buildFromTemplate convertToMenuTemplate(items)
|
||||
### The menu is expected to show asynchronously. ###
|
||||
setTimeout -> menu.popup remote.getCurrentWindow()
|
||||
|
||||
showFileChooserDialog = (callback) ->
|
||||
{remote} = require 'electron'
|
||||
{dialog} = remote
|
||||
files = dialog.showOpenDialog {}
|
||||
callback pathToHtml5FileObject files[0] if files?
|
||||
|
||||
pathToHtml5FileObject = (path) ->
|
||||
fs = require 'fs'
|
||||
blob = new Blob([fs.readFileSync(path)])
|
||||
blob.name = path
|
||||
blob
|
||||
|
||||
createFileSelectorElement = (callback) ->
|
||||
fileSelectorElement = document.createElement 'span'
|
||||
fileSelectorElement.style.display = 'none'
|
||||
fileSelectorElement.click = showFileChooserDialog.bind this, callback
|
||||
return fileSelectorElement
|
85
atom/renderer/lib/inspector.js
Normal file
85
atom/renderer/lib/inspector.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
var convertToMenuTemplate, createFileSelectorElement, createMenu, pathToHtml5FileObject, showFileChooserDialog;
|
||||
|
||||
window.onload = function() {
|
||||
|
||||
/* Use menu API to show context menu. */
|
||||
InspectorFrontendHost.showContextMenuAtPoint = createMenu;
|
||||
|
||||
/* Use dialog API to override file chooser dialog. */
|
||||
return WebInspector.createFileSelectorElement = createFileSelectorElement;
|
||||
};
|
||||
|
||||
convertToMenuTemplate = function(items) {
|
||||
var fn, i, item, len, template;
|
||||
template = [];
|
||||
fn = function(item) {
|
||||
var transformed;
|
||||
transformed = item.type === 'subMenu' ? {
|
||||
type: 'submenu',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
submenu: convertToMenuTemplate(item.subItems)
|
||||
} : item.type === 'separator' ? {
|
||||
type: 'separator'
|
||||
} : item.type === 'checkbox' ? {
|
||||
type: 'checkbox',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
checked: item.checked
|
||||
} : {
|
||||
type: 'normal',
|
||||
label: item.label,
|
||||
enabled: item.enabled
|
||||
};
|
||||
if (item.id != null) {
|
||||
transformed.click = function() {
|
||||
DevToolsAPI.contextMenuItemSelected(item.id);
|
||||
return DevToolsAPI.contextMenuCleared();
|
||||
};
|
||||
}
|
||||
return template.push(transformed);
|
||||
};
|
||||
for (i = 0, len = items.length; i < len; i++) {
|
||||
item = items[i];
|
||||
fn(item);
|
||||
}
|
||||
return template;
|
||||
};
|
||||
|
||||
createMenu = function(x, y, items, document) {
|
||||
var Menu, menu, remote;
|
||||
remote = require('electron').remote;
|
||||
Menu = remote.Menu;
|
||||
menu = Menu.buildFromTemplate(convertToMenuTemplate(items));
|
||||
|
||||
/* The menu is expected to show asynchronously. */
|
||||
return setTimeout(function() {
|
||||
return menu.popup(remote.getCurrentWindow());
|
||||
});
|
||||
};
|
||||
|
||||
showFileChooserDialog = function(callback) {
|
||||
var dialog, files, remote;
|
||||
remote = require('electron').remote;
|
||||
dialog = remote.dialog;
|
||||
files = dialog.showOpenDialog({});
|
||||
if (files != null) {
|
||||
return callback(pathToHtml5FileObject(files[0]));
|
||||
}
|
||||
};
|
||||
|
||||
pathToHtml5FileObject = function(path) {
|
||||
var blob, fs;
|
||||
fs = require('fs');
|
||||
blob = new Blob([fs.readFileSync(path)]);
|
||||
blob.name = path;
|
||||
return blob;
|
||||
};
|
||||
|
||||
createFileSelectorElement = function(callback) {
|
||||
var fileSelectorElement;
|
||||
fileSelectorElement = document.createElement('span');
|
||||
fileSelectorElement.style.display = 'none';
|
||||
fileSelectorElement.click = showFileChooserDialog.bind(this, callback);
|
||||
return fileSelectorElement;
|
||||
};
|
|
@ -1,129 +0,0 @@
|
|||
{ipcRenderer, remote} = require 'electron'
|
||||
|
||||
### Helper function to resolve relative url. ###
|
||||
a = window.top.document.createElement 'a'
|
||||
resolveURL = (url) ->
|
||||
a.href = url
|
||||
a.href
|
||||
|
||||
### Window object returned by "window.open". ###
|
||||
class BrowserWindowProxy
|
||||
@proxies: {}
|
||||
|
||||
@getOrCreate: (guestId) ->
|
||||
@proxies[guestId] ?= new BrowserWindowProxy(guestId)
|
||||
|
||||
@remove: (guestId) ->
|
||||
delete @proxies[guestId]
|
||||
|
||||
constructor: (@guestId) ->
|
||||
@closed = false
|
||||
ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", =>
|
||||
BrowserWindowProxy.remove(@guestId)
|
||||
@closed = true
|
||||
|
||||
close: ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', @guestId
|
||||
|
||||
focus: ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'focus'
|
||||
|
||||
blur: ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur'
|
||||
|
||||
postMessage: (message, targetOrigin='*') ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin, location.origin
|
||||
|
||||
eval: (args...) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args...
|
||||
|
||||
unless process.guestInstanceId?
|
||||
### Override default window.close. ###
|
||||
window.close = ->
|
||||
remote.getCurrentWindow().close()
|
||||
|
||||
### Make the browser window or guest view emit "new-window" event. ###
|
||||
window.open = (url, frameName='', features='') ->
|
||||
options = {}
|
||||
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 ###
|
||||
for feature in features.split /,\s*/
|
||||
[name, value] = feature.split /\s*=/
|
||||
options[name] =
|
||||
if value is 'yes' or value is '1'
|
||||
true
|
||||
else if value is 'no' or value is '0'
|
||||
false
|
||||
else
|
||||
value
|
||||
options.x ?= options.left if options.left
|
||||
options.y ?= options.top if options.top
|
||||
options.title ?= frameName
|
||||
options.width ?= 800
|
||||
options.height ?= 600
|
||||
|
||||
### Resolve relative urls. ###
|
||||
url = resolveURL url
|
||||
|
||||
(options[name] = parseInt(options[name], 10) if options[name]?) for name in ints
|
||||
|
||||
guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options
|
||||
if guestId
|
||||
BrowserWindowProxy.getOrCreate(guestId)
|
||||
else
|
||||
null
|
||||
|
||||
### Use the dialog API to implement alert(). ###
|
||||
window.alert = (message, title='') ->
|
||||
buttons = ['OK']
|
||||
message = message.toString()
|
||||
remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons}
|
||||
### Alert should always return undefined. ###
|
||||
return
|
||||
|
||||
### And the confirm(). ###
|
||||
window.confirm = (message, title='') ->
|
||||
buttons = ['OK', 'Cancel']
|
||||
cancelId = 1
|
||||
not remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId}
|
||||
|
||||
### But we do not support prompt(). ###
|
||||
window.prompt = ->
|
||||
throw new Error('prompt() is and will not be supported.')
|
||||
|
||||
if process.openerId?
|
||||
window.opener = BrowserWindowProxy.getOrCreate process.openerId
|
||||
|
||||
ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) ->
|
||||
### Manually dispatch event instead of using postMessage because we also need to ###
|
||||
### set event.source. ###
|
||||
event = document.createEvent 'Event'
|
||||
event.initEvent 'message', false, false
|
||||
event.data = message
|
||||
event.origin = sourceOrigin
|
||||
event.source = BrowserWindowProxy.getOrCreate(sourceId)
|
||||
window.dispatchEvent event
|
||||
|
||||
### Forward history operations to browser. ###
|
||||
sendHistoryOperation = (args...) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args...
|
||||
|
||||
getHistoryOperation = (args...) ->
|
||||
ipcRenderer.sendSync 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', args...
|
||||
|
||||
window.history.back = -> sendHistoryOperation 'goBack'
|
||||
window.history.forward = -> sendHistoryOperation 'goForward'
|
||||
window.history.go = (offset) -> sendHistoryOperation 'goToOffset', offset
|
||||
Object.defineProperty window.history, 'length',
|
||||
get: ->
|
||||
getHistoryOperation 'length'
|
||||
|
||||
### Make document.hidden and document.visibilityState return the correct value. ###
|
||||
Object.defineProperty document, 'hidden',
|
||||
get: ->
|
||||
currentWindow = remote.getCurrentWindow()
|
||||
currentWindow.isMinimized() || !currentWindow.isVisible()
|
||||
|
||||
Object.defineProperty document, 'visibilityState',
|
||||
get: ->
|
||||
if document.hidden then "hidden" else "visible"
|
249
atom/renderer/lib/override.js
Normal file
249
atom/renderer/lib/override.js
Normal file
|
@ -0,0 +1,249 @@
|
|||
var BrowserWindowProxy, a, getHistoryOperation, ipcRenderer, ref, remote, resolveURL, sendHistoryOperation,
|
||||
slice = [].slice;
|
||||
|
||||
ref = require('electron'), ipcRenderer = ref.ipcRenderer, remote = ref.remote;
|
||||
|
||||
|
||||
/* Helper function to resolve relative url. */
|
||||
|
||||
a = window.top.document.createElement('a');
|
||||
|
||||
resolveURL = function(url) {
|
||||
a.href = url;
|
||||
return a.href;
|
||||
};
|
||||
|
||||
|
||||
/* Window object returned by "window.open". */
|
||||
|
||||
BrowserWindowProxy = (function() {
|
||||
BrowserWindowProxy.proxies = {};
|
||||
|
||||
BrowserWindowProxy.getOrCreate = function(guestId) {
|
||||
var base;
|
||||
return (base = this.proxies)[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId);
|
||||
};
|
||||
|
||||
BrowserWindowProxy.remove = function(guestId) {
|
||||
return delete this.proxies[guestId];
|
||||
};
|
||||
|
||||
function BrowserWindowProxy(guestId1) {
|
||||
this.guestId = guestId1;
|
||||
this.closed = false;
|
||||
ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) {
|
||||
return function() {
|
||||
BrowserWindowProxy.remove(_this.guestId);
|
||||
return _this.closed = true;
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype.close = function() {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId);
|
||||
};
|
||||
|
||||
BrowserWindowProxy.prototype.focus = function() {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus');
|
||||
};
|
||||
|
||||
BrowserWindowProxy.prototype.blur = function() {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur');
|
||||
};
|
||||
|
||||
BrowserWindowProxy.prototype.postMessage = function(message, targetOrigin) {
|
||||
if (targetOrigin == null) {
|
||||
targetOrigin = '*';
|
||||
}
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, location.origin);
|
||||
};
|
||||
|
||||
BrowserWindowProxy.prototype["eval"] = function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(slice.call(args)));
|
||||
};
|
||||
|
||||
return BrowserWindowProxy;
|
||||
|
||||
})();
|
||||
|
||||
if (process.guestInstanceId == null) {
|
||||
|
||||
/* Override default window.close. */
|
||||
window.close = function() {
|
||||
return remote.getCurrentWindow().close();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* Make the browser window or guest view emit "new-window" event. */
|
||||
|
||||
window.open = function(url, frameName, features) {
|
||||
var feature, guestId, i, ints, j, len, len1, name, options, ref1, ref2, value;
|
||||
if (frameName == null) {
|
||||
frameName = '';
|
||||
}
|
||||
if (features == null) {
|
||||
features = '';
|
||||
}
|
||||
options = {};
|
||||
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 */
|
||||
ref1 = features.split(/,\s*/);
|
||||
for (i = 0, len = ref1.length; i < len; i++) {
|
||||
feature = ref1[i];
|
||||
ref2 = feature.split(/\s*=/), name = ref2[0], value = ref2[1];
|
||||
options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value;
|
||||
}
|
||||
if (options.left) {
|
||||
if (options.x == null) {
|
||||
options.x = options.left;
|
||||
}
|
||||
}
|
||||
if (options.top) {
|
||||
if (options.y == null) {
|
||||
options.y = options.top;
|
||||
}
|
||||
}
|
||||
if (options.title == null) {
|
||||
options.title = frameName;
|
||||
}
|
||||
if (options.width == null) {
|
||||
options.width = 800;
|
||||
}
|
||||
if (options.height == null) {
|
||||
options.height = 600;
|
||||
}
|
||||
|
||||
/* Resolve relative urls. */
|
||||
url = resolveURL(url);
|
||||
for (j = 0, len1 = ints.length; j < len1; j++) {
|
||||
name = ints[j];
|
||||
if (options[name] != null) {
|
||||
options[name] = parseInt(options[name], 10);
|
||||
}
|
||||
}
|
||||
guestId = ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options);
|
||||
if (guestId) {
|
||||
return BrowserWindowProxy.getOrCreate(guestId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Use the dialog API to implement alert(). */
|
||||
|
||||
window.alert = function(message, title) {
|
||||
var buttons;
|
||||
if (title == null) {
|
||||
title = '';
|
||||
}
|
||||
buttons = ['OK'];
|
||||
message = message.toString();
|
||||
remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: message,
|
||||
title: title,
|
||||
buttons: buttons
|
||||
});
|
||||
|
||||
/* Alert should always return undefined. */
|
||||
};
|
||||
|
||||
|
||||
/* And the confirm(). */
|
||||
|
||||
window.confirm = function(message, title) {
|
||||
var buttons, cancelId;
|
||||
if (title == null) {
|
||||
title = '';
|
||||
}
|
||||
buttons = ['OK', 'Cancel'];
|
||||
cancelId = 1;
|
||||
return !remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: message,
|
||||
title: title,
|
||||
buttons: buttons,
|
||||
cancelId: cancelId
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/* But we do not support prompt(). */
|
||||
|
||||
window.prompt = function() {
|
||||
throw new Error('prompt() is and will not be supported.');
|
||||
};
|
||||
|
||||
if (process.openerId != null) {
|
||||
window.opener = BrowserWindowProxy.getOrCreate(process.openerId);
|
||||
}
|
||||
|
||||
ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) {
|
||||
|
||||
/* Manually dispatch event instead of using postMessage because we also need to */
|
||||
|
||||
/* set event.source. */
|
||||
event = document.createEvent('Event');
|
||||
event.initEvent('message', false, false);
|
||||
event.data = message;
|
||||
event.origin = sourceOrigin;
|
||||
event.source = BrowserWindowProxy.getOrCreate(sourceId);
|
||||
return window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
|
||||
/* Forward history operations to browser. */
|
||||
|
||||
sendHistoryOperation = function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_NAVIGATION_CONTROLLER'].concat(slice.call(args)));
|
||||
};
|
||||
|
||||
getHistoryOperation = function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return ipcRenderer.sendSync.apply(ipcRenderer, ['ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER'].concat(slice.call(args)));
|
||||
};
|
||||
|
||||
window.history.back = function() {
|
||||
return sendHistoryOperation('goBack');
|
||||
};
|
||||
|
||||
window.history.forward = function() {
|
||||
return sendHistoryOperation('goForward');
|
||||
};
|
||||
|
||||
window.history.go = function(offset) {
|
||||
return sendHistoryOperation('goToOffset', offset);
|
||||
};
|
||||
|
||||
Object.defineProperty(window.history, 'length', {
|
||||
get: function() {
|
||||
return getHistoryOperation('length');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* Make document.hidden and document.visibilityState return the correct value. */
|
||||
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
get: function() {
|
||||
var currentWindow;
|
||||
currentWindow = remote.getCurrentWindow();
|
||||
return currentWindow.isMinimized() || !currentWindow.isVisible();
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
get: function() {
|
||||
if (document.hidden) {
|
||||
return "hidden";
|
||||
} else {
|
||||
return "visible";
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
{ipcRenderer, webFrame} = require 'electron'
|
||||
|
||||
requestId = 0
|
||||
|
||||
WEB_VIEW_EVENTS =
|
||||
'load-commit': ['url', 'isMainFrame']
|
||||
'did-finish-load': []
|
||||
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL']
|
||||
'did-frame-finish-load': ['isMainFrame']
|
||||
'did-start-loading': []
|
||||
'did-stop-loading': []
|
||||
'did-get-response-details': ['status', 'newURL', 'originalURL',
|
||||
'httpResponseCode', 'requestMethod', 'referrer',
|
||||
'headers']
|
||||
'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame']
|
||||
'dom-ready': []
|
||||
'console-message': ['level', 'message', 'line', 'sourceId']
|
||||
'devtools-opened': []
|
||||
'devtools-closed': []
|
||||
'devtools-focused': []
|
||||
'new-window': ['url', 'frameName', 'disposition', 'options']
|
||||
'will-navigate': ['url']
|
||||
'did-navigate': ['url']
|
||||
'did-navigate-in-page': ['url']
|
||||
'close': []
|
||||
'crashed': []
|
||||
'gpu-crashed': []
|
||||
'plugin-crashed': ['name', 'version']
|
||||
'media-started-playing': []
|
||||
'media-paused': []
|
||||
'did-change-theme-color': ['themeColor']
|
||||
'destroyed': []
|
||||
'page-title-updated': ['title', 'explicitSet']
|
||||
'page-favicon-updated': ['favicons']
|
||||
'enter-html-full-screen': []
|
||||
'leave-html-full-screen': []
|
||||
'found-in-page': ['result']
|
||||
|
||||
DEPRECATED_EVENTS =
|
||||
'page-title-updated': 'page-title-set'
|
||||
|
||||
dispatchEvent = (webView, eventName, eventKey, args...) ->
|
||||
if DEPRECATED_EVENTS[eventName]?
|
||||
dispatchEvent webView, DEPRECATED_EVENTS[eventName], eventKey, args...
|
||||
domEvent = new Event(eventName)
|
||||
for f, i in WEB_VIEW_EVENTS[eventKey]
|
||||
domEvent[f] = args[i]
|
||||
webView.dispatchEvent domEvent
|
||||
webView.onLoadCommit domEvent if eventName is 'load-commit'
|
||||
|
||||
module.exports =
|
||||
registerEvents: (webView, viewInstanceId) ->
|
||||
ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, eventName, args...) ->
|
||||
dispatchEvent webView, eventName, eventName, args...
|
||||
|
||||
ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}", (event, channel, args...) ->
|
||||
domEvent = new Event('ipc-message')
|
||||
domEvent.channel = channel
|
||||
domEvent.args = [args...]
|
||||
webView.dispatchEvent domEvent
|
||||
|
||||
ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}", (event, args...) ->
|
||||
domEvent = new Event('size-changed')
|
||||
for f, i in ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']
|
||||
domEvent[f] = args[i]
|
||||
webView.onSizeChanged domEvent
|
||||
|
||||
deregisterEvents: (viewInstanceId) ->
|
||||
ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}"
|
||||
ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}"
|
||||
ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}"
|
||||
|
||||
createGuest: (params, callback) ->
|
||||
requestId++
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId
|
||||
ipcRenderer.once "ATOM_SHELL_RESPONSE_#{requestId}", callback
|
||||
|
||||
attachGuest: (elementInstanceId, guestInstanceId, params) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params
|
||||
webFrame.attachGuest elementInstanceId
|
||||
|
||||
destroyGuest: (guestInstanceId) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId
|
||||
|
||||
setSize: (guestInstanceId, params) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params
|
||||
|
||||
setAllowTransparency: (guestInstanceId, allowtransparency) ->
|
||||
ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency
|
113
atom/renderer/lib/web-view/guest-view-internal.js
Normal file
113
atom/renderer/lib/web-view/guest-view-internal.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
var DEPRECATED_EVENTS, WEB_VIEW_EVENTS, dispatchEvent, ipcRenderer, ref, requestId, webFrame,
|
||||
slice = [].slice;
|
||||
|
||||
ref = require('electron'), ipcRenderer = ref.ipcRenderer, webFrame = ref.webFrame;
|
||||
|
||||
requestId = 0;
|
||||
|
||||
WEB_VIEW_EVENTS = {
|
||||
'load-commit': ['url', 'isMainFrame'],
|
||||
'did-finish-load': [],
|
||||
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'],
|
||||
'did-frame-finish-load': ['isMainFrame'],
|
||||
'did-start-loading': [],
|
||||
'did-stop-loading': [],
|
||||
'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'],
|
||||
'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'],
|
||||
'dom-ready': [],
|
||||
'console-message': ['level', 'message', 'line', 'sourceId'],
|
||||
'devtools-opened': [],
|
||||
'devtools-closed': [],
|
||||
'devtools-focused': [],
|
||||
'new-window': ['url', 'frameName', 'disposition', 'options'],
|
||||
'will-navigate': ['url'],
|
||||
'did-navigate': ['url'],
|
||||
'did-navigate-in-page': ['url'],
|
||||
'close': [],
|
||||
'crashed': [],
|
||||
'gpu-crashed': [],
|
||||
'plugin-crashed': ['name', 'version'],
|
||||
'media-started-playing': [],
|
||||
'media-paused': [],
|
||||
'did-change-theme-color': ['themeColor'],
|
||||
'destroyed': [],
|
||||
'page-title-updated': ['title', 'explicitSet'],
|
||||
'page-favicon-updated': ['favicons'],
|
||||
'enter-html-full-screen': [],
|
||||
'leave-html-full-screen': [],
|
||||
'found-in-page': ['result']
|
||||
};
|
||||
|
||||
DEPRECATED_EVENTS = {
|
||||
'page-title-updated': 'page-title-set'
|
||||
};
|
||||
|
||||
dispatchEvent = function() {
|
||||
var args, domEvent, eventKey, eventName, f, i, j, len, ref1, webView;
|
||||
webView = arguments[0], eventName = arguments[1], eventKey = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : [];
|
||||
if (DEPRECATED_EVENTS[eventName] != null) {
|
||||
dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(slice.call(args)));
|
||||
}
|
||||
domEvent = new Event(eventName);
|
||||
ref1 = WEB_VIEW_EVENTS[eventKey];
|
||||
for (i = j = 0, len = ref1.length; j < len; i = ++j) {
|
||||
f = ref1[i];
|
||||
domEvent[f] = args[i];
|
||||
}
|
||||
webView.dispatchEvent(domEvent);
|
||||
if (eventName === 'load-commit') {
|
||||
return webView.onLoadCommit(domEvent);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
registerEvents: function(webView, viewInstanceId) {
|
||||
ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId, function() {
|
||||
var args, event, eventName;
|
||||
event = arguments[0], eventName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
|
||||
return dispatchEvent.apply(null, [webView, eventName, eventName].concat(slice.call(args)));
|
||||
});
|
||||
ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId, function() {
|
||||
var args, channel, domEvent, event;
|
||||
event = arguments[0], channel = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
|
||||
domEvent = new Event('ipc-message');
|
||||
domEvent.channel = channel;
|
||||
domEvent.args = slice.call(args);
|
||||
return webView.dispatchEvent(domEvent);
|
||||
});
|
||||
return ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId, function() {
|
||||
var args, domEvent, event, f, i, j, len, ref1;
|
||||
event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
||||
domEvent = new Event('size-changed');
|
||||
ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight'];
|
||||
for (i = j = 0, len = ref1.length; j < len; i = ++j) {
|
||||
f = ref1[i];
|
||||
domEvent[f] = args[i];
|
||||
}
|
||||
return webView.onSizeChanged(domEvent);
|
||||
});
|
||||
},
|
||||
deregisterEvents: function(viewInstanceId) {
|
||||
ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId);
|
||||
ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId);
|
||||
return ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId);
|
||||
},
|
||||
createGuest: function(params, callback) {
|
||||
requestId++;
|
||||
ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId);
|
||||
return ipcRenderer.once("ATOM_SHELL_RESPONSE_" + requestId, callback);
|
||||
},
|
||||
attachGuest: function(elementInstanceId, guestInstanceId, params) {
|
||||
ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params);
|
||||
return webFrame.attachGuest(elementInstanceId);
|
||||
},
|
||||
destroyGuest: function(guestInstanceId) {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId);
|
||||
},
|
||||
setSize: function(guestInstanceId, params) {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params);
|
||||
},
|
||||
setAllowTransparency: function(guestInstanceId, allowtransparency) {
|
||||
return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency);
|
||||
}
|
||||
};
|
|
@ -1,240 +0,0 @@
|
|||
WebViewImpl = require './web-view'
|
||||
guestViewInternal = require './guest-view-internal'
|
||||
webViewConstants = require './web-view-constants'
|
||||
|
||||
{remote} = require 'electron'
|
||||
|
||||
### Helper function to resolve url set in attribute. ###
|
||||
a = document.createElement 'a'
|
||||
resolveURL = (url) ->
|
||||
a.href = url
|
||||
a.href
|
||||
|
||||
###
|
||||
Attribute objects.
|
||||
Default implementation of a WebView attribute.
|
||||
###
|
||||
class WebViewAttribute
|
||||
constructor: (name, webViewImpl) ->
|
||||
@name = name
|
||||
@webViewImpl = webViewImpl
|
||||
@ignoreMutation = false
|
||||
|
||||
@defineProperty()
|
||||
|
||||
### Retrieves and returns the attribute's value. ###
|
||||
getValue: -> @webViewImpl.webviewNode.getAttribute(@name) || ''
|
||||
|
||||
### Sets the attribute's value. ###
|
||||
setValue: (value) -> @webViewImpl.webviewNode.setAttribute(@name, value || '')
|
||||
|
||||
### Changes the attribute's value without triggering its mutation handler. ###
|
||||
setValueIgnoreMutation: (value) ->
|
||||
@ignoreMutation = true
|
||||
@setValue value
|
||||
@ignoreMutation = false
|
||||
|
||||
### Defines this attribute as a property on the webview node. ###
|
||||
defineProperty: ->
|
||||
Object.defineProperty @webViewImpl.webviewNode, @name,
|
||||
get: => @getValue()
|
||||
set: (value) => @setValue value
|
||||
enumerable: true
|
||||
|
||||
### Called when the attribute's value changes. ###
|
||||
handleMutation: ->
|
||||
|
||||
### An attribute that is treated as a Boolean. ###
|
||||
class BooleanAttribute extends WebViewAttribute
|
||||
constructor: (name, webViewImpl) ->
|
||||
super name, webViewImpl
|
||||
|
||||
getValue: -> @webViewImpl.webviewNode.hasAttribute @name
|
||||
|
||||
setValue: (value) ->
|
||||
unless value
|
||||
@webViewImpl.webviewNode.removeAttribute @name
|
||||
else
|
||||
@webViewImpl.webviewNode.setAttribute @name, ''
|
||||
|
||||
### Attribute that specifies whether transparency is allowed in the webview. ###
|
||||
class AllowTransparencyAttribute extends BooleanAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl
|
||||
|
||||
handleMutation: (oldValue, newValue) ->
|
||||
return unless @webViewImpl.guestInstanceId
|
||||
guestViewInternal.setAllowTransparency @webViewImpl.guestInstanceId, @getValue()
|
||||
|
||||
### Attribute used to define the demension limits of autosizing. ###
|
||||
class AutosizeDimensionAttribute extends WebViewAttribute
|
||||
constructor: (name, webViewImpl) ->
|
||||
super name, webViewImpl
|
||||
|
||||
getValue: -> parseInt(@webViewImpl.webviewNode.getAttribute(@name)) || 0
|
||||
|
||||
handleMutation: (oldValue, newValue) ->
|
||||
return unless @webViewImpl.guestInstanceId
|
||||
guestViewInternal.setSize @webViewImpl.guestInstanceId,
|
||||
enableAutoSize: @webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue()
|
||||
min:
|
||||
width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0
|
||||
height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0
|
||||
max:
|
||||
width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0
|
||||
height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0
|
||||
|
||||
### Attribute that specifies whether the webview should be autosized. ###
|
||||
class AutosizeAttribute extends BooleanAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl
|
||||
|
||||
handleMutation: AutosizeDimensionAttribute::handleMutation
|
||||
|
||||
### Attribute representing the state of the storage partition. ###
|
||||
class PartitionAttribute extends WebViewAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_PARTITION, webViewImpl
|
||||
@validPartitionId = true
|
||||
|
||||
handleMutation: (oldValue, newValue) ->
|
||||
newValue = newValue || ''
|
||||
|
||||
### The partition cannot change if the webview has already navigated. ###
|
||||
unless @webViewImpl.beforeFirstNavigation
|
||||
window.console.error webViewConstants.ERROR_MSG_ALREADY_NAVIGATED
|
||||
@setValueIgnoreMutation oldValue
|
||||
return
|
||||
|
||||
if newValue is 'persist:'
|
||||
@validPartitionId = false
|
||||
window.console.error webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE
|
||||
|
||||
### Attribute that handles the location and navigation of the webview. ###
|
||||
class SrcAttribute extends WebViewAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_SRC, webViewImpl
|
||||
@setupMutationObserver()
|
||||
|
||||
getValue: ->
|
||||
if @webViewImpl.webviewNode.hasAttribute @name
|
||||
resolveURL @webViewImpl.webviewNode.getAttribute(@name)
|
||||
else
|
||||
''
|
||||
|
||||
setValueIgnoreMutation: (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
|
||||
mutation observer |observer|, and then get handled even though we do not
|
||||
want to handle this mutation.
|
||||
###
|
||||
@observer.takeRecords()
|
||||
|
||||
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
|
||||
placeholder state.
|
||||
###
|
||||
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
|
||||
on every guest-initiated navigation.
|
||||
###
|
||||
@setValueIgnoreMutation oldValue
|
||||
return
|
||||
@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
|
||||
where the webview guest has crashed and navigating to the same address
|
||||
spawns off a new process.
|
||||
###
|
||||
setupMutationObserver: ->
|
||||
@observer = new MutationObserver (mutations) =>
|
||||
for mutation in mutations
|
||||
oldValue = mutation.oldValue
|
||||
newValue = @getValue()
|
||||
return if oldValue isnt newValue
|
||||
@handleMutation oldValue, newValue
|
||||
params =
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: [@name]
|
||||
@observer.observe @webViewImpl.webviewNode, params
|
||||
|
||||
parse: ->
|
||||
if not @webViewImpl.elementAttached or
|
||||
not @webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId or
|
||||
not @.getValue()
|
||||
return
|
||||
|
||||
unless @webViewImpl.guestInstanceId?
|
||||
if @webViewImpl.beforeFirstNavigation
|
||||
@webViewImpl.beforeFirstNavigation = false
|
||||
@webViewImpl.createGuest()
|
||||
return
|
||||
|
||||
### Navigate to |this.src|. ###
|
||||
opts = {}
|
||||
httpreferrer = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()
|
||||
if httpreferrer then opts.httpReferrer = httpreferrer
|
||||
|
||||
useragent = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue()
|
||||
if useragent then opts.userAgent = useragent
|
||||
|
||||
guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId)
|
||||
guestContents.loadURL @getValue(), opts
|
||||
|
||||
### Attribute specifies HTTP referrer. ###
|
||||
class HttpReferrerAttribute extends WebViewAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl
|
||||
|
||||
### Attribute specifies user agent ###
|
||||
class UserAgentAttribute extends WebViewAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl
|
||||
|
||||
### Attribute that set preload script. ###
|
||||
class PreloadAttribute extends WebViewAttribute
|
||||
constructor: (webViewImpl) ->
|
||||
super webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl
|
||||
|
||||
getValue: ->
|
||||
return '' unless @webViewImpl.webviewNode.hasAttribute @name
|
||||
preload = resolveURL @webViewImpl.webviewNode.getAttribute(@name)
|
||||
protocol = preload.substr 0, 5
|
||||
unless protocol is 'file:'
|
||||
console.error webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE
|
||||
preload = ''
|
||||
preload
|
||||
|
||||
### Sets up all of the webview attributes. ###
|
||||
WebViewImpl::setupWebViewAttributes = ->
|
||||
@attributes = {}
|
||||
|
||||
@attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this)
|
||||
@attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
|
||||
|
||||
autosizeAttributes = [
|
||||
webViewConstants.ATTRIBUTE_MAXHEIGHT
|
||||
webViewConstants.ATTRIBUTE_MAXWIDTH
|
||||
webViewConstants.ATTRIBUTE_MINHEIGHT
|
||||
webViewConstants.ATTRIBUTE_MINWIDTH
|
||||
]
|
||||
for attribute in autosizeAttributes
|
||||
@attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)
|
410
atom/renderer/lib/web-view/web-view-attributes.js
Normal file
410
atom/renderer/lib/web-view/web-view-attributes.js
Normal file
|
@ -0,0 +1,410 @@
|
|||
var AllowTransparencyAttribute, AutosizeAttribute, AutosizeDimensionAttribute, BooleanAttribute, HttpReferrerAttribute, PartitionAttribute, PreloadAttribute, SrcAttribute, UserAgentAttribute, WebViewAttribute, WebViewImpl, a, guestViewInternal, remote, resolveURL, webViewConstants,
|
||||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
hasProp = {}.hasOwnProperty;
|
||||
|
||||
WebViewImpl = require('./web-view');
|
||||
|
||||
guestViewInternal = require('./guest-view-internal');
|
||||
|
||||
webViewConstants = require('./web-view-constants');
|
||||
|
||||
remote = require('electron').remote;
|
||||
|
||||
|
||||
/* Helper function to resolve url set in attribute. */
|
||||
|
||||
a = document.createElement('a');
|
||||
|
||||
resolveURL = function(url) {
|
||||
a.href = url;
|
||||
return a.href;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Attribute objects.
|
||||
Default implementation of a WebView attribute.
|
||||
*/
|
||||
|
||||
WebViewAttribute = (function() {
|
||||
function WebViewAttribute(name, webViewImpl) {
|
||||
this.name = name;
|
||||
this.webViewImpl = webViewImpl;
|
||||
this.ignoreMutation = false;
|
||||
this.defineProperty();
|
||||
}
|
||||
|
||||
|
||||
/* Retrieves and returns the attribute's value. */
|
||||
|
||||
WebViewAttribute.prototype.getValue = function() {
|
||||
return this.webViewImpl.webviewNode.getAttribute(this.name) || '';
|
||||
};
|
||||
|
||||
|
||||
/* Sets the attribute's value. */
|
||||
|
||||
WebViewAttribute.prototype.setValue = function(value) {
|
||||
return this.webViewImpl.webviewNode.setAttribute(this.name, value || '');
|
||||
};
|
||||
|
||||
|
||||
/* Changes the attribute's value without triggering its mutation handler. */
|
||||
|
||||
WebViewAttribute.prototype.setValueIgnoreMutation = function(value) {
|
||||
this.ignoreMutation = true;
|
||||
this.setValue(value);
|
||||
return this.ignoreMutation = false;
|
||||
};
|
||||
|
||||
|
||||
/* Defines this attribute as a property on the webview node. */
|
||||
|
||||
WebViewAttribute.prototype.defineProperty = function() {
|
||||
return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
|
||||
get: (function(_this) {
|
||||
return function() {
|
||||
return _this.getValue();
|
||||
};
|
||||
})(this),
|
||||
set: (function(_this) {
|
||||
return function(value) {
|
||||
return _this.setValue(value);
|
||||
};
|
||||
})(this),
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/* Called when the attribute's value changes. */
|
||||
|
||||
WebViewAttribute.prototype.handleMutation = function() {};
|
||||
|
||||
return WebViewAttribute;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
/* An attribute that is treated as a Boolean. */
|
||||
|
||||
BooleanAttribute = (function(superClass) {
|
||||
extend(BooleanAttribute, superClass);
|
||||
|
||||
function BooleanAttribute(name, webViewImpl) {
|
||||
BooleanAttribute.__super__.constructor.call(this, name, webViewImpl);
|
||||
}
|
||||
|
||||
BooleanAttribute.prototype.getValue = function() {
|
||||
return this.webViewImpl.webviewNode.hasAttribute(this.name);
|
||||
};
|
||||
|
||||
BooleanAttribute.prototype.setValue = function(value) {
|
||||
if (!value) {
|
||||
return this.webViewImpl.webviewNode.removeAttribute(this.name);
|
||||
} else {
|
||||
return this.webViewImpl.webviewNode.setAttribute(this.name, '');
|
||||
}
|
||||
};
|
||||
|
||||
return BooleanAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute that specifies whether transparency is allowed in the webview. */
|
||||
|
||||
AllowTransparencyAttribute = (function(superClass) {
|
||||
extend(AllowTransparencyAttribute, superClass);
|
||||
|
||||
function AllowTransparencyAttribute(webViewImpl) {
|
||||
AllowTransparencyAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl);
|
||||
}
|
||||
|
||||
AllowTransparencyAttribute.prototype.handleMutation = function(oldValue, newValue) {
|
||||
if (!this.webViewImpl.guestInstanceId) {
|
||||
return;
|
||||
}
|
||||
return guestViewInternal.setAllowTransparency(this.webViewImpl.guestInstanceId, this.getValue());
|
||||
};
|
||||
|
||||
return AllowTransparencyAttribute;
|
||||
|
||||
})(BooleanAttribute);
|
||||
|
||||
|
||||
/* Attribute used to define the demension limits of autosizing. */
|
||||
|
||||
AutosizeDimensionAttribute = (function(superClass) {
|
||||
extend(AutosizeDimensionAttribute, superClass);
|
||||
|
||||
function AutosizeDimensionAttribute(name, webViewImpl) {
|
||||
AutosizeDimensionAttribute.__super__.constructor.call(this, name, webViewImpl);
|
||||
}
|
||||
|
||||
AutosizeDimensionAttribute.prototype.getValue = function() {
|
||||
return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0;
|
||||
};
|
||||
|
||||
AutosizeDimensionAttribute.prototype.handleMutation = function(oldValue, newValue) {
|
||||
if (!this.webViewImpl.guestInstanceId) {
|
||||
return;
|
||||
}
|
||||
return guestViewInternal.setSize(this.webViewImpl.guestInstanceId, {
|
||||
enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
|
||||
min: {
|
||||
width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0),
|
||||
height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0)
|
||||
},
|
||||
max: {
|
||||
width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0),
|
||||
height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return AutosizeDimensionAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute that specifies whether the webview should be autosized. */
|
||||
|
||||
AutosizeAttribute = (function(superClass) {
|
||||
extend(AutosizeAttribute, superClass);
|
||||
|
||||
function AutosizeAttribute(webViewImpl) {
|
||||
AutosizeAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl);
|
||||
}
|
||||
|
||||
AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation;
|
||||
|
||||
return AutosizeAttribute;
|
||||
|
||||
})(BooleanAttribute);
|
||||
|
||||
|
||||
/* Attribute representing the state of the storage partition. */
|
||||
|
||||
PartitionAttribute = (function(superClass) {
|
||||
extend(PartitionAttribute, superClass);
|
||||
|
||||
function PartitionAttribute(webViewImpl) {
|
||||
PartitionAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_PARTITION, webViewImpl);
|
||||
this.validPartitionId = true;
|
||||
}
|
||||
|
||||
PartitionAttribute.prototype.handleMutation = function(oldValue, newValue) {
|
||||
newValue = newValue || '';
|
||||
|
||||
/* The partition cannot change if the webview has already navigated. */
|
||||
if (!this.webViewImpl.beforeFirstNavigation) {
|
||||
window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED);
|
||||
this.setValueIgnoreMutation(oldValue);
|
||||
return;
|
||||
}
|
||||
if (newValue === 'persist:') {
|
||||
this.validPartitionId = false;
|
||||
return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE);
|
||||
}
|
||||
};
|
||||
|
||||
return PartitionAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute that handles the location and navigation of the webview. */
|
||||
|
||||
SrcAttribute = (function(superClass) {
|
||||
extend(SrcAttribute, superClass);
|
||||
|
||||
function SrcAttribute(webViewImpl) {
|
||||
SrcAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_SRC, webViewImpl);
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
|
||||
SrcAttribute.prototype.getValue = function() {
|
||||
if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
|
||||
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
SrcAttribute.prototype.setValueIgnoreMutation = function(value) {
|
||||
WebViewAttribute.prototype.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
|
||||
mutation observer |observer|, and then get handled even though we do not
|
||||
want to handle this mutation.
|
||||
*/
|
||||
return this.observer.takeRecords();
|
||||
};
|
||||
|
||||
SrcAttribute.prototype.handleMutation = function(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
|
||||
placeholder state.
|
||||
*/
|
||||
if (!newValue && oldValue) {
|
||||
|
||||
/*
|
||||
src attribute changes normally initiate a navigation. We suppress
|
||||
the next src attribute handler call to avoid reloading the page
|
||||
on every guest-initiated navigation.
|
||||
*/
|
||||
this.setValueIgnoreMutation(oldValue);
|
||||
return;
|
||||
}
|
||||
return this.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
|
||||
where the webview guest has crashed and navigating to the same address
|
||||
spawns off a new process.
|
||||
*/
|
||||
|
||||
SrcAttribute.prototype.setupMutationObserver = function() {
|
||||
var params;
|
||||
this.observer = new MutationObserver((function(_this) {
|
||||
return function(mutations) {
|
||||
var i, len, mutation, newValue, oldValue;
|
||||
for (i = 0, len = mutations.length; i < len; i++) {
|
||||
mutation = mutations[i];
|
||||
oldValue = mutation.oldValue;
|
||||
newValue = _this.getValue();
|
||||
if (oldValue !== newValue) {
|
||||
return;
|
||||
}
|
||||
_this.handleMutation(oldValue, newValue);
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
params = {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: [this.name]
|
||||
};
|
||||
return this.observer.observe(this.webViewImpl.webviewNode, params);
|
||||
};
|
||||
|
||||
SrcAttribute.prototype.parse = function() {
|
||||
var guestContents, httpreferrer, opts, useragent;
|
||||
if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) {
|
||||
return;
|
||||
}
|
||||
if (this.webViewImpl.guestInstanceId == null) {
|
||||
if (this.webViewImpl.beforeFirstNavigation) {
|
||||
this.webViewImpl.beforeFirstNavigation = false;
|
||||
this.webViewImpl.createGuest();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Navigate to |this.src|. */
|
||||
opts = {};
|
||||
httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue();
|
||||
if (httpreferrer) {
|
||||
opts.httpReferrer = httpreferrer;
|
||||
}
|
||||
useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue();
|
||||
if (useragent) {
|
||||
opts.userAgent = useragent;
|
||||
}
|
||||
guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId);
|
||||
return guestContents.loadURL(this.getValue(), opts);
|
||||
};
|
||||
|
||||
return SrcAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute specifies HTTP referrer. */
|
||||
|
||||
HttpReferrerAttribute = (function(superClass) {
|
||||
extend(HttpReferrerAttribute, superClass);
|
||||
|
||||
function HttpReferrerAttribute(webViewImpl) {
|
||||
HttpReferrerAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl);
|
||||
}
|
||||
|
||||
return HttpReferrerAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute specifies user agent */
|
||||
|
||||
UserAgentAttribute = (function(superClass) {
|
||||
extend(UserAgentAttribute, superClass);
|
||||
|
||||
function UserAgentAttribute(webViewImpl) {
|
||||
UserAgentAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl);
|
||||
}
|
||||
|
||||
return UserAgentAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Attribute that set preload script. */
|
||||
|
||||
PreloadAttribute = (function(superClass) {
|
||||
extend(PreloadAttribute, superClass);
|
||||
|
||||
function PreloadAttribute(webViewImpl) {
|
||||
PreloadAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl);
|
||||
}
|
||||
|
||||
PreloadAttribute.prototype.getValue = function() {
|
||||
var preload, protocol;
|
||||
if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
|
||||
return '';
|
||||
}
|
||||
preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
|
||||
protocol = preload.substr(0, 5);
|
||||
if (protocol !== 'file:') {
|
||||
console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE);
|
||||
preload = '';
|
||||
}
|
||||
return preload;
|
||||
};
|
||||
|
||||
return PreloadAttribute;
|
||||
|
||||
})(WebViewAttribute);
|
||||
|
||||
|
||||
/* Sets up all of the webview attributes. */
|
||||
|
||||
WebViewImpl.prototype.setupWebViewAttributes = function() {
|
||||
var attribute, autosizeAttributes, i, len, results;
|
||||
this.attributes = {};
|
||||
this.attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this);
|
||||
this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this);
|
||||
autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH];
|
||||
results = [];
|
||||
for (i = 0, len = autosizeAttributes.length; i < len; i++) {
|
||||
attribute = autosizeAttributes[i];
|
||||
results.push(this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this));
|
||||
}
|
||||
return results;
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
module.exports =
|
||||
### Attributes. ###
|
||||
ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency'
|
||||
ATTRIBUTE_AUTOSIZE: 'autosize'
|
||||
ATTRIBUTE_MAXHEIGHT: 'maxheight'
|
||||
ATTRIBUTE_MAXWIDTH: 'maxwidth'
|
||||
ATTRIBUTE_MINHEIGHT: 'minheight'
|
||||
ATTRIBUTE_MINWIDTH: 'minwidth'
|
||||
ATTRIBUTE_NAME: 'name'
|
||||
ATTRIBUTE_PARTITION: 'partition'
|
||||
ATTRIBUTE_SRC: 'src'
|
||||
ATTRIBUTE_HTTPREFERRER: 'httpreferrer'
|
||||
ATTRIBUTE_NODEINTEGRATION: 'nodeintegration'
|
||||
ATTRIBUTE_PLUGINS: 'plugins'
|
||||
ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity'
|
||||
ATTRIBUTE_ALLOWPOPUPS: 'allowpopups'
|
||||
ATTRIBUTE_PRELOAD: 'preload'
|
||||
ATTRIBUTE_USERAGENT: 'useragent'
|
||||
|
||||
### Internal attribute. ###
|
||||
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid'
|
||||
|
||||
### Error messages. ###
|
||||
ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.'
|
||||
ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' +
|
||||
'Script cannot be injected into content until the page has loaded.'
|
||||
ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.'
|
||||
ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.'
|
29
atom/renderer/lib/web-view/web-view-constants.js
Normal file
29
atom/renderer/lib/web-view/web-view-constants.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
module.exports = {
|
||||
|
||||
/* Attributes. */
|
||||
ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency',
|
||||
ATTRIBUTE_AUTOSIZE: 'autosize',
|
||||
ATTRIBUTE_MAXHEIGHT: 'maxheight',
|
||||
ATTRIBUTE_MAXWIDTH: 'maxwidth',
|
||||
ATTRIBUTE_MINHEIGHT: 'minheight',
|
||||
ATTRIBUTE_MINWIDTH: 'minwidth',
|
||||
ATTRIBUTE_NAME: 'name',
|
||||
ATTRIBUTE_PARTITION: 'partition',
|
||||
ATTRIBUTE_SRC: 'src',
|
||||
ATTRIBUTE_HTTPREFERRER: 'httpreferrer',
|
||||
ATTRIBUTE_NODEINTEGRATION: 'nodeintegration',
|
||||
ATTRIBUTE_PLUGINS: 'plugins',
|
||||
ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity',
|
||||
ATTRIBUTE_ALLOWPOPUPS: 'allowpopups',
|
||||
ATTRIBUTE_PRELOAD: 'preload',
|
||||
ATTRIBUTE_USERAGENT: 'useragent',
|
||||
|
||||
/* Internal attribute. */
|
||||
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',
|
||||
|
||||
/* Error messages. */
|
||||
ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.',
|
||||
ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' + 'Script cannot be injected into content until the page has loaded.',
|
||||
ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.',
|
||||
ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.'
|
||||
};
|
|
@ -1,356 +0,0 @@
|
|||
{deprecate, webFrame, remote, ipcRenderer} = require 'electron'
|
||||
v8Util = process.atomBinding 'v8_util'
|
||||
|
||||
guestViewInternal = require './guest-view-internal'
|
||||
webViewConstants = require './web-view-constants'
|
||||
|
||||
### ID generator. ###
|
||||
nextId = 0
|
||||
getNextId = -> ++nextId
|
||||
|
||||
### Represents the internal state of the WebView node. ###
|
||||
class WebViewImpl
|
||||
constructor: (@webviewNode) ->
|
||||
v8Util.setHiddenValue @webviewNode, 'internal', this
|
||||
@attached = false
|
||||
@elementAttached = false
|
||||
|
||||
@beforeFirstNavigation = true
|
||||
|
||||
### on* Event handlers. ###
|
||||
@on = {}
|
||||
|
||||
@browserPluginNode = @createBrowserPluginNode()
|
||||
shadowRoot = @webviewNode.createShadowRoot()
|
||||
@setupWebViewAttributes()
|
||||
@setupFocusPropagation()
|
||||
|
||||
@viewInstanceId = getNextId()
|
||||
|
||||
shadowRoot.appendChild @browserPluginNode
|
||||
|
||||
createBrowserPluginNode: ->
|
||||
###
|
||||
We create BrowserPlugin as a custom element in order to observe changes
|
||||
to attributes synchronously.
|
||||
###
|
||||
browserPluginNode = new WebViewImpl.BrowserPlugin()
|
||||
v8Util.setHiddenValue browserPluginNode, 'internal', this
|
||||
browserPluginNode
|
||||
|
||||
### Resets some state upon reattaching <webview> element to the DOM. ###
|
||||
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
|
||||
state. However, it may be the case that beforeFirstNavigation is false BUT
|
||||
guestInstanceId has yet to be initialized. This means that we have not
|
||||
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
|
||||
guestViewInternal.destroyGuest @guestInstanceId
|
||||
@webContents = null
|
||||
@guestInstanceId = undefined
|
||||
@beforeFirstNavigation = true
|
||||
@attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
|
||||
@internalInstanceId = 0
|
||||
|
||||
### Sets the <webview>.request property. ###
|
||||
setRequestPropertyOnWebViewNode: (request) ->
|
||||
Object.defineProperty @webviewNode, 'request', value: request, enumerable: true
|
||||
|
||||
setupFocusPropagation: ->
|
||||
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
|
||||
to allow <webview> to be focusable.
|
||||
See http://crbug.com/231664.
|
||||
###
|
||||
@webviewNode.setAttribute 'tabIndex', -1
|
||||
@webviewNode.addEventListener 'focus', (e) =>
|
||||
### Focus the BrowserPlugin when the <webview> takes focus. ###
|
||||
@browserPluginNode.focus()
|
||||
@webviewNode.addEventListener 'blur', (e) =>
|
||||
### Blur the BrowserPlugin when the <webview> loses focus. ###
|
||||
@browserPluginNode.blur()
|
||||
|
||||
###
|
||||
This observer monitors mutations to attributes of the <webview> and
|
||||
updates the BrowserPlugin properties accordingly. In turn, updating
|
||||
a BrowserPlugin property will update the corresponding BrowserPlugin
|
||||
attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
|
||||
details.
|
||||
###
|
||||
handleWebviewAttributeMutation: (attributeName, oldValue, newValue) ->
|
||||
if not @attributes[attributeName] or @attributes[attributeName].ignoreMutation
|
||||
return
|
||||
|
||||
### Let the changed attribute handle its own mutation; ###
|
||||
@attributes[attributeName].handleMutation oldValue, newValue
|
||||
|
||||
handleBrowserPluginAttributeMutation: (attributeName, oldValue, newValue) ->
|
||||
if attributeName is webViewConstants.ATTRIBUTE_INTERNALINSTANCEID and !oldValue and !!newValue
|
||||
@browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID
|
||||
@internalInstanceId = parseInt newValue
|
||||
|
||||
### Track when the element resizes using the element resize callback. ###
|
||||
webFrame.registerElementResizeCallback @internalInstanceId, @onElementResize.bind(this)
|
||||
|
||||
return unless @guestInstanceId
|
||||
|
||||
guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams()
|
||||
|
||||
onSizeChanged: (webViewEvent) ->
|
||||
newWidth = webViewEvent.newWidth
|
||||
newHeight = webViewEvent.newHeight
|
||||
|
||||
node = @webviewNode
|
||||
|
||||
width = node.offsetWidth
|
||||
height = node.offsetHeight
|
||||
|
||||
### Check the current bounds to make sure we do not resize <webview> ###
|
||||
### outside of current constraints. ###
|
||||
maxWidth = @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width
|
||||
maxHeight = @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width
|
||||
minWidth = @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width
|
||||
minHeight = @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width
|
||||
|
||||
minWidth = Math.min minWidth, maxWidth
|
||||
minHeight = Math.min minHeight, maxHeight
|
||||
|
||||
if not @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() or
|
||||
(newWidth >= minWidth and
|
||||
newWidth <= maxWidth and
|
||||
newHeight >= minHeight and
|
||||
newHeight <= maxHeight)
|
||||
node.style.width = newWidth + 'px'
|
||||
node.style.height = newHeight + 'px'
|
||||
###
|
||||
Only fire the DOM event if the size of the <webview> has actually
|
||||
changed.
|
||||
###
|
||||
@dispatchEvent webViewEvent
|
||||
|
||||
onElementResize: (newSize) ->
|
||||
### Dispatch the 'resize' event. ###
|
||||
resizeEvent = new Event('resize', bubbles: true)
|
||||
resizeEvent.newWidth = newSize.width
|
||||
resizeEvent.newHeight = newSize.height
|
||||
@dispatchEvent resizeEvent
|
||||
|
||||
if @guestInstanceId
|
||||
guestViewInternal.setSize @guestInstanceId, normal: newSize
|
||||
|
||||
createGuest: ->
|
||||
guestViewInternal.createGuest @buildParams(), (event, guestInstanceId) =>
|
||||
@attachWindow guestInstanceId
|
||||
|
||||
dispatchEvent: (webViewEvent) ->
|
||||
@webviewNode.dispatchEvent webViewEvent
|
||||
|
||||
### Adds an 'on<event>' property on the webview, which can be used to set/unset ###
|
||||
### an event handler. ###
|
||||
setupEventProperty: (eventName) ->
|
||||
propertyName = 'on' + eventName.toLowerCase()
|
||||
Object.defineProperty @webviewNode, propertyName,
|
||||
get: => @on[propertyName]
|
||||
set: (value) =>
|
||||
if @on[propertyName]
|
||||
@webviewNode.removeEventListener eventName, @on[propertyName]
|
||||
@on[propertyName] = value
|
||||
if value
|
||||
@webviewNode.addEventListener eventName, value
|
||||
enumerable: true
|
||||
|
||||
### Updates state upon loadcommit. ###
|
||||
onLoadCommit: (webViewEvent) ->
|
||||
oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC
|
||||
newValue = webViewEvent.url
|
||||
if webViewEvent.isMainFrame and (oldValue != newValue)
|
||||
###
|
||||
Touching the src attribute triggers a navigation. To avoid
|
||||
triggering a page reload on every guest-initiated navigation,
|
||||
we do not handle this mutation.
|
||||
###
|
||||
@attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation newValue
|
||||
|
||||
onAttach: (storagePartitionId) ->
|
||||
@attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue storagePartitionId
|
||||
|
||||
buildParams: ->
|
||||
params =
|
||||
instanceId: @viewInstanceId
|
||||
userAgentOverride: @userAgentOverride
|
||||
for own attributeName, attribute of @attributes
|
||||
params[attributeName] = attribute.getValue()
|
||||
###
|
||||
When the WebView is not participating in layout (display:none)
|
||||
then getBoundingClientRect() would report a width and height of 0.
|
||||
However, in the case where the WebView has a fixed size we can
|
||||
use that value to initially size the guest so as to avoid a relayout of
|
||||
the on display:block.
|
||||
###
|
||||
css = window.getComputedStyle @webviewNode, null
|
||||
elementRect = @webviewNode.getBoundingClientRect()
|
||||
params.elementWidth = parseInt(elementRect.width) ||
|
||||
parseInt(css.getPropertyValue('width'))
|
||||
params.elementHeight = parseInt(elementRect.height) ||
|
||||
parseInt(css.getPropertyValue('height'))
|
||||
params
|
||||
|
||||
attachWindow: (guestInstanceId) ->
|
||||
@guestInstanceId = guestInstanceId
|
||||
@webContents = remote.getGuestWebContents @guestInstanceId
|
||||
return true unless @internalInstanceId
|
||||
|
||||
guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams()
|
||||
|
||||
### Registers browser plugin <object> custom element. ###
|
||||
registerBrowserPluginElement = ->
|
||||
proto = Object.create HTMLObjectElement.prototype
|
||||
|
||||
proto.createdCallback = ->
|
||||
@setAttribute 'type', 'application/browser-plugin'
|
||||
@setAttribute 'id', 'browser-plugin-' + getNextId()
|
||||
### The <object> node fills in the <webview> container. ###
|
||||
@style.display = 'block'
|
||||
@style.width = '100%'
|
||||
@style.height = '100%'
|
||||
|
||||
proto.attributeChangedCallback = (name, oldValue, newValue) ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
return unless internal
|
||||
internal.handleBrowserPluginAttributeMutation name, oldValue, newValue
|
||||
|
||||
proto.attachedCallback = ->
|
||||
### Load the plugin immediately. ###
|
||||
unused = this.nonExistentAttribute
|
||||
|
||||
WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement 'browserplugin',
|
||||
extends: 'object', prototype: proto
|
||||
|
||||
delete proto.createdCallback
|
||||
delete proto.attachedCallback
|
||||
delete proto.detachedCallback
|
||||
delete proto.attributeChangedCallback
|
||||
|
||||
### Registers <webview> custom element. ###
|
||||
registerWebViewElement = ->
|
||||
proto = Object.create HTMLObjectElement.prototype
|
||||
|
||||
proto.createdCallback = ->
|
||||
new WebViewImpl(this)
|
||||
|
||||
proto.attributeChangedCallback = (name, oldValue, newValue) ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
return unless internal
|
||||
internal.handleWebviewAttributeMutation name, oldValue, newValue
|
||||
|
||||
proto.detachedCallback = ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
return unless internal
|
||||
guestViewInternal.deregisterEvents internal.viewInstanceId
|
||||
internal.elementAttached = false
|
||||
internal.reset()
|
||||
|
||||
proto.attachedCallback = ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
return unless internal
|
||||
unless internal.elementAttached
|
||||
guestViewInternal.registerEvents internal, internal.viewInstanceId
|
||||
internal.elementAttached = true
|
||||
internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
|
||||
|
||||
### Public-facing API methods. ###
|
||||
methods = [
|
||||
'getURL'
|
||||
'getTitle'
|
||||
'isLoading'
|
||||
'isWaitingForResponse'
|
||||
'stop'
|
||||
'reload'
|
||||
'reloadIgnoringCache'
|
||||
'canGoBack'
|
||||
'canGoForward'
|
||||
'canGoToOffset'
|
||||
'clearHistory'
|
||||
'goBack'
|
||||
'goForward'
|
||||
'goToIndex'
|
||||
'goToOffset'
|
||||
'isCrashed'
|
||||
'setUserAgent'
|
||||
'getUserAgent'
|
||||
'openDevTools'
|
||||
'closeDevTools'
|
||||
'isDevToolsOpened'
|
||||
'isDevToolsFocused'
|
||||
'inspectElement'
|
||||
'setAudioMuted'
|
||||
'isAudioMuted'
|
||||
'undo'
|
||||
'redo'
|
||||
'cut'
|
||||
'copy'
|
||||
'paste'
|
||||
'pasteAndMatchStyle'
|
||||
'delete'
|
||||
'selectAll'
|
||||
'unselect'
|
||||
'replace'
|
||||
'replaceMisspelling'
|
||||
'findInPage'
|
||||
'stopFindInPage'
|
||||
'getId'
|
||||
'downloadURL'
|
||||
'inspectServiceWorker'
|
||||
'print'
|
||||
'printToPDF'
|
||||
]
|
||||
|
||||
nonblockMethods = [
|
||||
'send',
|
||||
'sendInputEvent',
|
||||
'executeJavaScript',
|
||||
'insertCSS'
|
||||
]
|
||||
|
||||
### Forward proto.foo* method calls to WebViewImpl.foo*. ###
|
||||
createBlockHandler = (m) ->
|
||||
(args...) ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
internal.webContents[m] args...
|
||||
proto[m] = createBlockHandler m for m in methods
|
||||
|
||||
createNonBlockHandler = (m) ->
|
||||
(args...) ->
|
||||
internal = v8Util.getHiddenValue this, 'internal'
|
||||
ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m, args...)
|
||||
|
||||
proto[m] = createNonBlockHandler m for m in nonblockMethods
|
||||
|
||||
### Deprecated. ###
|
||||
deprecate.rename proto, 'getUrl', 'getURL'
|
||||
|
||||
window.WebView = webFrame.registerEmbedderCustomElement 'webview',
|
||||
prototype: proto
|
||||
|
||||
### Delete the callbacks so developers cannot call them and produce unexpected ###
|
||||
### behavior. ###
|
||||
delete proto.createdCallback
|
||||
delete proto.attachedCallback
|
||||
delete proto.detachedCallback
|
||||
delete proto.attributeChangedCallback
|
||||
|
||||
useCapture = true
|
||||
listener = (event) ->
|
||||
return if document.readyState == 'loading'
|
||||
registerBrowserPluginElement()
|
||||
registerWebViewElement()
|
||||
window.removeEventListener event.type, listener, useCapture
|
||||
window.addEventListener 'readystatechange', listener, true
|
||||
|
||||
module.exports = WebViewImpl
|
435
atom/renderer/lib/web-view/web-view.js
Normal file
435
atom/renderer/lib/web-view/web-view.js
Normal file
|
@ -0,0 +1,435 @@
|
|||
var WebViewImpl, deprecate, getNextId, guestViewInternal, ipcRenderer, listener, nextId, ref, registerBrowserPluginElement, registerWebViewElement, remote, useCapture, v8Util, webFrame, webViewConstants,
|
||||
hasProp = {}.hasOwnProperty,
|
||||
slice = [].slice;
|
||||
|
||||
ref = require('electron'), deprecate = ref.deprecate, webFrame = ref.webFrame, remote = ref.remote, ipcRenderer = ref.ipcRenderer;
|
||||
|
||||
v8Util = process.atomBinding('v8_util');
|
||||
|
||||
guestViewInternal = require('./guest-view-internal');
|
||||
|
||||
webViewConstants = require('./web-view-constants');
|
||||
|
||||
|
||||
/* ID generator. */
|
||||
|
||||
nextId = 0;
|
||||
|
||||
getNextId = function() {
|
||||
return ++nextId;
|
||||
};
|
||||
|
||||
|
||||
/* Represents the internal state of the WebView node. */
|
||||
|
||||
WebViewImpl = (function() {
|
||||
function WebViewImpl(webviewNode) {
|
||||
var shadowRoot;
|
||||
this.webviewNode = webviewNode;
|
||||
v8Util.setHiddenValue(this.webviewNode, 'internal', this);
|
||||
this.attached = false;
|
||||
this.elementAttached = false;
|
||||
this.beforeFirstNavigation = true;
|
||||
|
||||
/* on* Event handlers. */
|
||||
this.on = {};
|
||||
this.browserPluginNode = this.createBrowserPluginNode();
|
||||
shadowRoot = this.webviewNode.createShadowRoot();
|
||||
this.setupWebViewAttributes();
|
||||
this.setupFocusPropagation();
|
||||
this.viewInstanceId = getNextId();
|
||||
shadowRoot.appendChild(this.browserPluginNode);
|
||||
}
|
||||
|
||||
WebViewImpl.prototype.createBrowserPluginNode = function() {
|
||||
|
||||
/*
|
||||
We create BrowserPlugin as a custom element in order to observe changes
|
||||
to attributes synchronously.
|
||||
*/
|
||||
var browserPluginNode;
|
||||
browserPluginNode = new WebViewImpl.BrowserPlugin();
|
||||
v8Util.setHiddenValue(browserPluginNode, 'internal', this);
|
||||
return browserPluginNode;
|
||||
};
|
||||
|
||||
|
||||
/* Resets some state upon reattaching <webview> element to the DOM. */
|
||||
|
||||
WebViewImpl.prototype.reset = function() {
|
||||
|
||||
/*
|
||||
If guestInstanceId is defined then the <webview> has navigated and has
|
||||
already picked up a partition ID. Thus, we need to reset the initialization
|
||||
state. However, it may be the case that beforeFirstNavigation is false BUT
|
||||
guestInstanceId has yet to be initialized. This means that we have not
|
||||
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 (this.guestInstanceId) {
|
||||
guestViewInternal.destroyGuest(this.guestInstanceId);
|
||||
this.webContents = null;
|
||||
this.guestInstanceId = void 0;
|
||||
this.beforeFirstNavigation = true;
|
||||
this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true;
|
||||
}
|
||||
return this.internalInstanceId = 0;
|
||||
};
|
||||
|
||||
|
||||
/* Sets the <webview>.request property. */
|
||||
|
||||
WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function(request) {
|
||||
return Object.defineProperty(this.webviewNode, 'request', {
|
||||
value: request,
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.setupFocusPropagation = function() {
|
||||
if (!this.webviewNode.hasAttribute('tabIndex')) {
|
||||
|
||||
/*
|
||||
<webview> needs a tabIndex in order to be focusable.
|
||||
TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
|
||||
to allow <webview> to be focusable.
|
||||
See http://crbug.com/231664.
|
||||
*/
|
||||
this.webviewNode.setAttribute('tabIndex', -1);
|
||||
}
|
||||
this.webviewNode.addEventListener('focus', (function(_this) {
|
||||
return function(e) {
|
||||
|
||||
/* Focus the BrowserPlugin when the <webview> takes focus. */
|
||||
return _this.browserPluginNode.focus();
|
||||
};
|
||||
})(this));
|
||||
return this.webviewNode.addEventListener('blur', (function(_this) {
|
||||
return function(e) {
|
||||
|
||||
/* Blur the BrowserPlugin when the <webview> loses focus. */
|
||||
return _this.browserPluginNode.blur();
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
This observer monitors mutations to attributes of the <webview> and
|
||||
updates the BrowserPlugin properties accordingly. In turn, updating
|
||||
a BrowserPlugin property will update the corresponding BrowserPlugin
|
||||
attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
|
||||
details.
|
||||
*/
|
||||
|
||||
WebViewImpl.prototype.handleWebviewAttributeMutation = function(attributeName, oldValue, newValue) {
|
||||
if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Let the changed attribute handle its own mutation; */
|
||||
return this.attributes[attributeName].handleMutation(oldValue, newValue);
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function(attributeName, oldValue, newValue) {
|
||||
if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) {
|
||||
this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID);
|
||||
this.internalInstanceId = parseInt(newValue);
|
||||
|
||||
/* Track when the element resizes using the element resize callback. */
|
||||
webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this));
|
||||
if (!this.guestInstanceId) {
|
||||
return;
|
||||
}
|
||||
return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams());
|
||||
}
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.onSizeChanged = function(webViewEvent) {
|
||||
var height, maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width;
|
||||
newWidth = webViewEvent.newWidth;
|
||||
newHeight = webViewEvent.newHeight;
|
||||
node = this.webviewNode;
|
||||
width = node.offsetWidth;
|
||||
height = node.offsetHeight;
|
||||
|
||||
/* Check the current bounds to make sure we do not resize <webview> */
|
||||
|
||||
/* outside of current constraints. */
|
||||
maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width;
|
||||
maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width;
|
||||
minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width;
|
||||
minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width;
|
||||
minWidth = Math.min(minWidth, maxWidth);
|
||||
minHeight = Math.min(minHeight, maxHeight);
|
||||
if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) {
|
||||
node.style.width = newWidth + 'px';
|
||||
node.style.height = newHeight + 'px';
|
||||
|
||||
/*
|
||||
Only fire the DOM event if the size of the <webview> has actually
|
||||
changed.
|
||||
*/
|
||||
return this.dispatchEvent(webViewEvent);
|
||||
}
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.onElementResize = function(newSize) {
|
||||
|
||||
/* Dispatch the 'resize' event. */
|
||||
var resizeEvent;
|
||||
resizeEvent = new Event('resize', {
|
||||
bubbles: true
|
||||
});
|
||||
resizeEvent.newWidth = newSize.width;
|
||||
resizeEvent.newHeight = newSize.height;
|
||||
this.dispatchEvent(resizeEvent);
|
||||
if (this.guestInstanceId) {
|
||||
return guestViewInternal.setSize(this.guestInstanceId, {
|
||||
normal: newSize
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.createGuest = function() {
|
||||
return guestViewInternal.createGuest(this.buildParams(), (function(_this) {
|
||||
return function(event, guestInstanceId) {
|
||||
return _this.attachWindow(guestInstanceId);
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.dispatchEvent = function(webViewEvent) {
|
||||
return this.webviewNode.dispatchEvent(webViewEvent);
|
||||
};
|
||||
|
||||
|
||||
/* Adds an 'on<event>' property on the webview, which can be used to set/unset */
|
||||
|
||||
|
||||
/* an event handler. */
|
||||
|
||||
WebViewImpl.prototype.setupEventProperty = function(eventName) {
|
||||
var propertyName;
|
||||
propertyName = 'on' + eventName.toLowerCase();
|
||||
return Object.defineProperty(this.webviewNode, propertyName, {
|
||||
get: (function(_this) {
|
||||
return function() {
|
||||
return _this.on[propertyName];
|
||||
};
|
||||
})(this),
|
||||
set: (function(_this) {
|
||||
return function(value) {
|
||||
if (_this.on[propertyName]) {
|
||||
_this.webviewNode.removeEventListener(eventName, _this.on[propertyName]);
|
||||
}
|
||||
_this.on[propertyName] = value;
|
||||
if (value) {
|
||||
return _this.webviewNode.addEventListener(eventName, value);
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/* Updates state upon loadcommit. */
|
||||
|
||||
WebViewImpl.prototype.onLoadCommit = function(webViewEvent) {
|
||||
var newValue, oldValue;
|
||||
oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC);
|
||||
newValue = webViewEvent.url;
|
||||
if (webViewEvent.isMainFrame && (oldValue !== newValue)) {
|
||||
|
||||
/*
|
||||
Touching the src attribute triggers a navigation. To avoid
|
||||
triggering a page reload on every guest-initiated navigation,
|
||||
we do not handle this mutation.
|
||||
*/
|
||||
return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.onAttach = function(storagePartitionId) {
|
||||
return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId);
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.buildParams = function() {
|
||||
var attribute, attributeName, css, elementRect, params, ref1;
|
||||
params = {
|
||||
instanceId: this.viewInstanceId,
|
||||
userAgentOverride: this.userAgentOverride
|
||||
};
|
||||
ref1 = this.attributes;
|
||||
for (attributeName in ref1) {
|
||||
if (!hasProp.call(ref1, attributeName)) continue;
|
||||
attribute = ref1[attributeName];
|
||||
params[attributeName] = attribute.getValue();
|
||||
}
|
||||
|
||||
/*
|
||||
When the WebView is not participating in layout (display:none)
|
||||
then getBoundingClientRect() would report a width and height of 0.
|
||||
However, in the case where the WebView has a fixed size we can
|
||||
use that value to initially size the guest so as to avoid a relayout of
|
||||
the on display:block.
|
||||
*/
|
||||
css = window.getComputedStyle(this.webviewNode, null);
|
||||
elementRect = this.webviewNode.getBoundingClientRect();
|
||||
params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width'));
|
||||
params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height'));
|
||||
return params;
|
||||
};
|
||||
|
||||
WebViewImpl.prototype.attachWindow = function(guestInstanceId) {
|
||||
this.guestInstanceId = guestInstanceId;
|
||||
this.webContents = remote.getGuestWebContents(this.guestInstanceId);
|
||||
if (!this.internalInstanceId) {
|
||||
return true;
|
||||
}
|
||||
return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams());
|
||||
};
|
||||
|
||||
return WebViewImpl;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
/* Registers browser plugin <object> custom element. */
|
||||
|
||||
registerBrowserPluginElement = function() {
|
||||
var proto;
|
||||
proto = Object.create(HTMLObjectElement.prototype);
|
||||
proto.createdCallback = function() {
|
||||
this.setAttribute('type', 'application/browser-plugin');
|
||||
this.setAttribute('id', 'browser-plugin-' + getNextId());
|
||||
|
||||
/* The <object> node fills in the <webview> container. */
|
||||
this.style.display = 'block';
|
||||
this.style.width = '100%';
|
||||
return this.style.height = '100%';
|
||||
};
|
||||
proto.attributeChangedCallback = function(name, oldValue, newValue) {
|
||||
var internal;
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
if (!internal) {
|
||||
return;
|
||||
}
|
||||
return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
|
||||
};
|
||||
proto.attachedCallback = function() {
|
||||
|
||||
/* Load the plugin immediately. */
|
||||
var unused;
|
||||
return unused = this.nonExistentAttribute;
|
||||
};
|
||||
WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', {
|
||||
"extends": 'object',
|
||||
prototype: proto
|
||||
});
|
||||
delete proto.createdCallback;
|
||||
delete proto.attachedCallback;
|
||||
delete proto.detachedCallback;
|
||||
return delete proto.attributeChangedCallback;
|
||||
};
|
||||
|
||||
|
||||
/* Registers <webview> custom element. */
|
||||
|
||||
registerWebViewElement = function() {
|
||||
var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto;
|
||||
proto = Object.create(HTMLObjectElement.prototype);
|
||||
proto.createdCallback = function() {
|
||||
return new WebViewImpl(this);
|
||||
};
|
||||
proto.attributeChangedCallback = function(name, oldValue, newValue) {
|
||||
var internal;
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
if (!internal) {
|
||||
return;
|
||||
}
|
||||
return internal.handleWebviewAttributeMutation(name, oldValue, newValue);
|
||||
};
|
||||
proto.detachedCallback = function() {
|
||||
var internal;
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
if (!internal) {
|
||||
return;
|
||||
}
|
||||
guestViewInternal.deregisterEvents(internal.viewInstanceId);
|
||||
internal.elementAttached = false;
|
||||
return internal.reset();
|
||||
};
|
||||
proto.attachedCallback = function() {
|
||||
var internal;
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
if (!internal) {
|
||||
return;
|
||||
}
|
||||
if (!internal.elementAttached) {
|
||||
guestViewInternal.registerEvents(internal, internal.viewInstanceId);
|
||||
internal.elementAttached = true;
|
||||
return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse();
|
||||
}
|
||||
};
|
||||
|
||||
/* Public-facing API methods. */
|
||||
methods = ['getURL', 'getTitle', 'isLoading', 'isWaitingForResponse', 'stop', 'reload', 'reloadIgnoringCache', 'canGoBack', 'canGoForward', 'canGoToOffset', 'clearHistory', 'goBack', 'goForward', 'goToIndex', 'goToOffset', 'isCrashed', 'setUserAgent', 'getUserAgent', 'openDevTools', 'closeDevTools', 'isDevToolsOpened', 'isDevToolsFocused', 'inspectElement', 'setAudioMuted', 'isAudioMuted', 'undo', 'redo', 'cut', 'copy', 'paste', 'pasteAndMatchStyle', 'delete', 'selectAll', 'unselect', 'replace', 'replaceMisspelling', 'findInPage', 'stopFindInPage', 'getId', 'downloadURL', 'inspectServiceWorker', 'print', 'printToPDF'];
|
||||
nonblockMethods = ['send', 'sendInputEvent', 'executeJavaScript', 'insertCSS'];
|
||||
|
||||
/* Forward proto.foo* method calls to WebViewImpl.foo*. */
|
||||
createBlockHandler = function(m) {
|
||||
return function() {
|
||||
var args, internal, ref1;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
return (ref1 = internal.webContents)[m].apply(ref1, args);
|
||||
};
|
||||
};
|
||||
for (i = 0, len = methods.length; i < len; i++) {
|
||||
m = methods[i];
|
||||
proto[m] = createBlockHandler(m);
|
||||
}
|
||||
createNonBlockHandler = function(m) {
|
||||
return function() {
|
||||
var args, internal;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
internal = v8Util.getHiddenValue(this, 'internal');
|
||||
return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m].concat(slice.call(args)));
|
||||
};
|
||||
};
|
||||
for (j = 0, len1 = nonblockMethods.length; j < len1; j++) {
|
||||
m = nonblockMethods[j];
|
||||
proto[m] = createNonBlockHandler(m);
|
||||
}
|
||||
|
||||
/* Deprecated. */
|
||||
deprecate.rename(proto, 'getUrl', 'getURL');
|
||||
window.WebView = webFrame.registerEmbedderCustomElement('webview', {
|
||||
prototype: proto
|
||||
});
|
||||
|
||||
/* Delete the callbacks so developers cannot call them and produce unexpected */
|
||||
|
||||
/* behavior. */
|
||||
delete proto.createdCallback;
|
||||
delete proto.attachedCallback;
|
||||
delete proto.detachedCallback;
|
||||
return delete proto.attributeChangedCallback;
|
||||
};
|
||||
|
||||
useCapture = true;
|
||||
|
||||
listener = function(event) {
|
||||
if (document.readyState === 'loading') {
|
||||
return;
|
||||
}
|
||||
registerBrowserPluginElement();
|
||||
registerWebViewElement();
|
||||
return window.removeEventListener(event.type, listener, useCapture);
|
||||
};
|
||||
|
||||
window.addEventListener('readystatechange', listener, true);
|
||||
|
||||
module.exports = WebViewImpl;
|
Loading…
Add table
Add a link
Reference in a new issue