Merge pull request #9113 from electron/window_close_patch
browser: make destruction of webContents async
This commit is contained in:
commit
9e0c308b09
11 changed files with 186 additions and 26 deletions
|
@ -75,7 +75,7 @@ void BrowserView::Init(v8::Isolate* isolate,
|
|||
}
|
||||
|
||||
BrowserView::~BrowserView() {
|
||||
api_web_contents_->DestroyWebContents();
|
||||
api_web_contents_->DestroyWebContents(true /* async */);
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
|
@ -417,15 +417,28 @@ WebContents::~WebContents() {
|
|||
guest_delegate_->Destroy();
|
||||
|
||||
RenderViewDeleted(web_contents()->GetRenderViewHost());
|
||||
DestroyWebContents();
|
||||
|
||||
if (type_ == WEB_VIEW) {
|
||||
DestroyWebContents(false /* async */);
|
||||
} else {
|
||||
if (type_ == BROWSER_WINDOW && owner_window()) {
|
||||
owner_window()->CloseContents(nullptr);
|
||||
} else {
|
||||
DestroyWebContents(true /* async */);
|
||||
}
|
||||
// The WebContentsDestroyed will not be called automatically because we
|
||||
// destroy the webContents in the next tick. So we have to manually
|
||||
// call it here to make sure "destroyed" event is emitted.
|
||||
WebContentsDestroyed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebContents::DestroyWebContents() {
|
||||
void WebContents::DestroyWebContents(bool async) {
|
||||
// This event is only for internal use, which is emitted when WebContents is
|
||||
// being destroyed.
|
||||
Emit("will-destroy");
|
||||
ResetManagedWebContents();
|
||||
ResetManagedWebContents(async);
|
||||
}
|
||||
|
||||
bool WebContents::DidAddMessageToConsole(content::WebContents* source,
|
||||
|
@ -477,7 +490,7 @@ void WebContents::AddNewContents(content::WebContents* source,
|
|||
if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture,
|
||||
initial_rect.x(), initial_rect.y(), initial_rect.width(),
|
||||
initial_rect.height())) {
|
||||
api_web_contents->DestroyWebContents();
|
||||
api_web_contents->DestroyWebContents(true /* async */);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -816,10 +829,8 @@ void WebContents::DidFinishNavigation(
|
|||
|
||||
void WebContents::TitleWasSet(content::NavigationEntry* entry,
|
||||
bool explicit_set) {
|
||||
if (entry)
|
||||
Emit("-page-title-updated", entry->GetTitle(), explicit_set);
|
||||
else
|
||||
Emit("-page-title-updated", "", explicit_set);
|
||||
auto title = entry ? entry->GetTitle() : base::string16();
|
||||
Emit("page-title-updated", title, explicit_set);
|
||||
}
|
||||
|
||||
void WebContents::DidUpdateFaviconURL(
|
||||
|
|
|
@ -78,7 +78,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
|||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
// Notifies to destroy any guest web contents before destroying self.
|
||||
void DestroyWebContents();
|
||||
void DestroyWebContents(bool async);
|
||||
|
||||
int64_t GetID() const;
|
||||
int GetProcessID() const;
|
||||
|
|
|
@ -173,7 +173,7 @@ void Window::WillDestroyNativeObject() {
|
|||
}
|
||||
|
||||
void Window::OnWindowClosed() {
|
||||
api_web_contents_->DestroyWebContents();
|
||||
api_web_contents_->DestroyWebContents(true /* async */);
|
||||
|
||||
RemoveFromWeakMap();
|
||||
window_->RemoveObserver(this);
|
||||
|
|
|
@ -188,9 +188,14 @@ void CommonWebContentsDelegate::SetOwnerWindow(
|
|||
}
|
||||
}
|
||||
|
||||
void CommonWebContentsDelegate::ResetManagedWebContents() {
|
||||
void CommonWebContentsDelegate::ResetManagedWebContents(bool async) {
|
||||
if (async) {
|
||||
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
|
||||
web_contents_.release());
|
||||
} else {
|
||||
web_contents_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
content::WebContents* CommonWebContentsDelegate::GetWebContents() const {
|
||||
if (!web_contents_)
|
||||
|
|
|
@ -112,7 +112,7 @@ class CommonWebContentsDelegate
|
|||
#endif
|
||||
|
||||
// Destroy the managed InspectableWebContents object.
|
||||
void ResetManagedWebContents();
|
||||
void ResetManagedWebContents(bool async);
|
||||
|
||||
private:
|
||||
// Callback for when DevToolsSaveToFile has completed.
|
||||
|
|
|
@ -76,13 +76,9 @@ BrowserWindow.prototype._init = function () {
|
|||
|
||||
// Change window title to page title.
|
||||
this.webContents.on('page-title-updated', (event, title) => {
|
||||
// The page-title-updated event is not emitted immediately (see #3645), so
|
||||
// when the callback is called the BrowserWindow might have been closed.
|
||||
if (this.isDestroyed()) return
|
||||
|
||||
// Route the event to BrowserWindow.
|
||||
this.emit('page-title-updated', event, title)
|
||||
if (!event.defaultPrevented) this.setTitle(title)
|
||||
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
|
||||
})
|
||||
|
||||
// Sometimes the webContents doesn't get focus when window is shown, so we
|
||||
|
|
|
@ -268,13 +268,6 @@ WebContents.prototype._init = function () {
|
|||
this.reload()
|
||||
})
|
||||
|
||||
// Delays the page-title-updated event to next tick.
|
||||
this.on('-page-title-updated', function (...args) {
|
||||
setImmediate(() => {
|
||||
this.emit('page-title-updated', ...args)
|
||||
})
|
||||
})
|
||||
|
||||
app.emit('web-contents-created', {}, this)
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,42 @@ describe('BrowserWindow module', function () {
|
|||
})
|
||||
|
||||
describe('BrowserWindow.close()', function () {
|
||||
let server
|
||||
|
||||
before(function (done) {
|
||||
server = http.createServer((request, response) => {
|
||||
switch (request.url) {
|
||||
case '/404':
|
||||
response.statusCode = '404'
|
||||
response.end()
|
||||
break
|
||||
case '/301':
|
||||
response.statusCode = '301'
|
||||
response.setHeader('Location', '/200')
|
||||
response.end()
|
||||
break
|
||||
case '/200':
|
||||
response.statusCode = '200'
|
||||
response.end('hello')
|
||||
break
|
||||
case '/title':
|
||||
response.statusCode = '200'
|
||||
response.end('<title>Hello</title>')
|
||||
break
|
||||
default:
|
||||
done('unsupported endpoint')
|
||||
}
|
||||
}).listen(0, '127.0.0.1', () => {
|
||||
server.url = 'http://127.0.0.1:' + server.address().port
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(function () {
|
||||
server.close()
|
||||
server = null
|
||||
})
|
||||
|
||||
it('should emit unload handler', function (done) {
|
||||
w.webContents.on('did-finish-load', function () {
|
||||
w.close()
|
||||
|
@ -109,6 +145,38 @@ describe('BrowserWindow module', function () {
|
|||
})
|
||||
w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html'))
|
||||
})
|
||||
|
||||
it('should not crash when invoked synchronously inside navigation observer', function (done) {
|
||||
const events = [
|
||||
{ name: 'did-start-loading', url: `${server.url}/200` },
|
||||
{ name: 'did-get-redirect-request', url: `${server.url}/301` },
|
||||
{ name: 'did-get-response-details', url: `${server.url}/200` },
|
||||
{ name: 'dom-ready', url: `${server.url}/200` },
|
||||
{ name: 'page-title-updated', url: `${server.url}/title` },
|
||||
{ name: 'did-stop-loading', url: `${server.url}/200` },
|
||||
{ name: 'did-finish-load', url: `${server.url}/200` },
|
||||
{ name: 'did-frame-finish-load', url: `${server.url}/200` },
|
||||
{ name: 'did-fail-load', url: `${server.url}/404` }
|
||||
]
|
||||
const responseEvent = 'window-webContents-destroyed'
|
||||
|
||||
function* genNavigationEvent () {
|
||||
let eventOptions = null
|
||||
while ((eventOptions = events.shift()) && events.length) {
|
||||
let w = new BrowserWindow({show: false})
|
||||
eventOptions.id = w.id
|
||||
eventOptions.responseEvent = responseEvent
|
||||
ipcRenderer.send('test-webcontents-navigation-observer', eventOptions)
|
||||
yield 1
|
||||
}
|
||||
}
|
||||
|
||||
let gen = genNavigationEvent()
|
||||
ipcRenderer.on(responseEvent, function () {
|
||||
if (!gen.next().value) done()
|
||||
})
|
||||
gen.next()
|
||||
})
|
||||
})
|
||||
|
||||
describe('window.close()', function () {
|
||||
|
|
|
@ -542,4 +542,70 @@ describe('webContents module', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('destroy()', () => {
|
||||
let server
|
||||
|
||||
before(function (done) {
|
||||
server = http.createServer((request, response) => {
|
||||
switch (request.url) {
|
||||
case '/404':
|
||||
response.statusCode = '404'
|
||||
response.end()
|
||||
break
|
||||
case '/301':
|
||||
response.statusCode = '301'
|
||||
response.setHeader('Location', '/200')
|
||||
response.end()
|
||||
break
|
||||
case '/200':
|
||||
response.statusCode = '200'
|
||||
response.end('hello')
|
||||
break
|
||||
default:
|
||||
done('unsupported endpoint')
|
||||
}
|
||||
}).listen(0, '127.0.0.1', () => {
|
||||
server.url = 'http://127.0.0.1:' + server.address().port
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(function () {
|
||||
server.close()
|
||||
server = null
|
||||
})
|
||||
|
||||
it('should not crash when invoked synchronously inside navigation observer', (done) => {
|
||||
const events = [
|
||||
{ name: 'did-start-loading', url: `${server.url}/200` },
|
||||
{ name: 'did-get-redirect-request', url: `${server.url}/301` },
|
||||
{ name: 'did-get-response-details', url: `${server.url}/200` },
|
||||
{ name: 'dom-ready', url: `${server.url}/200` },
|
||||
{ name: 'did-stop-loading', url: `${server.url}/200` },
|
||||
{ name: 'did-finish-load', url: `${server.url}/200` },
|
||||
// FIXME: Multiple Emit calls inside an observer assume that object
|
||||
// will be alive till end of the observer. Synchronous `destroy` api
|
||||
// violates this contract and crashes.
|
||||
// { name: 'did-frame-finish-load', url: `${server.url}/200` },
|
||||
{ name: 'did-fail-load', url: `${server.url}/404` }
|
||||
]
|
||||
const responseEvent = 'webcontents-destroyed'
|
||||
|
||||
function* genNavigationEvent () {
|
||||
let eventOptions = null
|
||||
while ((eventOptions = events.shift()) && events.length) {
|
||||
eventOptions.responseEvent = responseEvent
|
||||
ipcRenderer.send('test-webcontents-navigation-observer', eventOptions)
|
||||
yield 1
|
||||
}
|
||||
}
|
||||
|
||||
let gen = genNavigationEvent()
|
||||
ipcRenderer.on(responseEvent, () => {
|
||||
if (!gen.next().value) done()
|
||||
})
|
||||
gen.next()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -338,6 +338,27 @@ ipcMain.on('crash-service-pid', (event, pid) => {
|
|||
event.returnValue = null
|
||||
})
|
||||
|
||||
ipcMain.on('test-webcontents-navigation-observer', (event, options) => {
|
||||
let contents = null
|
||||
let destroy = () => {}
|
||||
if (options.id) {
|
||||
const w = BrowserWindow.fromId(options.id)
|
||||
contents = w.webContents
|
||||
destroy = () => w.close()
|
||||
} else {
|
||||
contents = webContents.create()
|
||||
destroy = () => contents.destroy()
|
||||
}
|
||||
|
||||
contents.once(options.name, () => destroy())
|
||||
|
||||
contents.once('destroyed', () => {
|
||||
event.sender.send(options.responseEvent)
|
||||
})
|
||||
|
||||
contents.loadURL(options.url)
|
||||
})
|
||||
|
||||
// Suspend listeners until the next event and then restore them
|
||||
const suspendListeners = (emitter, eventName, callback) => {
|
||||
const listeners = emitter.listeners(eventName)
|
||||
|
|
Loading…
Reference in a new issue