Merge pull request #9113 from electron/window_close_patch

browser: make destruction of webContents async
This commit is contained in:
Cheng Zhao 2017-05-01 17:45:58 +09:00 committed by GitHub
commit 9e0c308b09
11 changed files with 186 additions and 26 deletions

View file

@ -75,7 +75,7 @@ void BrowserView::Init(v8::Isolate* isolate,
}
BrowserView::~BrowserView() {
api_web_contents_->DestroyWebContents();
api_web_contents_->DestroyWebContents(true /* async */);
}
// static

View file

@ -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(

View file

@ -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;

View file

@ -173,7 +173,7 @@ void Window::WillDestroyNativeObject() {
}
void Window::OnWindowClosed() {
api_web_contents_->DestroyWebContents();
api_web_contents_->DestroyWebContents(true /* async */);
RemoveFromWeakMap();
window_->RemoveObserver(this);

View file

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

View file

@ -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.

View file

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

View file

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

View file

@ -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 () {

View file

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

View file

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