| // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Tests for CppBoundClass, in conjunction with CppBindingExample. Binds |
| // a CppBindingExample class into JavaScript in a custom test shell and tests |
| // the binding from the outside by loading JS into the shell. |
| |
| #include <vector> |
| |
| #include "base/message_loop.h" |
| #include "base/string_util.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebData.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" |
| #include "webkit/glue/cpp_binding_example.h" |
| #include "webkit/glue/webkit_glue.h" |
| #include "webkit/tools/test_shell/test_shell_test.h" |
| |
| using WebKit::WebFrame; |
| |
| namespace { |
| |
| class CppBindingExampleSubObject : public CppBindingExample { |
| public: |
| CppBindingExampleSubObject() { |
| sub_value_.Set("sub!"); |
| BindProperty("sub_value", &sub_value_); |
| } |
| private: |
| CppVariant sub_value_; |
| }; |
| |
| |
| class CppBindingExampleWithOptionalFallback : public CppBindingExample { |
| public: |
| CppBindingExampleWithOptionalFallback() { |
| BindProperty("sub_object", sub_object_.GetAsCppVariant()); |
| } |
| |
| void set_fallback_method_enabled(bool state) { |
| BindFallbackMethod(state ? |
| &CppBindingExampleWithOptionalFallback::fallbackMethod |
| : NULL); |
| } |
| |
| // The fallback method does nothing, but because of it the JavaScript keeps |
| // running when a nonexistent method is called on an object. |
| void fallbackMethod(const CppArgumentList& args, CppVariant* result) { |
| } |
| |
| private: |
| CppBindingExampleSubObject sub_object_; |
| }; |
| |
| class ExampleTestShell : public TestShell { |
| public: |
| |
| ExampleTestShell(bool use_fallback_method) { |
| example_bound_class_.set_fallback_method_enabled(use_fallback_method); |
| } |
| |
| // When called by WebViewDelegate::WindowObjectCleared method, this binds a |
| // CppExampleObject to window.example. |
| virtual void BindJSObjectsToWindow(WebFrame* frame) { |
| example_bound_class_.BindToJavascript(frame, "example"); |
| // We use the layoutTestController binding for notifyDone. |
| TestShell::BindJSObjectsToWindow(frame); |
| } |
| |
| // This is a public interface to TestShell's protected method, so it |
| // can be called by our CreateEmptyWindow. |
| bool PublicInitialize(const std::string& starting_url) { |
| return Initialize(GURL(starting_url)); |
| } |
| |
| CppBindingExampleWithOptionalFallback example_bound_class_; |
| }; |
| |
| class CppBoundClassTest : public TestShellTest { |
| protected: |
| // Adapted from TestShell::CreateNewWindow, this creates an |
| // ExampleTestShellWindow rather than a regular TestShell. |
| virtual void CreateEmptyWindow() { |
| ExampleTestShell* host = new ExampleTestShell(useFallback()); |
| ASSERT_TRUE(host != NULL); |
| bool rv = host->PublicInitialize("about:blank"); |
| if (rv) { |
| test_shell_ = host; |
| TestShell::windowList()->push_back(host->mainWnd()); |
| webframe_ = test_shell_->webView()->mainFrame(); |
| ASSERT_TRUE(webframe_ != NULL); |
| } else { |
| delete host; |
| } |
| } |
| |
| // Wraps the given JavaScript snippet in <html><body><script> tags, then |
| // loads it into a webframe so it is executed. |
| void ExecuteJavaScript(const std::string& javascript) { |
| std::string html = "<html><body>"; |
| html.append(TestShellTest::kJavascriptDelayExitScript); |
| html.append("<script>"); |
| html.append(javascript); |
| html.append("</script></body></html>"); |
| // The base URL doesn't matter. |
| webframe_->loadHTMLString(html, GURL("about:blank")); |
| |
| test_shell_->WaitTestFinished(); |
| } |
| |
| // Executes the specified JavaScript and checks to be sure that the resulting |
| // document text is exactly "SUCCESS". |
| void CheckJavaScriptSuccess(const std::string& javascript) { |
| ExecuteJavaScript(javascript); |
| EXPECT_EQ("SUCCESS", |
| UTF16ToASCII(webkit_glue::DumpDocumentText(webframe_))); |
| } |
| |
| // Executes the specified JavaScript and checks that the resulting document |
| // text is empty. |
| void CheckJavaScriptFailure(const std::string& javascript) { |
| ExecuteJavaScript(javascript); |
| EXPECT_EQ("", UTF16ToASCII(webkit_glue::DumpDocumentText(webframe_))); |
| } |
| |
| // Constructs a JavaScript snippet that evaluates and compares the left and |
| // right expressions, printing "SUCCESS" to the page if they are equal and |
| // printing their actual values if they are not. Any strings in the |
| // expressions should be enclosed in single quotes, and no double quotes |
| // should appear in either expression (even if escaped). (If a test case |
| // is added that needs fancier quoting, Json::valueToQuotedString could be |
| // used here. For now, it's not worth adding the dependency.) |
| std::string BuildJSCondition(std::string left, std::string right) { |
| return "var leftval = " + left + ";" + |
| "var rightval = " + right + ";" + |
| "if (leftval == rightval) {" + |
| " document.writeln('SUCCESS');" + |
| "} else {" + |
| " document.writeln(\"" + |
| left + " [\" + leftval + \"] != " + |
| right + " [\" + rightval + \"]\");" + |
| "}"; |
| } |
| |
| protected: |
| virtual bool useFallback() { |
| return false; |
| } |
| |
| private: |
| WebFrame* webframe_; |
| }; |
| |
| class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest { |
| protected: |
| virtual bool useFallback() { |
| return true; |
| } |
| }; |
| |
| // Ensures that the example object has been bound to JS. |
| TEST_F(CppBoundClassTest, ObjectExists) { |
| std::string js = BuildJSCondition("typeof window.example", "'object'"); |
| CheckJavaScriptSuccess(js); |
| |
| // An additional check to test our test. |
| js = BuildJSCondition("typeof window.invalid_object", "'undefined'"); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| TEST_F(CppBoundClassTest, PropertiesAreInitialized) { |
| std::string js = BuildJSCondition("example.my_value", "10"); |
| CheckJavaScriptSuccess(js); |
| |
| js = BuildJSCondition("example.my_other_value", "'Reinitialized!'"); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| TEST_F(CppBoundClassTest, SubOject) { |
| std::string js = BuildJSCondition("typeof window.example.sub_object", |
| "'object'"); |
| CheckJavaScriptSuccess(js); |
| |
| js = BuildJSCondition("example.sub_object.sub_value", "'sub!'"); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| TEST_F(CppBoundClassTest, SetAndGetProperties) { |
| // The property on the left will be set to the value on the right, then |
| // checked to make sure it holds that same value. |
| static const std::string tests[] = { |
| "example.my_value", "7", |
| "example.my_value", "'test'", |
| "example.my_other_value", "3.14", |
| "example.my_other_value", "false", |
| "" // Array end marker: insert additional test pairs before this. |
| }; |
| |
| for (int i = 0; tests[i] != ""; i += 2) { |
| std::string left = tests[i]; |
| std::string right = tests[i + 1]; |
| // left = right; |
| std::string js = left; |
| js.append(" = "); |
| js.append(right); |
| js.append(";"); |
| js.append(BuildJSCondition(left, right)); |
| CheckJavaScriptSuccess(js); |
| } |
| } |
| |
| TEST_F(CppBoundClassTest, SetAndGetPropertiesWithCallbacks) { |
| // TODO(dglazkov): fix NPObject issues around failing property setters and |
| // getters and add tests for situations when GetProperty or SetProperty fail. |
| std::string js = "var result = 'SUCCESS';\n" |
| "example.my_value_with_callback = 10;\n" |
| "if (example.my_value_with_callback != 10)\n" |
| " result = 'FAIL: unable to set property.';\n" |
| "example.my_value_with_callback = 11;\n" |
| "if (example.my_value_with_callback != 11)\n" |
| " result = 'FAIL: unable to set property again';\n" |
| "if (example.same != 42)\n" |
| " result = 'FAIL: same property should always be 42';\n" |
| "example.same = 24;\n" |
| "if (example.same != 42)\n" |
| " result = 'FAIL: same property should always be 42';\n" |
| "document.writeln(result);\n"; |
| CheckJavaScriptSuccess(js); |
| } |
| |
| TEST_F(CppBoundClassTest, InvokeMethods) { |
| // The expression on the left is expected to return the value on the right. |
| static const std::string tests[] = { |
| "example.echoValue(true)", "true", |
| "example.echoValue(13)", "13", |
| "example.echoValue(2.718)", "2.718", |
| "example.echoValue('yes')", "'yes'", |
| "example.echoValue()", "null", // Too few arguments |
| |
| "example.echoType(false)", "true", |
| "example.echoType(19)", "3.14159", |
| "example.echoType(9.876)", "3.14159", |
| "example.echoType('test string')", "'Success!'", |
| "example.echoType()", "null", // Too few arguments |
| |
| // Comparing floats that aren't integer-valued is usually problematic due |
| // to rounding, but exact powers of 2 should also be safe. |
| "example.plus(2.5, 18.0)", "20.5", |
| "example.plus(2, 3.25)", "5.25", |
| "example.plus(2, 3)", "5", |
| "example.plus()", "null", // Too few arguments |
| "example.plus(1)", "null", // Too few arguments |
| "example.plus(1, 'test')", "null", // Wrong argument type |
| "example.plus('test', 2)", "null", // Wrong argument type |
| "example.plus('one', 'two')", "null", // Wrong argument type |
| "" // Array end marker: insert additional test pairs before this. |
| }; |
| |
| for (int i = 0; tests[i] != ""; i+= 2) { |
| std::string left = tests[i]; |
| std::string right = tests[i + 1]; |
| std::string js = BuildJSCondition(left, right); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| std::string js = "example.my_value = 3.25; example.my_other_value = 1.25;"; |
| js.append(BuildJSCondition( |
| "example.plus(example.my_value, example.my_other_value)", "4.5")); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| // Tests that invoking a nonexistent method with no fallback method stops the |
| // script's execution |
| TEST_F(CppBoundClassTest, |
| InvokeNonexistentMethodNoFallback) { |
| std::string js = "example.nonExistentMethod();document.writeln('SUCCESS');"; |
| CheckJavaScriptFailure(js); |
| } |
| |
| // Ensures existent methods can be invoked successfully when the fallback method |
| // is used |
| TEST_F(CppBoundClassWithFallbackMethodTest, |
| InvokeExistentMethodsWithFallback) { |
| std::string js = BuildJSCondition("example.echoValue(34)", "34"); |
| CheckJavaScriptSuccess(js); |
| } |
| |
| } // namespace |