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:
parent
9cbbb2a6c4
commit
4cdb1b8fc3
9 changed files with 139 additions and 113 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue