diff options
-rw-r--r-- | chrome/browser/browser.cc | 61 | ||||
-rw-r--r-- | chrome/browser/browser.h | 10 | ||||
-rw-r--r-- | chrome/browser/render_process_host.cc | 6 | ||||
-rw-r--r-- | chrome/browser/render_process_host.h | 3 | ||||
-rw-r--r-- | chrome/browser/render_view_host.cc | 46 | ||||
-rw-r--r-- | chrome/browser/render_view_host.h | 6 | ||||
-rw-r--r-- | chrome/browser/render_widget_helper.cc | 12 | ||||
-rw-r--r-- | chrome/browser/render_widget_helper.h | 6 | ||||
-rw-r--r-- | chrome/browser/render_widget_host.cc | 24 | ||||
-rw-r--r-- | chrome/browser/render_widget_host.h | 15 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_strip_model.cc | 8 | ||||
-rw-r--r-- | chrome/browser/views/hung_renderer_view.cc | 2 | ||||
-rw-r--r-- | chrome/browser/web_contents.cc | 7 |
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 |