upgrade electron 1.6.5 (#51)

This commit is contained in:
Lynn
2017-04-03 16:50:35 -07:00
committed by GitHub
parent 7ee0418c82
commit 662fa1d864
12 changed files with 67 additions and 1252 deletions

View File

@@ -71,10 +71,8 @@
notf.addEventListener('click', onclick);
function onclick(event) {
event.target.data.then(function(value) {
alert('notification clicked: ' + value.hello);
event.target.close();
})
event.target.close();
alert('notification clicked: ' + event.target.data.hello);
}
notf.addEventListener('close', onclose);

View File

@@ -47,7 +47,7 @@ function getUrlAndOpenMainWindow() {
getConfig()
.then(createWin).catch(function (err){
let title = 'Error loading configuration';
electron.dialog.showErrorBox(title, title + ': ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
}

View File

@@ -13,7 +13,6 @@ const badgeCount = require('./badgeCount.js');
const apiEnums = require('./enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const apiProxyCmds = apiEnums.proxyCmds
// can be overridden for testing
let checkValidWindow = true;
@@ -90,380 +89,8 @@ electron.ipcMain.on(apiName, (event, arg) => {
}
});
const Notify = require('./notify/notifyImpl.js');
// holds all project classes that can be created.
let api = {
Notify: Notify
}
// holds all proxy object instances
let liveObjs = {};
let id = 1;
function uniqueId() {
return id++;
}
/**
* Creates instance of given class for proxy in renderer process.
*
* @param {Object} args {
* className {String} name of class to create.
* }
*
* @return {Number} unique id for class intance created.
*/
electron.ipcMain.on(apiProxyCmds.createObject, function(event, args) {
if (!isValidWindow(event)) {
setResult(null);
return;
}
function setResult(value) {
/* eslint-disable no-param-reassign */
event.returnValue = value;
/* eslint-enable no-param-reassign */
}
if (args.className && api[args.className]) {
var obj = new api[args.className](...args.constructorArgsArray);
obj._callbacks = {};
let objId = uniqueId();
liveObjs[objId] = obj;
// special destroy event listener so implementation can raise event to
// clean up everything.
if (typeof obj.addEventListener === 'function' &&
typeof obj.removeEventListener === 'function') {
let destroy = function() {
var callbackIds = Object.keys(obj._callbacks);
callbackIds.forEach(function(callbackId) {
let callback = obj._callbacks[ callbackId ];
if (typeof callback === 'function') {
// invoke callback to proxy so it clean up itself.
callback(null, 'destroy');
if (callback.eventName) {
obj.removeEventListener(callback.eventName, callback);
}
}
});
obj.removeEventListener('destroy', destroy);
obj._callbacks = null;
var liveObj = liveObjs[objId];
if (liveObj) {
delete liveObjs[objId];
}
}
obj.addEventListener('destroy', destroy);
}
setResult(objId);
} else {
setResult(null);
}
});
/**
* Invokes a method for the proxy.
*
* @param {Object} args {
* objId {Number} id of object previously created, not needed for static call.
* invokeId {Number} id used by proxy to uniquely identify this method call
* methodName {String} name of method to call
* arguments: {Array} arguments to invoke method with.
* isStatic: {bool} true is this is a static method.
* className {String} name of class on which func is invoked.
* }
*
* @return {Object} {
* returnValue {Object} result of calling method
* invokeId {Number} id so proxy can identify method call
* }
*/
electron.ipcMain.on(apiProxyCmds.invokeMethod, function(event, args) {
if (!isValidWindow(event) || !args.invokeId) {
return;
}
if (!args.isStatic && (!args.objId || !liveObjs[args.objId])) {
event.sender.send(apiProxyCmds.invokeResult, {
error: 'calling obj is not present',
invokeId: args.invokeId
});
return;
}
// static method call must have className and class must exist in api
if (args.isStatic) {
if (!args.className) {
event.sender.send(apiProxyCmds.invokeResult, {
error: 'for static method must provide class name',
invokeId: args.invokeId
});
return;
}
if (!api[args.className]) {
event.sender.send(apiProxyCmds.invokeResult, {
error: 'no class exists: ' + args.className,
invokeId: args.invokeId
});
return;
}
}
let result;
let funcArgs = args.arguments || [];
if (args.isStatic) {
let classType = api[args.className];
if (!args.methodName || !classType[args.methodName]) {
event.sender.send(apiProxyCmds.invokeResult, {
error: 'no such static method',
invokeId: args.invokeId
});
return;
}
result = classType[args.methodName](...funcArgs);
} else {
let obj = liveObjs[args.objId];
if (!args.methodName || !obj[args.methodName]) {
event.sender.send(apiProxyCmds.invokeResult, {
error: 'no such method',
invokeId: args.invokeId
});
return;
}
// special method to lose ref to obj
if (args.methodName === 'destroy') {
delete liveObjs[args.objId];
}
result = obj[args.methodName](...funcArgs);
}
event.sender.send(apiProxyCmds.invokeResult, {
returnValue: result,
invokeId: args.invokeId
});
});
/**
* Getter implementation. Allows proxy to retrieve value from implementation
* object.
*
* @param {Object} args {
* objId {Number} id of object previously created (not needed for static get)
* getterId {Number} id used by proxy to uniquely identify this getter call.
* getterProperty {String} name of getter property to retrieve.
* isStatic {boo} true if this if getter is for static property.
* className {String} name of class we are operating on here.
* }
*
* @return {Object} {
* returnValue {Object} result of calling method
* getterId {Number} id so proxy can identify getter call
* }
*/
electron.ipcMain.on(apiProxyCmds.get, function(event, args) {
if (!isValidWindow(event) || !args.getterId) {
return;
}
// non-static calls must have an live instance available
if (!args.isStatic && (!args.objId || !liveObjs[args.objId])) {
event.sender.send(apiProxyCmds.getResult, {
error: 'calling obj is not present',
getterId: args.getterId
});
return;
}
// static get must have className and class must exist in api
if (args.isStatic) {
if (!args.className) {
event.sender.send(apiProxyCmds.getResult, {
error: 'for static getter must provide class name',
getterId: args.getterId
});
return;
}
if (!api[args.className]) {
event.sender.send(apiProxyCmds.getResult, {
error: 'no class exists: ' + args.className,
getterId: args.getterId
});
return;
}
}
if (!args.getterProperty) {
event.sender.send(apiProxyCmds.getResult, {
error: 'property name not provided',
getterId: args.getterId
});
return;
}
let result;
if (args.isStatic) {
let classType = api[args.className]
result = classType[args.getterProperty];
} else {
let obj = liveObjs[args.objId];
result = obj[args.getterProperty];
}
event.sender.send(apiProxyCmds.getResult, {
returnValue: result,
getterId: args.getterId
});
});
/**
* Setter implementation. Allows proxy to set value on implementation object.
*
* @param {Object} args {
* objId {Number} id of object previously created.
* setterProperty {String} name of setter property.
* setterValue {object} new value to set.
* }
*
* @return {Object} input setter value
*/
electron.ipcMain.on(apiProxyCmds.set, function(event, args) {
if (!isValidWindow(event)) {
setResult(null);
return;
}
if (!args.objId || !liveObjs[args.objId]) {
setResult(null);
return;
}
if (!args.setterProperty) {
setResult(null);
return;
}
function setResult(value) {
/* eslint-disable no-param-reassign */
event.returnValue = value;
/* eslint-enable no-param-reassign */
}
let obj = liveObjs[args.objId];
obj[args.setterProperty] = args.setterValue;
setResult(args.setterValue);
});
/**
* Listens to an event and calls back to renderer proxy when given event occurs.
*
* @param {Object} args {
* objId {Number} id of object previously created.
* callbackId {Number} id used by proxy to uniquely identify this event.
* eventName {String} name of event to listen for.
* }
*
* @return {Object} {
* result {Object} result from invoking callback.
* callbackId {Number} id so proxy can identify event that occurred.
* }
*/
electron.ipcMain.on(apiProxyCmds.addEvent, function(event, args) {
if (!isValidWindow(event)) {
return;
}
/* eslint-disable no-console */
if (!args.objId || !liveObjs[args.objId]) {
console.log('calling obj is not present');
return;
}
if (!args.callbackId) {
console.log('no callback id provided');
return;
}
if (!args.eventName) {
console.log('no eventName provided');
return;
}
/* eslint-enable no-console */
let obj = liveObjs[args.objId];
// callback invoked from implementation or locally in destroy to
// clean up event handlers.
let callbackFunc = function(result, type) {
event.sender.send(apiProxyCmds.eventCallback, {
callbackId: args.callbackId,
result: result,
type: type
});
}
callbackFunc.eventName = args.eventName;
obj._callbacks[args.callbackId] = callbackFunc;
obj.addEventListener(args.eventName, callbackFunc);
});
/**
* Stops listening to given event.
*
* @param {Object} args {
* objId {Number} id of object previously created.
* callbackId {Number} id used by proxy to uniquely identify this event.
* eventName {String} name of event to listen for.
* }
*/
electron.ipcMain.on(apiProxyCmds.removeEvent, function(event, args) {
if (!isValidWindow(event)) {
return;
}
/* eslint-disable no-console */
if (!args.objId || !liveObjs[args.objId]) {
console.log('calling obj is not present');
return;
}
if (!args.callbackId) {
console.log('no callback id provided');
return;
}
if (!args.eventName) {
console.log('no eventName provided');
return;
}
/* eslint-enable no-console */
let obj = liveObjs[args.objId];
let callbackFunc = obj._callbacks[args.callbackId];
if (typeof callbackFunc === 'function') {
obj.removeEventListener(args.eventName, callbackFunc);
}
});
function addNewInterface(name, interfaceClass) {
api[name] = interfaceClass;
}
// expose these methods primarily for testing...
module.exports = {
addNewInterface: addNewInterface,
shouldCheckValidWindow: function(shouldCheck) {
checkValidWindow = shouldCheck;
}

View File

@@ -41,7 +41,7 @@ function setStyle(config) {
setStyleOnDomElement(config.defaultStyleClose, close);
}
function setContents(notificationObj) {
function setContents(event, notificationObj) {
// sound
if (notificationObj.sound) {
// Check if file is accessible
@@ -122,7 +122,7 @@ function setStyleOnDomElement(styleObj, domElement) {
}
}
function loadConfig(conf) {
function loadConfig(event, conf) {
setStyle(conf || {})
}

View File

@@ -131,7 +131,13 @@ let config = {
// calcDimensions();
// }
app.on('ready', function() {
if (app.isReady) {
setup();
} else {
app.on('ready', setup);
}
function setup() {
setupConfig();
// if display added/removed/changed then re-run setup and remove all existing
@@ -139,8 +145,7 @@ app.on('ready', function() {
electron.screen.on('display-added', setupConfig);
electron.screen.on('display-removed', setupConfig);
electron.screen.on('display-metrics-changed', setupConfig);
});
}
function getTemplatePath() {
let templatePath = path.join(__dirname, 'electron-notify.html');

View File

@@ -8,6 +8,24 @@ const { notify } = require('./electron-notify.js');
* wrapper around electron-notify.
*/
class Notify {
/**
* Dislays a notifications
*
* @param {String} title Title of notification
* @param {Object} options {
* body {string} main text to display in notifications
* image {string} url of image to show in notification
* icon {string} url of image to show in notification
* flash {bool} true if notification should flash (default false)
* color {string} background color for notification
* tag {string} non-empty string to unique identify notf, if another
* notification arrives with same tag then it's content will
* replace existing notification.
* sticky {bool} if true notification will stay until user closes. default
* is false.
* data {object} arbitrary object to be stored with notification
* }
*/
constructor(title, options) {
this.emitter = new EventEmitter();
@@ -36,13 +54,17 @@ class Notify {
function onClick(arg) {
if (arg.id === this._id) {
this.emitter.emit('click');
this.emitter.emit('click', {
target: this
});
}
}
function onClose(arg) {
if (arg.id === this._id || arg.event === 'close-all') {
this.emitter.emit('close');
this.emitter.emit('close', {
target: this
});
this.destroy();
}
}
@@ -59,6 +81,9 @@ class Notify {
}
}
/**
* close notification
*/
close() {
if (typeof this._closeNotification === 'function') {
this._closeNotification('close');
@@ -66,20 +91,39 @@ class Notify {
this.destroy();
}
/**
* always allow showing notifications.
* @return {string} 'granted'
*/
static get permission() {
return 'granted';
}
/**
* returns data object passed in via constructor options
*/
get data() {
return this._data;
}
/**
* add event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to listen for
* @param {func} cb callback invoked when event occurs
*/
addEventListener(event, cb) {
if (event && typeof cb === 'function') {
this.emitter.on(event, cb);
}
}
/**
* remove event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to stop listening for.
* @param {func} cb callback associated with original addEventListener
*/
removeEventListener(event, cb) {
if (event && typeof cb === 'function') {
this.emitter.removeListener(event, cb);
@@ -89,10 +133,7 @@ class Notify {
//
// private stuff below here
//
destroy() {
// allow live instance to be destroyed
this.emitter.emit('destroy');
this.emitter.removeAllListeners();
}

View File

@@ -1,72 +0,0 @@
'use strict';
/**
* interface defn for notifications. Implementation of this interface
* is in notifyImpl.js
*
* Used by preloadMain.js to create proxy for actual implementation.
*
* Keep interface here in sync with implementation, in order
* to expose methods/props to renderer.
*
* Note: getters and method calls here return a promise.
*/
/* eslint-disable */
class Notify {
/**
* Dislays a notifications
*
* @param {String} title Title of notification
* @param {Object} options {
* body {string} main text to display in notifications
* image {string} url of image to show in notification
* icon {string} url of image to show in notification
* flash {bool} true if notification should flash (default false)
* color {string} background color for notification
* tag {string} non-empty string to unique identify notf, if another
* notification arrives with same tag then it's content will
* replace existing notification.
* sticky {bool} if true notification will stay until user closes. default
* is false.
* data {object} arbitrary object to be stored with notification
* }
*/
constructor(title, options) {}
/**
* close notification
*/
close() {}
/**
* This returns a promise and is always 'granted'
* @return {promise} promise fullfilled with 'granted'
*/
static get permission() {}
/**
* returns data object passed in via constructor options, return a
* promise that will be fullfilled with the data.
*/
get data() {}
/**
* add event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to listen for
* @param {func} cb callback invoked when event occurs
*/
addEventListener(event, cb) {}
/**
* remove event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to stop listening for.
* @param {func} cb callback associated with original addEventListener
*/
removeEventListener(event, cb) {}
}
/* eslint-enable */
module.exports = Notify;

View File

@@ -1,279 +0,0 @@
'use strict';
const { ipcRenderer } = require('electron');
const apiEnums = require('../enums/api.js');
const proxyCmds = apiEnums.proxyCmds;
/**
* Creates and returns a proxy (in renderer process) that will use IPC
* with main process where "real" instance is created.
*
* The constructor is executed synchronously, so take care to not block
* processes.
*
* All method calls will be sent over IPC to main process, evaulated and
* result returned back to main process. Method calls return a promise.
*
* Special method calls: "addEventListener" and "removeEventListener" allow
* attaching/detaching to events.
*
* Getters (e.g., x.y) will return a promise that gets fullfilled
* when ipc returns value.
*
* Setters are synchronously executed (so take care).
*
* Note: The "real" instance should implement a destroy method (e.g., close) that
* should be used to destroy the instance held in main process, otherwise a
* memory leak will occur - as renderer can not know when instance is no longer
* used. Would like to incorporate: https://github.com/EvolveLabs/electron-weak
*
* @param {Class} ApiClass reference to prototype/class constructor.
* @return {object} proxy for ApiClass.
*/
function createProxy(ApiClass) {
return new Proxy(ApiClass, constructorHandler);
}
let id = 1;
function uniqueId() {
return id++;
}
let constructorHandler = {
construct: function(target, argumentsList) {
var arg = {
className: target.name,
constructorArgsArray: argumentsList
};
var objId = ipcRenderer.sendSync(proxyCmds.createObject, arg);
if (!objId) {
throw new Error('can not create obj: ' + target.name);
}
var ProxyClass = new target();
ProxyClass._objId = objId;
ProxyClass._callbacks = new WeakMap();
let instanceHandler = {
get: instanceGetHandler,
set: instanceSetHandler
}
// work like to incorporate something like https://github.com/EvolveLabs/electron-weak
// here to tell when object is destroyed so we can ipc main process to
// loss ref to liveObj.
return new Proxy(ProxyClass, instanceHandler);
},
// static getter and method handler
get: staticGetHandler
}
function instanceGetHandler(target, name) {
// all methods and getters we support should be on the prototype
let prototype = Object.getPrototypeOf(target);
let desc = Object.getOwnPropertyDescriptor(prototype, name);
// does this have a "getter"
if (desc && desc.get) {
return getHandler(target, name, false);
}
// does this have a method
if (desc && typeof desc.value === 'function') {
if (name === 'addEventListener') {
return addEventHandler(target);
}
if (name === 'removeEventListener') {
return removeEventHandler(target);
}
return methodHandler(target, name, false);
}
return null;
}
function addEventHandler(target) {
return function(eventName, callback) {
var callbackId = eventName + uniqueId();
var args = {
callbackId: callbackId,
objId: target._objId,
eventName: eventName
};
ipcRenderer.send(proxyCmds.addEvent, args);
let callbackFunc = function(arg) {
if (arg.callbackId === callbackId) {
// special destroy callback so we can clean up event listeners.
if (arg.type === 'destroy') {
ipcRenderer.removeListener(proxyCmds.eventCallback,
callbackFunc);
target._callbacks.delete(callbackFunc);
return;
}
callback({
target: this,
type: eventName,
result: arg.result
});
}
}.bind(this);
ipcRenderer.on(proxyCmds.eventCallback, callbackFunc);
target._callbacks.set(callback, {
callbackId: callbackId,
callbackFunc: callbackFunc
});
}
}
function removeEventHandler(target) {
return function(eventName, callback) {
if (target._callbacks && target._callbacks.has(callback)) {
let callbackObj = target._callbacks.get(callback);
let args = {
eventName: eventName,
callbackId: callbackObj.callbackId,
objId: target._objId
}
ipcRenderer.removeListener(proxyCmds.eventCallback,
callbackObj.callbackFunc);
ipcRenderer.send(proxyCmds.removeEvent, args);
target._callbacks.delete(callback);
}
}
}
function methodHandler(target, methodName, isStatic) {
return function(...argPassedToMethod) {
return new Promise(function(resolve, reject) {
var invokeId = methodName + uniqueId();
var args = {
invokeId: invokeId,
objId: target._objId,
methodName: methodName,
arguments: argPassedToMethod,
isStatic: isStatic,
className: target.name
}
if (!isStatic) {
args.objId = target._objId;
}
ipcRenderer.on(proxyCmds.invokeResult, resultCallback);
ipcRenderer.send(proxyCmds.invokeMethod, args);
function removeEventListener() {
ipcRenderer.removeListener(proxyCmds.invokeResult,
resultCallback);
}
function resultCallback(arg) {
if (arg.invokeId === invokeId) {
window.clearTimeout(timer);
removeEventListener();
if (arg.error) {
reject('method called failed: ' + arg.error);
} else {
resolve(arg.returnValue);
}
}
}
// timeout in case we never hear anything back from main process
let timer = setTimeout(function() {
removeEventListener();
reject('timeout_no_reponse');
}, 5000);
});
}
}
function getHandler(target, property, isStatic) {
return new Promise(function(resolve, reject) {
var getterId = property + uniqueId();
var args = {
getterId: getterId,
getterProperty: property,
isStatic: isStatic,
className: target.name
}
if (!isStatic) {
args.objId = target._objId;
}
ipcRenderer.on(proxyCmds.getResult, resultCallback);
ipcRenderer.send(proxyCmds.get, args);
function removeEventListener() {
ipcRenderer.removeListener(proxyCmds.getResult,
resultCallback);
}
function resultCallback(arg) {
if (arg.getterId === getterId) {
window.clearTimeout(timer);
removeEventListener();
if (arg.error) {
reject('getter called failed: ' + arg.error);
} else {
resolve(arg.returnValue);
}
}
}
// timeout in case we never hear anything back from main process
let timer = setTimeout(function() {
removeEventListener();
reject('timeout_no_reponse');
}, 5000);
});
}
function instanceSetHandler(target, property, value) {
let prototype = Object.getPrototypeOf(target);
let desc = Object.getOwnPropertyDescriptor(prototype, property);
if (desc && desc.set) {
var args = {
objId: target._objId,
setterProperty: property,
setterValue: value
}
ipcRenderer.sendSync(proxyCmds.set, args);
return true;
}
return false;
}
function staticGetHandler(target, name) {
// all methods and getters we support should be on the prototype
let desc = Object.getOwnPropertyDescriptor(target, name);
// does this have a static "getter"
if (desc && desc.get) {
return getHandler(target, name, true);
}
// does this have a static method
if (desc && typeof desc.value === 'function') {
return methodHandler(target, name, true);
}
return null;
}
module.exports = createProxy

View File

@@ -12,22 +12,20 @@
// https://github.com/electron/electron/issues/2984
//
const { ipcRenderer } = require('electron');
const { ipcRenderer, remote } = require('electron');
const throttle = require('../utils/throttle.js');
const apiEnums = require('../enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const notifyInterface = require('../notify/notifyInterface.js');
const createProxy = require('./createProxy.js');
// hold ref so doesn't get GC'ed
const local = {
ipcRenderer: ipcRenderer,
};
var notify = remote.require('./notify/notifyImpl.js');
// throttle calls to this func to at most once per sec, called on leading edge.
const throttledSetBadgeCount = throttle(1000, function(count) {
local.ipcRenderer.send(apiName, {
@@ -63,9 +61,9 @@ window.SYM_API = {
/**
* provides api similar to html5 Notification, see details
* in notify/notifyInterface.js
* in notify/notifyImpl.js
*/
Notification: createProxy(notifyInterface),
Notification: notify,
/**
* allows JS to register a logger that can be used by electron main process.
@@ -93,7 +91,7 @@ window.SYM_API = {
Object.freeze(window.SYM_API);
// listen for log message from main process
local.ipcRenderer.on('log', (arg) => {
local.ipcRenderer.on('log', (event, arg) => {
if (local.logger && arg && arg.level && arg.msg) {
local.logger({
logLevel: arg.level,
@@ -109,7 +107,7 @@ local.ipcRenderer.on('log', (arg) => {
* need to use ipcRenderer to callback to main process.
* @type {object} arg.count - number: count to be displayed
*/
local.ipcRenderer.on('createBadgeDataUrl', (arg) => {
local.ipcRenderer.on('createBadgeDataUrl', (event, arg) => {
const count = arg && arg.count || 0;
// create 32 x 32 img

View File

@@ -69,7 +69,7 @@
"babel-preset-es2015": "^6.24.0",
"browserify": "^14.1.0",
"cross-env": "^3.2.4",
"electron": "1.5.1",
"electron": "1.6.5",
"electron-builder": "^13.9.0",
"electron-builder-squirrel-windows": "^12.3.0",
"electron-packager": "^8.5.2",

View File

@@ -1,184 +0,0 @@
// Note: fork of polyfill since it doesn't handle creating
// a Proxy from an existing Proxy.
// ToDo: need to submit PR to https://github.com/GoogleChrome/proxy-polyfill
/*
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
// note: polyfill here can be removed when everyone is using node >= 6.4.0
'use strict';
(function(scope) {
if (scope['Proxy']) {
return;
}
let lastRevokeFn = null;
/**
* @param {*} o
* @return {boolean} whether this is probably a (non-null) Object
*/
function isObject(o) {
return o ? (typeof o == 'object' || typeof o == 'function') : false;
}
/**
* @constructor
* @param {!Object} target
* @param {{apply, construct, get, set}} handler
*/
scope.Proxy = function(target, handler) {
if (!isObject(target) || !isObject(handler)) {
throw new TypeError('Cannot create proxy with a non-object as target or handler');
}
// Construct revoke function, and set lastRevokeFn so that Proxy.revocable can steal it.
// The caller might get the wrong revoke function if a user replaces or wraps scope.Proxy
// to call itself, but that seems unlikely especially when using the polyfill.
let throwRevoked = function() {};
lastRevokeFn = function() {
throwRevoked = function(trap) {
throw new TypeError(`Cannot perform '${trap}' on a proxy that has been revoked`);
};
};
// Fail on unsupported traps: Chrome doesn't do this, but ensure that users of the polyfill
// are a bit more careful. Copy the internal parts of handler to prevent user changes.
let unsafeHandler = handler;
handler = {'get': null, 'set': null, 'apply': null, 'construct': null};
for (let k in unsafeHandler) {
if (!(k in handler)) {
throw new TypeError(`Proxy polyfill does not support trap '${k}'`);
}
handler[k] = unsafeHandler[k];
}
if (typeof unsafeHandler == 'function') {
// Allow handler to be a function (which has an 'apply' method). This matches what is
// probably a bug in native versions. It treats the apply call as a trap to be configured.
handler.apply = unsafeHandler.apply.bind(unsafeHandler);
}
// Define proxy as this, or a Function (if either it's callable, or apply is set).
// TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it.
let proxy = this;
let isMethod = false;
let targetIsFunction = typeof target == 'function';
if (handler.apply || handler['construct'] || targetIsFunction) {
proxy = function Proxy() {
let usingNew = (this && this.constructor === proxy);
throwRevoked(usingNew ? 'construct' : 'apply');
if (usingNew && handler['construct']) {
return handler['construct'].call(this, target, arguments);
} else if (!usingNew && handler.apply) {
return handler.apply(target, this, arguments);
} else if (targetIsFunction) {
// since the target was a function, fallback to calling it directly.
if (usingNew) {
// inspired by answers to https://stackoverflow.com/q/1606797
let all = Array.prototype.slice.call(arguments);
all.unshift(target); // pass class as first arg to constructor, although irrelevant
// nb. cast to convince Closure compiler that this is a constructor
let f = /** @type {!Function} */ (target.bind.apply(target, all));
return new f();
}
return target.apply(this, arguments);
}
throw new TypeError(usingNew ? 'not a constructor' : 'not a function');
};
isMethod = true;
}
// Create default getters/setters. Create different code paths as handler.get/handler.set can't
// change after creation.
let getter = handler.get ? function(prop) {
throwRevoked('get');
return handler.get(this, prop, proxy);
} : function(prop) {
throwRevoked('get');
return this[prop];
};
let setter = handler.set ? function(prop, value) {
throwRevoked('set');
let status = handler.set(this, prop, value, proxy);
if (!status) {
// TODO(samthor): If the calling code is in strict mode, throw TypeError.
// It's (sometimes) possible to work this out, if this code isn't strict- try to load the
// callee, and if it's available, that code is non-strict. However, this isn't exhaustive.
}
} : function(prop, value) {
throwRevoked('set');
this[prop] = value;
};
// Clone direct properties (i.e., not part of a prototype).
let propertyNames = Object.getOwnPropertyNames(target);
if (target.__proto__) {
propertyNames = propertyNames.concat(Object.getOwnPropertyNames(target.__proto__))
}
let propertyMap = {};
propertyNames.forEach(function(prop) {
if (isMethod && prop in proxy) {
return; // ignore properties already here, e.g. 'bind', 'prototype' etc
}
let real = Object.getOwnPropertyDescriptor(target, prop);
let desc = {
enumerable: real && !!real.enumerable,
get: getter.bind(target, prop),
set: setter.bind(target, prop),
};
Object.defineProperty(proxy, prop, desc);
propertyMap[prop] = true;
});
// Set the prototype, or clone all prototype methods (always required if a getter is provided).
// TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward.
// An alternative here would be to _just_ clone methods to keep behavior consistent.
let prototypeOk = true;
if (Object.setPrototypeOf) {
Object.setPrototypeOf(proxy, Object.getPrototypeOf(target));
} else if (proxy.__proto__) {
proxy.__proto__ = target.__proto__;
} else {
prototypeOk = false;
}
if (handler.get || !prototypeOk) {
for (let k in target) {
if (propertyMap[k]) {
continue;
}
Object.defineProperty(proxy, k, {get: getter.bind(target, k)});
}
}
// The Proxy polyfill cannot handle adding new properties. Seal the target and proxy.
Object.seal(target);
Object.seal(proxy);
return proxy; // nb. if isMethod is true, proxy != this
};
scope.Proxy.revocable = function(target, handler) {
let p = new scope.Proxy(target, handler);
return {'proxy': p, 'revoke': lastRevokeFn};
};
scope.Proxy['revocable'] = scope.Proxy.revocable;
scope['Proxy'] = scope.Proxy;
})(typeof module !== 'undefined' && module['exports'] ? global : window);

View File

@@ -1,319 +0,0 @@
const EventEmitter = require('events');
const createProxy = require('../../js/preload/createProxy.js');
const mainApiMgr = require('../../js/mainApiMgr.js');
mainApiMgr.shouldCheckValidWindow(false);
// need polyfil for html5 Proxy
require('./proxy-polyfill');
class testInterface {
constructor(arg1, arg2) {}
getArg1() {};
addToArg2(value) {}
get argumentOne() {}
set newArgOneValue(newValue) {}
static staticMethodSum(a, b) {}
static get staticGetter() {}
addEventListener(event,cb) {}
removeEventListener(event,cb) {}
emitEvent(event) {}
close() {}
destroy() {}
}
class testImpl {
constructor(arg1, arg2) {
this._arg1 = arg1;
this._arg2 = arg2;
this.emitter = new EventEmitter();
}
getArg1() {
return this._arg1;
};
addToArg2(value) {
return this._arg2 + value;
}
get argumentOne() {
return this._arg1;
}
set newArgOneValue(newValue) {
this._arg1 = newValue;
}
static staticMethodSum(a, b) {
return a + b;
}
static get staticGetter() {
return 'hello world';
}
addEventListener(event, cb) {
this.emitter.on(event, cb);
}
removeEventListener(event,cb) {
this.emitter.removeListener(event, cb);
}
emitEvent(event) {
this.emitter.emit(event);
}
close() {
this.emitter.emit('destroy');
}
destroy() {}
}
mainApiMgr.addNewInterface('testInterface', testImpl);
describe('proxy tests...', function() {
var inst;
var TestInterfaceProxy;
const arg1 = 3, arg2 = 2;
beforeEach(function() {
TestInterfaceProxy = createProxy(testInterface);
inst = new TestInterfaceProxy(arg1, arg2);
});
test('getArg1 method', function(done) {
inst.getArg1().then(function(result) {
expect(result).toBe(arg1);
done();
});
});
test('addToArg2 method', function(done) {
inst.addToArg2(4).then(function(result) {
expect(result).toBe(arg2 + 4);
done();
});
});
test('getter: argumentOne', function(done) {
inst.argumentOne.then(function(result) {
expect(result).toBe(arg1);
done();
});
});
test('setter: newArgOneValue', function(done) {
inst.newArgOneValue = 10;
inst.argumentOne.then(function(result) {
expect(result).toBe(10);
done();
});
});
test('static method', function(done) {
TestInterfaceProxy.staticMethodSum(5, 6).then(function(result) {
expect(result).toBe(11);
done();
});
});
test('static getter', function(done) {
TestInterfaceProxy.staticGetter.then(function(result) {
expect(result).toBe('hello world');
done();
});
});
test('should call click handler', function(done) {
inst.addEventListener('click', function() {
done();
});
inst.emitEvent('click');
});
test('should call click handler twice', function(done) {
var timesCalled = 0;
inst.addEventListener('click', function() {
timesCalled++;
if (timesCalled === 2) {
done();
}
});
inst.emitEvent('click');
inst.emitEvent('click');
});
test('should only call close handler', function(done) {
inst.addEventListener('click', function() {
// shouldn't hit here
expect(false).toBe(true);
});
inst.addEventListener('close', function() {
done();
});
inst.emitEvent('close');
});
test('should not emit event addEventHandler', function(done) {
inst.addEventListener('click', function() {
// shouldn't hit here
expect(false).toBe(true);
});
inst.emitEvent('wrong-event');
setTimeout(done, 500);
});
test('should not call click handler after removed', function(done) {
function onClick() {
// shouldn't hit here
expect(true).toBe(false);
}
inst.addEventListener('click', onClick);
inst.removeEventListener('click', onClick);
inst.emitEvent('click');
setTimeout(done, 500);
});
test('should call click handler after add, remove, add', function(done) {
function onClick() {
done();
}
inst.addEventListener('click', onClick);
inst.removeEventListener('click', onClick);
inst.addEventListener('click', onClick);
inst.emitEvent('click');
});
});
describe('proxy test with multiple instances...', function() {
var inst1, inst2;
var TestInterfaceProxy;
const arg1 = 3, arg2 = 2;
beforeEach(function() {
TestInterfaceProxy = createProxy(testInterface);
inst1 = new TestInterfaceProxy(arg1, arg2);
inst2 = new TestInterfaceProxy(arg1, arg2);
});
test('should have indepdendent setters', function(done) {
inst1.newArgOneValue = 10;
inst2.newArgOneValue = 5;
inst1.argumentOne.then(function(result) {
expect(result).toBe(10);
inst2.argumentOne.then(function(result) {
expect(result).toBe(5);
done();
});
});
});
test('should only call event handler for inst2', function(done) {
inst1.addEventListener('click', function() {
// shouldn't hit here
expect(true).toBe(false);
});
inst2.addEventListener('click', function() {
done();
});
inst2.emitEvent('click');
});
test('should call event handler for inst1 and inst2', function(done) {
let isInst1Clicked = false;
let isInst2Clicked = false;
inst1.addEventListener('click', function() {
if (isInst1Clicked) { return; }
isInst1Clicked = true;
clicked();
});
inst2.addEventListener('click', function() {
if (isInst2Clicked) { return; }
isInst2Clicked = true;
clicked();
});
function clicked() {
if (isInst1Clicked && isInst1Clicked) {
done();
}
}
inst1.emitEvent('click');
inst2.emitEvent('click');
});
});
describe('proxy destroy tests...', function() {
var inst, arg1 = 5, arg2 = 4;
var TestInterfaceProxy;
beforeEach(function() {
TestInterfaceProxy = createProxy(testInterface);
inst = new TestInterfaceProxy(arg1, arg2);
});
test('can not use inst after destroy is invoked', function(done) {
inst.destroy();
inst.getArg1()
.then(function() {
// shouldn't get here
})
.catch(function(err) {
expect(err).toBe('method called failed: calling obj is not present')
done();
});
});
test('destroy from implementation side', function(done) {
inst.close();
inst.getArg1()
.then(function() {
// shouldn't get here
})
.catch(function(err) {
expect(err).toBe('method called failed: calling obj is not present')
done();
});
});
test('after destroy should not raise events', function(done) {
inst.destroy();
var clickedCalled = false;
inst.addEventListener('click', function() {
clickedCalled = true;
});
inst.emitEvent('click');
setTimeout(function() {
if (!clickedCalled) {
done();
}
}, 200);
})
});