diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-14 06:10:31 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-14 06:10:31 +0000 |
commit | ad23b99078f3db28bf5e88821cadc2d5effac52b (patch) | |
tree | 0df04a0908e71c383853e71f7586c381cbc4b9c8 /chrome | |
parent | 0c9366d5f357759e3bd968a267b6eb1100e5835c (diff) | |
download | chromium_src-ad23b99078f3db28bf5e88821cadc2d5effac52b.zip chromium_src-ad23b99078f3db28bf5e88821cadc2d5effac52b.tar.gz chromium_src-ad23b99078f3db28bf5e88821cadc2d5effac52b.tar.bz2 |
Add JsonSchema-based validation for the tab APIs.
Arv: can you take json_schema.js and json_schema_test.js.
Matt: you take the rest.
Review URL: http://codereview.chromium.org/66006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13649 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
20 files changed, 1358 insertions, 228 deletions
diff --git a/chrome/browser/extensions/extension_messages_unittest.cc b/chrome/browser/extensions/extension_messages_unittest.cc new file mode 100644 index 0000000..41bcabc --- /dev/null +++ b/chrome/browser/extensions/extension_messages_unittest.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2009 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/common/render_messages.h" +#include "chrome/renderer/extensions/renderer_extension_bindings.h" +#include "chrome/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Tests that the bindings for opening a channel to an extension and sending +// and receiving messages through that channel all works. +TEST_F(RenderViewTest, ExtensionMessagesOpenChannel) { + render_thread_.sink().ClearMessages(); + LoadHTML("<body></body>"); + ExecuteJavaScript( + "var e = new chromium.Extension('foobar');" + "var port = e.connect();" + "port.onmessage.addListener(doOnMessage);" + "port.postMessage('content ready');" + "function doOnMessage(msg, port) {" + " alert('content got: ' + msg);" + "}"); + + // Verify that we opened a channel and sent a message through it. + const IPC::Message* open_channel_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_OpenChannelToExtension::ID); + EXPECT_TRUE(open_channel_msg); + + const IPC::Message* post_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionPostMessage::ID); + EXPECT_TRUE(post_msg); + ViewHostMsg_ExtensionPostMessage::Param post_params; + ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params); + EXPECT_EQ("content ready", post_params.b); + + // Now simulate getting a message back from the other side. + render_thread_.sink().ClearMessages(); + const int kPortId = 0; + RendererExtensionBindings::HandleMessage("42", kPortId); + + // Verify that we got it. + const IPC::Message* alert_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_RunJavaScriptMessage::ID); + EXPECT_TRUE(alert_msg); + void* iter = IPC::SyncMessage::GetDataIterator(alert_msg); + ViewHostMsg_RunJavaScriptMessage::SendParam alert_param; + IPC::ReadParam(alert_msg, &iter, &alert_param); + EXPECT_EQ(L"content got: 42", alert_param.a); +} + +// Tests that the bindings for handling a new channel connection and sending +// and receiving messages through that channel all works. +TEST_F(RenderViewTest, ExtensionMessagesOnConnect) { + LoadHTML("<body></body>"); + ExecuteJavaScript( + "chromium.onconnect.addListener(function (port) {" + " port.onmessage.addListener(doOnMessage);" + " port.postMessage('onconnect');" + "});" + "function doOnMessage(msg, port) {" + " alert('got: ' + msg);" + "}"); + + render_thread_.sink().ClearMessages(); + + // Simulate a new connection being opened. + const int kPortId = 0; + RendererExtensionBindings::HandleConnect(kPortId); + + // Verify that we handled the new connection by posting a message. + const IPC::Message* post_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionPostMessage::ID); + EXPECT_TRUE(post_msg); + ViewHostMsg_ExtensionPostMessage::Param post_params; + ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params); + EXPECT_EQ("onconnect", post_params.b); + + // Now simulate getting a message back from the channel opener. + render_thread_.sink().ClearMessages(); + RendererExtensionBindings::HandleMessage("42", kPortId); + + // Verify that we got it. + const IPC::Message* alert_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_RunJavaScriptMessage::ID); + EXPECT_TRUE(alert_msg); + void* iter = IPC::SyncMessage::GetDataIterator(alert_msg); + ViewHostMsg_RunJavaScriptMessage::SendParam alert_param; + IPC::ReadParam(alert_msg, &iter, &alert_param); + EXPECT_EQ(L"got: 42", alert_param.a); +} diff --git a/chrome/browser/extensions/extension_view_unittest.cc b/chrome/browser/extensions/extension_view_unittest.cc index a1dd565..a08b549 100755 --- a/chrome/browser/extensions/extension_view_unittest.cc +++ b/chrome/browser/extensions/extension_view_unittest.cc @@ -13,6 +13,7 @@ #include "chrome/browser/tab_contents/site_instance.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" #include "chrome/test/in_process_browser_test.h" #include "chrome/test/ui_test_utils.h" diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 89f0d93..335e446 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -2227,7 +2227,9 @@ 'common/unzip_unittest.cc', 'common/win_util_unittest.cc', 'common/worker_thread_ticker_unittest.cc', + 'renderer/extensions/extension_api_client_unittest.cc', 'renderer/extensions/greasemonkey_api_unittest.cc', + 'renderer/extensions/json_schema_unittest.cc', 'renderer/net/render_dns_master_unittest.cc', 'renderer/net/render_dns_queue_unittest.cc', 'renderer/render_process_unittest.cc', @@ -2240,6 +2242,8 @@ 'test/browser_with_test_window_test.h', 'test/in_process_browser_test.cc', 'test/in_process_browser_test.h', + 'test/render_view_test.cc', + 'test/render_view_test.h', 'test/test_notification_tracker.cc', 'test/test_notification_tracker.h', 'test/test_tab_contents.cc', @@ -2311,6 +2315,7 @@ 'browser/bookmarks/bookmark_table_model_unittest.cc', 'browser/browser_commands_unittest.cc', 'browser/extensions/extension_content_script_inject_unittest.cc', + 'browser/extensions/extension_messages_unittest.cc', 'browser/extensions/test_extension_loader.cc', 'browser/extensions/user_script_master_unittest.cc', 'browser/importer/firefox_importer_unittest.cc', diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc new file mode 100755 index 0000000..1c3530f --- /dev/null +++ b/chrome/renderer/extensions/extension_api_client_unittest.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2009 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/common/render_messages.h" +#include "chrome/renderer/extensions/extension_process_bindings.h" +#include "chrome/renderer/extensions/renderer_extension_bindings.h" +#include "chrome/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +class ExtensionAPIClientTest : public RenderViewTest { + protected: + virtual void SetUp() { + RenderViewTest::SetUp(); + + render_thread_.sink().ClearMessages(); + LoadHTML("<body></body>"); + } + + std::string GetConsoleMessage() { + const IPC::Message* message = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_AddMessageToConsole::ID); + ViewHostMsg_AddMessageToConsole::Param params; + if (message) { + ViewHostMsg_AddMessageToConsole::Read(message, ¶ms); + render_thread_.sink().ClearMessages(); + return WideToASCII(params.a); + } else { + return ""; + } + } + + void ExpectJsFail(const std::string& js, const std::string& message) { + ExecuteJavaScript(js.c_str()); + EXPECT_EQ(message, GetConsoleMessage()); + } +}; + +// Tests that callback dispatching works correctly and that JSON is properly +// deserialized before handing off to the extension code. We use the createTab +// API here, but we could use any of them since they all dispatch callbacks the +// same way. +TEST_F(ExtensionAPIClientTest, CallbackDispatching) { + ExecuteJavaScript( + "function assert(truth, message) {" + " if (!truth) {" + " throw new Error(message);" + " }" + "}" + "function callback(result) {" + " assert(typeof result == 'object', 'result not object');" + " assert(goog.json.serialize(result) == '{\"foo\":\"bar\"}', " + " 'incorrect result');" + " console.log('pass')" + "}" + "chromium.tabs.createTab({}, callback);" + ); + + // Ok, we should have gotten a message to create a tab, grab the callback ID. + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + int callback_id = params.c; + ASSERT_TRUE(callback_id >= 0); + + // Now send the callback a response + ExtensionProcessBindings::ExecuteCallbackInFrame( + GetMainFrame(), callback_id, "{\"foo\":\"bar\"}"); + + // And verify that it worked + ASSERT_EQ("pass", GetConsoleMessage()); +} + +// The remainder of these tests exercise the client side of the various +// extension functions. We test both error and success conditions, but do not +// test errors exhaustively as json schema code is well tested by itself. + +TEST_F(ExtensionAPIClientTest, GetTabsForWindow) { + ExpectJsFail("chromium.tabs.getTabsForWindow(42, function(){});", + "Uncaught Error: Too many arguments."); + + ExecuteJavaScript("chromium.tabs.getTabsForWindow(function(){})"); + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + ASSERT_EQ("GetTabsForWindow", params.a); + ASSERT_EQ("null", params.b); +} + +TEST_F(ExtensionAPIClientTest, GetTab) { + ExpectJsFail("chromium.tabs.getTab(null, function(){});", + "Uncaught Error: Argument 0 is required."); + + ExecuteJavaScript("chromium.tabs.getTab(42)"); + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + ASSERT_EQ("GetTab", params.a); + ASSERT_EQ("42", params.b); +} + +TEST_F(ExtensionAPIClientTest, CreateTab) { + ExpectJsFail("chromium.tabs.createTab({windowId: 'foo'}, function(){});", + "Uncaught Error: Invalid value for argument 0. Property " + "'windowId': Expected 'integer' but got 'string'."); + ExpectJsFail("chromium.tabs.createTab({url: 42}, function(){});", + "Uncaught Error: Invalid value for argument 0. Property " + "'url': Expected 'string' but got 'integer'."); + ExpectJsFail("chromium.tabs.createTab({selected: null}, function(){});", + "Uncaught Error: Invalid value for argument 0. Property " + "'selected': Expected 'boolean' but got 'null'."); + + ExecuteJavaScript("chromium.tabs.createTab({" + " url:'http://www.google.com/'," + " selected:true," + " windowId:4" + "})"); + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + ASSERT_EQ("CreateTab", params.a); + ASSERT_EQ("{\"url\":\"http://www.google.com/\"," + "\"selected\":true," + "\"windowId\":4}", params.b); +} + +TEST_F(ExtensionAPIClientTest, UpdateTab) { + ExpectJsFail("chromium.tabs.updateTab({id: null});", + "Uncaught Error: Invalid value for argument 0. Property " + "'id': Expected 'integer' but got 'null'."); + ExpectJsFail("chromium.tabs.updateTab({id: 42, windowId: 'foo'});", + "Uncaught Error: Invalid value for argument 0. Property " + "'windowId': Expected 'integer' but got 'string'."); + ExpectJsFail("chromium.tabs.updateTab({id: 42, url: 42});", + "Uncaught Error: Invalid value for argument 0. Property " + "'url': Expected 'string' but got 'integer'."); + ExpectJsFail("chromium.tabs.updateTab({id: 42, selected: null});", + "Uncaught Error: Invalid value for argument 0. Property " + "'selected': Expected 'boolean' but got 'null'."); + + ExecuteJavaScript("chromium.tabs.updateTab({" + " id:42," + " url:'http://www.google.com/'," + " selected:true," + " windowId:4" + "})"); + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + ASSERT_EQ("UpdateTab", params.a); + ASSERT_EQ("{\"id\":42," + "\"url\":\"http://www.google.com/\"," + "\"selected\":true," + "\"windowId\":4}", params.b); +} + +TEST_F(ExtensionAPIClientTest, RemoveTab) { + ExpectJsFail("chromium.tabs.removeTab('foobar', function(){});", + "Uncaught Error: Too many arguments."); + + ExecuteJavaScript("chromium.tabs.removeTab(21)"); + const IPC::Message* request_msg = + render_thread_.sink().GetUniqueMessageMatching( + ViewHostMsg_ExtensionRequest::ID); + ASSERT_TRUE(request_msg); + ViewHostMsg_ExtensionRequest::Param params; + ViewHostMsg_ExtensionRequest::Read(request_msg, ¶ms); + ASSERT_EQ("RemoveTab", params.a); + ASSERT_EQ("21", params.b); +} diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index 2d8f3db..4bc4409 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -18,14 +18,17 @@ using WebKit::WebString; namespace { const char kExtensionName[] = "chrome/ExtensionProcessBindings"; -const char* kExtensionDeps[] = { JsonJsV8Extension::kName }; +const char* kDeps[] = { + BaseJsV8Extension::kName, + JsonJsV8Extension::kName, + JsonSchemaJsV8Extension::kName +}; class ExtensionImpl : public v8::Extension { public: ExtensionImpl() : v8::Extension( kExtensionName, GetStringResource<IDR_EXTENSION_PROCESS_BINDINGS_JS>(), - arraysize(kExtensionDeps), kExtensionDeps) { - } + arraysize(kDeps), kDeps) {} static void SetFunctionNames(const std::vector<std::string>& names) { function_names_ = new std::set<std::string>(); diff --git a/chrome/renderer/extensions/extension_process_bindings.h b/chrome/renderer/extensions/extension_process_bindings.h index 8246b4b..01c335b 100644 --- a/chrome/renderer/extensions/extension_process_bindings.h +++ b/chrome/renderer/extensions/extension_process_bindings.h @@ -1,25 +1,26 @@ -// Copyright (c) 2009 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.
-
-// Exposes extension APIs into the extension process.
-
-#ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_
-#define CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_
-
-#include <string>
-#include <vector>
-
-#include "v8/include/v8.h"
-
-class WebFrame;
-
-class ExtensionProcessBindings {
- public:
- static void SetFunctionNames(const std::vector<std::string>& names);
- static v8::Extension* Get();
- static void ExecuteCallbackInFrame(WebFrame* frame, int callback_id,
- const std::string& response);
-};
-
-#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_
+// Copyright (c) 2009 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. + +// Exposes extension APIs into the extension process. + +#ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ +#define CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ + +#include <string> +#include <vector> + +#include "v8/include/v8.h" + +class RenderThreadBase; +class WebFrame; + +class ExtensionProcessBindings { + public: + static void SetFunctionNames(const std::vector<std::string>& names); + static v8::Extension* Get(); + static void ExecuteCallbackInFrame(WebFrame* frame, int callback_id, + const std::string& response); +}; + +#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ diff --git a/chrome/renderer/extensions/json_schema_unittest.cc b/chrome/renderer/extensions/json_schema_unittest.cc new file mode 100755 index 0000000..7df624d --- /dev/null +++ b/chrome/renderer/extensions/json_schema_unittest.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2009 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/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/test/v8_unit_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "grit/renderer_resources.h" + +// TODO(port) +#if defined(OS_WIN) + +static const char kJsonSchema[] = "json_schema.js"; +static const char kJsonSchemaTest[] = "json_schema_test.js"; + +class JsonSchemaTest : public V8UnitTest { + public: + JsonSchemaTest() {} + + virtual void SetUp() { + V8UnitTest::SetUp(); + + // Add the json schema code to the context. + StringPiece js = ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_JSON_SCHEMA_JS); + ExecuteScriptInContext(js, kJsonSchema); + + // Add the test functions to the context. + std::wstring test_js_file_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_js_file_path)); + file_util::AppendToPath(&test_js_file_path, L"extensions"); + file_util::AppendToPath(&test_js_file_path, UTF8ToWide(kJsonSchemaTest)); + std::string test_js; + ASSERT_TRUE(file_util::ReadFileToString(test_js_file_path, &test_js)); + ExecuteScriptInContext(test_js, kJsonSchemaTest); + } +}; + +TEST_F(JsonSchemaTest, TestFormatError) { + TestFunction("testFormatError"); +} + +TEST_F(JsonSchemaTest, TestComplex) { + TestFunction("testComplex"); +} + +TEST_F(JsonSchemaTest, TestEnum) { + TestFunction("testEnum"); +} + +TEST_F(JsonSchemaTest, TestExtends) { + TestFunction("testExtends"); +} + +TEST_F(JsonSchemaTest, TestObject) { + TestFunction("testObject"); +} + +TEST_F(JsonSchemaTest, TestArrayTuple) { + TestFunction("testArrayTuple"); +} + +TEST_F(JsonSchemaTest, TestArrayNonTuple) { + TestFunction("testArrayNonTuple"); +} + +TEST_F(JsonSchemaTest, TestString) { + TestFunction("testString"); +} + +TEST_F(JsonSchemaTest, TestNumber) { + TestFunction("testNumber"); +} + +TEST_F(JsonSchemaTest, TestType) { + TestFunction("testType"); +} + +#endif // #if defined(OSWIN) diff --git a/chrome/renderer/js_only_v8_extensions.cc b/chrome/renderer/js_only_v8_extensions.cc index 5ca3ae7..d304ef1 100644 --- a/chrome/renderer/js_only_v8_extensions.cc +++ b/chrome/renderer/js_only_v8_extensions.cc @@ -5,6 +5,7 @@ #include "chrome/renderer/js_only_v8_extensions.h" #include "chrome/renderer/extensions/bindings_utils.h" +#include "grit/renderer_resources.h" #include "grit/webkit_resources.h" // BaseJsV8Extension @@ -23,3 +24,10 @@ v8::Extension* JsonJsV8Extension::Get() { return new v8::Extension(kName, GetStringResource<IDR_DEVTOOLS_JSON_JS>(), arraysize(deps), deps); } + +// JsonSchemaJsV8Extension +const char* JsonSchemaJsV8Extension::kName = "chrome/jsonschema"; +v8::Extension* JsonSchemaJsV8Extension::Get() { + return new v8::Extension(kName, GetStringResource<IDR_JSON_SCHEMA_JS>(), + 0, NULL); +} diff --git a/chrome/renderer/js_only_v8_extensions.h b/chrome/renderer/js_only_v8_extensions.h index d7fc7a8..771103e 100644 --- a/chrome/renderer/js_only_v8_extensions.h +++ b/chrome/renderer/js_only_v8_extensions.h @@ -22,4 +22,10 @@ class JsonJsV8Extension { static v8::Extension* Get(); }; +class JsonSchemaJsV8Extension { + public: + static const char* kName; + static v8::Extension* Get(); +}; + #endif // CHROME_RENDERER_JS_ONLY_V8_EXTENSIONS_H_ diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index 2865cc4..16d5a2b 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -299,6 +299,7 @@ void RenderThread::EnsureWebKitInitialized() { if (command_line.HasSwitch(switches::kEnableExtensions)) { WebKit::registerExtension(BaseJsV8Extension::Get()); WebKit::registerExtension(JsonJsV8Extension::Get()); + WebKit::registerExtension(JsonSchemaJsV8Extension::Get()); WebKit::registerExtension(EventBindings::Get()); WebKit::registerExtension(RendererExtensionBindings::Get(this)); } diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 3dd7887..624f9d9 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -3050,8 +3050,6 @@ void RenderView::SendExtensionRequest(const std::string& name, const std::string& args, int callback_id, WebFrame* callback_frame) { - DCHECK(RenderThread::current()->message_loop() == MessageLoop::current()); - if (callback_id != -1) { DCHECK(callback_frame) << "Callback specified without frame"; pending_extension_callbacks_.AddWithID(callback_frame, callback_id); diff --git a/chrome/renderer/render_view_unittest.cc b/chrome/renderer/render_view_unittest.cc index f28b8d0..3be31bd 100644 --- a/chrome/renderer/render_view_unittest.cc +++ b/chrome/renderer/render_view_unittest.cc @@ -2,107 +2,9 @@ // 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/common/render_messages.h" -#include "chrome/renderer/extensions/event_bindings.h" -#include "chrome/renderer/extensions/renderer_extension_bindings.h" -#include "chrome/renderer/js_only_v8_extensions.h" -#include "chrome/renderer/mock_render_process.h" -#include "chrome/renderer/mock_render_thread.h" -#include "chrome/renderer/render_view.h" -#include "chrome/renderer/renderer_webkitclient_impl.h" +#include "chrome/test/render_view_test.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" -#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h" -#include "webkit/glue/webframe.h" -#include "webkit/glue/weburlrequest.h" -#include "webkit/glue/webview.h" - -using WebKit::WebScriptSource; -using WebKit::WebString; - -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()->ExecuteScript(WebScriptSource(WebString::fromUTF8(js))); - } - - // 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() { - WebKit::initialize(&webkitclient_); - WebKit::registerExtension(BaseJsV8Extension::Get()); - WebKit::registerExtension(JsonJsV8Extension::Get()); - WebKit::registerExtension(EventBindings::Get()); - WebKit::registerExtension(RendererExtensionBindings::Get(&render_thread_)); - - mock_process_.reset(new MockProcess()); - - 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; - - mock_process_.reset(); - WebKit::shutdown(); - - msg_loop_.RunAllPending(); - } - - MessageLoop msg_loop_; - MockRenderThread render_thread_; - scoped_ptr<MockProcess> mock_process_; - scoped_refptr<RenderView> view_; - RendererWebKitClientImpl webkitclient_; -}; - TEST_F(RenderViewTest, OnLoadAlternateHTMLText) { // Test a new navigation. @@ -374,90 +276,3 @@ TEST_F(RenderViewTest, OnSetTextDirection) { EXPECT_EQ(output, kTextDirection[i].expected_result); } } - -// Tests that the bindings for opening a channel to an extension and sending -// and receiving messages through that channel all works. -TEST_F(RenderViewTest, ExtensionMessagesOpenChannel) { - render_thread_.sink().ClearMessages(); - LoadHTML("<body></body>"); - ExecuteJavaScript( - "var e = new chromium.Extension('foobar');" - "var port = e.connect();" - "port.onmessage.addListener(doOnMessage);" - "port.postMessage({message: 'content ready'});" - "function doOnMessage(msg, port) {" - " alert('content got: ' + msg.val);" - "}"); - - // Verify that we opened a channel and sent a message through it. - const IPC::Message* open_channel_msg = - render_thread_.sink().GetUniqueMessageMatching( - ViewHostMsg_OpenChannelToExtension::ID); - EXPECT_TRUE(open_channel_msg); - - const IPC::Message* post_msg = - render_thread_.sink().GetUniqueMessageMatching( - ViewHostMsg_ExtensionPostMessage::ID); - ASSERT_TRUE(post_msg); - ViewHostMsg_ExtensionPostMessage::Param post_params; - ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params); - EXPECT_EQ("{\"message\":\"content ready\"}", post_params.b); - - // Now simulate getting a message back from the other side. - render_thread_.sink().ClearMessages(); - const int kPortId = 0; - RendererExtensionBindings::HandleMessage("{\"val\": 42}", kPortId); - - // Verify that we got it. - const IPC::Message* alert_msg = - render_thread_.sink().GetUniqueMessageMatching( - ViewHostMsg_RunJavaScriptMessage::ID); - ASSERT_TRUE(alert_msg); - void* iter = IPC::SyncMessage::GetDataIterator(alert_msg); - ViewHostMsg_RunJavaScriptMessage::SendParam alert_param; - IPC::ReadParam(alert_msg, &iter, &alert_param); - EXPECT_EQ(L"content got: 42", alert_param.a); -} - -// Tests that the bindings for handling a new channel connection and sending -// and receiving messages through that channel all works. -TEST_F(RenderViewTest, ExtensionMessagesOnConnect) { - LoadHTML("<body></body>"); - ExecuteJavaScript( - "chromium.onconnect.addListener(function (port) {" - " port.onmessage.addListener(doOnMessage);" - " port.postMessage({message: 'onconnect'});" - "});" - "function doOnMessage(msg, port) {" - " alert('got: ' + msg.val);" - "}"); - - render_thread_.sink().ClearMessages(); - - // Simulate a new connection being opened. - const int kPortId = 0; - RendererExtensionBindings::HandleConnect(kPortId); - - // Verify that we handled the new connection by posting a message. - const IPC::Message* post_msg = - render_thread_.sink().GetUniqueMessageMatching( - ViewHostMsg_ExtensionPostMessage::ID); - ASSERT_TRUE(post_msg); - ViewHostMsg_ExtensionPostMessage::Param post_params; - ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params); - EXPECT_EQ("{\"message\":\"onconnect\"}", post_params.b); - - // Now simulate getting a message back from the channel opener. - render_thread_.sink().ClearMessages(); - RendererExtensionBindings::HandleMessage("{\"val\": 42}", kPortId); - - // Verify that we got it. - const IPC::Message* alert_msg = - render_thread_.sink().GetUniqueMessageMatching( - ViewHostMsg_RunJavaScriptMessage::ID); - ASSERT_TRUE(alert_msg); - void* iter = IPC::SyncMessage::GetDataIterator(alert_msg); - ViewHostMsg_RunJavaScriptMessage::SendParam alert_param; - IPC::ReadParam(alert_msg, &iter, &alert_param); - EXPECT_EQ(L"got: 42", alert_param.a); -} diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index 5589cc0..e7084d5 100755 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -9,13 +9,14 @@ </outputs> <release seq="1"> <includes> - <include name="IDR_NET_ERROR_HTML" file="resources\neterror.html" type="BINDATA" /> - <include name="IDR_INSECURE_CONTENT_STAMP" file="resources\insecure_content_stamp.png" type="BINDATA" /> <include name="IDR_ERROR_NO_DETAILS_HTML" file="resources\error_no_details.html" type="BINDATA" /> - <include name="IDR_GREASEMONKEY_API_JS" file="resources\greasemonkey_api.js" type="BINDATA" /> + <include name="IDR_EVENT_BINDINGS_JS" file="resources\event_bindings.js" type="BINDATA" /> <include name="IDR_EXTENSION_PROCESS_BINDINGS_JS" file="resources\extension_process_bindings.js" type="BINDATA" /> + <include name="IDR_GREASEMONKEY_API_JS" file="resources\greasemonkey_api.js" type="BINDATA" /> + <include name="IDR_INSECURE_CONTENT_STAMP" file="resources\insecure_content_stamp.png" type="BINDATA" /> + <include name="IDR_JSON_SCHEMA_JS" file="resources\json_schema.js" type="BINDATA" /> + <include name="IDR_NET_ERROR_HTML" file="resources\neterror.html" type="BINDATA" /> <include name="IDR_RENDERER_EXTENSION_BINDINGS_JS" file="resources\renderer_extension_bindings.js" type="BINDATA" /> - <include name="IDR_EVENT_BINDINGS_JS" file="resources\event_bindings.js" type="BINDATA" /> </includes> </release> </grit>
\ No newline at end of file diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 3a18c97..ff7ac7e 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -1,8 +1,46 @@ var chromium; (function() { + native function GetNextCallbackId(); + native function GetTabsForWindow(); + native function GetTab(); + native function CreateTab(); + native function UpdateTab(); + native function RemoveTab(); + if (!chromium) chromium = {}; + // Validate arguments. + function validate(inst, schemas) { + if (inst.length > schemas.length) + throw new Error("Too many arguments."); + + for (var i = 0; i < schemas.length; i++) { + if (inst[i]) { + var validator = new chromium.JSONSchemaValidator(); + validator.validate(inst[i], schemas[i]); + if (validator.errors.length == 0) + continue; + + var message = "Invalid value for argument " + i + ". "; + for (var i = 0, err; err = validator.errors[i]; i++) { + if (err.path) { + message += "Property '" + err.path + "': "; + } + message += err.message; + message = message.substring(0, message.length - 1); + message += ", "; + } + message = message.substring(0, message.length - 2); + message += "."; + + throw new Error(message); + } else if (!schemas[i].optional) { + throw new Error("Argument " + i + " is required."); + } + } + } + // callback handling var callbacks = []; chromium._dispatchCallback = function(callbackId, str) { @@ -22,7 +60,6 @@ var chromium; var sargs = goog.json.serialize(args); var callbackId = -1; if (callback) { - native function GetNextCallbackId(); callbackId = GetNextCallbackId(); callbacks[callbackId] = callback; } @@ -33,23 +70,61 @@ var chromium; chromium.tabs = {}; // TODO(aa): This should eventually take an optional windowId param. chromium.tabs.getTabsForWindow = function(callback) { - native function GetTabsForWindow(); + validate(arguments, arguments.callee.params); sendRequest(GetTabsForWindow, null, callback); }; + chromium.tabs.getTabsForWindow.params = [ + chromium.types.optFun + ]; + chromium.tabs.getTab = function(tabId, callback) { - native function GetTab(); + validate(arguments, arguments.callee.params); sendRequest(GetTab, tabId, callback); }; + chromium.tabs.getTab.params = [ + chromium.types.pInt, + chromium.types.optFun + ]; + chromium.tabs.createTab = function(tab, callback) { - native function CreateTab(); + validate(arguments, arguments.callee.params); sendRequest(CreateTab, tab, callback); }; + chromium.tabs.createTab.params = [ + { + type: "object", + properties: { + windowId: chromium.types.optPInt, + url: chromium.types.optStr, + selected: chromium.types.optBool + }, + additionalProperties: false + }, + chromium.types.optFun + ]; + chromium.tabs.updateTab = function(tab) { - native function UpdateTab(); + validate(arguments, arguments.callee.params); sendRequest(UpdateTab, tab); }; + chromium.tabs.updateTab.params = [ + { + type: "object", + properties: { + id: chromium.types.pInt, + windowId: chromium.types.optPInt, + url: chromium.types.optStr, + selected: chromium.types.optBool + }, + additionalProperties: false + } + ]; + chromium.tabs.removeTab = function(tabId) { - native function RemoveTab(); + validate(arguments, arguments.callee.params); sendRequest(RemoveTab, tabId); }; + chromium.tabs.removeTab.params = [ + chromium.types.pInt + ]; })(); diff --git a/chrome/renderer/resources/json_schema.js b/chrome/renderer/resources/json_schema.js new file mode 100755 index 0000000..f2d14fb --- /dev/null +++ b/chrome/renderer/resources/json_schema.js @@ -0,0 +1,328 @@ +// Copyright (c) 2009 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. + +//============================================================================== +// This file contains a class that implements a subset of JSON Schema. +// See: http://www.json.com/json-schema-proposal/ for more details. +// +// The following features of JSON Schema are not implemented: +// - requires +// - unique +// - disallow +// - union types +// +// The following properties are not applicable to the interface exposed by +// this class: +// - options +// - readonly +// - title +// - description +// - format +// - default +// - transient +// - hidden +//============================================================================== + +var chromium = chromium || {}; + +/** + * Validates an instance against a schema and accumulates errors. Usage: + * + * var validator = new chromium.JSONSchemaValidator(); + * validator.validate(inst, schema); + * if (validator.errors.length == 0) + * console.log("Valid!"); + * else + * console.log(validator.errors); + * + * The errors property contains a list of objects. Each object has two + * properties: "path" and "message". The "path" property contains the path to + * the key that had the problem, and the "message" property contains a sentence + * describing the error. + */ +chromium.JSONSchemaValidator = function() { + this.errors = []; +}; + +chromium.JSONSchemaValidator.messages = { + invalidEnum: "Value must be one of: [*].", + propertyRequired: "Property is required.", + unexpectedProperty: "Unexpected property.", + arrayMinItems: "Array must have at least * items.", + arrayMaxItems: "Array must not have more than * items.", + itemRequired: "Item is required.", + stringMinLength: "String must be at least * characters long.", + stringMaxLength: "String must not be more than * characters long.", + stringPattern: "String must match the pattern: *.", + numberMinValue: "Value must not be less than *.", + numberMaxValue: "Value must not be greater than *.", + numberMaxDecimal: "Value must not have more than * decimal places.", + invalidType: "Expected '*' but got '*'." +}; + +/** + * Builds an error message. Key is the property in the |errors| object, and + * |opt_replacements| is an array of values to replace "*" characters with. + */ +chromium.JSONSchemaValidator.formatError = function(key, opt_replacements) { + var message = this.messages[key]; + if (opt_replacements) { + for (var i = 0; i < opt_replacements.length; i++) { + message = message.replace("*", opt_replacements[i]); + } + } + return message; +}; + +/** + * Classifies a value as one of the JSON schema primitive types. Note that we + * don't explicitly disallow 'function', because we want to allow functions in + * the input values. + */ +chromium.JSONSchemaValidator.getType = function(value) { + var s = typeof value; + + if (s == "object") { + if (value === null) { + return "null"; + } else if (value instanceof Array || + Object.prototype.toString.call(value) == "[Object Array]") { + return "array"; + } + } else if (s == "number") { + if (value % 1 == 0) { + return "integer"; + } + } + + return s; +}; + +/** + * Validates an instance against a schema. The instance can be any JavaScript + * value and will be validated recursively. When this method returns, the + * |errors| property will contain a list of errors, if any. + */ +chromium.JSONSchemaValidator.prototype.validate = function(instance, schema, + opt_path) { + var path = opt_path || ""; + + // If the schema has an extends property, the instance must validate against + // that schema too. + if (schema.extends) + this.validate(instance, schema.extends, path); + + // If the schema has an enum property, the instance must be one of those + // values. + if (schema.enum) { + if (!this.validateEnum(instance, schema, path)) + return; + } + + if (schema.type && schema.type != "any") { + if (!this.validateType(instance, schema, path)) + return; + + // Type-specific validation. + switch (schema.type) { + case "object": + this.validateObject(instance, schema, path); + break; + case "array": + this.validateArray(instance, schema, path); + break; + case "string": + this.validateString(instance, schema, path); + break; + case "number": + case "integer": + this.validateNumber(instance, schema, path); + break; + } + } +}; + +/** + * Validates an instance against a schema with an enum type. Populates the + * |errors| property, and returns a boolean indicating whether the instance + * validates. + */ +chromium.JSONSchemaValidator.prototype.validateEnum = function(instance, schema, + path) { + for (var i = 0; i < schema.enum.length; i++) { + if (instance === schema.enum[i]) + return true; + } + + this.addError(path, "invalidEnum", [schema.enum.join(", ")]); + return false; +}; + +/** + * Validates an instance against an object schema and populates the errors + * property. + */ +chromium.JSONSchemaValidator.prototype.validateObject = function(instance, + schema, path) { + for (var prop in schema.properties) { + var propPath = path ? path + "." + prop : prop; + if (instance.hasOwnProperty(prop)) { + this.validate(instance[prop], schema.properties[prop], propPath); + } else if (!schema.properties[prop].optional) { + this.addError(propPath, "propertyRequired"); + } + } + + // The additionalProperties property can either be |false| or a schema + // definition. If |false|, additional properties are not allowed. If a schema + // defintion, all additional properties must validate against that schema. + if (typeof schema.additionalProperties != "undefined") { + for (var prop in instance) { + if (instance.hasOwnProperty(prop)) { + var propPath = path ? path + "." + prop : prop; + if (!schema.properties.hasOwnProperty(prop)) { + if (schema.additionalProperties === false) + this.addError(propPath, "unexpectedProperty"); + else + this.validate(instance[prop], schema.additionalProperties, propPath); + } + } + } + } +}; + +/** + * Validates an instance against an array schema and populates the errors + * property. + */ +chromium.JSONSchemaValidator.prototype.validateArray = function(instance, + schema, path) { + var typeOfItems = chromium.JSONSchemaValidator.getType(schema.items); + + if (typeOfItems == 'object') { + if (schema.minItems && instance.length < schema.minItems) { + this.addError(path, "arrayMinItems", [schema.minItems]); + } + + if (typeof schema.maxItems != "undefined" && + instance.length > schema.maxItems) { + this.addError(path, "arrayMaxItems", [schema.maxItems]); + } + + // If the items property is a single schema, each item in the array must + // have that schema. + for (var i = 0; i < instance.length; i++) { + this.validate(instance[i], schema.items, path + "[" + i + "]"); + } + } else if (typeOfItems == 'array') { + // If the items property is an array of schemas, each item in the array must + // validate against the corresponding schema. + for (var i = 0; i < schema.items.length; i++) { + var itemPath = path ? path + "[" + i + "]" : String(i); + if (instance.hasOwnProperty(i)) { + this.validate(instance[i], schema.items[i], itemPath); + } else if (!schema.items[i].optional) { + this.addError(itemPath, "itemRequired"); + } + } + + if (schema.additionalProperties === false) { + if (instance.length > schema.items.length) { + this.addError(path, "arrayMaxItems", [schema.items.length]); + } + } else if (schema.additionalProperties) { + for (var i = schema.items.length; i < instance.length; i++) { + var itemPath = path ? path + "[" + i + "]" : String(i); + this.validate(instance[i], schema.additionalProperties, itemPath); + } + } + } +}; + +/** + * Validates a string and populates the errors property. + */ +chromium.JSONSchemaValidator.prototype.validateString = function(instance, + schema, path) { + if (schema.minLength && instance.length < schema.minLength) + this.addError(path, "stringMinLength", [schema.minLength]); + + if (schema.maxLength && instance.length > schema.maxLength) + this.addError(path, "stringMaxLength", [schema.maxLength]); + + if (schema.pattern && !schema.pattern.test(instance)) + this.addError(path, "stringPattern", [schema.pattern]); +}; + +/** + * Validates a number and populates the errors property. The instance is + * assumed to be a number. + */ +chromium.JSONSchemaValidator.prototype.validateNumber = function(instance, + schema, path) { + if (schema.minimum && instance < schema.minimum) + this.addError(path, "numberMinValue", [schema.minimum]); + + if (schema.maximum && instance > schema.maximum) + this.addError(path, "numberMaxValue", [schema.maximum]); + + if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1) + this.addError(path, "numberMaxDecimal", [schema.maxDecimal]); +}; + +/** + * Validates the primitive type of an instance and populates the errors + * property. Returns true if the instance validates, false otherwise. + */ +chromium.JSONSchemaValidator.prototype.validateType = function(instance, schema, + path) { + var actualType = chromium.JSONSchemaValidator.getType(instance); + if (schema.type != actualType && !(schema.type == "number" && + actualType == "integer")) { + this.addError(path, "invalidType", [schema.type, actualType]); + return false; + } + + return true; +}; + +/** + * Adds an error message. |key| is an index into the |messages| object. + * |replacements| is an array of values to replace '*' characters in the + * message. + */ +chromium.JSONSchemaValidator.prototype.addError = function(path, key, + replacements) { + this.errors.push({ + path: path, + message: chromium.JSONSchemaValidator.formatError(key, replacements) + }); +}; + +// Set up chromium.types with some commonly used types... +(function() { + function extend(base, ext) { + var result = {}; + for (var p in base) + result[p] = base[p]; + for (var p in ext) + result[p] = ext[p]; + return result; + } + + var types = {}; + types.opt = {optional: true}; + types.bool = {type: "boolean"}; + types.int = {type: "integer"}; + types.str = {type: "string"}; + types.fun = {type: "function"}; + types.pInt = extend(types.int, {minimum: 0}); + types.optBool = extend(types.bool, types.opt); + types.optInt = extend(types.int, types.opt); + types.optStr = extend(types.str, types.opt); + types.optFun = extend(types.fun, types.opt); + types.optPInt = extend(types.pInt, types.opt); + + chromium.types = types; +})(); diff --git a/chrome/test/data/extensions/json_schema_test.js b/chrome/test/data/extensions/json_schema_test.js new file mode 100755 index 0000000..e77c52e --- /dev/null +++ b/chrome/test/data/extensions/json_schema_test.js @@ -0,0 +1,314 @@ +// Copyright (c) 2009 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. + +function assert(truth) {
+ if (!truth)
+ throw new Error("Assertion failed.");
+}
+
+function assertValid(type, instance, schema) {
+ var validator = new chromium.JSONSchemaValidator();
+ validator["validate" + type](instance, schema, "");
+ if (validator.errors.length != 0) {
+ console.log("Got unexpected errors");
+ console.log(validator.errors);
+ assert(false);
+ }
+}
+
+function assertNotValid(type, instance, schema, errors) {
+ var validator = new chromium.JSONSchemaValidator();
+ validator["validate" + type](instance, schema, "");
+ assert(validator.errors.length === errors.length);
+ for (var i = 0; i < errors.length; i++) {
+ if (validator.errors[i].message == errors[i]) {
+ log("Got expected error: " + validator.errors[i].message + " for path: " +
+ validator.errors[i].path);
+ } else {
+ log("Missed expected error: " + errors[i] + ". Got: " +
+ validator.errors[i].message + " instead.");
+ assert(false);
+ }
+ }
+}
+
+function formatError(key, replacements) {
+ return chromium.JSONSchemaValidator.formatError(key, replacements);
+}
+
+function testFormatError() {
+ assert(formatError("propertyRequired") == "Property is required.");
+ assert(formatError("invalidEnum", ["foo, bar"]) ==
+ "Value must be one of: [foo, bar].");
+ assert(formatError("invalidType", ["foo", "bar"]) ==
+ "Expected 'foo' but got 'bar'.");
+}
+
+function testComplex() {
+ var schema = {
+ type: "array",
+ items: [
+ {
+ type: "object",
+ properties: {
+ id: {
+ type: "integer",
+ minimum: 1
+ },
+ url: {
+ type: "string",
+ pattern: /^https?\:/,
+ optional: true
+ },
+ index: {
+ type: "integer",
+ minimum: 0,
+ optional: true
+ },
+ selected: {
+ type: "boolean",
+ optional: true
+ }
+ }
+ },
+ { type: "function", optional: true },
+ { type: "function", optional: true }
+ ]
+ };
+
+ var instance = [
+ {
+ id: 42,
+ url: "http://www.google.com/",
+ index: 2,
+ selected: true
+ },
+ function(){},
+ function(){}
+ ];
+
+ assertValid("", instance, schema);
+ instance.length = 2;
+ assertValid("", instance, schema);
+ instance.length = 1;
+ instance.push({});
+ assertNotValid("", instance, schema,
+ [formatError("invalidType", ["function", "object"])]);
+ instance[1] = function(){};
+
+ instance[0].url = "foo";
+ assertNotValid("", instance, schema,
+ [formatError("stringPattern",
+ [schema.items[0].properties.url.pattern])]);
+ delete instance[0].url;
+ assertValid("", instance, schema);
+
+ instance[0].id = 0;
+ assertNotValid("", instance, schema,
+ [formatError("numberMinValue",
+ [schema.items[0].properties.id.minimum])]);
+}
+
+function testEnum() {
+ var schema = {
+ enum: ["foo", 42, false]
+ };
+
+ assertValid("", "foo", schema);
+ assertValid("", 42, schema);
+ assertValid("", false, schema);
+ assertNotValid("", "42", schema, [formatError("invalidEnum",
+ [schema.enum.join(", ")])]);
+ assertNotValid("", null, schema, [formatError("invalidEnum",
+ [schema.enum.join(", ")])]);
+}
+
+function testExtends() {
+ var parent = {
+ type: "number"
+ }
+ var schema = {
+ extends: parent
+ };
+
+ assertValid("", 42, schema);
+ assertNotValid("", "42", schema,
+ [formatError("invalidType", ["number", "string"])]);
+
+ // Make the derived schema more restrictive
+ parent.minimum = 43;
+ assertNotValid("", 42, schema, [formatError("numberMinValue", [43])]);
+ assertValid("", 43, schema);
+}
+
+function testObject() {
+ var schema = {
+ properties: {
+ "foo": {
+ type: "string"
+ },
+ "bar": {
+ type: "integer"
+ }
+ }
+ };
+
+ assertValid("Object", {foo:"foo",bar:42}, schema);
+ assertValid("Object", {foo:"foo",bar:42,"extra":true}, schema);
+ assertNotValid("Object", {foo:"foo"}, schema,
+ [formatError("propertyRequired")]);
+ assertNotValid("Object", {foo:"foo", bar:"42"}, schema,
+ [formatError("invalidType", ["integer", "string"])]);
+
+ schema.additionalProperties = false;
+ assertNotValid("Object", {foo:"foo",bar:42,"extra":true}, schema,
+ [formatError("unexpectedProperty")]);
+
+ schema.additionalProperties = {
+ type: "boolean"
+ };
+ assertValid("Object", {foo:"foo",bar:42,"extra":true}, schema);
+
+ schema.properties.bar.optional = true;
+ assertValid("Object", {foo:"foo",bar:42}, schema);
+ assertValid("Object", {foo:"foo"}, schema);
+ assertNotValid("Object", {foo:"foo", bar:"42"}, schema,
+ [formatError("invalidType", ["integer", "string"])]);
+}
+
+function testArrayTuple() {
+ var schema = {
+ items: [
+ {
+ type: "string"
+ },
+ {
+ type: "integer"
+ }
+ ]
+ };
+
+ assertValid("Array", ["42", 42], schema);
+ assertValid("Array", ["42", 42, "anything"], schema);
+ assertNotValid("Array", ["42"], schema, [formatError("itemRequired")]);
+ assertNotValid("Array", [42, 42], schema,
+ [formatError("invalidType", ["string", "integer"])]);
+
+ schema.additionalProperties = false;
+ assertNotValid("Array", ["42", 42, "anything"], schema,
+ [formatError("arrayMaxItems", [schema.items.length])]);
+
+ schema.additionalProperties = {
+ type: "boolean"
+ };
+ assertNotValid("Array", ["42", 42, "anything"], schema,
+ [formatError("invalidType", ["boolean", "string"])]);
+ assertValid("Array", ["42", 42, false], schema);
+
+ schema.items[0].optional = true;
+ assertValid("Array", ["42", 42], schema);
+ assertValid("Array", [, 42], schema);
+ assertNotValid("Array", [42, 42], schema,
+ [formatError("invalidType", ["string", "integer"])]);
+}
+
+function testArrayNonTuple() {
+ var schema = {
+ items: {
+ type: "string"
+ },
+ minItems: 2,
+ maxItems: 3
+ };
+
+ assertValid("Array", ["x", "x"], schema);
+ assertValid("Array", ["x", "x", "x"], schema);
+
+ assertNotValid("Array", ["x"], schema,
+ [formatError("arrayMinItems", [schema.minItems])]);
+ assertNotValid("Array", ["x", "x", "x", "x"], schema,
+ [formatError("arrayMaxItems", [schema.maxItems])]);
+ assertNotValid("Array", [42], schema,
+ [formatError("arrayMinItems", [schema.minItems]),
+ formatError("invalidType", ["string", "integer"])]);
+}
+
+function testString() {
+ var schema = {
+ minLength: 1,
+ maxLength: 10,
+ pattern: /^x/
+ };
+
+ assertValid("String", "x", schema);
+ assertValid("String", "xxxxxxxxxx", schema);
+
+ assertNotValid("String", "y", schema,
+ [formatError("stringPattern", [schema.pattern])]);
+ assertNotValid("String", "xxxxxxxxxxx", schema,
+ [formatError("stringMaxLength", [schema.maxLength])]);
+ assertNotValid("String", "", schema,
+ [formatError("stringMinLength", [schema.minLength]),
+ formatError("stringPattern", [schema.pattern])]);
+}
+
+function testNumber() {
+ var schema = {
+ minimum: 1,
+ maximum: 100,
+ maxDecimal: 2
+ };
+
+ assertValid("Number", 1, schema);
+ assertValid("Number", 50, schema);
+ assertValid("Number", 100, schema);
+ assertValid("Number", 88.88, schema);
+
+ assertNotValid("Number", 0.5, schema,
+ [formatError("numberMinValue", [schema.minimum])]);
+ assertNotValid("Number", 100.1, schema,
+ [formatError("numberMaxValue", [schema.maximum])]);
+ assertNotValid("Number", 100.111, schema,
+ [formatError("numberMaxValue", [schema.maximum]),
+ formatError("numberMaxDecimal", [schema.maxDecimal])]);
+}
+
+function testType() {
+ // valid
+ assertValid("Type", {}, {type:"object"});
+ assertValid("Type", [], {type:"array"});
+ assertValid("Type", function(){}, {type:"function"});
+ assertValid("Type", "foobar", {type:"string"});
+ assertValid("Type", "", {type:"string"});
+ assertValid("Type", 88.8, {type:"number"});
+ assertValid("Type", 42, {type:"number"});
+ assertValid("Type", 0, {type:"number"});
+ assertValid("Type", 42, {type:"integer"});
+ assertValid("Type", 0, {type:"integer"});
+ assertValid("Type", true, {type:"boolean"});
+ assertValid("Type", false, {type:"boolean"});
+ assertValid("Type", null, {type:"null"});
+
+ // not valid
+ assertNotValid("Type", [], {type: "object"},
+ [formatError("invalidType", ["object", "array"])]);
+ assertNotValid("Type", null, {type: "object"},
+ [formatError("invalidType", ["object", "null"])]);
+ assertNotValid("Type", function(){}, {type: "object"},
+ [formatError("invalidType", ["object", "function"])]);
+ assertNotValid("Type", 42, {type: "array"},
+ [formatError("invalidType", ["array", "integer"])]);
+ assertNotValid("Type", 42, {type: "string"},
+ [formatError("invalidType", ["string", "integer"])]);
+ assertNotValid("Type", "42", {type: "number"},
+ [formatError("invalidType", ["number", "string"])]);
+ assertNotValid("Type", 88.8, {type: "integer"},
+ [formatError("invalidType", ["integer", "number"])]);
+ assertNotValid("Type", 1, {type: "boolean"},
+ [formatError("invalidType", ["boolean", "integer"])]);
+ assertNotValid("Type", false, {type: "null"},
+ [formatError("invalidType", ["null", "boolean"])]);
+ assertNotValid("Type", {}, {type: "function"},
+ [formatError("invalidType", ["function", "object"])]);
+}
diff --git a/chrome/test/data/js_test_runner.html b/chrome/test/data/js_test_runner.html new file mode 100755 index 0000000..55d0448 --- /dev/null +++ b/chrome/test/data/js_test_runner.html @@ -0,0 +1,53 @@ +<!--
+Copyright (c) 2009 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. + +This file is a manual test runner for JavaScript unit tests. It finds all the
+global functions starting with "test" and runs them. It is useful for developing
+and debugging JavaScript unit tests.
+
+See: chrome/test/data/extensions/schema_test.js for an example.
+-->
+<textarea style="position:absolute; left:5px; top:5px; right:5px; bottom:5px;">
+</textarea>
+
+<!-- Add a reference to the script and the script test files here. -->
+<script src="../../../renderer/resources/schema.js"></script>
+<script src="schema_test.js"></script>
+
+<script>
+function log() {
+ console.log.apply(console, arguments);
+}
+
+function runAllTests() {
+ for (var p in window) {
+ if (p.substring(0, 4) == "test") {
+ runTest(p);
+ }
+ }
+ window.setTimeout(function() {
+ log("DONE");
+ }, 0);
+}
+
+function runTest(p) {
+ window.setTimeout(function() {
+ var success = false;
+ try {
+ window[p]();
+ success = true;
+ } finally {
+ print((success ? "PASS" : "FAIL") + " " + p);
+ }
+ }, 0);
+}
+
+function print(msg) {
+ document.getElementsByTagName("textarea")[0].value += msg + "\n";
+}
+
+runAllTests();
+
+</script>
diff --git a/chrome/test/render_view_test.cc b/chrome/test/render_view_test.cc new file mode 100644 index 0000000..49d4c63 --- /dev/null +++ b/chrome/test/render_view_test.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2009 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/test/render_view_test.h" + +#include "chrome/common/render_messages.h" +#include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/renderer/extensions/event_bindings.h" +#include "chrome/renderer/extensions/extension_process_bindings.h" +#include "chrome/renderer/extensions/renderer_extension_bindings.h" +#include "chrome/renderer/js_only_v8_extensions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h" +#include "webkit/glue/weburlrequest.h" +#include "webkit/glue/webview.h" + +using WebKit::WebScriptSource; +using WebKit::WebString; + +namespace { + +const int32 kRouteId = 5; +const int32 kOpenerId = 7; + +}; + +void RenderViewTest::ProcessPendingMessages() { + msg_loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask()); + msg_loop_.Run(); +} + +WebFrame* RenderViewTest::GetMainFrame() { + return view_->webview()->GetMainFrame(); +} + +void RenderViewTest::ExecuteJavaScript(const char* js) { + GetMainFrame()->ExecuteScript(WebScriptSource(WebString::fromUTF8(js))); +} + +void RenderViewTest::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(); +} + +void RenderViewTest::SetUp() { + WebKit::initialize(&webkitclient_); + WebKit::registerExtension(BaseJsV8Extension::Get()); + WebKit::registerExtension(JsonJsV8Extension::Get()); + WebKit::registerExtension(JsonSchemaJsV8Extension::Get()); + WebKit::registerExtension(EventBindings::Get()); + WebKit::registerExtension(ExtensionProcessBindings::Get()); + WebKit::registerExtension(RendererExtensionBindings::Get(&render_thread_)); + + // TODO(aa): Should some of this go to some other inheriting class? + std::vector<std::string> names; + ExtensionFunctionDispatcher::GetAllFunctionNames(&names); + ExtensionProcessBindings::SetFunctionNames(names); + + mock_process_.reset(new MockProcess()); + + 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); +} +void RenderViewTest::TearDown() { + render_thread_.SendCloseMessage(); + + // Run the loop so the release task from the renderwidget executes. + ProcessPendingMessages(); + + view_ = NULL; + + mock_process_.reset(); + WebKit::shutdown(); + + msg_loop_.RunAllPending(); +} diff --git a/chrome/test/render_view_test.h b/chrome/test/render_view_test.h new file mode 100644 index 0000000..abda898 --- /dev/null +++ b/chrome/test/render_view_test.h @@ -0,0 +1,47 @@ +// Copyright (c) 2009 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_TEST_RENDER_VIEW_TEST_H_ +#define CHROME_TEST_RENDER_VIEW_TEST_H_ + +#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 "chrome/renderer/renderer_webkitclient_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/webframe.h" + +class RenderViewTest : public testing::Test { + public: + RenderViewTest() {} + ~RenderViewTest() {} + + protected: + // Spins the message loop to process all messages that are currently pending. + void ProcessPendingMessages(); + + // Returns a pointer to the main frame. + WebFrame* 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); + + // Loads the given HTML into the main frame as a data: URL. + void LoadHTML(const char* html); + + // testing::Test + virtual void SetUp(); + + virtual void TearDown(); + + MessageLoop msg_loop_; + MockRenderThread render_thread_; + scoped_ptr<MockProcess> mock_process_; + scoped_refptr<RenderView> view_; + RendererWebKitClientImpl webkitclient_; +}; + +#endif // CHROME_TEST_RENDER_VIEW_TEST_H_ diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index 4711923f..7750528 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -192,6 +192,14 @@ > </File> <File + RelativePath="..\render_view_test.cc" + > + </File> + <File + RelativePath="..\render_view_test.h" + > + </File> + <File RelativePath=".\run_all_unittests.cc" > </File> @@ -488,11 +496,15 @@ > </File> <File - RelativePath="..\..\browser\extensions\extension_process_manager_unittest.cc" + RelativePath="..\..\browser\extensions\extension_content_script_inject_unittest.cc" > </File> <File - RelativePath="..\..\browser\extensions\extension_content_script_inject_unittest.cc" + RelativePath="..\..\browser\extensions\extension_messages_unittest.cc" + > + </File> + <File + RelativePath="..\..\browser\extensions\extension_process_manager_unittest.cc" > </File> <File @@ -856,10 +868,18 @@ Name="renderer" > <File + RelativePath="..\..\renderer\extensions\extension_api_client_unittest.cc" + > + </File> + <File RelativePath="..\..\renderer\extensions\greasemonkey_api_unittest.cc" > </File> <File + RelativePath="..\..\renderer\extensions\json_schema_unittest.cc" + > + </File> + <File RelativePath="..\..\renderer\net\render_dns_master_unittest.cc" > </File> |