Browse Source

Merge pull request #6867 from electron/remote-function-properties

Allow accessing remote function properties
Cheng Zhao 8 years ago
parent
commit
f854b27bfb

+ 37 - 4
lib/renderer/api/remote.js

@@ -99,7 +99,7 @@ const setObjectMembers = function (ref, object, metaId, members) {
 
     let descriptor = { enumerable: member.enumerable }
     if (member.type === 'method') {
-      let remoteMemberFunction = function () {
+      const remoteMemberFunction = function () {
         if (this && this.constructor === remoteMemberFunction) {
           // Constructor call.
           let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(arguments))
@@ -110,13 +110,16 @@ const setObjectMembers = function (ref, object, metaId, members) {
           return metaToValue(ret)
         }
       }
+
+      let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name)
+
       descriptor.get = function () {
-        remoteMemberFunction.ref = ref  // The member should reference its object.
-        return remoteMemberFunction
+        descriptorFunction.ref = ref  // The member should reference its object.
+        return descriptorFunction
       }
       // Enable monkey-patch the method
       descriptor.set = function (value) {
-        remoteMemberFunction = value
+        descriptorFunction = value
         return value
       }
       descriptor.configurable = true
@@ -148,6 +151,36 @@ const setObjectPrototype = function (ref, object, metaId, descriptor) {
   Object.setPrototypeOf(object, proto)
 }
 
+// Wrap function in Proxy for accessing remote properties
+const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) {
+  let loaded = false
+
+  // Lazily load function properties
+  const loadRemoteProperties = () => {
+    if (loaded) return
+    loaded = true
+    const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, name)
+    setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
+  }
+
+  return new Proxy(remoteMemberFunction, {
+    get: (target, property, receiver) => {
+      if (!target.hasOwnProperty(property)) loadRemoteProperties()
+      return target[property]
+    },
+    ownKeys: (target) => {
+      loadRemoteProperties()
+      return Object.getOwnPropertyNames(target)
+    },
+    getOwnPropertyDescriptor: (target, property) => {
+      let descriptor = Object.getOwnPropertyDescriptor(target, property)
+      if (descriptor != null) return descriptor
+      loadRemoteProperties()
+      return Object.getOwnPropertyDescriptor(target, property)
+    }
+  })
+}
+
 // Convert meta data from browser into real value.
 const metaToValue = function (meta) {
   var el, i, len, ref1, results, ret

+ 32 - 0
spec/api-ipc-spec.js

@@ -52,6 +52,34 @@ describe('ipc module', function () {
       comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules'))
     })
 
+    it('should work with function properties', function () {
+      var a = remote.require(path.join(fixtures, 'module', 'export-function-with-properties.js'))
+      assert.equal(typeof a, 'function')
+      assert.equal(a.bar, 'baz')
+
+      a = remote.require(path.join(fixtures, 'module', 'function-with-properties.js'))
+      assert.equal(typeof a, 'object')
+      assert.equal(a.foo(), 'hello')
+      assert.equal(a.foo.bar, 'baz')
+      assert.equal(a.foo.nested.prop, 'yes')
+      assert.equal(a.foo.method1(), 'world')
+      assert.equal(a.foo.method1.prop1(), 123)
+
+      assert.ok(Object.keys(a.foo).includes('bar'))
+      assert.ok(Object.keys(a.foo).includes('nested'))
+      assert.ok(Object.keys(a.foo).includes('method1'))
+    })
+
+    it('should work with static class members', function () {
+      var a = remote.require(path.join(fixtures, 'module', 'remote-static.js'))
+      assert.equal(typeof a.Foo, 'function')
+      assert.equal(a.Foo.foo(), 3)
+      assert.equal(a.Foo.bar, 'baz')
+
+      var foo = new a.Foo()
+      assert.equal(foo.baz(), 123)
+    })
+
     it('handles circular references in arrays and objects', function () {
       var a = remote.require(path.join(fixtures, 'module', 'circular.js'))
 
@@ -120,6 +148,10 @@ describe('ipc module', function () {
       assert.equal(property.property, 1127)
       property.property = 1007
       assert.equal(property.property, 1007)
+      assert.equal(property.getFunctionProperty(), 'foo-browser')
+      property.func.property = 'bar'
+      assert.equal(property.getFunctionProperty(), 'bar-browser')
+
       var property2 = remote.require(path.join(fixtures, 'module', 'property.js'))
       assert.equal(property2.property, 1007)
       property.property = 1127

+ 4 - 0
spec/fixtures/module/export-function-with-properties.js

@@ -0,0 +1,4 @@
+function foo () {}
+foo.bar = 'baz'
+
+module.exports = foo

+ 17 - 0
spec/fixtures/module/function-with-properties.js

@@ -0,0 +1,17 @@
+function foo () {
+  return 'hello'
+}
+foo.bar = 'baz'
+foo.nested = {
+  prop: 'yes'
+}
+foo.method1 = function () {
+  return 'world'
+}
+foo.method1.prop1 = function () {
+  return 123
+}
+
+module.exports = {
+  foo: foo
+}

+ 10 - 0
spec/fixtures/module/property.js

@@ -1 +1,11 @@
 exports.property = 1127
+
+function func () {
+
+}
+func.property = 'foo'
+exports.func = func
+
+exports.getFunctionProperty = () => {
+  return `${func.property}-${process.type}`
+}

+ 15 - 0
spec/fixtures/module/remote-static.js

@@ -0,0 +1,15 @@
+class Foo {
+  static foo () {
+    return 3
+  }
+
+  baz () {
+    return 123
+  }
+}
+
+Foo.bar = 'baz'
+
+module.exports = {
+  Foo: Foo
+}