diff options
-rw-r--r-- | base/ref_counted.h | 14 | ||||
-rw-r--r-- | chrome/browser/render_view_host.cc | 5 | ||||
-rw-r--r-- | chrome/browser/render_view_host.h | 5 | ||||
-rw-r--r-- | chrome/browser/tab_contents.cc | 10 | ||||
-rw-r--r-- | chrome/browser/tab_contents.h | 3 | ||||
-rw-r--r-- | chrome/browser/views/constrained_window_impl_interactive_uitest.cc | 55 | ||||
-rw-r--r-- | chrome/browser/web_contents.cc | 4 | ||||
-rw-r--r-- | chrome/browser/web_contents.h | 1 | ||||
-rw-r--r-- | chrome/common/render_messages_internal.h | 6 | ||||
-rw-r--r-- | chrome/renderer/render_thread.cc | 5 | ||||
-rw-r--r-- | chrome/renderer/render_view.cc | 47 | ||||
-rw-r--r-- | chrome/renderer/render_view.h | 44 | ||||
-rw-r--r-- | chrome/test/data/constrained_files/infinite_popups.html | 12 | ||||
-rw-r--r-- | chrome/test/data/constrained_files/infinite_popups_impl.html | 13 |
14 files changed, 198 insertions, 26 deletions
diff --git a/base/ref_counted.h b/base/ref_counted.h index 0e0ea67..595a551 100644 --- a/base/ref_counted.h +++ b/base/ref_counted.h @@ -109,6 +109,19 @@ class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe<T>); }; +// +// A wrapper for some piece of data so we can place other things in +// scoped_refptrs<>. +// +template<typename T> +class RefCountedData : public base::RefCounted< base::RefCountedData<T> > { + public: + RefCountedData() : data() {} + RefCountedData(const T& in_value) : data(in_value) {} + + T data; +}; + } // namespace base // @@ -213,4 +226,3 @@ class scoped_refptr { }; #endif // BASE_REF_COUNTED_H_ - diff --git a/chrome/browser/render_view_host.cc b/chrome/browser/render_view_host.cc index 03eec5a..0ac3e16 100644 --- a/chrome/browser/render_view_host.cc +++ b/chrome/browser/render_view_host.cc @@ -1005,6 +1005,10 @@ void RenderViewHost::OnPersonalizationEvent(const std::string& message, } #endif +void RenderViewHost::DisassociateFromPopupCount() { + Send(new ViewMsg_DisassociateFromPopupCount(routing_id_)); +} + void RenderViewHost::OnMsgGoToEntryAtOffset(int offset) { delegate_->GoToEntryAtOffset(offset); } @@ -1222,4 +1226,3 @@ void RenderViewHost::ForwardMessageFromExternalHost( const std::string& target, const std::string& message) { Send(new ViewMsg_HandleMessageFromExternalHost(routing_id_, target, message)); } - diff --git a/chrome/browser/render_view_host.h b/chrome/browser/render_view_host.h index 34616a3..8bf8e7f 100644 --- a/chrome/browser/render_view_host.h +++ b/chrome/browser/render_view_host.h @@ -382,6 +382,10 @@ class RenderViewHost : public RenderWidgetHost { void ForwardMessageFromExternalHost(const std::string& target, const std::string& message); + // Message the renderer that we should be counted as a new document and not + // as a popup. + void DisassociateFromPopupCount(); + protected: // Overridden from RenderWidgetHost: virtual void UnhandledInputEvent(const WebInputEvent& event); @@ -579,4 +583,3 @@ class RenderViewHostFactory { }; #endif // CHROME_BROWSER_RENDER_VIEW_HOST_H__ - diff --git a/chrome/browser/tab_contents.cc b/chrome/browser/tab_contents.cc index fcd2b93..6234abb 100644 --- a/chrome/browser/tab_contents.cc +++ b/chrome/browser/tab_contents.cc @@ -21,8 +21,6 @@ #include "generated_resources.h" -static size_t kMaxNumberOfConstrainedPopups = 20; - namespace { BOOL CALLBACK InvalidateWindow(HWND hwnd, LPARAM lparam) { @@ -283,6 +281,8 @@ void TabContents::AddNewContents(TabContents* new_contents, popup_owner = our_owner; popup_owner->AddConstrainedPopup(new_contents, initial_pos); } else { + new_contents->DisassociateFromPopupCount(); + delegate_->AddNewContents(this, new_contents, disposition, initial_pos, user_gesture); } @@ -290,11 +290,6 @@ void TabContents::AddNewContents(TabContents* new_contents, void TabContents::AddConstrainedPopup(TabContents* new_contents, const gfx::Rect& initial_pos) { - if (child_windows_.size() > kMaxNumberOfConstrainedPopups) { - new_contents->CloseContents(); - return; - } - ConstrainedWindow* window = ConstrainedWindow::CreateConstrainedPopup( this, initial_pos, new_contents); @@ -499,6 +494,7 @@ void TabContents::DetachContents(ConstrainedWindow* window, int frame_component) { WillClose(window); if (delegate_) { + contents->DisassociateFromPopupCount(); delegate_->StartDraggingDetachedContents( this, contents, contents_bounds, mouse_pt, frame_component); } diff --git a/chrome/browser/tab_contents.h b/chrome/browser/tab_contents.h index 99daf5d..3b38963 100644 --- a/chrome/browser/tab_contents.h +++ b/chrome/browser/tab_contents.h @@ -314,6 +314,9 @@ class TabContents : public PageNavigator, virtual void Copy() { } virtual void Paste() { } + // Called on a TabContents when it isn't a popup, but a new window. + virtual void DisassociateFromPopupCount() { } + // Window management --------------------------------------------------------- // Create a new window constrained to this TabContents' clip and visibility. diff --git a/chrome/browser/views/constrained_window_impl_interactive_uitest.cc b/chrome/browser/views/constrained_window_impl_interactive_uitest.cc index 3dc184f..860a009 100644 --- a/chrome/browser/views/constrained_window_impl_interactive_uitest.cc +++ b/chrome/browser/views/constrained_window_impl_interactive_uitest.cc @@ -143,3 +143,58 @@ TEST_F(InteractiveConstrainedWindowTest, ClickingXClosesConstrained) { EXPECT_TRUE(automation()->GetBrowserWindowCount(&browser_window_count)); EXPECT_EQ(browser_window_count, 1); } + +// Tests that in the window.open() equivalent of a fork bomb, we stop building +// windows. +TEST_F(InteractiveConstrainedWindowTest, DontSpawnEndlessPopups) { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + + scoped_ptr<WindowProxy> window( + automation()->GetWindowForBrowser(browser.get())); + ASSERT_TRUE(window.get()); + + scoped_ptr<TabProxy> tab(browser->GetTab(0)); + ASSERT_TRUE(tab.get()); + + std::wstring filename(test_data_directory_); + file_util::AppendToPath(&filename, L"constrained_files"); + file_util::AppendToPath(&filename, + L"infinite_popups.html"); + ASSERT_TRUE(tab->NavigateToURL(net::FilePathToFileURL(filename))); + + gfx::Rect tab_view_bounds; + ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, + &tab_view_bounds, true)); + + // Simulate a click of the actual link to force user_gesture to be + // true; if we don't, the resulting popup will be constrained, which + // isn't what we want to test. + POINT link_point(tab_view_bounds.CenterPoint().ToPOINT()); + ASSERT_TRUE(window->SimulateOSClick(link_point, + views::Event::EF_LEFT_BUTTON_DOWN)); + + ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 1000)); + + scoped_ptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1)); + ASSERT_TRUE(popup_browser.get()); + scoped_ptr<TabProxy> popup_tab(popup_browser->GetTab(0)); + ASSERT_TRUE(popup_tab.get()); + + // And now we spin on this, waiting to make sure that we don't spawn popup + // windows endlessly. The current limit is 25, so allowing for possible race + // conditions and one off errors, don't break out until we go over 35 popup + // windows (in which case we are bork bork bork). + const int kMaxPopupWindows = 35; + int constrained_window_count = 0; + int new_constrained_window_count; + bool continuing = true; + while (continuing && constrained_window_count < kMaxPopupWindows) { + continuing = popup_tab->WaitForChildWindowCountToChange( + constrained_window_count, &new_constrained_window_count, + 100000); + EXPECT_GE(new_constrained_window_count, constrained_window_count); + EXPECT_LE(new_constrained_window_count, kMaxPopupWindows); + constrained_window_count = new_constrained_window_count; + } +} diff --git a/chrome/browser/web_contents.cc b/chrome/browser/web_contents.cc index e842db8..f7c9daf 100644 --- a/chrome/browser/web_contents.cc +++ b/chrome/browser/web_contents.cc @@ -414,6 +414,10 @@ void WebContents::Paste() { render_view_host()->Paste(); } +void WebContents::DisassociateFromPopupCount() { + render_view_host()->DisassociateFromPopupCount(); +} + void WebContents::DidBecomeSelected() { TabContents::DidBecomeSelected(); diff --git a/chrome/browser/web_contents.h b/chrome/browser/web_contents.h index 8a5f6aa..a58cf58 100644 --- a/chrome/browser/web_contents.h +++ b/chrome/browser/web_contents.h @@ -92,6 +92,7 @@ class WebContents : public TabContents, virtual void Cut(); virtual void Copy(); virtual void Paste(); + virtual void DisassociateFromPopupCount(); virtual void DidBecomeSelected(); virtual void WasHidden(); virtual void ShowContents(); diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index beaa3bf..a998b49 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -454,6 +454,12 @@ IPC_BEGIN_MESSAGES(View, 1) std::string /* The target for the message */, std::string /* The message */) + // Sent to the renderer when a popup window should no longer count against + // the current popup count (either because it's not a popup or because it was + // a generated by a user action or because a constrained popup got turned + // into a full window). + IPC_MESSAGE_ROUTED0(ViewMsg_DisassociateFromPopupCount) + IPC_END_MESSAGES(View) diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index 56ed3a8..fd262de 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -205,9 +205,9 @@ void RenderThread::OnCreateNewView(HWND parent_hwnd, int32 view_id) { // TODO(darin): once we have a RenderThread per RenderView, this will need to // change to assert that we are not creating more than one view. - RenderView::Create( - parent_hwnd, modal_dialog_event, MSG_ROUTING_NONE, webkit_prefs, view_id); + parent_hwnd, modal_dialog_event, MSG_ROUTING_NONE, webkit_prefs, + new SharedRenderViewCounter(0), view_id); } void RenderThread::OnSetCacheCapacities(size_t min_dead_capacity, @@ -238,4 +238,3 @@ void RenderThread::InformHostOfCacheStatsLater() { &RenderThread::InformHostOfCacheStats), kCacheStatsDelayMS); } - diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 48f1a4c..253a869 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -94,6 +94,9 @@ const TimeDelta kDelayForNavigationSync = TimeDelta::FromSeconds(5); // globally unique in the renderer. static int32 next_page_id_ = 1; +// The maximum number of popups that can be spawned from one page. +static const int kMaximumNumberOfUnacknowledgedPopups = 25; + static const char* const kUnreachableWebDataURL = "chrome-resource://chromewebdata/"; @@ -152,6 +155,7 @@ RenderView::RenderView() history_forward_list_count_(0), disable_popup_blocking_(false), has_unload_listener_(false), + decrement_shared_popup_at_destruction_(false), greasemonkey_enabled_(false) { resource_dispatcher_ = new ResourceDispatcher(this); #ifdef CHROME_PERSONALIZATION @@ -160,6 +164,9 @@ RenderView::RenderView() } RenderView::~RenderView() { + if (decrement_shared_popup_at_destruction_) + shared_popup_counter_->data--; + resource_dispatcher_->ClearMessageSender(); // Clear any back-pointers that might still be held by plugins. PluginDelegateList::iterator it = plugin_delegates_.begin(); @@ -177,17 +184,20 @@ RenderView::~RenderView() { } /*static*/ -RenderView* RenderView::Create(HWND parent_hwnd, - HANDLE modal_dialog_event, - int32 opener_id, - const WebPreferences& webkit_prefs, - int32 routing_id) { +RenderView* RenderView::Create( + HWND parent_hwnd, + HANDLE modal_dialog_event, + int32 opener_id, + const WebPreferences& webkit_prefs, + SharedRenderViewCounter* counter, + int32 routing_id) { DCHECK(routing_id != MSG_ROUTING_NONE); scoped_refptr<RenderView> view = new RenderView(); view->Init(parent_hwnd, modal_dialog_event, opener_id, webkit_prefs, + counter, routing_id); // adds reference return view; } @@ -227,12 +237,22 @@ void RenderView::Init(HWND parent_hwnd, HANDLE modal_dialog_event, int32 opener_id, const WebPreferences& webkit_prefs, + SharedRenderViewCounter* counter, int32 routing_id) { DCHECK(!webview()); if (opener_id != MSG_ROUTING_NONE) opener_id_ = opener_id; + if (counter) { + shared_popup_counter_ = counter; + shared_popup_counter_->data++; + decrement_shared_popup_at_destruction_ = true; + } else { + shared_popup_counter_ = new SharedRenderViewCounter(0); + decrement_shared_popup_at_destruction_ = false; + } + // Avoid a leak here by not assigning, since WebView::Create addrefs for us. WebWidget* view = WebView::Create(this, webkit_prefs); webwidget_.swap(&view); @@ -344,6 +364,8 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { #endif IPC_MESSAGE_HANDLER(ViewMsg_HandleMessageFromExternalHost, OnMessageFromExternalHost) + IPC_MESSAGE_HANDLER(ViewMsg_DisassociateFromPopupCount, + OnDisassociateFromPopupCount) // Have the super handle all other messages. IPC_MESSAGE_UNHANDLED(RenderWidget::OnMessageReceived(message)) IPC_END_MESSAGE_MAP() @@ -1673,6 +1695,10 @@ void RenderView::DebuggerOutput(const std::wstring& out) { } WebView* RenderView::CreateWebView(WebView* webview, bool user_gesture) { + // Check to make sure we aren't overloading on popups. + if (shared_popup_counter_->data > kMaximumNumberOfUnacknowledgedPopups) + return NULL; + int32 routing_id = MSG_ROUTING_NONE; HANDLE modal_dialog_event = NULL; bool result = RenderThread::current()->Send( @@ -1686,7 +1712,8 @@ WebView* RenderView::CreateWebView(WebView* webview, bool user_gesture) { // The WebView holds a reference to this new RenderView const WebPreferences& prefs = webview->GetPreferences(); RenderView* view = RenderView::Create(NULL, modal_dialog_event, routing_id_, - prefs, routing_id); + prefs, shared_popup_counter_, + routing_id); view->set_opened_by_user_gesture(user_gesture); // Copy over the alternate error page URL so we can have alt error pages in @@ -2595,6 +2622,13 @@ void RenderView::OnMessageFromExternalHost( main_frame->LoadRequest(request.get()); } +void RenderView::OnDisassociateFromPopupCount() { + if (decrement_shared_popup_at_destruction_) + shared_popup_counter_->data--; + shared_popup_counter_ = new SharedRenderViewCounter(0); + decrement_shared_popup_at_destruction_ = false; +} + std::string RenderView::GetAltHTMLForTemplate( const DictionaryValue& error_strings, int template_resource_id) const { const StringPiece template_html( @@ -2609,4 +2643,3 @@ std::string RenderView::GetAltHTMLForTemplate( return jstemplate_builder::GetTemplateHtml( template_html, &error_strings, "t"); } - diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index d307e48..cba9df9 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -51,6 +51,20 @@ namespace webkit_glue { struct FileUploadData; } +// We need to prevent a page from trying to create infinite popups. It is not +// as simple as keeping a count of the number of immediate children +// popups. Having an html file that window.open()s itself would create +// an unlimited chain of RenderViews who only have one RenderView child. +// +// Therefore, each new top level RenderView creates a new counter and shares it +// with all its children and grandchildren popup RenderViews created with +// CreateWebView() to have a sort of global limit for the page so no more than +// kMaximumNumberOfPopups popups are created. +// +// This is a RefCounted holder of an int because I can't say +// scoped_refptr<int>. +typedef base::RefCountedData<int> SharedRenderViewCounter; + // // RenderView is an object that manages a WebView object, and provides a // communication interface with an embedding application process @@ -64,12 +78,15 @@ class RenderView : public RenderWidget, public WebViewDelegate, // the renderer and plugin processes know to pump window messages. If this // is a constrained popup or as a new tab, opener_id is the routing ID of the // RenderView responsible for creating this RenderView (corresponding to the - // parent_hwnd). - static RenderView* Create(HWND parent_hwnd, - HANDLE modal_dialog_event, - int32 opener_id, - const WebPreferences& webkit_prefs, - int32 routing_id); + // parent_hwnd). |counter| is either a currently initialized counter, or NULL + // (in which case we treat this RenderView as a top level window). + static RenderView* Create( + HWND parent_hwnd, + HANDLE modal_dialog_event, + int32 opener_id, + const WebPreferences& webkit_prefs, + SharedRenderViewCounter* counter, + int32 routing_id); // Sets the "next page id" counter. static void SetNextPageID(int32 next_page_id); @@ -293,6 +310,7 @@ class RenderView : public RenderWidget, public WebViewDelegate, HANDLE modal_dialog_event, int32 opener_id, const WebPreferences& webkit_prefs, + SharedRenderViewCounter* counter, int32 routing_id); void UpdateURL(WebFrame* frame); @@ -443,6 +461,10 @@ class RenderView : public RenderWidget, public WebViewDelegate, void OnMessageFromExternalHost(const std::string& target, const std::string& message); + // Message that we should no longer be part of the current popup window + // grouping, and should form our own grouping. + void OnDisassociateFromPopupCount(); + // Switches the frame's CSS media type to "print" and calculate the number of // printed pages that are to be expected. |frame| will be used to calculate // the number of expected pages for this frame only. @@ -619,6 +641,16 @@ class RenderView : public RenderWidget, public WebViewDelegate, // True if the page has any frame-level unload or beforeunload listeners. bool has_unload_listener_; + // The total number of unrequested popups that exist and can be followed back + // to a common opener. This count is shared among all RenderViews created + // with CreateWebView(). All popups are treated as unrequested until + // specifically instructed otherwise by the Browser process. + scoped_refptr<SharedRenderViewCounter> shared_popup_counter_; + + // Whether this is a top level window (instead of a popup). Top level windows + // shouldn't count against their own |shared_popup_counter_|. + bool decrement_shared_popup_at_destruction_; + // Handles accessibility requests into the renderer side, as well as // maintains the cache and other features of the accessibility tree. scoped_ptr<GlueAccessibility> glue_accessibility_; diff --git a/chrome/test/data/constrained_files/infinite_popups.html b/chrome/test/data/constrained_files/infinite_popups.html new file mode 100644 index 0000000..126dd86 --- /dev/null +++ b/chrome/test/data/constrained_files/infinite_popups.html @@ -0,0 +1,12 @@ +<html>
+ <head>
+<title>Popup Fork Bomb!</title>
+</head>
+<body onClick="javascript:window.open('infinite_popups_impl.html', '',
+'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=50,height=50');void(0);" />
+<h1>Popup Fork Bomb!</h1>
+
+<em>Original credit to <a href="http://crypto.stanford.edu/~abarth/">http://crypto.stanford.edu/~abarth/</a>.</em>
+
+</body>
+</html>
diff --git a/chrome/test/data/constrained_files/infinite_popups_impl.html b/chrome/test/data/constrained_files/infinite_popups_impl.html new file mode 100644 index 0000000..4b336d7 --- /dev/null +++ b/chrome/test/data/constrained_files/infinite_popups_impl.html @@ -0,0 +1,13 @@ +<html>
+ <head>
+<title>Oh noes!</title>
+</head>
+<body>
+<h1>Oh noes!</h1>
+
+<body onload="window.open('infinite_popups_impl.html', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=50,height=50')">
+Over and...
+</body>
+
+</body>
+</html>
|