refactor: cleanup how WebContents is destroyed (#27920)

This commit is contained in:
Cheng Zhao 2021-03-07 21:14:12 +09:00 committed by GitHub
parent b3a0743121
commit f4e1a343b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 131 additions and 235 deletions

View file

@ -909,51 +909,49 @@ void WebContents::InitWithWebContents(content::WebContents* web_contents,
}
WebContents::~WebContents() {
MarkDestroyed();
// The destroy() is called.
if (inspectable_web_contents_) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (type_ == Type::kBackgroundPage) {
// Background pages are owned by extensions::ExtensionHost
inspectable_web_contents_->ReleaseWebContents();
}
#endif
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (web_contents()) {
RenderViewDeleted(web_contents()->GetRenderViewHost());
}
if (type_ == Type::kBrowserWindow && owner_window()) {
// For BrowserWindow we should close the window and clean up everything
// before WebContents is destroyed.
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnCloseContents();
// BrowserWindow destroys WebContents asynchronously, manually emit the
// destroyed event here.
WebContentsDestroyed();
} else if (Browser::Get()->is_shutting_down()) {
// Destroy WebContents directly when app is shutting down.
DestroyWebContents(false /* async */);
} else {
// Destroy WebContents asynchronously unless app is shutting down,
// because destroy() might be called inside WebContents's event handler.
bool is_browser_view = type_ == Type::kBrowserView;
DestroyWebContents(!(IsGuest() || is_browser_view) /* 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();
}
if (!inspectable_web_contents_) {
WebContentsDestroyed();
return;
}
}
void WebContents::DestroyWebContents(bool async) {
if (guest_delegate_)
guest_delegate_->WillDestroy();
// This event is only for internal use, which is emitted when WebContents is
// being destroyed.
Emit("will-destroy");
ResetManagedWebContents(async);
// For guest view based on OOPIF, the WebContents is released by the embedder
// frame, and we need to clear the reference to the memory.
bool not_owned_by_this = IsGuest() && attached_;
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// And background pages are owned by extensions::ExtensionHost.
if (type_ == Type::kBackgroundPage)
not_owned_by_this = true;
#endif
if (not_owned_by_this) {
inspectable_web_contents_->ReleaseWebContents();
WebContentsDestroyed();
}
// InspectableWebContents will be automatically destroyed.
}
void WebContents::Destroy() {
// The content::WebContents should be destroyed asyncronously when possible
// as user may choose to destroy WebContents during an event of it.
if (Browser::Get()->is_shutting_down() || IsGuest() ||
type_ == Type::kBrowserView) {
delete this;
} else {
base::PostTask(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(
[](base::WeakPtr<WebContents> contents) {
if (contents)
delete contents.get();
},
GetWeakPtr()));
}
}
bool WebContents::DidAddMessageToConsole(
@ -1063,7 +1061,7 @@ void WebContents::AddNewContents(
initial_rect.x(), initial_rect.y(), initial_rect.width(),
initial_rect.height(), tracker->url, tracker->frame_name,
tracker->referrer, tracker->raw_features, tracker->body)) {
api_web_contents->DestroyWebContents(false /* async */);
api_web_contents->Destroy();
}
}
@ -1133,8 +1131,6 @@ void WebContents::CloseContents(content::WebContents* source) {
autofill_driver_factory->CloseAllPopups();
}
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnCloseContents();
}
@ -1811,21 +1807,6 @@ void WebContents::SetOwnerWindow(content::WebContents* web_contents,
#endif
}
void WebContents::ResetManagedWebContents(bool async) {
if (async) {
// Browser context should be destroyed only after the WebContents,
// this is guaranteed in the sync mode by the order of declaration,
// in the async version we maintain a reference until the WebContents
// is destroyed.
// //electron/patches/chromium/content_browser_main_loop.patch
// is required to get the right quit closure for the main message loop.
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
FROM_HERE, inspectable_web_contents_.release());
} else {
inspectable_web_contents_.reset();
}
}
content::WebContents* WebContents::GetWebContents() const {
if (!inspectable_web_contents_)
return nullptr;
@ -1838,7 +1819,8 @@ content::WebContents* WebContents::GetDevToolsWebContents() const {
return inspectable_web_contents_->GetDevToolsWebContents();
}
void WebContents::MarkDestroyed() {
void WebContents::WebContentsDestroyed() {
// Clear the pointer stored in wrapper.
if (GetAllWebContents().Lookup(id_))
GetAllWebContents().Remove(id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
@ -1847,52 +1829,9 @@ void WebContents::MarkDestroyed() {
if (!GetWrapper(isolate).ToLocal(&wrapper))
return;
wrapper->SetAlignedPointerInInternalField(0, nullptr);
}
// There are three ways of destroying a webContents:
// 1. call webContents.destroy();
// 2. garbage collection;
// 3. user closes the window of webContents;
// 4. the embedder detaches the frame.
// For webview only #4 will happen, for BrowserWindow both #1 and #3 may
// happen. The #2 should never happen for webContents, because webview is
// managed by GuestViewManager, and BrowserWindow's webContents is managed
// by api::BrowserWindow.
// For #1, the destructor will do the cleanup work and we only need to make
// sure "destroyed" event is emitted. For #3, the content::WebContents will
// be destroyed on close, and WebContentsDestroyed would be called for it, so
// we need to make sure the api::WebContents is also deleted.
// For #4, the WebContents will be destroyed by embedder.
void WebContents::WebContentsDestroyed() {
// Give chance for guest delegate to cleanup its observers
// since the native class is only destroyed in the next tick.
if (guest_delegate_)
guest_delegate_->WillDestroy();
// Cleanup relationships with other parts.
// We can not call Destroy here because we need to call Emit first, but we
// also do not want any method to be used, so just mark as destroyed here.
MarkDestroyed();
Observe(nullptr); // this->web_contents() will return nullptr
Observe(nullptr);
Emit("destroyed");
// For guest view based on OOPIF, the WebContents is released by the embedder
// frame, and we need to clear the reference to the memory.
if (IsGuest() && inspectable_web_contents_) {
inspectable_web_contents_->ReleaseWebContents();
ResetManagedWebContents(false);
}
// Destroy the native class in next tick.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(
[](base::WeakPtr<WebContents> wc) {
if (wc)
delete wc.get();
},
GetWeakPtr()));
}
void WebContents::NavigationEntryCommitted(
@ -2885,6 +2824,7 @@ bool WebContents::IsGuest() const {
void WebContents::AttachToIframe(content::WebContents* embedder_web_contents,
int embedder_frame_id) {
attached_ = true;
if (guest_delegate_)
guest_delegate_->AttachToIframe(embedder_web_contents, embedder_frame_id);
}
@ -3537,17 +3477,11 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
gin::CreateFunctionTemplate(
isolate, base::BindRepeating(&gin_helper::Destroyable::IsDestroyed),
options));
templ->Set(
gin::StringToSymbol(isolate, "destroy"),
gin::CreateFunctionTemplate(
isolate, base::BindRepeating([](gin::Handle<WebContents> handle) {
delete handle.get();
}),
options));
// We use gin_helper::ObjectTemplateBuilder instead of
// gin::ObjectTemplateBuilder here to handle the fact that WebContents is
// destroyable.
return gin_helper::ObjectTemplateBuilder(isolate, templ)
.SetMethod("destroy", &WebContents::Destroy)
.SetMethod("getBackgroundThrottling",
&WebContents::GetBackgroundThrottling)
.SetMethod("setBackgroundThrottling",