fix double-freeing remote references

After the page does navigations, garbage collection can still happen in
the old context. This commit changes to store references to remote objects
by _pages_, instead of by _WebContents_.
This commit is contained in:
Cheng Zhao 2018-07-10 17:15:40 +09:00
parent 9cbbb2a6c4
commit 4cdb1b8fc3
9 changed files with 139 additions and 113 deletions

View file

@ -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) {
switch (meta.type) {
case 'value':
@ -177,7 +177,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':
@ -201,26 +201,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
}
@ -233,18 +233,18 @@ 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) {
const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
const 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 {
const 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
@ -257,105 +257,105 @@ 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) {
throwRPCError(`Cannot call constructor on missing remote object ${id}`)
}
event.returnValue = valueToMeta(event.sender, new constructor(...args))
event.returnValue = valueToMeta(event.sender, contextId, new constructor(...args))
} 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) {
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
}
event.returnValue = valueToMeta(event.sender, new object[method](...args))
event.returnValue = valueToMeta(event.sender, contextId, new object[method](...args))
} 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) {
@ -365,11 +365,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)
@ -377,14 +377,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) => {
@ -392,16 +392,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)
@ -413,7 +413,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)
}
})