summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser.cc61
-rw-r--r--chrome/browser/browser.h10
-rw-r--r--chrome/browser/render_process_host.cc6
-rw-r--r--chrome/browser/render_process_host.h3
-rw-r--r--chrome/browser/render_view_host.cc46
-rw-r--r--chrome/browser/render_view_host.h6
-rw-r--r--chrome/browser/render_widget_helper.cc12
-rw-r--r--chrome/browser/render_widget_helper.h6
-rw-r--r--chrome/browser/render_widget_host.cc24
-rw-r--r--chrome/browser/render_widget_host.h15
-rw-r--r--chrome/browser/tabs/tab_strip_model.cc8
-rw-r--r--chrome/browser/views/hung_renderer_view.cc2
-rw-r--r--chrome/browser/web_contents.cc7
13 files changed, 161 insertions, 45 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index 75c6bb4..aebd7cf 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -211,7 +211,7 @@ Browser::Browser(const gfx::Rect& initial_bounds,
controller_(this),
toolbar_(&controller_, this),
chrome_updater_factory_(this),
- frame_method_factory_(this),
+ method_factory_(this),
hung_window_detector_(&hung_plugin_action_),
ticker_(0),
tabstrip_model_(this, profile),
@@ -788,6 +788,12 @@ void Browser::Observe(NotificationType type,
frame_->ShelfVisibilityChanged();
}
}
+ } else if (type == NOTIFY_WEB_CONTENTS_DISCONNECTED) {
+ // Need to do this asynchronously as it will close the tab, which is
+ // currently on the call stack above us.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(&Browser::ClearUnloadStateOnCrash,
+ Source<TabContents>(source).ptr()));
} else {
NOTREACHED() << "Got a notification we didn't register for.";
}
@@ -1002,19 +1008,27 @@ void Browser::MoveToFront(bool should_activate) {
bool Browser::ShouldCloseWindow() {
if (is_processing_tab_unload_events_) {
- return false;
+ return tabs_needing_before_unload_fired_.empty() &&
+ tabs_needing_unload_fired_.empty();
}
is_processing_tab_unload_events_ = true;
for (int i = 0; i < tab_count(); ++i) {
- if (tabstrip_model_.TabHasUnloadListener(i))
- tabs_needing_before_unload_fired_.push_back(GetTabContentsAt(i));
+ if (tabstrip_model_.TabHasUnloadListener(i)) {
+ TabContents* tab = GetTabContentsAt(i);
+
+ // If the tab crashes in the beforeunload or unload handler, it won't be
+ // able to ack. But we know we can close it.
+ NotificationService::current()->
+ AddObserver(this, NOTIFY_WEB_CONTENTS_DISCONNECTED,
+ Source<TabContents>(tab));
+
+ tabs_needing_before_unload_fired_.push_back(tab);
+ }
}
- if (tabs_needing_before_unload_fired_.empty()) {
- is_processing_tab_unload_events_ = false;
+ if (tabs_needing_before_unload_fired_.empty())
return true;
- }
ProcessPendingBeforeUnloadTabs();
return false;
@@ -1080,16 +1094,43 @@ void Browser::UnloadFired(TabContents* tab) {
}
}
+ NotificationService::current()->
+ RemoveObserver(this, NOTIFY_WEB_CONTENTS_DISCONNECTED,
+ Source<TabContents>(tab));
+
if (tabs_needing_unload_fired_.empty()) {
// We've finished all the unload events and can proceed to close the
// browser.
- is_processing_tab_unload_events_ = false;
OnWindowClosing();
} else {
ProcessPendingUnloadTabs();
}
}
+void Browser::ClearUnloadStateOnCrash(TabContents* tab) {
+ bool is_waiting_on_before_unload = false;
+
+ for (UnloadListenerVector::iterator it =
+ tabs_needing_before_unload_fired_.begin();
+ it != tabs_needing_before_unload_fired_.end();
+ ++it) {
+ if (*it == tab) {
+ is_waiting_on_before_unload = true;
+ break;
+ }
+ }
+
+ if (is_waiting_on_before_unload) {
+ // Even though beforeunload didn't really fire, we call the same function
+ // so that all the appropriate cleanup happens.
+ BeforeUnloadFired(tab, true);
+ } else {
+ // Even though unload didn't really fire, we call the same function
+ // so that all the appropriate cleanup happens.
+ UnloadFired(tab);
+ }
+}
+
void Browser::OnWindowClosing() {
if (!ShouldCloseWindow())
@@ -1269,7 +1310,7 @@ void Browser::CloseFrameAfterDragSession() {
// otherwise the frame will think the drag session is still active and ignore
// the request.
MessageLoop::current()->PostTask(FROM_HERE,
- frame_method_factory_.NewRunnableMethod(&Browser::CloseFrame));
+ method_factory_.NewRunnableMethod(&Browser::CloseFrame));
}
////////////////////////////////////////////////////////////////////////////////
@@ -1417,7 +1458,7 @@ void Browser::TabStripEmpty() {
// NOTE: If you change to be immediate (no invokeLater) then you'll need to
// update BrowserList::CloseAllBrowsers.
MessageLoop::current()->PostTask(FROM_HERE,
- frame_method_factory_.NewRunnableMethod(&Browser::CloseFrame));
+ method_factory_.NewRunnableMethod(&Browser::CloseFrame));
}
void Browser::RemoveShelvesForTabContents(TabContents* contents) {
diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h
index aaf892b..58d6324 100644
--- a/chrome/browser/browser.h
+++ b/chrome/browser/browser.h
@@ -517,11 +517,15 @@ class Browser : public TabStripModelDelegate,
// Processes the next tab that needs it's beforeunload event fired before
// we can proceed with closing the browser.
- void Browser::ProcessPendingBeforeUnloadTabs();
+ void ProcessPendingBeforeUnloadTabs();
// Processes the next tab that needs it's unload event fired before we can
// proceed with closing the browser.
- void Browser::ProcessPendingUnloadTabs();
+ void ProcessPendingUnloadTabs();
+
+ // Cleans up state appropriately when we are trying to close the browser
+ // and a tab crashes in it's beforeunload/unload handler.
+ void ClearUnloadStateOnCrash(TabContents* tab);
// The frame
ChromeFrame* frame_;
@@ -580,7 +584,7 @@ class Browser : public TabStripModelDelegate,
ScopedRunnableMethodFactory<Browser> chrome_updater_factory_;
// The following factory is used to close the frame at a later time.
- ScopedRunnableMethodFactory<Browser> frame_method_factory_;
+ ScopedRunnableMethodFactory<Browser> method_factory_;
// This object is used to perform periodic actions in a worker
// thread. It is currently used to monitor hung plugin windows.
diff --git a/chrome/browser/render_process_host.cc b/chrome/browser/render_process_host.cc
index a434320..2520651 100644
--- a/chrome/browser/render_process_host.cc
+++ b/chrome/browser/render_process_host.cc
@@ -531,9 +531,11 @@ void RenderProcessHost::UpdateMaxPageID(int32 page_id) {
}
void RenderProcessHost::CrossSiteClosePageACK(int new_render_process_host_id,
- int new_request_id) {
+ int new_request_id,
+ bool is_closing_browser) {
widget_helper_->CrossSiteClosePageACK(new_render_process_host_id,
- new_request_id);
+ new_request_id,
+ is_closing_browser);
}
void RenderProcessHost::OnMessageReceived(const IPC::Message& msg) {
diff --git a/chrome/browser/render_process_host.h b/chrome/browser/render_process_host.h
index 117ad55..6384fd9 100644
--- a/chrome/browser/render_process_host.h
+++ b/chrome/browser/render_process_host.h
@@ -149,7 +149,8 @@ class RenderProcessHost : public IPC::Channel::Listener,
// Necessary for a cross-site request, in the case that the original
// RenderViewHost is not live and thus cannot run an onunload handler.
void CrossSiteClosePageACK(int new_render_process_host_id,
- int new_request_id);
+ int new_request_id,
+ bool is_closing_browser);
// IPC channel listener
virtual void OnMessageReceived(const IPC::Message& msg);
diff --git a/chrome/browser/render_view_host.cc b/chrome/browser/render_view_host.cc
index 3080705..2b3ba62 100644
--- a/chrome/browser/render_view_host.cc
+++ b/chrome/browser/render_view_host.cc
@@ -74,6 +74,9 @@ void FilterURL(RendererSecurityPolicy* policy, int renderer_id, GURL* url) {
}
}
+// Delay to wait on closing the tab for a beforeunload/unload handler to fire.
+const int kUnloadTimeoutMS = 1000;
+
} // namespace
///////////////////////////////////////////////////////////////////////////////
@@ -107,7 +110,8 @@ RenderViewHost::RenderViewHost(SiteInstance* instance,
navigations_suspended_(false),
suspended_nav_message_(NULL),
run_modal_reply_msg_(NULL),
- has_unload_listener_(false) {
+ has_unload_listener_(false),
+ is_waiting_for_unload_ack_(false) {
DCHECK(instance_);
DCHECK(delegate_);
if (modal_dialog_event == NULL)
@@ -243,6 +247,11 @@ void RenderViewHost::SetNavigationsSuspended(bool suspend) {
void RenderViewHost::AttemptToClosePage(bool is_closing_browser) {
if (IsRenderViewLive()) {
+ // Start the hang monitor in case the renderer hangs in the beforeunload
+ // handler.
+ DCHECK(!is_waiting_for_unload_ack_);
+ is_waiting_for_unload_ack_ = true;
+ StartHangMonitorTimeout(kUnloadTimeoutMS);
Send(new ViewMsg_ShouldClose(routing_id_, is_closing_browser));
} else {
// This RenderViewHost doesn't have a live renderer, so just skip running
@@ -252,10 +261,13 @@ void RenderViewHost::AttemptToClosePage(bool is_closing_browser) {
}
void RenderViewHost::OnProceedWithClosePage(bool is_closing_browser) {
- Send(new ViewMsg_ClosePage(routing_id_,
- site_instance()->process_host_id(),
- routing_id(),
- is_closing_browser));
+ // Start the hang monitor in case the renderer hangs in the unload handler.
+ DCHECK(!is_waiting_for_unload_ack_);
+ is_waiting_for_unload_ack_ = true;
+ StartHangMonitorTimeout(kUnloadTimeoutMS);
+ ClosePage(site_instance()->process_host_id(),
+ routing_id(),
+ is_closing_browser);
}
// static
@@ -267,6 +279,10 @@ void RenderViewHost::ClosePageIgnoringUnloadEvents(int render_process_host_id,
if (!rvh)
return;
+ rvh->StopHangMonitorTimeout();
+ DCHECK(rvh->is_waiting_for_unload_ack_);
+ rvh->is_waiting_for_unload_ack_ = false;
+
// The RenderViewHost's delegate is a WebContents.
TabContents* tab = static_cast<WebContents*>(rvh->delegate());
rvh->UnloadListenerHasFired();
@@ -279,18 +295,20 @@ void RenderViewHost::ClosePageIgnoringUnloadEvents(int render_process_host_id,
}
void RenderViewHost::ClosePage(int new_render_process_host_id,
- int new_request_id) {
+ int new_request_id,
+ bool is_closing_browser) {
if (IsRenderViewLive()) {
Send(new ViewMsg_ClosePage(routing_id_,
new_render_process_host_id,
new_request_id,
- false)); // is_closing_browser
+ is_closing_browser));
} else {
// This RenderViewHost doesn't have a live renderer, so just skip closing
// the page. We must notify the ResourceDispatcherHost on the IO thread,
// which we will do through the RenderProcessHost's widget helper.
process()->CrossSiteClosePageACK(new_render_process_host_id,
- new_request_id);
+ new_request_id,
+ is_closing_browser);
}
}
@@ -1138,6 +1156,10 @@ void RenderViewHost::OnReceivedSerializedHtmlData(const GURL& frame_url,
void RenderViewHost::OnMsgShouldCloseACK(bool proceed,
bool is_closing_browser) {
+ StopHangMonitorTimeout();
+ DCHECK(is_waiting_for_unload_ack_);
+ is_waiting_for_unload_ack_ = false;
+
if (is_closing_browser) {
// The RenderViewHost's delegate is a WebContents.
TabContents* tab = static_cast<WebContents*>(delegate());
@@ -1155,6 +1177,14 @@ void RenderViewHost::OnUnloadListenerChanged(bool has_listener) {
}
void RenderViewHost::NotifyRendererUnresponsive() {
+ if (is_waiting_for_unload_ack_) {
+ // If the tab hangs in the beforeunload/unload handler there's really
+ // nothing we can do to recover. We can safely kill the process and the
+ // Browser will deal with the crash appropriately.
+ TerminateProcess(process()->process(), 0);
+ return;
+ }
+
// If the debugger is attached, we're going to be unresponsive anytime it's
// stopped at a breakpoint.
if (!debugger_attached_)
diff --git a/chrome/browser/render_view_host.h b/chrome/browser/render_view_host.h
index d1b58b2..488131a 100644
--- a/chrome/browser/render_view_host.h
+++ b/chrome/browser/render_view_host.h
@@ -172,7 +172,9 @@ class RenderViewHost : public RenderWidgetHost {
// ResourceDispatcherHost when it is finished. |new_render_process_host_id|
// and |new_request_id| will help the ResourceDispatcherHost identify which
// response is associated with this event.
- virtual void ClosePage(int new_render_process_host_id, int new_request_id);
+ virtual void ClosePage(int new_render_process_host_id,
+ int new_request_id,
+ bool is_closing_browser);
// Sets whether this RenderViewHost has an outstanding cross-site request,
// for which another renderer will need to run an onunload event handler.
@@ -569,6 +571,8 @@ class RenderViewHost : public RenderWidgetHost {
bool has_unload_listener_;
+ bool is_waiting_for_unload_ack_;
+
DISALLOW_EVIL_CONSTRUCTORS(RenderViewHost);
};
diff --git a/chrome/browser/render_widget_helper.cc b/chrome/browser/render_widget_helper.cc
index 9e34d75..708c236 100644
--- a/chrome/browser/render_widget_helper.cc
+++ b/chrome/browser/render_widget_helper.cc
@@ -86,14 +86,16 @@ void RenderWidgetHelper::CancelResourceRequests(int render_widget_id) {
}
void RenderWidgetHelper::CrossSiteClosePageACK(int new_render_process_host_id,
- int new_request_id) {
+ int new_request_id,
+ bool is_closing_browser) {
if (g_browser_process->io_thread())
g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this,
&RenderWidgetHelper::OnCrossSiteClosePageACK,
g_browser_process->resource_dispatcher_host(),
new_render_process_host_id,
- new_request_id));
+ new_request_id,
+ is_closing_browser));
}
bool RenderWidgetHelper::WaitForPaintMsg(int render_widget_id,
@@ -196,8 +198,10 @@ void RenderWidgetHelper::OnCancelResourceRequests(
void RenderWidgetHelper::OnCrossSiteClosePageACK(
ResourceDispatcherHost* dispatcher,
int new_render_process_host_id,
- int new_request_id) {
- dispatcher->OnClosePageACK(new_render_process_host_id, new_request_id, false);
+ int new_request_id,
+ bool is_closing_browser) {
+ dispatcher->OnClosePageACK(new_render_process_host_id, new_request_id,
+ is_closing_browser);
}
void RenderWidgetHelper::CreateView(int opener_id,
diff --git a/chrome/browser/render_widget_helper.h b/chrome/browser/render_widget_helper.h
index 1a7f939..27360a3 100644
--- a/chrome/browser/render_widget_helper.h
+++ b/chrome/browser/render_widget_helper.h
@@ -116,7 +116,8 @@ class RenderWidgetHelper :
// that the original RenderViewHost is not live and thus cannot run an
// onunload handler.
void CrossSiteClosePageACK(int new_render_process_host_id,
- int new_request_id);
+ int new_request_id,
+ bool is_closing_browser);
// Called on the UI thread to wait for the next PaintRect message for the
// specified render widget. Returns true if successful, and the msg out-
@@ -158,7 +159,8 @@ class RenderWidgetHelper :
// Called on the IO thread to resume a cross-site response.
void OnCrossSiteClosePageACK(ResourceDispatcherHost* dispatcher,
int new_render_process_host_id,
- int new_request_id);
+ int new_request_id,
+ bool is_closing_browser);
// A map of live paint messages. Must hold pending_paints_lock_ to access.
// The PaintMsgProxy objects are not owned by this map. (See PaintMsgProxy
diff --git a/chrome/browser/render_widget_host.cc b/chrome/browser/render_widget_host.cc
index bfcce2a..2415925 100644
--- a/chrome/browser/render_widget_host.cc
+++ b/chrome/browser/render_widget_host.cc
@@ -338,8 +338,7 @@ void RenderWidgetHost::OnMsgInputEventAck(const IPC::Message& message) {
UMA_HISTOGRAM_TIMES(L"MPArch.RWH_InputEventDelta", delta);
// Cancel pending hung renderer checks since the renderer is responsive.
- ResetHangMonitorTimeout();
- RendererIsResponsive();
+ StopHangMonitorTimeout();
void* iter = NULL;
int type = 0;
@@ -396,8 +395,7 @@ void RenderWidgetHost::WasHidden() {
is_hidden_ = true;
// Don't bother reporting hung state when we aren't the active tab.
- ResetHangMonitorTimeout();
- RendererIsResponsive();
+ StopHangMonitorTimeout();
// If we have a renderer, then inform it that we are being hidden so it can
// reduce its resource utilization.
@@ -499,9 +497,7 @@ void RenderWidgetHost::ForwardInputEvent(const WebInputEvent& input_event,
// any input event cancels a pending mouse move event
next_mouse_move_.reset();
- MessageLoop::current()->PostDelayedTask(FROM_HERE,
- hung_renderer_factory_.NewRunnableMethod(
- &RenderWidgetHost::RendererIsUnresponsive), kHungRendererDelayMs);
+ StartHangMonitorTimeout(kHungRendererDelayMs);
}
void RenderWidgetHost::Shutdown() {
@@ -694,6 +690,18 @@ void RenderWidgetHost::ScrollRect(HANDLE bitmap, const gfx::Rect& bitmap_rect,
}
}
-void RenderWidgetHost::ResetHangMonitorTimeout() {
+void RenderWidgetHost::RestartHangMonitorTimeout() {
+ hung_renderer_factory_.RevokeAll();
+ StartHangMonitorTimeout(kHungRendererDelayMs);
+}
+
+void RenderWidgetHost::StopHangMonitorTimeout() {
hung_renderer_factory_.RevokeAll();
+ RendererIsResponsive();
+}
+
+void RenderWidgetHost::StartHangMonitorTimeout(int delay) {
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ hung_renderer_factory_.NewRunnableMethod(
+ &RenderWidgetHost::RendererIsUnresponsive), delay);
}
diff --git a/chrome/browser/render_widget_host.h b/chrome/browser/render_widget_host.h
index e7a14e6..bb71df8 100644
--- a/chrome/browser/render_widget_host.h
+++ b/chrome/browser/render_widget_host.h
@@ -202,11 +202,22 @@ class RenderWidgetHost : public IPC::Channel::Listener {
// javascript call.
virtual bool CanBlur() const { return true; }
- // Reset the active hang monitor timeout.
+ // Restart the active hang monitor timeout. Clears all existing timeouts
+ // and starts with a new one.
// This can be because the renderer has become active, the tab is being
// hidden, or the user has chosen to wait some more to give the tab a chance
// to become active and we don't want to display a warning too soon.
- void ResetHangMonitorTimeout();
+ void RestartHangMonitorTimeout();
+
+ // Stops all existing hang monitor timeouts and assumes the renderer is
+ // responsive.
+ void StopHangMonitorTimeout();
+
+ // Starts a hang monitor timeout. If there's already a hang monitor timeout
+ // the new one will only fire if it has a shorter delay than the time
+ // left on the existing timeouts.
+ void StartHangMonitorTimeout(int delay);
+
protected:
// Represents a cache of BackingStore objects indexed by RenderWidgetHost.
diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc
index 614d29a6..7ada01d 100644
--- a/chrome/browser/tabs/tab_strip_model.cc
+++ b/chrome/browser/tabs/tab_strip_model.cc
@@ -269,7 +269,13 @@ bool TabStripModel::TabsAreLoading() const {
bool TabStripModel::TabHasUnloadListener(int index) {
WebContents* web_contents = GetContentsAt(index)->AsWebContents();
if (web_contents) {
- return web_contents->render_view_host()->HasUnloadListener();
+ // If the WebContents is not connected yet, then there's no unload
+ // handler we can fire even if the WebContents has an unload listener.
+ // One case where we hit this is in a tab that has an infinite loop
+ // before load.
+ return web_contents->notify_disconnection() &&
+ !web_contents->IsShowingInterstitialPage() &&
+ web_contents->render_view_host()->HasUnloadListener();
}
return false;
}
diff --git a/chrome/browser/views/hung_renderer_view.cc b/chrome/browser/views/hung_renderer_view.cc
index efb03c5..645c6df 100644
--- a/chrome/browser/views/hung_renderer_view.cc
+++ b/chrome/browser/views/hung_renderer_view.cc
@@ -322,7 +322,7 @@ bool HungRendererWarningView::Accept(bool window_closing) {
// Start waiting again for responsiveness.
if (contents_ && contents_->render_view_host())
- contents_->render_view_host()->ResetHangMonitorTimeout();
+ contents_->render_view_host()->RestartHangMonitorTimeout();
return true;
}
diff --git a/chrome/browser/web_contents.cc b/chrome/browser/web_contents.cc
index d86d5f4..6d55ed8 100644
--- a/chrome/browser/web_contents.cc
+++ b/chrome/browser/web_contents.cc
@@ -1023,9 +1023,12 @@ void WebContents::OnCrossSiteResponse(int new_render_process_host_id,
if (IsShowingInterstitialPage()) {
DCHECK(original_render_view_host_);
original_render_view_host_->ClosePage(new_render_process_host_id,
- new_request_id);
+ new_request_id,
+ false); // is_closing_browser
} else {
- render_view_host_->ClosePage(new_render_process_host_id, new_request_id);
+ render_view_host_->ClosePage(new_render_process_host_id,
+ new_request_id,
+ false); // is_closing_browser
}
// ResourceDispatcherHost has told us to run the onunload handler, which