Browse Source

refactor: convert some more remote stuff to typescript (#20324)

Jeremy Apthorp 5 years ago
parent
commit
b7b0992768

+ 2 - 2
filenames.auto.gni

@@ -258,8 +258,8 @@ auto_filenames = {
     "lib/browser/ipc-main-internal-utils.ts",
     "lib/browser/ipc-main-internal.ts",
     "lib/browser/navigation-controller.js",
-    "lib/browser/remote/objects-registry.js",
-    "lib/browser/remote/server.js",
+    "lib/browser/remote/objects-registry.ts",
+    "lib/browser/remote/server.ts",
     "lib/browser/rpc-server.js",
     "lib/browser/utils.ts",
     "lib/common/api/clipboard.js",

+ 24 - 24
lib/browser/remote/objects-registry.js → lib/browser/remote/objects-registry.ts

@@ -1,27 +1,27 @@
 'use strict'
 
+import { WebContents } from 'electron'
+
 const v8Util = process.electronBinding('v8_util')
 
-const getOwnerKey = (webContents, contextId) => {
+const getOwnerKey = (webContents: WebContents, contextId: string) => {
   return `${webContents.id}-${contextId}`
 }
 
 class ObjectsRegistry {
-  constructor () {
-    this.nextId = 0
+  private nextId: number = 0
 
-    // Stores all objects by ref-counting.
-    // (id) => {object, count}
-    this.storage = {}
+  // Stores all objects by ref-counting.
+  // (id) => {object, count}
+  private storage: Record<number, { count: number, object: any }> = {}
 
-    // Stores the IDs + refCounts of objects referenced by WebContents.
-    // (ownerKey) => { id: refCount }
-    this.owners = {}
-  }
+  // Stores the IDs + refCounts of objects referenced by WebContents.
+  // (ownerKey) => { id: refCount }
+  private owners: Record<string, Map<number, number>> = {}
 
   // Register a new object and return its assigned ID. If the object is already
   // registered then the already assigned ID would be returned.
-  add (webContents, contextId, obj) {
+  add (webContents: WebContents, contextId: string, obj: any) {
     // Get or assign an ID to the object.
     const id = this.saveToStorage(obj)
 
@@ -38,12 +38,12 @@ class ObjectsRegistry {
       this.storage[id].count++
     }
 
-    owner.set(id, owner.get(id) + 1)
+    owner.set(id, owner.get(id)! + 1)
     return id
   }
 
   // Get an object according to its ID.
-  get (id) {
+  get (id: number) {
     const pointer = this.storage[id]
     if (pointer != null) return pointer.object
   }
@@ -59,11 +59,11 @@ class ObjectsRegistry {
   // object
   // For more details on why we do renderer side ref counting see
   // https://github.com/electron/electron/pull/17464
-  remove (webContents, contextId, id, rendererSideRefCount) {
+  remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) {
     const ownerKey = getOwnerKey(webContents, contextId)
     const owner = this.owners[ownerKey]
     if (owner && owner.has(id)) {
-      const newRefCount = owner.get(id) - rendererSideRefCount
+      const newRefCount = owner.get(id)! - rendererSideRefCount
 
       // Only completely remove if the number of references GCed in the
       // renderer is the same as the number of references we sent them
@@ -79,7 +79,7 @@ class ObjectsRegistry {
   }
 
   // Clear all references to objects refrenced by the WebContents.
-  clear (webContents, contextId) {
+  clear (webContents: WebContents, contextId: string) {
     const ownerKey = getOwnerKey(webContents, contextId)
     const owner = this.owners[ownerKey]
     if (!owner) return
@@ -90,8 +90,8 @@ class ObjectsRegistry {
   }
 
   // Private: Saves the object into storage and assigns an ID for it.
-  saveToStorage (object) {
-    let id = v8Util.getHiddenValue(object, 'atomId')
+  saveToStorage (object: any) {
+    let id: number = v8Util.getHiddenValue(object, 'atomId')
     if (!id) {
       id = ++this.nextId
       this.storage[id] = {
@@ -104,7 +104,7 @@ class ObjectsRegistry {
   }
 
   // Private: Dereference the object from store.
-  dereference (id) {
+  dereference (id: number) {
     const pointer = this.storage[id]
     if (pointer == null) {
       return
@@ -117,18 +117,18 @@ class ObjectsRegistry {
   }
 
   // Private: Clear the storage when renderer process is destroyed.
-  registerDeleteListener (webContents, contextId) {
+  registerDeleteListener (webContents: WebContents, contextId: string) {
     // contextId => ${processHostId}-${contextCount}
     const processHostId = contextId.split('-')[0]
-    const listener = (event, deletedProcessHostId) => {
+    const listener = (_: any, deletedProcessHostId: string) => {
       if (deletedProcessHostId &&
           deletedProcessHostId.toString() === processHostId) {
-        webContents.removeListener('render-view-deleted', listener)
+        webContents.removeListener('render-view-deleted' as any, listener)
         this.clear(webContents, contextId)
       }
     }
-    webContents.on('render-view-deleted', listener)
+    webContents.on('render-view-deleted' as any, listener)
   }
 }
 
-module.exports = new ObjectsRegistry()
+export default new ObjectsRegistry()

+ 179 - 80
lib/browser/remote/server.js → lib/browser/remote/server.ts

@@ -1,7 +1,13 @@
 'use strict'
 
-const electron = require('electron')
-const { EventEmitter } = require('events')
+import * as electron from 'electron'
+import { EventEmitter } from 'events'
+import { isBuffer, bufferToMeta, BufferMeta, metaToBuffer } from '@electron/internal/common/remote/buffer-utils'
+import objectsRegistry from './objects-registry'
+import { ipcMainInternal } from '../ipc-main-internal'
+import * as guestViewManager from '@electron/internal/browser/guest-view-manager'
+import * as errorUtils from '@electron/internal/common/error-utils'
+import { isPromise } from '@electron/internal/common/remote/is-promise'
 
 const v8Util = process.electronBinding('v8_util')
 const eventBinding = process.electronBinding('event')
@@ -11,13 +17,6 @@ if (!features.isRemoteModuleEnabled()) {
   throw new Error('remote module is disabled')
 }
 
-const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
-const objectsRegistry = require('@electron/internal/browser/remote/objects-registry')
-const guestViewManager = require('@electron/internal/browser/guest-view-manager')
-const bufferUtils = require('@electron/internal/common/remote/buffer-utils')
-const errorUtils = require('@electron/internal/common/error-utils')
-const { isPromise } = require('@electron/internal/common/remote/is-promise')
-
 const hasProp = {}.hasOwnProperty
 
 // The internal properties of Function.
@@ -29,8 +28,16 @@ const FUNCTION_PROPERTIES = [
 // id => Function
 const rendererFunctions = v8Util.createDoubleIDWeakMap()
 
+type ObjectMember = {
+  name: string,
+  value?: any,
+  enumerable?: boolean,
+  writable?: boolean,
+  type?: 'method' | 'get'
+}
+
 // Return the description of object's members:
-const getObjectMembers = function (object) {
+const getObjectMembers = function (object: any): ObjectMember[] {
   let names = Object.getOwnPropertyNames(object)
   // For Function, we should not override following properties even though they
   // are "own" properties.
@@ -41,20 +48,26 @@ const getObjectMembers = function (object) {
   }
   // Map properties to descriptors.
   return names.map((name) => {
-    const descriptor = Object.getOwnPropertyDescriptor(object, name)
-    const member = { name, enumerable: descriptor.enumerable, writable: false }
+    const descriptor = Object.getOwnPropertyDescriptor(object, name)!
+    let type: ObjectMember['type']
+    let writable = false
     if (descriptor.get === undefined && typeof object[name] === 'function') {
-      member.type = 'method'
+      type = 'method'
     } else {
-      if (descriptor.set || descriptor.writable) member.writable = true
-      member.type = 'get'
+      if (descriptor.set || descriptor.writable) writable = true
+      type = 'get'
     }
-    return member
+    return { name, enumerable: descriptor.enumerable, writable, type }
   })
 }
 
+type ObjProtoDescriptor = {
+  members: ObjectMember[],
+  proto: ObjProtoDescriptor
+} | null
+
 // Return the description of object's prototype.
-const getObjectPrototype = function (object) {
+const getObjectPrototype = function (object: any): ObjProtoDescriptor {
   const proto = Object.getPrototypeOf(object)
   if (proto === null || proto === Object.prototype) return null
   return {
@@ -63,74 +76,128 @@ const getObjectPrototype = function (object) {
   }
 }
 
+type MetaType = {
+  type: 'number',
+  value: number
+} | {
+  type: 'boolean',
+  value: boolean
+} | {
+  type: 'string',
+  value: string
+} | {
+  type: 'bigint',
+  value: bigint
+} | {
+  type: 'symbol',
+  value: symbol
+} | {
+  type: 'undefined',
+  value: undefined
+} | {
+  type: 'object' | 'function',
+  name: string,
+  members: ObjectMember[],
+  proto: ObjProtoDescriptor,
+  id: number,
+} | {
+  type: 'value',
+  value: any,
+} | {
+  type: 'buffer',
+  value: BufferMeta,
+} | {
+  type: 'array',
+  members: MetaType[]
+} | {
+  type: 'error',
+  members: ObjectMember[]
+} | {
+  type: 'date',
+  value: number
+} | {
+  type: 'promise',
+  then: MetaType
+}
+
 // Convert a real value into meta data.
-const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
+const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType {
   // Determine the type of value.
-  const meta = { type: typeof value }
-  if (meta.type === 'object') {
+  let type: MetaType['type'] = typeof value
+  if (type === 'object') {
     // Recognize certain types of objects.
     if (value === null) {
-      meta.type = 'value'
-    } else if (bufferUtils.isBuffer(value)) {
-      meta.type = 'buffer'
+      type = 'value'
+    } else if (isBuffer(value)) {
+      type = 'buffer'
     } else if (Array.isArray(value)) {
-      meta.type = 'array'
+      type = 'array'
     } else if (value instanceof Error) {
-      meta.type = 'error'
+      type = 'error'
     } else if (value instanceof Date) {
-      meta.type = 'date'
+      type = 'date'
     } else if (isPromise(value)) {
-      meta.type = 'promise'
+      type = 'promise'
     } else if (hasProp.call(value, 'callee') && value.length != null) {
       // Treat the arguments object as array.
-      meta.type = 'array'
+      type = 'array'
     } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
       // Treat simple objects as value.
-      meta.type = 'value'
+      type = 'value'
     }
   }
 
   // Fill the meta object according to value's type.
-  if (meta.type === 'array') {
-    meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
-  } else if (meta.type === 'object' || meta.type === 'function') {
-    meta.name = value.constructor ? value.constructor.name : ''
-
-    // Reference the original value if it's an object, because when it's
-    // passed to renderer we would assume the renderer keeps a reference of
-    // it.
-    meta.id = objectsRegistry.add(sender, contextId, value)
-    meta.members = getObjectMembers(value)
-    meta.proto = getObjectPrototype(value)
-  } else if (meta.type === 'buffer') {
-    meta.value = bufferUtils.bufferToMeta(value)
-  } else if (meta.type === 'promise') {
+  if (type === 'array') {
+    return {
+      type,
+      members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
+    }
+  } else if (type === 'object' || type === 'function') {
+    return {
+      type,
+      name: value.constructor ? value.constructor.name : '',
+      // Reference the original value if it's an object, because when it's
+      // passed to renderer we would assume the renderer keeps a reference of
+      // it.
+      id: objectsRegistry.add(sender, contextId, value),
+      members: getObjectMembers(value),
+      proto: getObjectPrototype(value)
+    }
+  } else if (type === 'buffer') {
+    return { type, value: bufferToMeta(value) }
+  } else if (type === 'promise') {
     // Add default handler to prevent unhandled rejections in main process
     // Instead they should appear in the renderer process
     value.then(function () {}, function () {})
 
-    meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
-      value.then(onFulfilled, onRejected)
-    })
-  } else if (meta.type === 'error') {
-    meta.members = plainObjectToMeta(value)
+    return {
+      type,
+      then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) {
+        value.then(onFulfilled, onRejected)
+      })
+    }
+  } else if (type === 'error') {
+    const members = plainObjectToMeta(value)
 
     // Error.name is not part of own properties.
-    meta.members.push({
+    members.push({
       name: 'name',
       value: value.name
     })
-  } else if (meta.type === 'date') {
-    meta.value = value.getTime()
+    return { type, members }
+  } else if (type === 'date') {
+    return { type, value: value.getTime() }
   } else {
-    meta.type = 'value'
-    meta.value = value
+    return {
+      type: 'value',
+      value
+    }
   }
-  return meta
 }
 
 // Convert object to meta by value.
-const plainObjectToMeta = function (obj) {
+const plainObjectToMeta = function (obj: any): ObjectMember[] {
   return Object.getOwnPropertyNames(obj).map(function (name) {
     return {
       name: name,
@@ -140,21 +207,21 @@ const plainObjectToMeta = function (obj) {
 }
 
 // Convert Error into meta data.
-const exceptionToMeta = function (error) {
+const exceptionToMeta = function (error: Error) {
   return {
     type: 'exception',
     value: errorUtils.serialize(error)
   }
 }
 
-const throwRPCError = function (message) {
-  const error = new Error(message)
+const throwRPCError = function (message: string) {
+  const error = new Error(message) as Error & {code: string, errno: number}
   error.code = 'EBADRPC'
   error.errno = -72
   throw error
 }
 
-const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
+const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => {
   const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
   let message = `Attempting to call a function in a renderer window that has been closed or released.` +
     `\nFunction provided here: ${location}`
@@ -167,7 +234,7 @@ const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
     if (remoteEvents.length > 0) {
       message += `\nRemote event names: ${remoteEvents.join(', ')}`
       remoteEvents.forEach((eventName) => {
-        sender.removeListener(eventName, callIntoRenderer)
+        sender.removeListener(eventName as any, callIntoRenderer)
       })
     }
   }
@@ -175,9 +242,41 @@ const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
   console.warn(message)
 }
 
+type MetaTypeFromRenderer = {
+  type: 'value',
+  value: any
+} | {
+  type: 'remote-object',
+  id: number
+} | {
+  type: 'array',
+  value: MetaTypeFromRenderer[]
+} | {
+  type: 'buffer',
+  value: BufferMeta
+} | {
+  type: 'date',
+  value: number
+} | {
+  type: 'promise',
+  then: MetaTypeFromRenderer
+} | {
+  type: 'object',
+  name: string,
+  members: { name: string, value: MetaTypeFromRenderer }[]
+} | {
+  type: 'function-with-return-value',
+  value: MetaTypeFromRenderer
+} | {
+  type: 'function',
+  id: number,
+  location: string,
+  length: number
+}
+
 // Convert array of meta data from renderer into array of real values.
-const unwrapArgs = function (sender, frameId, contextId, args) {
-  const metaToValue = function (meta) {
+const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
+  const metaToValue = function (meta: MetaTypeFromRenderer): any {
     switch (meta.type) {
       case 'value':
         return meta.value
@@ -186,7 +285,7 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
       case 'array':
         return unwrapArgs(sender, frameId, contextId, meta.value)
       case 'buffer':
-        return bufferUtils.metaToBuffer(meta.value)
+        return metaToBuffer(meta.value)
       case 'date':
         return new Date(meta.value)
       case 'promise':
@@ -194,7 +293,7 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
           then: metaToValue(meta.then)
         })
       case 'object': {
-        const ret = {}
+        const ret: any = {}
         Object.defineProperty(ret.constructor, 'name', { value: meta.name })
 
         for (const { name, value } of meta.members) {
@@ -217,10 +316,10 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
           return rendererFunctions.get(objectId)
         }
 
-        const callIntoRenderer = function (...args) {
+        const callIntoRenderer = function (this: any, ...args: any[]) {
           let succeed = false
           if (!sender.isDestroyed()) {
-            succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
+            succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
           }
           if (!succeed) {
             removeRemoteListenersAndLogWarning(this, callIntoRenderer)
@@ -234,20 +333,20 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
         return callIntoRenderer
       }
       default:
-        throw new TypeError(`Unknown type: ${meta.type}`)
+        throw new TypeError(`Unknown type: ${(meta as any).type}`)
     }
   }
   return args.map(metaToValue)
 }
 
-const isRemoteModuleEnabledImpl = function (contents) {
-  const webPreferences = contents.getLastWebPreferences() || {}
+const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) {
+  const webPreferences = (contents as any).getLastWebPreferences() || {}
   return !!webPreferences.enableRemoteModule
 }
 
 const isRemoteModuleEnabledCache = new WeakMap()
 
-const isRemoteModuleEnabled = function (contents) {
+const isRemoteModuleEnabled = function (contents: electron.WebContents) {
   if (!isRemoteModuleEnabledCache.has(contents)) {
     isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents))
   }
@@ -255,8 +354,8 @@ const isRemoteModuleEnabled = function (contents) {
   return isRemoteModuleEnabledCache.get(contents)
 }
 
-const handleRemoteCommand = function (channel, handler) {
-  ipcMainInternal.on(channel, (event, contextId, ...args) => {
+const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) {
+  ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => {
     let returnValue
     if (!isRemoteModuleEnabled(event.sender)) {
       event.returnValue = null
@@ -275,7 +374,7 @@ const handleRemoteCommand = function (channel, handler) {
   })
 }
 
-const emitCustomEvent = function (contents, eventName, ...args) {
+const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) {
   const event = eventBinding.createWithSender(contents)
 
   electron.app.emit(eventName, event, contents, ...args)
@@ -300,7 +399,7 @@ handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, modu
     if (customEvent.defaultPrevented) {
       throw new Error(`Blocked remote.require('${moduleName}')`)
     } else {
-      customEvent.returnValue = process.mainModule.require(moduleName)
+      customEvent.returnValue = process.mainModule!.require(moduleName)
     }
   }
 
@@ -314,7 +413,7 @@ handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId,
     if (customEvent.defaultPrevented) {
       throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
     } else {
-      customEvent.returnValue = electron[moduleName]
+      customEvent.returnValue = (electron as any)[moduleName]
     }
   }
 
@@ -328,7 +427,7 @@ handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globa
     if (customEvent.defaultPrevented) {
       throw new Error(`Blocked remote.getGlobal('${globalName}')`)
     } else {
-      customEvent.returnValue = global[globalName]
+      customEvent.returnValue = (global as any)[globalName]
     }
   }
 
@@ -385,8 +484,8 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId
   try {
     return valueToMeta(event.sender, contextId, func(...args), true)
   } catch (error) {
-    const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
-    err.cause = error
+    const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
+    (err as any).cause = error
     throw err
   }
 })
@@ -413,8 +512,8 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId,
   try {
     return valueToMeta(event.sender, contextId, object[method](...args), true)
   } catch (error) {
-    const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
-    err.cause = error
+    const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
+    (err as any).cause = error
     throw err
   }
 })

+ 1 - 1
lib/common/remote/buffer-utils.ts

@@ -39,7 +39,7 @@ export function isBuffer (value: BufferLike) {
   return ArrayBuffer.isView(value) || value instanceof ArrayBuffer
 }
 
-interface BufferMeta {
+export interface BufferMeta {
   type: keyof typeof typedArrays;
   data: Buffer;
   length: number | undefined;

+ 2 - 0
typings/internal-ambient.d.ts

@@ -29,6 +29,8 @@ declare namespace NodeJS {
     setHiddenValue<T>(obj: any, key: string, value: T): void;
     deleteHiddenValue(obj: any, key: string): void;
     requestGarbageCollectionForTesting(): void;
+    createDoubleIDWeakMap(): any;
+    setRemoteCallbackFreer(fn: Function, contextId: String, id: number, sender: any): void
   }
 
   interface Process {