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() {
|
BrowserView::~BrowserView() {
|
||||||
api_web_contents_->DestroyWebContents();
|
api_web_contents_->DestroyWebContents(true /* async */);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
|
|
@ -417,15 +417,28 @@ WebContents::~WebContents() {
|
||||||
guest_delegate_->Destroy();
|
guest_delegate_->Destroy();
|
||||||
|
|
||||||
RenderViewDeleted(web_contents()->GetRenderViewHost());
|
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
|
// This event is only for internal use, which is emitted when WebContents is
|
||||||
// being destroyed.
|
// being destroyed.
|
||||||
Emit("will-destroy");
|
Emit("will-destroy");
|
||||||
ResetManagedWebContents();
|
ResetManagedWebContents(async);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebContents::DidAddMessageToConsole(content::WebContents* source,
|
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,
|
if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture,
|
||||||
initial_rect.x(), initial_rect.y(), initial_rect.width(),
|
initial_rect.x(), initial_rect.y(), initial_rect.width(),
|
||||||
initial_rect.height())) {
|
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,
|
void WebContents::TitleWasSet(content::NavigationEntry* entry,
|
||||||
bool explicit_set) {
|
bool explicit_set) {
|
||||||
if (entry)
|
auto title = entry ? entry->GetTitle() : base::string16();
|
||||||
Emit("-page-title-updated", entry->GetTitle(), explicit_set);
|
Emit("page-title-updated", title, explicit_set);
|
||||||
else
|
|
||||||
Emit("-page-title-updated", "", explicit_set);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::DidUpdateFaviconURL(
|
void WebContents::DidUpdateFaviconURL(
|
||||||
|
|
|
@ -78,7 +78,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||||
v8::Local<v8::FunctionTemplate> prototype);
|
v8::Local<v8::FunctionTemplate> prototype);
|
||||||
|
|
||||||
// Notifies to destroy any guest web contents before destroying self.
|
// Notifies to destroy any guest web contents before destroying self.
|
||||||
void DestroyWebContents();
|
void DestroyWebContents(bool async);
|
||||||
|
|
||||||
int64_t GetID() const;
|
int64_t GetID() const;
|
||||||
int GetProcessID() const;
|
int GetProcessID() const;
|
||||||
|
|
|
@ -173,7 +173,7 @@ void Window::WillDestroyNativeObject() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::OnWindowClosed() {
|
void Window::OnWindowClosed() {
|
||||||
api_web_contents_->DestroyWebContents();
|
api_web_contents_->DestroyWebContents(true /* async */);
|
||||||
|
|
||||||
RemoveFromWeakMap();
|
RemoveFromWeakMap();
|
||||||
window_->RemoveObserver(this);
|
window_->RemoveObserver(this);
|
||||||
|
|
|
@ -188,8 +188,13 @@ void CommonWebContentsDelegate::SetOwnerWindow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommonWebContentsDelegate::ResetManagedWebContents() {
|
void CommonWebContentsDelegate::ResetManagedWebContents(bool async) {
|
||||||
web_contents_.reset();
|
if (async) {
|
||||||
|
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
|
||||||
|
web_contents_.release());
|
||||||
|
} else {
|
||||||
|
web_contents_.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content::WebContents* CommonWebContentsDelegate::GetWebContents() const {
|
content::WebContents* CommonWebContentsDelegate::GetWebContents() const {
|
||||||
|
|
|
@ -112,7 +112,7 @@ class CommonWebContentsDelegate
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Destroy the managed InspectableWebContents object.
|
// Destroy the managed InspectableWebContents object.
|
||||||
void ResetManagedWebContents();
|
void ResetManagedWebContents(bool async);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Callback for when DevToolsSaveToFile has completed.
|
// Callback for when DevToolsSaveToFile has completed.
|
||||||
|
|
|
@ -76,13 +76,9 @@ BrowserWindow.prototype._init = function () {
|
||||||
|
|
||||||
// Change window title to page title.
|
// Change window title to page title.
|
||||||
this.webContents.on('page-title-updated', (event, 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.
|
// Route the event to BrowserWindow.
|
||||||
this.emit('page-title-updated', event, title)
|
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
|
// Sometimes the webContents doesn't get focus when window is shown, so we
|
||||||
|
|
|
@ -268,13 +268,6 @@ WebContents.prototype._init = function () {
|
||||||
this.reload()
|
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)
|
app.emit('web-contents-created', {}, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,42 @@ describe('BrowserWindow module', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('BrowserWindow.close()', 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) {
|
it('should emit unload handler', function (done) {
|
||||||
w.webContents.on('did-finish-load', function () {
|
w.webContents.on('did-finish-load', function () {
|
||||||
w.close()
|
w.close()
|
||||||
|
@ -109,6 +145,38 @@ describe('BrowserWindow module', function () {
|
||||||
})
|
})
|
||||||
w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html'))
|
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 () {
|
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
|
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
|
// Suspend listeners until the next event and then restore them
|
||||||
const suspendListeners = (emitter, eventName, callback) => {
|
const suspendListeners = (emitter, eventName, callback) => {
|
||||||
const listeners = emitter.listeners(eventName)
|
const listeners = emitter.listeners(eventName)
|
||||||
|
|
Loading…
Reference in a new issue