'use strict' const v8Util = process.atomBinding('v8_util') const getOwnerKey = (webContents, contextId) => { return `${webContents.id}-${contextId}` } class ObjectsRegistry { constructor () { this.nextId = 0 // Stores all objects by ref-counting. // (id) => {object, count} this.storage = {} // Stores the IDs of objects referenced by WebContents. // (ownerKey) => [id] this.owners = {} } // 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, contextId, obj) { // Get or assign an ID to the object. const id = this.saveToStorage(obj) // Add object to the set of referenced objects. const ownerKey = getOwnerKey(webContents, contextId) let owner = this.owners[ownerKey] if (!owner) { owner = this.owners[ownerKey] = new Set() this.registerDeleteListener(webContents, contextId) } if (!owner.has(id)) { owner.add(id) // Increase reference count if not referenced before. this.storage[id].count++ } return id } // Get an object according to its ID. get (id) { const pointer = this.storage[id] if (pointer != null) return pointer.object } // Dereference an object according to its ID. // Note that an object may be double-freed (cleared when page is reloaded, and // then garbage collected in old page). remove (webContents, contextId, id) { const ownerKey = getOwnerKey(webContents, contextId) const owner = this.owners[ownerKey] if (owner) { // Remove the reference in owner. owner.delete(id) // Dereference from the storage. this.dereference(id) } } // Clear all references to objects refrenced by the WebContents. clear (webContents, contextId) { const ownerKey = getOwnerKey(webContents, contextId) const owner = this.owners[ownerKey] if (!owner) return for (const id of owner) this.dereference(id) delete this.owners[ownerKey] } // Private: Saves the object into storage and assigns an ID for it. saveToStorage (object) { let id = v8Util.getHiddenValue(object, 'atomId') if (!id) { id = ++this.nextId this.storage[id] = { count: 0, object: object } v8Util.setHiddenValue(object, 'atomId', id) } return id } // Private: Dereference the object from store. dereference (id) { const pointer = this.storage[id] if (pointer == null) { return } pointer.count -= 1 if (pointer.count === 0) { v8Util.deleteHiddenValue(pointer.object, 'atomId') delete this.storage[id] } } // Private: Clear the storage when renderer process is destroyed. registerDeleteListener (webContents, contextId) { // contextId => ${processHostId}-${contextCount} const processHostId = contextId.split('-')[0] const listener = (event, deletedProcessHostId) => { if (deletedProcessHostId && deletedProcessHostId.toString() === processHostId) { webContents.removeListener('render-view-deleted', listener) this.clear(webContents, contextId) } } webContents.on('render-view-deleted', listener) } } module.exports = new ObjectsRegistry()