// 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 #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 "); // 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