summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-05 19:17:24 +0000
committerbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-05 19:17:24 +0000
commit81a34415cb55543b8f8db86ee6872cd70cd24445 (patch)
tree6eaf45857f6a4a03d4f2ef55fe352b5bfe7bd233
parent52381d5530f56b905fb752e90e6ba24029eac199 (diff)
downloadchromium_src-81a34415cb55543b8f8db86ee6872cd70cd24445.zip
chromium_src-81a34415cb55543b8f8db86ee6872cd70cd24445.tar.gz
chromium_src-81a34415cb55543b8f8db86ee6872cd70cd24445.tar.bz2
Refactor the render widget unittest so it can be reused to create a render view
unit test. Change the mock render thread to save all IPC messages it is asked to send so that tests can verify that the correct ones were sent. There are some new functions that support this checking. Plumb the form state change notification through the render view so that we will correctly update the form state to the browser. Write two RenderView unit tests. One arbitrarily tests OnLoadAlternateHTMLText which I used as a testcase for my testing framework. The other tests the above form state change notification. I had to expose the timeout of this message through the RenderView API so that the test can change it. Review URL: http://codereview.chromium.org/16482 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7549 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/common/ipc_message_utils.h4
-rw-r--r--chrome/renderer/mock_render_process.h25
-rw-r--r--chrome/renderer/mock_render_thread.cc135
-rw-r--r--chrome/renderer/mock_render_thread.h116
-rw-r--r--chrome/renderer/render_thread.cc6
-rw-r--r--chrome/renderer/render_thread.h14
-rw-r--r--chrome/renderer/render_view.cc86
-rw-r--r--chrome/renderer/render_view.h26
-rw-r--r--chrome/renderer/render_view_unittest.cc128
-rw-r--r--chrome/renderer/render_widget_unittest.cc171
-rw-r--r--chrome/test/unit/unittests.vcproj40
-rw-r--r--webkit/glue/chrome_client_impl.cc6
-rw-r--r--webkit/glue/chrome_client_impl.h3
-rw-r--r--webkit/glue/chromium_bridge_impl.cc6
-rw-r--r--webkit/glue/webkit_glue.h5
15 files changed, 545 insertions, 226 deletions
diff --git a/chrome/common/ipc_message_utils.h b/chrome/common/ipc_message_utils.h
index 4234737..476d7f9 100644
--- a/chrome/common/ipc_message_utils.h
+++ b/chrome/common/ipc_message_utils.h
@@ -996,9 +996,11 @@ struct ParamTraits<webkit_glue::WebApplicationInfo> {
// Generic message subclasses
// Used for asynchronous messages.
-template <class Param>
+template <class ParamType>
class MessageWithTuple : public Message {
public:
+ typedef ParamType Param;
+
MessageWithTuple(int32 routing_id, uint16 type, const Param& p)
: Message(routing_id, type, PRIORITY_NORMAL) {
WriteParam(this, p);
diff --git a/chrome/renderer/mock_render_process.h b/chrome/renderer/mock_render_process.h
new file mode 100644
index 0000000..d7bd178
--- /dev/null
+++ b/chrome/renderer/mock_render_process.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MOCK_RENDER_PROCESS_H_
+#define CHROME_RENDERER_MOCK_RENDER_PROCESS_H_
+
+#include <string>
+
+#include "chrome/common/child_process.h"
+
+// This class is a trivial mock of the child process singleton. It is necessary
+// so we don't trip DCHECKs in ChildProcess::ReleaseProcess() when destroying
+// a render widget instance.
+class MockProcess : public ChildProcess {
+ public:
+ explicit MockProcess(const std::wstring& channel_name) {}
+ static void GlobalInit() {
+ ChildProcessFactory<MockProcess> factory;
+ ChildProcess::GlobalInit(L"dummy", &factory);
+ }
+};
+
+#endif // CHROME_RENDERER_MOCK_RENDER_PROCESS_H_
+
diff --git a/chrome/renderer/mock_render_thread.cc b/chrome/renderer/mock_render_thread.cc
new file mode 100644
index 0000000..050774e
--- /dev/null
+++ b/chrome/renderer/mock_render_thread.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/mock_render_thread.h"
+
+#include "base/logging.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "chrome/common/render_messages.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+MockRenderThread::MockRenderThread()
+ : routing_id_(0),
+ opener_id_(0),
+ widget_(NULL),
+ reply_deserializer_(NULL) {
+}
+
+MockRenderThread::~MockRenderThread() {
+ // Don't leak the allocated message bodies.
+ ClearMessages();
+}
+
+// Called by the Widget. The routing_id must match the routing id assigned
+// to the Widget in reply to ViewHostMsg_CreateWidget message.
+void MockRenderThread::AddRoute(int32 routing_id,
+ IPC::Channel::Listener* listener) {
+ EXPECT_EQ(routing_id_, routing_id);
+ widget_ = listener;
+}
+
+// Called by the Widget. The routing id must match the routing id of AddRoute.
+void MockRenderThread::RemoveRoute(int32 routing_id) {
+ EXPECT_EQ(routing_id_, routing_id);
+ widget_ = NULL;
+}
+
+// Called by the Widget. Used to send messages to the browser.
+// We short-circuit the mechanim and handle the messages right here on this
+// class.
+bool MockRenderThread::Send(IPC::Message* msg) {
+ // We need to simulate a synchronous channel, thus we are going to receive
+ // through this function messages, messages with reply and reply messages.
+ // We can only handle one synchronous message at a time.
+ if (msg->is_reply()) {
+ if (reply_deserializer_) {
+ reply_deserializer_->SerializeOutputParameters(*msg);
+ delete reply_deserializer_;
+ reply_deserializer_ = NULL;
+ }
+ } else {
+ if (msg->is_sync()) {
+ reply_deserializer_ =
+ static_cast<IPC::SyncMessage*>(msg)->GetReplyDeserializer();
+ }
+ OnMessageReceived(*msg);
+ }
+ delete msg;
+ return true;
+}
+
+void MockRenderThread::ClearMessages() {
+ for (size_t i = 0; i < messages_.size(); i++)
+ delete[] messages_[i].second;
+ messages_.clear();
+}
+
+const IPC::Message* MockRenderThread::GetMessageAt(size_t index) const {
+ if (index >= messages_.size())
+ return NULL;
+ return &messages_[index].first;
+}
+
+const IPC::Message* MockRenderThread::GetFirstMessageMatching(uint16 id) const {
+ for (size_t i = 0; i < messages_.size(); i++) {
+ if (messages_[i].first.type() == id)
+ return &messages_[i].first;
+ }
+ return NULL;
+}
+
+const IPC::Message* MockRenderThread::GetUniqueMessageMatching(
+ uint16 id) const {
+ size_t found_index = 0;
+ size_t found_count = 0;
+ for (size_t i = 0; i < messages_.size(); i++) {
+ if (messages_[i].first.type() == id) {
+ found_count++;
+ found_index = i;
+ }
+ }
+ if (found_count != 1)
+ return NULL; // Didn't find a unique one.
+ return &messages_[found_index].first;
+}
+
+void MockRenderThread::SendCloseMessage() {
+ ViewMsg_Close msg(routing_id_);
+ widget_->OnMessageReceived(msg);
+}
+
+void MockRenderThread::OnMessageReceived(const IPC::Message& msg) {
+ // Copy the message into a pair. This is tricky since the message doesn't
+ // manage its data itself.
+ char* data_copy;
+ if (msg.size()) {
+ data_copy = new char[msg.size()];
+ memcpy(data_copy, msg.data(), msg.size());
+ } else {
+ // Dummy data so we can treat everything the same.
+ data_copy = new char[1];
+ data_copy[0] = 0;
+ }
+
+ // Save the message.
+ messages_.push_back(
+ std::make_pair(IPC::Message(data_copy, msg.size()), data_copy));
+
+ // Some messages we do special handling.
+ bool handled = true;
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MockRenderThread, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget);
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+}
+
+// The Widget expects to be returned valid route_id.
+void MockRenderThread::OnMsgCreateWidget(int opener_id,
+ bool focus_on_show,
+ int* route_id) {
+ opener_id_ = opener_id;
+ *route_id = routing_id_;
+}
+
diff --git a/chrome/renderer/mock_render_thread.h b/chrome/renderer/mock_render_thread.h
new file mode 100644
index 0000000..950353a
--- /dev/null
+++ b/chrome/renderer/mock_render_thread.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_MOCK_RENDER_THREAD_H_
+#define CHROME_RENDERER_MOCK_RENDER_THREAD_H_
+
+#include <vector>
+
+#include "chrome/renderer/render_thread.h"
+
+// This class is very simple mock of RenderThread. It simulates an IPC channel
+// which supports only two messages:
+// ViewHostMsg_CreateWidget : sync message sent by the Widget.
+// ViewMsg_Close : async, send to the Widget.
+class MockRenderThread : public RenderThreadBase {
+ public:
+ // Encapusulates an IPC message and its associated data (which is not
+ // otherwise bound to the lifetime of the message).
+ typedef std::pair<IPC::Message, char*> MessagePair;
+
+ MockRenderThread();
+ virtual ~MockRenderThread();
+
+ // Called by the Widget. Not used in the test.
+ virtual bool InSend() const {
+ return false;
+ }
+
+ // Called by the Widget. The routing_id must match the routing id assigned
+ // to the Widget in reply to ViewHostMsg_CreateWidget message.
+ virtual void AddRoute(int32 routing_id, IPC::Channel::Listener* listener);
+
+ // Called by the Widget. The routing id must match the routing id of AddRoute.
+ virtual void RemoveRoute(int32 routing_id);
+
+ // Called by the Widget. Used to send messages to the browser.
+ // We short-circuit the mechanim and handle the messages right here on this
+ // class.
+ virtual bool Send(IPC::Message* msg);
+
+ // Our mock thread doesn't do filtering.
+ virtual void AddFilter(IPC::ChannelProxy::MessageFilter* filter) {
+ }
+ virtual void RemoveFilter(IPC::ChannelProxy::MessageFilter* filter) {
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // The following functions are called by the test itself.
+
+ void set_routing_id(int32 id) {
+ routing_id_ = id;
+ }
+
+ int32 opener_id() const {
+ return opener_id_;
+ }
+
+ bool has_widget() const {
+ return widget_ ? true : false;
+ }
+
+ // Returns the number of messages in the queue.
+ size_t message_count() const { return messages_.size(); }
+
+ // Clears the message queue of saved messages.
+ void ClearMessages();
+
+ // Returns the message at the given index in the queue. The index may be out
+ // of range, in which case the return value is NULL. The returned pointer will
+ // only be valid until another message is received or the list is cleared.
+ const IPC::Message* GetMessageAt(size_t index) const;
+
+ // Returns the first message with the given ID in the queue. If there is no
+ // message with the given ID, returns NULL. The returned pointer will only be
+ // valid until another message is received or the list is cleared.
+ const IPC::Message* GetFirstMessageMatching(uint16 id) const;
+
+ // Returns the message with the given ID in the queue. If there is no such
+ // message or there is more than one of that message, this will return NULL
+ // (with the expectation that you'll do an ASSERT_TRUE() on the result).
+ // The returned pointer will only be valid until another message is received
+ // or the list is cleared.
+ const IPC::Message* GetUniqueMessageMatching(uint16 id) const;
+
+ // Simulates the Widget receiving a close message. This should result
+ // on releasing the internal reference counts and destroying the internal
+ // state.
+ void SendCloseMessage();
+
+ private:
+ // This function operates as a regular IPC listener.
+ void OnMessageReceived(const IPC::Message& msg);
+
+ // The Widget expects to be returned valid route_id.
+ void OnMsgCreateWidget(int opener_id,
+ bool focus_on_show,
+ int* route_id);
+
+ // Routing id what will be assigned to the Widget.
+ int32 routing_id_;
+
+ // Opener id reported by the Widget.
+ int32 opener_id_;
+
+ // We only keep track of one Widget, we learn its pointer when it
+ // adds a new route.
+ IPC::Channel::Listener* widget_;
+
+ // The last known good deserializer for sync messages.
+ IPC::MessageReplyDeserializer* reply_deserializer_;
+
+ std::vector<MessagePair> messages_;
+};
+
+#endif // CHROME_RENDERER_MOCK_RENDER_THREAD_H_
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index 9a4e4ed..c5be113 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -71,8 +71,8 @@ void RenderThread::RemoveFilter(IPC::ChannelProxy::MessageFilter* filter) {
}
void RenderThread::Resolve(const char* name, size_t length) {
- return render_dns_master_->Resolve(name, length);
- }
+ return render_dns_master_->Resolve(name, length);
+}
void RenderThread::AddRoute(int32 routing_id,
IPC::Channel::Listener* listener) {
@@ -199,7 +199,7 @@ void RenderThread::OnCreateNewView(HWND parent_hwnd,
// 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,
+ this, parent_hwnd, modal_dialog_event, MSG_ROUTING_NONE, webkit_prefs,
new SharedRenderViewCounter(0), view_id);
}
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index 68c51cf..ae94815 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -20,9 +20,9 @@ class RenderDnsMaster;
class NotificationService;
class GreasemonkeySlave;
-// The RenderThreadBase is the minimal interface that a RenderWidget expects
-// from a render thread. The interface basically abstracts a way to send and
-// receive messages. It is currently only used for testing.
+// The RenderThreadBase is the minimal interface that a RenderView/Widget
+// expects from a render thread. The interface basically abstracts a way to send
+// and receive messages.
class RenderThreadBase : public IPC::Message::Sender {
public:
virtual ~RenderThreadBase() {}
@@ -34,6 +34,9 @@ class RenderThreadBase : public IPC::Message::Sender {
// These methods normally get delegated to a MessageRouter.
virtual void AddRoute(int32 routing_id, IPC::Channel::Listener* listener) = 0;
virtual void RemoveRoute(int32 routing_id) = 0;
+
+ virtual void AddFilter(IPC::ChannelProxy::MessageFilter* filter) = 0;
+ virtual void RemoveFilter(IPC::ChannelProxy::MessageFilter* filter) = 0;
};
// The RenderThread class represents a background thread where RenderView
@@ -59,8 +62,9 @@ class RenderThread : public IPC::Channel::Listener,
// IPC::Message::Sender implementation:
virtual bool Send(IPC::Message* msg);
- void AddFilter(IPC::ChannelProxy::MessageFilter* filter);
- void RemoveFilter(IPC::ChannelProxy::MessageFilter* filter);
+ // Overridded from RenderThreadBase.
+ virtual void AddFilter(IPC::ChannelProxy::MessageFilter* filter);
+ virtual void RemoveFilter(IPC::ChannelProxy::MessageFilter* filter);
// Gets the VisitedLinkSlave instance for this thread
VisitedLinkSlave* visited_link_slave() const { return visited_link_slave_; }
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 161c482..dfd6b8f 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -89,9 +89,9 @@ static const int kDelayForCaptureMs = 500;
// delay.
static const int kDelayForForcedCaptureMs = 6000;
-// How often we will sync the navigation state when the user is changing form
-// elements or scroll position.
-const TimeDelta kDelayForNavigationSync = TimeDelta::FromSeconds(5);
+// The default value for RenderView.delay_seconds_for_form_state_sync_, see
+// that variable for more.
+const int kDefaultDelaySecondsForFormStateSync = 5;
// The next available page ID to use. This ensures that the page IDs are
// globally unique in the renderer.
@@ -142,29 +142,30 @@ class RenderViewExtraRequestData : public WebRequest::ExtraData {
///////////////////////////////////////////////////////////////////////////////
-RenderView::RenderView()
- : RenderWidget(g_render_thread, true),
- is_loading_(false),
- page_id_(-1),
- last_page_id_sent_to_browser_(-1),
- last_indexed_page_id_(-1),
- method_factory_(this),
- opened_by_user_gesture_(true),
- enable_dom_automation_(false),
- enable_dom_ui_bindings_(false),
- target_url_status_(TARGET_NONE),
- printed_document_width_(0),
- first_default_plugin_(NULL),
- navigation_gesture_(NavigationGestureUnknown),
- history_back_list_count_(0),
- history_forward_list_count_(0),
- disable_popup_blocking_(false),
- has_unload_listener_(false),
- decrement_shared_popup_at_destruction_(false),
- greasemonkey_enabled_(false),
- waiting_for_create_window_ack_(false),
- form_field_autofill_request_id_(0),
- popup_notification_visible_(false) {
+RenderView::RenderView(RenderThreadBase* render_thread)
+ : RenderWidget(render_thread, true),
+ is_loading_(false),
+ page_id_(-1),
+ last_page_id_sent_to_browser_(-1),
+ last_indexed_page_id_(-1),
+ method_factory_(this),
+ opened_by_user_gesture_(true),
+ enable_dom_automation_(false),
+ enable_dom_ui_bindings_(false),
+ target_url_status_(TARGET_NONE),
+ printed_document_width_(0),
+ first_default_plugin_(NULL),
+ navigation_gesture_(NavigationGestureUnknown),
+ history_back_list_count_(0),
+ history_forward_list_count_(0),
+ disable_popup_blocking_(false),
+ has_unload_listener_(false),
+ decrement_shared_popup_at_destruction_(false),
+ greasemonkey_enabled_(false),
+ waiting_for_create_window_ack_(false),
+ form_field_autofill_request_id_(0),
+ popup_notification_visible_(false),
+ delay_seconds_for_form_state_sync_(kDefaultDelaySecondsForFormStateSync) {
resource_dispatcher_ = new ResourceDispatcher(this);
#ifdef CHROME_PERSONALIZATION
personalization_ = Personalization::CreateRendererPersonalization();
@@ -183,7 +184,7 @@ RenderView::~RenderView() {
it = plugin_delegates_.erase(it);
}
- g_render_thread->RemoveFilter(debug_message_handler_);
+ render_thread_->RemoveFilter(debug_message_handler_);
#ifdef CHROME_PERSONALIZATION
Personalization::CleanupRendererPersonalization(personalization_);
@@ -193,6 +194,7 @@ RenderView::~RenderView() {
/*static*/
RenderView* RenderView::Create(
+ RenderThreadBase* render_thread,
HWND parent_hwnd,
HANDLE modal_dialog_event,
int32 opener_id,
@@ -200,7 +202,7 @@ RenderView* RenderView::Create(
SharedRenderViewCounter* counter,
int32 routing_id) {
DCHECK(routing_id != MSG_ROUTING_NONE);
- scoped_refptr<RenderView> view = new RenderView();
+ scoped_refptr<RenderView> view = new RenderView(render_thread);
view->Init(parent_hwnd,
modal_dialog_event,
opener_id,
@@ -271,7 +273,7 @@ void RenderView::Init(HWND parent_hwnd,
webview()->SetBackForwardListSize(1);
routing_id_ = routing_id;
- g_render_thread->AddRoute(routing_id_, this);
+ render_thread_->AddRoute(routing_id_, this);
// Take a reference on behalf of the RenderThread. This will be balanced
// when we receive ViewMsg_Close.
AddRef();
@@ -295,7 +297,7 @@ void RenderView::Init(HWND parent_hwnd,
command_line.HasSwitch(switches::kEnableGreasemonkey);
debug_message_handler_ = new DebugMessageHandler(this);
- g_render_thread->AddFilter(debug_message_handler_);
+ render_thread_->AddFilter(debug_message_handler_);
}
void RenderView::OnMessageReceived(const IPC::Message& message) {
@@ -1458,9 +1460,10 @@ void RenderView::DidFinishDocumentLoadForFrame(WebView* webview,
// do inject into any other document.
if (greasemonkey_enabled_) {
const GURL &gurl = frame->GetURL();
- if (gurl.SchemeIs("file") ||
- gurl.SchemeIs("http") ||
- gurl.SchemeIs("https")) {
+ if (g_render_thread && // Will be NULL when testing.
+ (gurl.SchemeIs("file") ||
+ gurl.SchemeIs("http") ||
+ gurl.SchemeIs("https"))) {
g_render_thread->greasemonkey_slave()->InjectScripts(frame);
}
}
@@ -1785,7 +1788,7 @@ WebView* RenderView::CreateWebView(WebView* webview, bool user_gesture) {
int32 routing_id = MSG_ROUTING_NONE;
HANDLE modal_dialog_event = NULL;
- bool result = g_render_thread->Send(
+ bool result = render_thread_->Send(
new ViewHostMsg_CreateWindow(routing_id_, user_gesture, &routing_id,
&modal_dialog_event));
if (routing_id == MSG_ROUTING_NONE) {
@@ -1795,7 +1798,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_,
+ RenderView* view = RenderView::Create(render_thread_,
+ NULL, modal_dialog_event, routing_id_,
prefs, shared_popup_counter_,
routing_id);
view->set_opened_by_user_gesture(user_gesture);
@@ -1811,7 +1815,7 @@ WebView* RenderView::CreateWebView(WebView* webview, bool user_gesture) {
WebWidget* RenderView::CreatePopupWidget(WebView* webview,
bool focus_on_show) {
RenderWidget* widget = RenderWidget::Create(routing_id_,
- g_render_thread,
+ render_thread_,
focus_on_show);
return widget->webwidget();
}
@@ -1839,7 +1843,7 @@ WebPluginDelegate* RenderView::CreatePluginDelegate(
bool is_gears = false;
if (ShouldLoadPluginInProcess(mime_type, &is_gears)) {
std::wstring path;
- g_render_thread->Send(
+ render_thread_->Send(
new ViewHostMsg_GetPluginPath(url, mime_type, clsid, &path,
actual_mime_type));
if (path.empty())
@@ -2369,9 +2373,11 @@ int RenderView::GetHistoryForwardListCount() {
}
void RenderView::OnNavStateChanged(WebView* webview) {
- if (!nav_state_sync_timer_.IsRunning())
- nav_state_sync_timer_.Start(kDelayForNavigationSync, this,
- &RenderView::SyncNavigationState);
+ if (!nav_state_sync_timer_.IsRunning()) {
+ nav_state_sync_timer_.Start(
+ TimeDelta::FromSeconds(delay_seconds_for_form_state_sync_), this,
+ &RenderView::SyncNavigationState);
+ }
}
void RenderView::SetTooltipText(WebView* webview,
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index cea0ce1..24f8f9b 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -25,6 +25,7 @@
#include "chrome/renderer/external_js_object.h"
#include "chrome/renderer/render_process.h"
#include "chrome/renderer/render_widget.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
#include "webkit/glue/console_message_level.h"
#include "webkit/glue/dom_serializer_delegate.h"
#include "webkit/glue/glue_accessibility.h"
@@ -38,6 +39,7 @@
class DebugMessageHandler;
class GURL;
+class RenderThread;
class SkBitmap;
struct ThumbnailScore;
class WebError;
@@ -68,7 +70,8 @@ typedef base::RefCountedData<int> SharedRenderViewCounter;
// RenderView is an object that manages a WebView object, and provides a
// communication interface with an embedding application process
//
-class RenderView : public RenderWidget, public WebViewDelegate,
+class RenderView : public RenderWidget,
+ public WebViewDelegate,
public webkit_glue::DomSerializerDelegate {
public:
// Creates a new RenderView. The parent_hwnd specifies a HWND to use as the
@@ -80,6 +83,7 @@ class RenderView : public RenderWidget, public WebViewDelegate,
// 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(
+ RenderThreadBase* render_thread,
HWND parent_hwnd,
HANDLE modal_dialog_event,
int32 opener_id,
@@ -305,12 +309,18 @@ class RenderView : public RenderWidget, public WebViewDelegate,
// This is called from within the renderer, not via an IPC message.
void OnDebugDetach();
+ int delay_seconds_for_form_state_sync() const {
+ return delay_seconds_for_form_state_sync_;
+ }
+ void set_delay_seconds_for_form_state_sync(int delay_in_seconds) {
+ delay_seconds_for_form_state_sync_ = delay_in_seconds;
+ }
+
private:
- RenderView();
+ FRIEND_TEST(RenderViewTest, OnLoadAlternateHTMLText);
+ FRIEND_TEST(RenderViewTest, OnNavStateChanged);
- // When we are created from window.open from an already existing view, this
- // constructor stores that view ID.
- explicit RenderView(int32 opener_id);
+ explicit RenderView(RenderThreadBase* render_thread);
// Initializes this view with the given parent and ID. The |routing_id| can be
// set to 'MSG_ROUTING_NONE' if the true ID is not yet known. In this case,
@@ -711,6 +721,12 @@ class RenderView : public RenderWidget, public WebViewDelegate,
// from the Browser process telling us otherwise.
bool popup_notification_visible_;
+ // Time in seconds of the delay between syncing page state such as form
+ // elements and scroll position. This timeout allows us to avoid spamming the
+ // browser process with every little thing that changes. This normally doesn't
+ // change but is overridden by tests.
+ int delay_seconds_for_form_state_sync_;
+
DISALLOW_COPY_AND_ASSIGN(RenderView);
};
diff --git a/chrome/renderer/render_view_unittest.cc b/chrome/renderer/render_view_unittest.cc
new file mode 100644
index 0000000..71ab2e2
--- /dev/null
+++ b/chrome/renderer/render_view_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_ptr.h"
+#include "chrome/renderer/mock_render_process.h"
+#include "chrome/renderer/mock_render_thread.h"
+#include "chrome/renderer/render_view.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/webframe.h"
+#include "webkit/glue/weburlrequest.h"
+#include "webkit/glue/webview.h"
+
+namespace {
+
+const int32 kRouteId = 5;
+const int32 kOpenerId = 7;
+
+class RenderViewTest : public testing::Test {
+ public:
+ RenderViewTest() {}
+ ~RenderViewTest() {}
+
+ protected:
+ // Spins the message loop to process all messages that are currently pending.
+ void ProcessPendingMessages() {
+ msg_loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ msg_loop_.Run();
+ }
+
+ // Returns a pointer to the main frame.
+ WebFrame* GetMainFrame() {
+ return view_->webview()->GetMainFrame();
+ }
+
+ // Executes the given JavaScript in the context of the main frame. The input
+ // is a NULL-terminated UTF-8 string.
+ void ExecuteJavaScript(const char* js) {
+ GetMainFrame()->ExecuteJavaScript(js, GURL());
+ }
+
+ // Loads the given HTML into the main frame as a data: URL.
+ void LoadHTML(const char* html) {
+ std::string url_str = "data:text/html;charset=utf-8,";
+ url_str.append(html);
+ GURL url(url_str);
+
+ scoped_ptr<WebRequest> request(WebRequest::Create(url));
+ GetMainFrame()->LoadRequest(request.get());
+
+ // The load actually happens asynchronously, so we pump messages to process
+ // the pending continuation.
+ ProcessPendingMessages();
+ }
+
+ // testing::Test
+ virtual void SetUp() {
+ MockProcess::GlobalInit();
+
+ render_thread_.set_routing_id(kRouteId);
+
+ // This needs to pass the mock render thread to the view.
+ view_ = RenderView::Create(&render_thread_, NULL, NULL, kOpenerId,
+ WebPreferences(),
+ new SharedRenderViewCounter(0), kRouteId);
+ }
+ virtual void TearDown() {
+ render_thread_.SendCloseMessage();
+
+ // Run the loop so the release task from the renderwidget executes.
+ ProcessPendingMessages();
+
+ view_ = NULL;
+
+ // There is a delayed task that the child process posts to terminate the
+ // message loop so we need to spin the message loop to delete the task.
+ MockProcess::GlobalCleanup();
+ msg_loop_.Run();
+ }
+
+ MessageLoop msg_loop_;
+ MockRenderThread render_thread_;
+
+ scoped_refptr<RenderView> view_;
+};
+
+} // namespace
+
+TEST_F(RenderViewTest, OnLoadAlternateHTMLText) {
+ // Test a new navigation.
+ GURL test_url("http://www.google.com/some_test_url");
+ view_->OnLoadAlternateHTMLText("<html></html>", true, test_url,
+ std::string());
+
+ // We should have gotten two different types of start messages in the
+ // following order.
+ ASSERT_EQ(2, render_thread_.message_count());
+ const IPC::Message* msg = render_thread_.GetMessageAt(0);
+ EXPECT_EQ(ViewHostMsg_DidStartLoading::ID, msg->type());
+
+ msg = render_thread_.GetMessageAt(1);
+ EXPECT_EQ(ViewHostMsg_DidStartProvisionalLoadForFrame::ID, msg->type());
+ ViewHostMsg_DidStartProvisionalLoadForFrame::Param start_params;
+ ViewHostMsg_DidStartProvisionalLoadForFrame::Read(msg, &start_params);
+ EXPECT_EQ(GURL("chrome://chromewebdata/"), start_params.b);
+}
+
+// Test that we get form state change notifications when input fields change.
+TEST_F(RenderViewTest, OnNavStateChanged) {
+ // Don't want any delay for form state sync changes. This will still post a
+ // message so updates will get coalesced, but as soon as we spin the message
+ // loop, it will generate an update.
+ view_->set_delay_seconds_for_form_state_sync(0);
+
+ LoadHTML("<input type=\"text\" id=\"elt_text\"></input>");
+
+ // We should NOT have gotten a form state change notification yet.
+ EXPECT_FALSE(render_thread_.GetFirstMessageMatching(
+ ViewHostMsg_UpdateState::ID));
+ render_thread_.ClearMessages();
+
+ // Change the value of the input. We should have gotten an update state
+ // notification. We need to spin the message loop to catch this update.
+ ExecuteJavaScript("document.getElementById('elt_text').value = 'foo';");
+ ProcessPendingMessages();
+ EXPECT_TRUE(render_thread_.GetUniqueMessageMatching(
+ ViewHostMsg_UpdateState::ID));
+}
diff --git a/chrome/renderer/render_widget_unittest.cc b/chrome/renderer/render_widget_unittest.cc
index f280aa7..6c2b24f 100644
--- a/chrome/renderer/render_widget_unittest.cc
+++ b/chrome/renderer/render_widget_unittest.cc
@@ -5,163 +5,58 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "base/ref_counted.h"
-#include "chrome/common/child_process.h"
+#include "chrome/renderer/mock_render_process.h"
+#include "chrome/renderer/mock_render_thread.h"
#include "chrome/renderer/render_widget.h"
#include "chrome/renderer/render_thread.h"
-// This class is a trivial mock of the child process singleton. It is necessary
-// so we don't trip DCHECKs in ChildProcess::ReleaseProcess() when destroying
-// a render widget instance.
-class MockProcess : public ChildProcess {
- public:
- explicit MockProcess(const std::wstring& channel_name) {}
- static void GlobalInit() {
- ChildProcessFactory<MockProcess> factory;
- ChildProcess::GlobalInit(L"dummy", &factory);
- }
-};
-
-// This class is very simple mock of RenderThread. It simulates an IPC channel
-// which supports only two messages:
-// ViewHostMsg_CreateWidget : sync message sent by the Widget.
-// ViewMsg_Close : async, send to the Widget.
-class MockRenderThread : public RenderThreadBase {
- public:
- MockRenderThread()
- : routing_id_(0), opener_id_(0), widget_(NULL),
- reply_deserializer_(NULL) {
- }
- virtual ~MockRenderThread() {
- }
+namespace {
- // Called by the Widget. Not used in the test.
- virtual bool InSend() const {
- return false;
- }
-
- // Called by the Widget. The routing_id must match the routing id assigned
- // to the Widget in reply to ViewHostMsg_CreateWidget message.
- virtual void AddRoute(int32 routing_id, IPC::Channel::Listener* listener) {
- EXPECT_EQ(routing_id_, routing_id);
- widget_ = listener;
- }
-
- // Called by the Widget. The routing id must match the routing id of AddRoute.
- virtual void RemoveRoute(int32 routing_id) {
- EXPECT_EQ(routing_id_, routing_id);
- widget_ = NULL;
- }
-
- // Called by the Widget. Used to send messages to the browser.
- // We short-circuit the mechanim and handle the messages right here on this
- // class.
- virtual bool Send(IPC::Message* msg) {
- // We need to simulate a synchronous channel, thus we are going to receive
- // through this function messages, messages with reply and reply messages.
- // We can only handle one synchronous message at a time.
- if (msg->is_reply()) {
- if (reply_deserializer_)
- reply_deserializer_->SerializeOutputParameters(*msg);
- delete reply_deserializer_;
- reply_deserializer_ = NULL;
- } else {
- if (msg->is_sync()) {
- reply_deserializer_ =
- static_cast<IPC::SyncMessage*>(msg)->GetReplyDeserializer();
- }
- OnMessageReceived(*msg);
- }
- delete msg;
- return true;
- }
+const int32 kRouteId = 5;
+const int32 kOpenerId = 7;
- //////////////////////////////////////////////////////////////////////////
- // The following functions are called by the test itself.
-
- void set_routing_id(int32 id) {
- routing_id_ = id;
- }
-
- int32 opener_id() const {
- return opener_id_;
- }
+class RenderWidgetTest : public testing::Test {
+ public:
- bool has_widget() const {
- return widget_ ? true : false;
- }
+ protected:
+ MessageLoop msg_loop_;
+ MockRenderThread render_thread_;
- // Simulates the Widget receiving a close message. This should result
- // on releasing the internal reference counts and destroying the internal
- // state.
- void SendCloseMessage() {
- ViewMsg_Close msg(routing_id_);
- widget_->OnMessageReceived(msg);
- }
+ // The widget, each test should verify this is non-NULL before continuing.
+ scoped_refptr<RenderWidget> widget_;
private:
+ // testing::Test
+ virtual void SetUp() {
+ MockProcess::GlobalInit();
- // This function operates as a regular IPC listener.
- void OnMessageReceived(const IPC::Message& msg) {
- bool handled = true;
- bool msg_is_ok = true;
- IPC_BEGIN_MESSAGE_MAP_EX(MockRenderThread, msg, msg_is_ok)
- IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget);
- IPC_MESSAGE_UNHANDLED(handled = false)
- IPC_END_MESSAGE_MAP_EX()
-
- // This test must handle all messages that RenderWidget generates.
- EXPECT_TRUE(handled);
+ render_thread_.set_routing_id(kRouteId);
+ widget_ = RenderWidget::Create(kOpenerId, &render_thread_, true);
+ ASSERT_TRUE(widget_);
}
+ virtual void TearDown() {
+ widget_ = NULL;
- // The Widget expects to be returned valid route_id.
- void OnMsgCreateWidget(int opener_id,
- bool focus_on_show,
- int* route_id) {
- opener_id_ = opener_id;
- *route_id = routing_id_;
+ // There is a delayed task that the child process posts to terminate the
+ // message loop so we need to spin the message loop to delete the task.
+ MockProcess::GlobalCleanup();
+ msg_loop_.Run();
}
-
- // Routing id what will be assigned to the Widget.
- int32 routing_id_;
- // Opener id reported by the Widget.
- int32 opener_id_;
- // We only keep track of one Widget, we learn its pointer when it
- // adds a new route.
- IPC::Channel::Listener* widget_;
- // The last known good deserializer for sync messages.
- IPC::MessageReplyDeserializer* reply_deserializer_;
};
-TEST(RenderWidgetTest, CreateAndCloseWidget) {
- MessageLoop msg_loop;
- MockRenderThread render_thread;
- MockProcess::GlobalInit();
-
- const int32 kRouteId = 5;
- const int32 kOpenerId = 7;
- render_thread.set_routing_id(kRouteId);
-
- {
- scoped_refptr<RenderWidget> rw =
- RenderWidget::Create(kOpenerId, &render_thread, true);
- ASSERT_TRUE(rw != NULL);
+} // namespace
+TEST_F(RenderWidgetTest, CreateAndCloseWidget) {
// After the RenderWidget it must have sent a message to the render thread
// that sets the opener id.
- EXPECT_EQ(kOpenerId, render_thread.opener_id());
- ASSERT_TRUE(render_thread.has_widget());
+ EXPECT_EQ(kOpenerId, render_thread_.opener_id());
+ ASSERT_TRUE(render_thread_.has_widget());
// Now simulate a close of the Widget.
- render_thread.SendCloseMessage();
- EXPECT_FALSE(render_thread.has_widget());
-
- // Run the loop so the release task from the renderwidget executes.
- msg_loop.PostTask(FROM_HERE, new MessageLoop::QuitTask());
- msg_loop.Run();
-}
+ render_thread_.SendCloseMessage();
+ EXPECT_FALSE(render_thread_.has_widget());
- // There is a delayed task that the child process posts to terminate the
- // message loop so we need to spin the message loop to delete the task.
- MockProcess::GlobalCleanup();
- msg_loop.Run();
+ // Run the loop so the release task from the renderwidget executes.
+ msg_loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ msg_loop_.Run();
}
diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj
index f3fa1c0..d8baadc 100644
--- a/chrome/test/unit/unittests.vcproj
+++ b/chrome/test/unit/unittests.vcproj
@@ -17,7 +17,7 @@
<Configuration
Name="Debug|Win32"
ConfigurationType="1"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;..\..\tools\build\win\precompiled_wtl.vsprops;..\..\tools\build\win\unit_test.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;..\..\..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops;..\..\third_party\hunspell\using_hunspell.vsprops"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;..\..\tools\build\win\precompiled_wtl.vsprops;..\..\tools\build\win\unit_test.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops;..\..\third_party\hunspell\using_hunspell.vsprops;..\..\..\third_party\npapi\using_npapi.vsprops"
>
<Tool
Name="VCPreBuildEventTool"
@@ -79,7 +79,7 @@
<Configuration
Name="Release|Win32"
ConfigurationType="1"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;..\..\tools\build\win\unit_test.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;..\..\..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops;..\..\third_party\hunspell\using_hunspell.vsprops"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;..\..\tools\build\win\unit_test.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops;..\..\third_party\hunspell\using_hunspell.vsprops;..\..\..\third_party\npapi\using_npapi.vsprops"
>
<Tool
Name="VCPreBuildEventTool"
@@ -292,24 +292,6 @@
<File
RelativePath="..\..\browser\renderer_host\resource_dispatcher_host_unittest.cc"
>
- <FileConfiguration
- Name="Debug|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- ObjectFile="$(IntDir)\$(InputName)1.obj"
- XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
- />
- </FileConfiguration>
- <FileConfiguration
- Name="Release|Win32"
- >
- <Tool
- Name="VCCLCompilerTool"
- ObjectFile="$(IntDir)\$(InputName)1.obj"
- XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
- />
- </FileConfiguration>
</File>
<File
RelativePath="..\..\common\resource_dispatcher_unittest.cc"
@@ -1049,9 +1031,25 @@
</File>
</Filter>
<Filter
- Name="TestRenderWidget"
+ Name="TestRenderer"
>
<File
+ RelativePath="..\..\renderer\mock_render_process.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\renderer\mock_render_thread.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\renderer\mock_render_thread.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\renderer\render_view_unittest.cc"
+ >
+ </File>
+ <File
RelativePath="..\..\renderer\render_widget_unittest.cc"
>
</File>
diff --git a/webkit/glue/chrome_client_impl.cc b/webkit/glue/chrome_client_impl.cc
index 860d9b1..b7cc3ae 100644
--- a/webkit/glue/chrome_client_impl.cc
+++ b/webkit/glue/chrome_client_impl.cc
@@ -488,3 +488,9 @@ void ChromeClientImpl::disableSuddenTermination() {
if (d)
d->DisableSuddenTermination();
}
+
+void ChromeClientImpl::formStateDidChange(const WebCore::Node*) {
+ WebViewDelegate* d = webview_->delegate();
+ if (d)
+ d->OnNavStateChanged(webview_);
+}
diff --git a/webkit/glue/chrome_client_impl.h b/webkit/glue/chrome_client_impl.h
index 7d69ee1..27fd2c7 100644
--- a/webkit/glue/chrome_client_impl.h
+++ b/webkit/glue/chrome_client_impl.h
@@ -123,8 +123,7 @@ public:
virtual void enableSuddenTermination();
virtual void disableSuddenTermination();
- // TODO(brettw): Implement me!
- virtual void formStateDidChange(const WebCore::Node*) { }
+ virtual void formStateDidChange(const WebCore::Node*);
private:
WebViewImpl* webview_; // weak pointer
diff --git a/webkit/glue/chromium_bridge_impl.cc b/webkit/glue/chromium_bridge_impl.cc
index b178d55..c75ab15 100644
--- a/webkit/glue/chromium_bridge_impl.cc
+++ b/webkit/glue/chromium_bridge_impl.cc
@@ -244,12 +244,6 @@ bool ChromiumBridge::ensureFontLoaded(HFONT font) {
}
#endif
-// Forms ----------------------------------------------------------------------
-
-void ChromiumBridge::notifyFormStateChanged(const Document* doc) {
- webkit_glue::NotifyFormStateChanged(doc);
-}
-
// JavaScript -----------------------------------------------------------------
void ChromiumBridge::notifyJSOutOfMemory(Frame* frame) {
diff --git a/webkit/glue/webkit_glue.h b/webkit/glue/webkit_glue.h
index 78ad7ee..f3335b5 100644
--- a/webkit/glue/webkit_glue.h
+++ b/webkit/glue/webkit_glue.h
@@ -245,11 +245,6 @@ ScreenInfo GetScreenInfo(gfx::NativeView window);
// Functions implemented by webkit_glue for WebKit ----------------------------
-// Notifies the embedder that a form element value has changed. The document
-// pointer, which MAY BE NULL, indicates the document that owns the form
-// element that changed, if any.
-void NotifyFormStateChanged(const WebCore::Document* document);
-
// Returns a bool indicating if the Null plugin should be enabled or not.
bool IsDefaultPluginEnabled();