summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/ref_counted.h14
-rw-r--r--chrome/browser/render_view_host.cc5
-rw-r--r--chrome/browser/render_view_host.h5
-rw-r--r--chrome/browser/tab_contents.cc10
-rw-r--r--chrome/browser/tab_contents.h3
-rw-r--r--chrome/browser/views/constrained_window_impl_interactive_uitest.cc55
-rw-r--r--chrome/browser/web_contents.cc4
-rw-r--r--chrome/browser/web_contents.h1
-rw-r--r--chrome/common/render_messages_internal.h6
-rw-r--r--chrome/renderer/render_thread.cc5
-rw-r--r--chrome/renderer/render_view.cc47
-rw-r--r--chrome/renderer/render_view.h44
-rw-r--r--chrome/test/data/constrained_files/infinite_popups.html12
-rw-r--r--chrome/test/data/constrained_files/infinite_popups_impl.html13
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>