Browse Source

refactor: tsify remote (#24034)

Jeremy Rose 4 years ago
parent
commit
7274467f73
5 changed files with 183 additions and 187 deletions
  1. 3 3
      filenames.auto.gni
  2. 35 128
      lib/browser/remote/server.ts
  3. 85 0
      lib/common/remote/types.ts
  4. 57 56
      lib/renderer/api/remote.ts
  5. 3 0
      typings/internal-ambient.d.ts

+ 3 - 3
filenames.auto.gni

@@ -149,7 +149,7 @@ auto_filenames = {
     "lib/renderer/api/crash-reporter.ts",
     "lib/renderer/api/desktop-capturer.ts",
     "lib/renderer/api/ipc-renderer.ts",
-    "lib/renderer/api/remote.js",
+    "lib/renderer/api/remote.ts",
     "lib/renderer/api/web-frame.ts",
     "lib/renderer/inspector.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
@@ -283,7 +283,7 @@ auto_filenames = {
     "lib/renderer/api/exports/electron.ts",
     "lib/renderer/api/ipc-renderer.ts",
     "lib/renderer/api/module-list.ts",
-    "lib/renderer/api/remote.js",
+    "lib/renderer/api/remote.ts",
     "lib/renderer/api/web-frame.ts",
     "lib/renderer/init.ts",
     "lib/renderer/inspector.ts",
@@ -326,7 +326,7 @@ auto_filenames = {
     "lib/renderer/api/exports/electron.ts",
     "lib/renderer/api/ipc-renderer.ts",
     "lib/renderer/api/module-list.ts",
-    "lib/renderer/api/remote.js",
+    "lib/renderer/api/remote.ts",
     "lib/renderer/api/web-frame.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
     "lib/renderer/ipc-renderer-internal.ts",

+ 35 - 128
lib/browser/remote/server.ts

@@ -1,11 +1,9 @@
-'use strict';
-
 import * as electron from 'electron';
 import { EventEmitter } from 'events';
 import objectsRegistry from './objects-registry';
 import { ipcMainInternal } from '../ipc-main-internal';
-import { isPromise, isSerializableObject, deserialize, serialize } from '@electron/internal/common/type-utils';
-import { Size } from 'electron/main';
+import { isPromise, isSerializableObject, deserialize, serialize } from '../../common/type-utils';
+import type { MetaTypeFromRenderer, ObjectMember, MetaType, ObjProtoDescriptor } from '../../common/remote/types';
 
 const v8Util = process.electronBinding('v8_util');
 const eventBinding = process.electronBinding('event');
@@ -27,14 +25,6 @@ const FUNCTION_PROPERTIES = [
 // id => Function
 const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>();
 
-type ObjectMember = {
-  name: string,
-  value?: any,
-  enumerable?: boolean,
-  writable?: boolean,
-  type?: 'method' | 'get'
-}
-
 // Return the description of object's members:
 const getObjectMembers = function (object: any): ObjectMember[] {
   let names = Object.getOwnPropertyNames(object);
@@ -60,11 +50,6 @@ const getObjectMembers = function (object: any): ObjectMember[] {
   });
 };
 
-type ObjProtoDescriptor = {
-  members: ObjectMember[],
-  proto: ObjProtoDescriptor
-} | null
-
 // Return the description of object's prototype.
 const getObjectPrototype = function (object: any): ObjProtoDescriptor {
   const proto = Object.getPrototypeOf(object);
@@ -75,76 +60,42 @@ const getObjectPrototype = function (object: any): ObjProtoDescriptor {
   };
 };
 
-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: Uint8Array,
-} | {
-  type: 'array',
-  members: MetaType[]
-} | {
-  type: 'error',
-  value: Error,
-  members: ObjectMember[]
-} | {
-  type: 'promise',
-  then: MetaType
-} | {
-  type: 'nativeimage'
-  value: electron.NativeImage
-}
-
 // Convert a real value into meta data.
 const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType {
   // Determine the type of value.
-  let type: MetaType['type'] = typeof value;
-  if (type === 'object') {
-    // Recognize certain types of objects.
-    if (value instanceof Buffer) {
-      type = 'buffer';
-    } else if (value instanceof NativeImage) {
-      type = 'nativeimage';
-    } else if (Array.isArray(value)) {
-      type = 'array';
-    } else if (value instanceof Error) {
-      type = 'error';
-    } else if (isSerializableObject(value)) {
-      type = 'value';
-    } else if (isPromise(value)) {
-      type = 'promise';
-    } else if (hasProp.call(value, 'callee') && value.length != null) {
-      // Treat the arguments object as array.
-      type = 'array';
-    } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
-      // Treat simple objects as value.
+  let type: MetaType['type'];
+
+  switch (typeof value) {
+    case 'object':
+      // Recognize certain types of objects.
+      if (value instanceof Buffer) {
+        type = 'buffer';
+      } else if (value instanceof NativeImage) {
+        type = 'nativeimage';
+      } else if (Array.isArray(value)) {
+        type = 'array';
+      } else if (value instanceof Error) {
+        type = 'error';
+      } else if (isSerializableObject(value)) {
+        type = 'value';
+      } else if (isPromise(value)) {
+        type = 'promise';
+      } else if (hasProp.call(value, 'callee') && value.length != null) {
+        // Treat the arguments object as array.
+        type = 'array';
+      } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
+        // Treat simple objects as value.
+        type = 'value';
+      } else {
+        type = 'object';
+      }
+      break;
+    case 'function':
+      type = 'function';
+      break;
+    default:
       type = 'value';
-    }
+      break;
   }
 
   // Fill the meta object according to value's type.
@@ -224,46 +175,6 @@ const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...a
   console.warn(message);
 };
 
-type MetaTypeFromRenderer = {
-  type: 'value',
-  value: any
-} | {
-  type: 'remote-object',
-  id: number
-} | {
-  type: 'array',
-  value: MetaTypeFromRenderer[]
-} | {
-  type: 'buffer',
-  value: Uint8Array
-} | {
-  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
-} | {
-  type: 'nativeimage',
-  value: {
-    size: Size,
-    buffer: Buffer,
-    scaleFactor: number,
-    dataURL: string
-  }[]
-}
-
 const fakeConstructor = (constructor: Function, name: string) =>
   new Proxy(Object, {
     get (target, prop, receiver) {
@@ -349,7 +260,7 @@ const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) {
 
 const isRemoteModuleEnabledCache = new WeakMap();
 
-const isRemoteModuleEnabled = function (contents: electron.WebContents) {
+export const isRemoteModuleEnabled = function (contents: electron.WebContents) {
   if (!isRemoteModuleEnabledCache.has(contents)) {
     isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents));
   }
@@ -564,7 +475,3 @@ handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId,
 handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
   objectsRegistry.clear(event.sender, contextId);
 });
-
-module.exports = {
-  isRemoteModuleEnabled
-};

+ 85 - 0
lib/common/remote/types.ts

@@ -0,0 +1,85 @@
+import type { Size } from 'electron/main';
+import type { NativeImage } from 'electron/common';
+
+export type ObjectMember = {
+  name: string,
+  value?: any,
+  enumerable?: boolean,
+  writable?: boolean,
+  type?: 'method' | 'get'
+}
+
+export type ObjProtoDescriptor = {
+  members: ObjectMember[],
+  proto: ObjProtoDescriptor
+} | null
+
+export type MetaType = {
+  type: 'object' | 'function',
+  name: string,
+  members: ObjectMember[],
+  proto: ObjProtoDescriptor,
+  id: number,
+} | {
+  type: 'value',
+  value: any,
+} | {
+  type: 'buffer',
+  value: Uint8Array,
+} | {
+  type: 'array',
+  members: MetaType[]
+} | {
+  type: 'error',
+  value: Error,
+  members: ObjectMember[]
+} | {
+  type: 'exception',
+  value: MetaType,
+} | {
+  type: 'promise',
+  then: MetaType
+} | {
+  type: 'nativeimage'
+  value: NativeImage
+}
+
+export type MetaTypeFromRenderer = {
+  type: 'value',
+  value: any
+} | {
+  type: 'remote-object',
+  id: number
+} | {
+  type: 'array',
+  value: MetaTypeFromRenderer[]
+} | {
+  type: 'buffer',
+  value: Uint8Array
+} | {
+  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
+} | {
+  type: 'nativeimage',
+  value: {
+    size: Size,
+    buffer: Buffer,
+    scaleFactor: number,
+    dataURL: string
+  }[]
+}

+ 57 - 56
lib/renderer/api/remote.js → lib/renderer/api/remote.ts

@@ -1,18 +1,20 @@
-'use strict';
+import { CallbacksRegistry } from '../remote/callbacks-registry';
+import { isPromise, isSerializableObject, serialize, deserialize } from '../../common/type-utils';
+import { MetaTypeFromRenderer, ObjectMember, ObjProtoDescriptor, MetaType } from '../../common/remote/types';
+import { ipcRendererInternal } from '../ipc-renderer-internal';
+import type { BrowserWindow, WebContents } from 'electron/main';
+import { browserModuleNames } from '@electron/internal/browser/api/module-names';
+import { commonModuleList } from '@electron/internal/common/api/module-list';
 
 const v8Util = process.electronBinding('v8_util');
 const { hasSwitch } = process.electronBinding('command_line');
 const { NativeImage } = process.electronBinding('native_image');
 
-const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
-const { isPromise, isSerializableObject, serialize, deserialize } = require('@electron/internal/common/type-utils');
-const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
-
 const callbacksRegistry = new CallbacksRegistry();
 const remoteObjectCache = v8Util.createIDWeakMap();
 
 // An unique ID that can represent current context.
-const contextId = v8Util.getHiddenValue(global, 'contextId');
+const contextId = v8Util.getHiddenValue<string>(global, 'contextId');
 
 // Notify the main process when current context is going to be released.
 // Note that when the renderer process is destroyed, the message may not be
@@ -26,8 +28,8 @@ process.on('exit', () => {
 const IS_REMOTE_PROXY = Symbol('is-remote-proxy');
 
 // Convert the arguments object into an array of meta data.
-function wrapArgs (args, visited = new Set()) {
-  const valueToMeta = (value) => {
+function wrapArgs (args: any[], visited = new Set()): any {
+  const valueToMeta = (value: any): any => {
     // Check for circular reference.
     if (visited.has(value)) {
       return {
@@ -60,7 +62,7 @@ function wrapArgs (args, visited = new Set()) {
       if (isPromise(value)) {
         return {
           type: 'promise',
-          then: valueToMeta(function (onFulfilled, onRejected) {
+          then: valueToMeta(function (onFulfilled: Function, onRejected: Function) {
             value.then(onFulfilled, onRejected);
           })
         };
@@ -71,7 +73,7 @@ function wrapArgs (args, visited = new Set()) {
         };
       }
 
-      const meta = {
+      const meta: MetaTypeFromRenderer = {
         type: 'object',
         name: value.constructor ? value.constructor.name : '',
         members: []
@@ -110,15 +112,15 @@ function wrapArgs (args, visited = new Set()) {
 // Populate object's members from descriptors.
 // The |ref| will be kept referenced by |members|.
 // This matches |getObjectMemebers| in rpc-server.
-function setObjectMembers (ref, object, metaId, members) {
+function setObjectMembers (ref: any, object: any, metaId: number, members: ObjectMember[]) {
   if (!Array.isArray(members)) return;
 
   for (const member of members) {
     if (Object.prototype.hasOwnProperty.call(object, member.name)) continue;
 
-    const descriptor = { enumerable: member.enumerable };
+    const descriptor: PropertyDescriptor = { enumerable: member.enumerable };
     if (member.type === 'method') {
-      const remoteMemberFunction = function (...args) {
+      const remoteMemberFunction = function (this: any, ...args: any[]) {
         let command;
         if (this && this.constructor === remoteMemberFunction) {
           command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
@@ -165,7 +167,7 @@ function setObjectMembers (ref, object, metaId, members) {
 
 // Populate object's prototype from descriptor.
 // This matches |getObjectPrototype| in rpc-server.
-function setObjectPrototype (ref, object, metaId, descriptor) {
+function setObjectPrototype (ref: any, object: any, metaId: number, descriptor: ObjProtoDescriptor) {
   if (descriptor === null) return;
   const proto = {};
   setObjectMembers(ref, proto, metaId, descriptor.members);
@@ -174,7 +176,7 @@ function setObjectPrototype (ref, object, metaId, descriptor) {
 }
 
 // Wrap function in Proxy for accessing remote properties
-function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
+function proxyFunctionProperties (remoteMemberFunction: Function, metaId: number, name: string) {
   let loaded = false;
 
   // Lazily load function properties
@@ -186,13 +188,13 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
     setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
   };
 
-  return new Proxy(remoteMemberFunction, {
-    set: (target, property, value, receiver) => {
+  return new Proxy(remoteMemberFunction as any, {
+    set: (target, property, value) => {
       if (property !== 'ref') loadRemoteProperties();
       target[property] = value;
       return true;
     },
-    get: (target, property, receiver) => {
+    get: (target, property) => {
       if (property === IS_REMOTE_PROXY) return true;
       if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties();
       const value = target[property];
@@ -215,28 +217,30 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
 }
 
 // Convert meta data from browser into real value.
-function metaToValue (meta) {
-  const types = {
-    value: () => meta.value,
-    array: () => meta.members.map((member) => metaToValue(member)),
-    nativeimage: () => deserialize(meta.value),
-    buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
-    promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
-    error: () => metaToError(meta),
-    exception: () => { throw metaToError(meta.value); }
-  };
-
-  if (Object.prototype.hasOwnProperty.call(types, meta.type)) {
-    return types[meta.type]();
+function metaToValue (meta: MetaType): any {
+  if (meta.type === 'value') {
+    return meta.value;
+  } else if (meta.type === 'array') {
+    return meta.members.map((member) => metaToValue(member));
+  } else if (meta.type === 'nativeimage') {
+    return deserialize(meta.value);
+  } else if (meta.type === 'buffer') {
+    return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength);
+  } else if (meta.type === 'promise') {
+    return Promise.resolve({ then: metaToValue(meta.then) });
+  } else if (meta.type === 'error') {
+    return metaToError(meta);
+  } else if (meta.type === 'exception') {
+    if (meta.value.type === 'error') { throw metaToError(meta.value); } else { throw new Error(`Unexpected value type in exception: ${meta.value.type}`); }
   } else {
     let ret;
-    if (remoteObjectCache.has(meta.id)) {
+    if ('id' in meta && remoteObjectCache.has(meta.id)) {
       return remoteObjectCache.get(meta.id);
     }
 
     // A shadow class to represent the remote function object.
     if (meta.type === 'function') {
-      const remoteFunction = function (...args) {
+      const remoteFunction = function (this: any, ...args: any[]) {
         let command;
         if (this && this.constructor === remoteFunction) {
           command = 'ELECTRON_BROWSER_CONSTRUCTOR';
@@ -253,7 +257,7 @@ function metaToValue (meta) {
 
     setObjectMembers(ret, ret, meta.id, meta.members);
     setObjectPrototype(ret, ret, meta.id, meta.proto);
-    if (ret.constructor && ret.constructor[IS_REMOTE_PROXY]) {
+    if (ret.constructor && (ret.constructor as any)[IS_REMOTE_PROXY]) {
       Object.defineProperty(ret.constructor, 'name', { value: meta.name });
     }
 
@@ -265,7 +269,7 @@ function metaToValue (meta) {
   }
 }
 
-function metaToError (meta) {
+function metaToError (meta: { type: 'error', value: any, members: ObjectMember[] }) {
   const obj = meta.value;
   for (const { name, value } of meta.members) {
     obj[name] = metaToValue(value);
@@ -273,7 +277,7 @@ function metaToError (meta) {
   return obj;
 }
 
-function handleMessage (channel, handler) {
+function handleMessage (channel: string, handler: Function) {
   ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
     if (passedContextId === contextId) {
       handler(id, ...args);
@@ -286,8 +290,8 @@ function handleMessage (channel, handler) {
 
 const enableStacks = hasSwitch('enable-api-filtering-logging');
 
-function getCurrentStack () {
-  const target = {};
+function getCurrentStack (): string | undefined {
+  const target = { stack: undefined as string | undefined };
   if (enableStacks) {
     Error.captureStackTrace(target, getCurrentStack);
   }
@@ -295,47 +299,47 @@ function getCurrentStack () {
 }
 
 // Browser calls a callback in renderer.
-handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
+handleMessage('ELECTRON_RENDERER_CALLBACK', (id: number, args: any) => {
   callbacksRegistry.apply(id, metaToValue(args));
 });
 
 // A callback in browser is released.
-handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
+handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id: number) => {
   callbacksRegistry.remove(id);
 });
 
-exports.require = (module) => {
+exports.require = (module: string) => {
   const command = 'ELECTRON_BROWSER_REQUIRE';
   const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
   return metaToValue(meta);
 };
 
 // Alias to remote.require('electron').xxx.
-exports.getBuiltin = (module) => {
+export function getBuiltin (module: string) {
   const command = 'ELECTRON_BROWSER_GET_BUILTIN';
   const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
   return metaToValue(meta);
-};
+}
 
-exports.getCurrentWindow = () => {
+export function getCurrentWindow (): BrowserWindow {
   const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
   const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
   return metaToValue(meta);
-};
+}
 
 // Get current WebContents object.
-exports.getCurrentWebContents = () => {
+export function getCurrentWebContents (): WebContents {
   const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
   const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
   return metaToValue(meta);
-};
+}
 
 // Get a global object in browser.
-exports.getGlobal = (name) => {
+export function getGlobal<T = any> (name: string): T {
   const command = 'ELECTRON_BROWSER_GLOBAL';
   const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack());
   return metaToValue(meta);
-};
+}
 
 // Get the process object in browser.
 Object.defineProperty(exports, 'process', {
@@ -343,22 +347,19 @@ Object.defineProperty(exports, 'process', {
 });
 
 // Create a function that will return the specified value when called in browser.
-exports.createFunctionWithReturnValue = (returnValue) => {
+export function createFunctionWithReturnValue<T> (returnValue: T): () => T {
   const func = () => returnValue;
   v8Util.setHiddenValue(func, 'returnValue', true);
   return func;
-};
+}
 
-const addBuiltinProperty = (name) => {
+const addBuiltinProperty = (name: string) => {
   Object.defineProperty(exports, name, {
     get: () => exports.getBuiltin(name)
   });
 };
 
-const { commonModuleList } = require('@electron/internal/common/api/module-list');
-const { browserModuleNames } = require('@electron/internal/browser/api/module-names');
-
-const browserModules = commonModuleList.concat(browserModuleNames.map(name => ({ name })));
+const browserModules = commonModuleList.concat(browserModuleNames.map(name => ({ name, loader: () => {} })));
 
 // And add a helper receiver for each one.
 browserModules

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

@@ -44,6 +44,9 @@ declare namespace NodeJS {
     weaklyTrackValue(value: any): void;
     clearWeaklyTrackedValues(): void;
     getWeaklyTrackedValues(): any[];
+    addRemoteObjectRef(contextId: string, id: number): void;
+    setRemoteCallbackFreer(fn: Function, contextId: string, id: number, sender: any): void
+    setRemoteObjectFreer(object: any, contextId: string, id: number): void
   }
 
   type DataPipe = {