summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-14 06:10:31 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-14 06:10:31 +0000
commitad23b99078f3db28bf5e88821cadc2d5effac52b (patch)
tree0df04a0908e71c383853e71f7586c381cbc4b9c8 /chrome
parent0c9366d5f357759e3bd968a267b6eb1100e5835c (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/extensions/extension_messages_unittest.cc95
-rwxr-xr-xchrome/browser/extensions/extension_view_unittest.cc1
-rw-r--r--chrome/chrome.gyp5
-rwxr-xr-xchrome/renderer/extensions/extension_api_client_unittest.cc186
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc9
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.h51
-rwxr-xr-xchrome/renderer/extensions/json_schema_unittest.cc84
-rw-r--r--chrome/renderer/js_only_v8_extensions.cc8
-rw-r--r--chrome/renderer/js_only_v8_extensions.h6
-rw-r--r--chrome/renderer/render_thread.cc1
-rw-r--r--chrome/renderer/render_view.cc2
-rw-r--r--chrome/renderer/render_view_unittest.cc187
-rwxr-xr-xchrome/renderer/renderer_resources.grd9
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js87
-rwxr-xr-xchrome/renderer/resources/json_schema.js328
-rwxr-xr-xchrome/test/data/extensions/json_schema_test.js314
-rwxr-xr-xchrome/test/data/js_test_runner.html53
-rw-r--r--chrome/test/render_view_test.cc89
-rw-r--r--chrome/test/render_view_test.h47
-rw-r--r--chrome/test/unit/unittests.vcproj24
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, &params);
+ 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, &params);
+ 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, &params);
+ 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, &params);
+ 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, &params);
+ 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, &params);
+ 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, &params);
+ 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>