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

@ -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)
}