Merge pull request #13637 from electron/fix-remote-3-0-x
fix: guard against double-freeing remote references (3-0-x)
This commit is contained in:
		
				commit
				
					
						e3204a5ec1
					
				
			
		
					 9 changed files with 160 additions and 113 deletions
				
			
		|  | @ -12,17 +12,27 @@ | |||
| #include "atom/common/native_mate_converters/gurl_converter.h" | ||||
| #include "atom/common/node_includes.h" | ||||
| #include "base/hash.h" | ||||
| #include "base/process/process_handle.h" | ||||
| #include "base/strings/stringprintf.h" | ||||
| #include "native_mate/dictionary.h" | ||||
| #include "url/origin.h" | ||||
| #include "v8/include/v8-profiler.h" | ||||
| 
 | ||||
| // This is defined in later versions of Chromium, remove this if you see
 | ||||
| // compiler complaining duplicate defines.
 | ||||
| #if defined(OS_WIN) || defined(OS_FUCHSIA) | ||||
| #define CrPRIdPid "ld" | ||||
| #else | ||||
| #define CrPRIdPid "d" | ||||
| #endif | ||||
| 
 | ||||
| namespace std { | ||||
| 
 | ||||
| // The hash function used by DoubleIDWeakMap.
 | ||||
| template <typename Type1, typename Type2> | ||||
| struct hash<std::pair<Type1, Type2>> { | ||||
|   std::size_t operator()(std::pair<Type1, Type2> value) const { | ||||
|     return base::HashInts<Type1, Type2>(value.first, value.second); | ||||
|     return base::HashInts(base::Hash(value.first), value.second); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
|  | @ -90,6 +100,16 @@ int32_t GetObjectHash(v8::Local<v8::Object> object) { | |||
|   return object->GetIdentityHash(); | ||||
| } | ||||
| 
 | ||||
| std::string GetContextID(v8::Isolate* isolate) { | ||||
|   // When a page is reloaded, V8 and blink may have optimizations that do not
 | ||||
|   // free blink::WebLocalFrame and v8::Context and reuse them for the new page,
 | ||||
|   // while we always recreate node::Environment when a page is loaded.
 | ||||
|   // So the only reliable way to return an identity for a page, is to return the
 | ||||
|   // address of the node::Environment instance.
 | ||||
|   node::Environment* env = node::Environment::GetCurrent(isolate); | ||||
|   return base::StringPrintf("%" CrPRIdPid "-%p", base::GetCurrentProcId(), env); | ||||
| } | ||||
| 
 | ||||
| void TakeHeapSnapshot(v8::Isolate* isolate) { | ||||
|   isolate->GetHeapProfiler()->TakeHeapSnapshot(); | ||||
| } | ||||
|  | @ -112,12 +132,14 @@ void Initialize(v8::Local<v8::Object> exports, | |||
|   dict.SetMethod("setHiddenValue", &SetHiddenValue); | ||||
|   dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); | ||||
|   dict.SetMethod("getObjectHash", &GetObjectHash); | ||||
|   dict.SetMethod("getContextId", &GetContextID); | ||||
|   dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); | ||||
|   dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo); | ||||
|   dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo); | ||||
|   dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create); | ||||
|   dict.SetMethod("createDoubleIDWeakMap", | ||||
|                  &atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create); | ||||
|   dict.SetMethod( | ||||
|       "createDoubleIDWeakMap", | ||||
|       &atom::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create); | ||||
|   dict.SetMethod("requestGarbageCollectionForTesting", | ||||
|                  &RequestGarbageCollectionForTesting); | ||||
|   dict.SetMethod("isSameOrigin", &IsSameOrigin); | ||||
|  |  | |||
|  | @ -15,17 +15,20 @@ namespace atom { | |||
| // static
 | ||||
| void RemoteCallbackFreer::BindTo(v8::Isolate* isolate, | ||||
|                                  v8::Local<v8::Object> target, | ||||
|                                  const std::string& context_id, | ||||
|                                  int object_id, | ||||
|                                  content::WebContents* web_contents) { | ||||
|   new RemoteCallbackFreer(isolate, target, object_id, web_contents); | ||||
|   new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents); | ||||
| } | ||||
| 
 | ||||
| RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate, | ||||
|                                          v8::Local<v8::Object> target, | ||||
|                                          const std::string& context_id, | ||||
|                                          int object_id, | ||||
|                                          content::WebContents* web_contents) | ||||
|     : ObjectLifeMonitor(isolate, target), | ||||
|       content::WebContentsObserver(web_contents), | ||||
|       context_id_(context_id), | ||||
|       object_id_(object_id) {} | ||||
| 
 | ||||
| RemoteCallbackFreer::~RemoteCallbackFreer() {} | ||||
|  | @ -34,6 +37,7 @@ void RemoteCallbackFreer::RunDestructor() { | |||
|   base::string16 channel = | ||||
|       base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK"); | ||||
|   base::ListValue args; | ||||
|   args.AppendString(context_id_); | ||||
|   args.AppendInteger(object_id_); | ||||
|   auto* frame_host = web_contents()->GetMainFrame(); | ||||
|   if (frame_host) { | ||||
|  |  | |||
|  | @ -4,6 +4,9 @@ | |||
| 
 | ||||
| #ifndef ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_ | ||||
| #define ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_ | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include "atom/common/api/object_life_monitor.h" | ||||
| #include "content/public/browser/web_contents_observer.h" | ||||
| 
 | ||||
|  | @ -14,12 +17,14 @@ class RemoteCallbackFreer : public ObjectLifeMonitor, | |||
|  public: | ||||
|   static void BindTo(v8::Isolate* isolate, | ||||
|                      v8::Local<v8::Object> target, | ||||
|                      const std::string& context_id, | ||||
|                      int object_id, | ||||
|                      content::WebContents* web_conents); | ||||
| 
 | ||||
|  protected: | ||||
|   RemoteCallbackFreer(v8::Isolate* isolate, | ||||
|                       v8::Local<v8::Object> target, | ||||
|                       const std::string& context_id, | ||||
|                       int object_id, | ||||
|                       content::WebContents* web_conents); | ||||
|   ~RemoteCallbackFreer() override; | ||||
|  | @ -30,6 +35,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor, | |||
|   void RenderViewDeleted(content::RenderViewHost*) override; | ||||
| 
 | ||||
|  private: | ||||
|   std::string context_id_; | ||||
|   int object_id_; | ||||
| 
 | ||||
|   DISALLOW_COPY_AND_ASSIGN(RemoteCallbackFreer); | ||||
|  |  | |||
|  | @ -29,14 +29,17 @@ content::RenderFrame* GetCurrentRenderFrame() { | |||
| // static
 | ||||
| void RemoteObjectFreer::BindTo(v8::Isolate* isolate, | ||||
|                                v8::Local<v8::Object> target, | ||||
|                                const std::string& context_id, | ||||
|                                int object_id) { | ||||
|   new RemoteObjectFreer(isolate, target, object_id); | ||||
|   new RemoteObjectFreer(isolate, target, context_id, object_id); | ||||
| } | ||||
| 
 | ||||
| RemoteObjectFreer::RemoteObjectFreer(v8::Isolate* isolate, | ||||
|                                      v8::Local<v8::Object> target, | ||||
|                                      const std::string& context_id, | ||||
|                                      int object_id) | ||||
|     : ObjectLifeMonitor(isolate, target), | ||||
|       context_id_(context_id), | ||||
|       object_id_(object_id), | ||||
|       routing_id_(MSG_ROUTING_NONE) { | ||||
|   content::RenderFrame* render_frame = GetCurrentRenderFrame(); | ||||
|  | @ -56,6 +59,7 @@ void RemoteObjectFreer::RunDestructor() { | |||
|   base::string16 channel = base::ASCIIToUTF16("ipc-message"); | ||||
|   base::ListValue args; | ||||
|   args.AppendString("ELECTRON_BROWSER_DEREFERENCE"); | ||||
|   args.AppendString(context_id_); | ||||
|   args.AppendInteger(object_id_); | ||||
|   render_frame->Send(new AtomFrameHostMsg_Message(render_frame->GetRoutingID(), | ||||
|                                                   channel, args)); | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ | |||
| #ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_ | ||||
| #define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_ | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include "atom/common/api/object_life_monitor.h" | ||||
| 
 | ||||
| namespace atom { | ||||
|  | @ -13,17 +15,20 @@ class RemoteObjectFreer : public ObjectLifeMonitor { | |||
|  public: | ||||
|   static void BindTo(v8::Isolate* isolate, | ||||
|                      v8::Local<v8::Object> target, | ||||
|                      const std::string& context_id, | ||||
|                      int object_id); | ||||
| 
 | ||||
|  protected: | ||||
|   RemoteObjectFreer(v8::Isolate* isolate, | ||||
|                     v8::Local<v8::Object> target, | ||||
|                     const std::string& context_id, | ||||
|                     int object_id); | ||||
|   ~RemoteObjectFreer() override; | ||||
| 
 | ||||
|   void RunDestructor() override; | ||||
| 
 | ||||
|  private: | ||||
|   std::string context_id_; | ||||
|   int object_id_; | ||||
|   int routing_id_; | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,16 +17,15 @@ class ObjectsRegistry { | |||
| 
 | ||||
|   // 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, obj) { | ||||
|   add (webContents, contextId, obj) { | ||||
|     // Get or assign an ID to the object.
 | ||||
|     const id = this.saveToStorage(obj) | ||||
| 
 | ||||
|     // Add object to the set of referenced objects.
 | ||||
|     const webContentsId = webContents.getId() | ||||
|     let owner = this.owners[webContentsId] | ||||
|     let owner = this.owners[contextId] | ||||
|     if (!owner) { | ||||
|       owner = this.owners[webContentsId] = new Set() | ||||
|       this.registerDeleteListener(webContents, webContentsId) | ||||
|       owner = this.owners[contextId] = new Set() | ||||
|       this.registerDeleteListener(webContents, contextId) | ||||
|     } | ||||
|     if (!owner.has(id)) { | ||||
|       owner.add(id) | ||||
|  | @ -43,25 +42,26 @@ class ObjectsRegistry { | |||
|   } | ||||
| 
 | ||||
|   // Dereference an object according to its ID.
 | ||||
|   remove (webContentsId, id) { | ||||
|   // Note that an object may be double-freed (cleared when page is reloaded, and
 | ||||
|   // then garbage collected in old page).
 | ||||
|   remove (contextId, id) { | ||||
|     let owner = this.owners[contextId] | ||||
|     if (owner) { | ||||
|       // Remove the reference in owner.
 | ||||
|       owner.delete(id) | ||||
|       // Dereference from the storage.
 | ||||
|       this.dereference(id) | ||||
| 
 | ||||
|     // Also remove the reference in owner.
 | ||||
|     let owner = this.owners[webContentsId] | ||||
|     if (owner) { | ||||
|       owner.delete(id) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Clear all references to objects refrenced by the WebContents.
 | ||||
|   clear (webContentsId) { | ||||
|     let owner = this.owners[webContentsId] | ||||
|   clear (contextId) { | ||||
|     let owner = this.owners[contextId] | ||||
|     if (!owner) return | ||||
| 
 | ||||
|     for (let id of owner) this.dereference(id) | ||||
| 
 | ||||
|     delete this.owners[webContentsId] | ||||
|     delete this.owners[contextId] | ||||
|   } | ||||
| 
 | ||||
|   // Private: Saves the object into storage and assigns an ID for it.
 | ||||
|  | @ -92,12 +92,12 @@ class ObjectsRegistry { | |||
|   } | ||||
| 
 | ||||
|   // Private: Clear the storage when webContents is reloaded/navigated.
 | ||||
|   registerDeleteListener (webContents, webContentsId) { | ||||
|   registerDeleteListener (webContents, contextId) { | ||||
|     const processId = webContents.getProcessId() | ||||
|     const listener = (event, deletedProcessId) => { | ||||
|       if (deletedProcessId === processId) { | ||||
|         webContents.removeListener('render-view-deleted', listener) | ||||
|         this.clear(webContentsId) | ||||
|         this.clear(contextId) | ||||
|       } | ||||
|     } | ||||
|     webContents.on('render-view-deleted', listener) | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ let getObjectPrototype = function (object) { | |||
| } | ||||
| 
 | ||||
| // Convert a real value into meta data.
 | ||||
| let valueToMeta = function (sender, value, optimizeSimpleObject = false) { | ||||
| let valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) { | ||||
|   // Determine the type of value.
 | ||||
|   const meta = { type: typeof value } | ||||
|   if (meta.type === 'object') { | ||||
|  | @ -84,14 +84,14 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) { | |||
| 
 | ||||
|   // Fill the meta object according to value's type.
 | ||||
|   if (meta.type === 'array') { | ||||
|     meta.members = value.map((el) => valueToMeta(sender, el, optimizeSimpleObject)) | ||||
|     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, value) | ||||
|     meta.id = objectsRegistry.add(sender, contextId, value) | ||||
|     meta.members = getObjectMembers(value) | ||||
|     meta.proto = getObjectPrototype(value) | ||||
|   } else if (meta.type === 'buffer') { | ||||
|  | @ -101,7 +101,7 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) { | |||
|     // Instead they should appear in the renderer process
 | ||||
|     value.then(function () {}, function () {}) | ||||
| 
 | ||||
|     meta.then = valueToMeta(sender, function (onFulfilled, onRejected) { | ||||
|     meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) { | ||||
|       value.then(onFulfilled, onRejected) | ||||
|     }) | ||||
|   } else if (meta.type === 'error') { | ||||
|  | @ -132,12 +132,12 @@ const plainObjectToMeta = function (obj) { | |||
| } | ||||
| 
 | ||||
| // Convert Error into meta data.
 | ||||
| const exceptionToMeta = function (sender, error) { | ||||
| const exceptionToMeta = function (sender, contextId, error) { | ||||
|   return { | ||||
|     type: 'exception', | ||||
|     message: error.message, | ||||
|     stack: error.stack || error, | ||||
|     cause: valueToMeta(sender, error.cause) | ||||
|     cause: valueToMeta(sender, contextId, error.cause) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -169,7 +169,7 @@ const removeRemoteListenersAndLogWarning = (sender, meta, callIntoRenderer) => { | |||
| } | ||||
| 
 | ||||
| // Convert array of meta data from renderer into array of real values.
 | ||||
| const unwrapArgs = function (sender, args) { | ||||
| const unwrapArgs = function (sender, contextId, args) { | ||||
|   const metaToValue = function (meta) { | ||||
|     let i, len, member, ref, returnValue | ||||
|     switch (meta.type) { | ||||
|  | @ -178,7 +178,7 @@ const unwrapArgs = function (sender, args) { | |||
|       case 'remote-object': | ||||
|         return objectsRegistry.get(meta.id) | ||||
|       case 'array': | ||||
|         return unwrapArgs(sender, meta.value) | ||||
|         return unwrapArgs(sender, contextId, meta.value) | ||||
|       case 'buffer': | ||||
|         return bufferUtils.metaToBuffer(meta.value) | ||||
|       case 'date': | ||||
|  | @ -204,26 +204,26 @@ const unwrapArgs = function (sender, args) { | |||
|           return returnValue | ||||
|         } | ||||
|       case 'function': { | ||||
|         // Merge webContentsId and meta.id, since meta.id can be the same in
 | ||||
|         // Merge contextId and meta.id, since meta.id can be the same in
 | ||||
|         // different webContents.
 | ||||
|         const webContentsId = sender.getId() | ||||
|         const objectId = [webContentsId, meta.id] | ||||
|         const objectId = [contextId, meta.id] | ||||
| 
 | ||||
|         // Cache the callbacks in renderer.
 | ||||
|         if (rendererFunctions.has(objectId)) { | ||||
|           return rendererFunctions.get(objectId) | ||||
|         } | ||||
| 
 | ||||
|         const webContentsId = sender.getId() | ||||
|         let callIntoRenderer = function (...args) { | ||||
|           if (!sender.isDestroyed() && webContentsId === sender.getId()) { | ||||
|             sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) | ||||
|             sender.send('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args)) | ||||
|           } else { | ||||
|             removeRemoteListenersAndLogWarning(this, meta, callIntoRenderer) | ||||
|           } | ||||
|         } | ||||
|         Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) | ||||
| 
 | ||||
|         v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender) | ||||
|         v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender) | ||||
|         rendererFunctions.set(objectId, callIntoRenderer) | ||||
|         return callIntoRenderer | ||||
|       } | ||||
|  | @ -236,19 +236,19 @@ const unwrapArgs = function (sender, args) { | |||
| 
 | ||||
| // Call a function and send reply asynchronously if it's a an asynchronous
 | ||||
| // style function and the caller didn't pass a callback.
 | ||||
| const callFunction = function (event, func, caller, args) { | ||||
| const callFunction = function (event, contextId, func, caller, args) { | ||||
|   let err, funcMarkedAsync, funcName, funcPassedCallback, ref, ret | ||||
|   funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') | ||||
|   funcPassedCallback = typeof args[args.length - 1] === 'function' | ||||
|   try { | ||||
|     if (funcMarkedAsync && !funcPassedCallback) { | ||||
|       args.push(function (ret) { | ||||
|         event.returnValue = valueToMeta(event.sender, ret, true) | ||||
|         event.returnValue = valueToMeta(event.sender, contextId, ret, true) | ||||
|       }) | ||||
|       func.apply(caller, args) | ||||
|     } else { | ||||
|       ret = func.apply(caller, args) | ||||
|       event.returnValue = valueToMeta(event.sender, ret, true) | ||||
|       event.returnValue = valueToMeta(event.sender, contextId, ret, true) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     // Catch functions thrown further down in function invocation and wrap
 | ||||
|  | @ -261,45 +261,45 @@ const callFunction = function (event, func, caller, args) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) { | ||||
| ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) { | ||||
|   try { | ||||
|     event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, process.mainModule.require(module)) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) { | ||||
| ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) { | ||||
|   try { | ||||
|     event.returnValue = valueToMeta(event.sender, electron[module]) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, electron[module]) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) { | ||||
| ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, contextId, name) { | ||||
|   try { | ||||
|     event.returnValue = valueToMeta(event.sender, global[name]) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, global[name]) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) { | ||||
| ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) { | ||||
|   try { | ||||
|     event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow()) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) { | ||||
|   event.returnValue = valueToMeta(event.sender, event.sender) | ||||
| ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) { | ||||
|   event.returnValue = valueToMeta(event.sender, contextId, event.sender) | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { | ||||
|   try { | ||||
|     args = unwrapArgs(event.sender, args) | ||||
|     args = unwrapArgs(event.sender, contextId, args) | ||||
|     let constructor = objectsRegistry.get(id) | ||||
| 
 | ||||
|     if (constructor == null) { | ||||
|  | @ -309,30 +309,30 @@ ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) { | |||
|     // Call new with array of arguments.
 | ||||
|     // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
 | ||||
|     let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))() | ||||
|     event.returnValue = valueToMeta(event.sender, obj) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, obj) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { | ||||
|   try { | ||||
|     args = unwrapArgs(event.sender, args) | ||||
|     args = unwrapArgs(event.sender, contextId, args) | ||||
|     let func = objectsRegistry.get(id) | ||||
| 
 | ||||
|     if (func == null) { | ||||
|       throwRPCError(`Cannot call function on missing remote object ${id}`) | ||||
|     } | ||||
| 
 | ||||
|     callFunction(event, func, global, args) | ||||
|     callFunction(event, contextId, func, global, args) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { | ||||
|   try { | ||||
|     args = unwrapArgs(event.sender, args) | ||||
|     args = unwrapArgs(event.sender, contextId, args) | ||||
|     let object = objectsRegistry.get(id) | ||||
| 
 | ||||
|     if (object == null) { | ||||
|  | @ -342,30 +342,30 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, a | |||
|     // Call new with array of arguments.
 | ||||
|     let constructor = object[method] | ||||
|     let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))() | ||||
|     event.returnValue = valueToMeta(event.sender, obj) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, obj) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { | ||||
|   try { | ||||
|     args = unwrapArgs(event.sender, args) | ||||
|     args = unwrapArgs(event.sender, contextId, args) | ||||
|     let obj = objectsRegistry.get(id) | ||||
| 
 | ||||
|     if (obj == null) { | ||||
|       throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`) | ||||
|     } | ||||
| 
 | ||||
|     callFunction(event, obj[method], obj, args) | ||||
|     callFunction(event, contextId, obj[method], obj, args) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { | ||||
|   try { | ||||
|     args = unwrapArgs(event.sender, args) | ||||
|     args = unwrapArgs(event.sender, contextId, args) | ||||
|     let obj = objectsRegistry.get(id) | ||||
| 
 | ||||
|     if (obj == null) { | ||||
|  | @ -375,11 +375,11 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) { | |||
|     obj[name] = args[0] | ||||
|     event.returnValue = null | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) { | ||||
| ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) { | ||||
|   try { | ||||
|     let obj = objectsRegistry.get(id) | ||||
| 
 | ||||
|  | @ -387,14 +387,14 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) { | |||
|       throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`) | ||||
|     } | ||||
| 
 | ||||
|     event.returnValue = valueToMeta(event.sender, obj[name]) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, obj[name]) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) { | ||||
|   objectsRegistry.remove(event.sender.getId(), id) | ||||
| ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) { | ||||
|   objectsRegistry.remove(contextId, id) | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => { | ||||
|  | @ -402,16 +402,16 @@ ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => { | |||
|   e.returnValue = null | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) { | ||||
| ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) { | ||||
|   try { | ||||
|     let guestViewManager = require('./guest-view-manager') | ||||
|     event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)) | ||||
|     event.returnValue = valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId)) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) { | ||||
| ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, contextId, requestId, guestInstanceId, method, ...args) { | ||||
|   try { | ||||
|     let guestViewManager = require('./guest-view-manager') | ||||
|     let guest = guestViewManager.getGuest(guestInstanceId) | ||||
|  | @ -423,7 +423,7 @@ ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, request | |||
|     } | ||||
|     guest[method].apply(guest, args) | ||||
|   } catch (error) { | ||||
|     event.returnValue = exceptionToMeta(event.sender, error) | ||||
|     event.returnValue = exceptionToMeta(event.sender, contextId, error) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,18 @@ const bufferUtils = require('../../common/buffer-utils') | |||
| const callbacksRegistry = new CallbacksRegistry() | ||||
| const remoteObjectCache = v8Util.createIDWeakMap() | ||||
| 
 | ||||
| // An unique ID that can represent current context.
 | ||||
| const contextId = v8Util.getContextId() | ||||
| 
 | ||||
| // 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
 | ||||
| // sent, we also listen to the "render-view-deleted" event in the main process
 | ||||
| // to guard that situation.
 | ||||
| process.on('exit', () => { | ||||
|   const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' | ||||
|   ipcRenderer.sendSync(command, contextId) | ||||
| }) | ||||
| 
 | ||||
| // Convert the arguments object into an array of meta data.
 | ||||
| function wrapArgs (args, visited = new Set()) { | ||||
|   const valueToMeta = (value) => { | ||||
|  | @ -107,7 +119,7 @@ function setObjectMembers (ref, object, metaId, members) { | |||
|         } else { | ||||
|           command = 'ELECTRON_BROWSER_MEMBER_CALL' | ||||
|         } | ||||
|         const ret = ipcRenderer.sendSync(command, metaId, member.name, wrapArgs(args)) | ||||
|         const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args)) | ||||
|         return metaToValue(ret) | ||||
|       } | ||||
| 
 | ||||
|  | @ -126,7 +138,7 @@ function setObjectMembers (ref, object, metaId, members) { | |||
|     } else if (member.type === 'get') { | ||||
|       descriptor.get = () => { | ||||
|         const command = 'ELECTRON_BROWSER_MEMBER_GET' | ||||
|         const meta = ipcRenderer.sendSync(command, metaId, member.name) | ||||
|         const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name) | ||||
|         return metaToValue(meta) | ||||
|       } | ||||
| 
 | ||||
|  | @ -134,7 +146,7 @@ function setObjectMembers (ref, object, metaId, members) { | |||
|         descriptor.set = (value) => { | ||||
|           const args = wrapArgs([value]) | ||||
|           const command = 'ELECTRON_BROWSER_MEMBER_SET' | ||||
|           const meta = ipcRenderer.sendSync(command, metaId, member.name, args) | ||||
|           const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args) | ||||
|           if (meta != null) metaToValue(meta) | ||||
|           return value | ||||
|         } | ||||
|  | @ -164,7 +176,7 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) { | |||
|     if (loaded) return | ||||
|     loaded = true | ||||
|     const command = 'ELECTRON_BROWSER_MEMBER_GET' | ||||
|     const meta = ipcRenderer.sendSync(command, metaId, name) | ||||
|     const meta = ipcRenderer.sendSync(command, contextId, metaId, name) | ||||
|     setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) | ||||
|   } | ||||
| 
 | ||||
|  | @ -224,7 +236,7 @@ function metaToValue (meta) { | |||
|         } else { | ||||
|           command = 'ELECTRON_BROWSER_FUNCTION_CALL' | ||||
|         } | ||||
|         const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args)) | ||||
|         const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args)) | ||||
|         return metaToValue(obj) | ||||
|       } | ||||
|       ret = remoteFunction | ||||
|  | @ -237,7 +249,7 @@ function metaToValue (meta) { | |||
|     Object.defineProperty(ret.constructor, 'name', { value: meta.name }) | ||||
| 
 | ||||
|     // Track delegate obj's lifetime & tell browser to clean up when object is GCed.
 | ||||
|     v8Util.setRemoteObjectFreer(ret, meta.id) | ||||
|     v8Util.setRemoteObjectFreer(ret, contextId, meta.id) | ||||
|     v8Util.setHiddenValue(ret, 'atomId', meta.id) | ||||
|     remoteObjectCache.set(meta.id, ret) | ||||
|     return ret | ||||
|  | @ -264,60 +276,51 @@ function metaToException (meta) { | |||
| } | ||||
| 
 | ||||
| // Browser calls a callback in renderer.
 | ||||
| ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => { | ||||
| ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, passedContextId, id, args) => { | ||||
|   if (passedContextId !== contextId) { | ||||
|     // The invoked callback belongs to an old page in this renderer.
 | ||||
|     return | ||||
|   } | ||||
|   callbacksRegistry.apply(id, metaToValue(args)) | ||||
| }) | ||||
| 
 | ||||
| // A callback in browser is released.
 | ||||
| ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, id) => { | ||||
| ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, passedContextId, id) => { | ||||
|   if (passedContextId !== contextId) { | ||||
|     // The freed callback belongs to an old page in this renderer.
 | ||||
|     return | ||||
|   } | ||||
|   callbacksRegistry.remove(id) | ||||
| }) | ||||
| 
 | ||||
| process.on('exit', () => { | ||||
|   const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' | ||||
|   ipcRenderer.sendSync(command, initialContext) | ||||
| }) | ||||
| 
 | ||||
| exports.require = (module) => { | ||||
|   const command = 'ELECTRON_BROWSER_REQUIRE' | ||||
|   const meta = ipcRenderer.sendSync(command, module) | ||||
|   const meta = ipcRenderer.sendSync(command, contextId, module) | ||||
|   return metaToValue(meta) | ||||
| } | ||||
| 
 | ||||
| // Alias to remote.require('electron').xxx.
 | ||||
| exports.getBuiltin = (module) => { | ||||
|   const command = 'ELECTRON_BROWSER_GET_BUILTIN' | ||||
|   const meta = ipcRenderer.sendSync(command, module) | ||||
|   const meta = ipcRenderer.sendSync(command, contextId, module) | ||||
|   return metaToValue(meta) | ||||
| } | ||||
| 
 | ||||
| exports.getCurrentWindow = () => { | ||||
|   const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' | ||||
|   const meta = ipcRenderer.sendSync(command) | ||||
|   const meta = ipcRenderer.sendSync(command, contextId) | ||||
|   return metaToValue(meta) | ||||
| } | ||||
| 
 | ||||
| // Get current WebContents object.
 | ||||
| exports.getCurrentWebContents = () => { | ||||
|   return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS')) | ||||
| } | ||||
| 
 | ||||
| const CONTEXT_ARG = '--context-id=' | ||||
| let initialContext = process.argv.find(arg => arg.startsWith(CONTEXT_ARG)) | ||||
| if (process.webContentsId) { | ||||
|   // set by sandbox renderer init script
 | ||||
|   initialContext = process.webContentsId | ||||
| } else if (initialContext) { | ||||
|   initialContext = parseInt(initialContext.substr(CONTEXT_ARG.length), 10) | ||||
| } else { | ||||
|   // if not available, pull from remote
 | ||||
|   initialContext = exports.getCurrentWebContents().getId() | ||||
|   return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId)) | ||||
| } | ||||
| 
 | ||||
| // Get a global object in browser.
 | ||||
| exports.getGlobal = (name) => { | ||||
|   const command = 'ELECTRON_BROWSER_GLOBAL' | ||||
|   const meta = ipcRenderer.sendSync(command, name) | ||||
|   const meta = ipcRenderer.sendSync(command, contextId, name) | ||||
|   return metaToValue(meta) | ||||
| } | ||||
| 
 | ||||
|  | @ -334,7 +337,7 @@ exports.createFunctionWithReturnValue = (returnValue) => { | |||
| // Get the guest WebContents from guestInstanceId.
 | ||||
| exports.getGuestWebContents = (guestInstanceId) => { | ||||
|   const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS' | ||||
|   const meta = ipcRenderer.sendSync(command, guestInstanceId) | ||||
|   const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId) | ||||
|   return metaToValue(meta) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,6 +8,9 @@ const webViewConstants = require('./web-view-constants') | |||
| 
 | ||||
| const hasProp = {}.hasOwnProperty | ||||
| 
 | ||||
| // An unique ID that can represent current context.
 | ||||
| const contextId = v8Util.getContextId() | ||||
| 
 | ||||
| // ID generator.
 | ||||
| let nextId = 0 | ||||
| 
 | ||||
|  | @ -396,7 +399,7 @@ const registerWebViewElement = function () { | |||
|   const createNonBlockHandler = function (m) { | ||||
|     return function (...args) { | ||||
|       const internal = v8Util.getHiddenValue(this, 'internal') | ||||
|       ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m, ...args) | ||||
|       ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, null, internal.guestInstanceId, m, ...args) | ||||
|     } | ||||
|   } | ||||
|   for (const method of nonblockMethods) { | ||||
|  | @ -410,7 +413,7 @@ const registerWebViewElement = function () { | |||
|       hasUserGesture = false | ||||
|     } | ||||
|     const requestId = getNextId() | ||||
|     ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture) | ||||
|     ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture) | ||||
|     ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) { | ||||
|       if (callback) callback(result) | ||||
|     }) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cheng Zhao
				Cheng Zhao