1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Tests for CppBoundClass, in conjunction with CppBindingExample. Binds 6 // a CppBindingExample class into JavaScript in a custom test shell and tests 7 // the binding from the outside by loading JS into the shell. 8 9 #include <vector> 10 11 #include "base/message_loop.h" 12 #include "base/string_util.h" 13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebData.h" 14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" 15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" 16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" 17 #include "webkit/glue/cpp_binding_example.h" 18 #include "webkit/glue/webkit_glue.h" 19 #include "webkit/tools/test_shell/test_shell_test.h" 20 21 using WebKit::WebFrame; 22 23 namespace { 24 25 class CppBindingExampleSubObject : public CppBindingExample { 26 public: 27 CppBindingExampleSubObject() { 28 sub_value_.Set("sub!"); 29 BindProperty("sub_value", &sub_value_); 30 } 31 private: 32 CppVariant sub_value_; 33 }; 34 35 36 class CppBindingExampleWithOptionalFallback : public CppBindingExample { 37 public: 38 CppBindingExampleWithOptionalFallback() { 39 BindProperty("sub_object", sub_object_.GetAsCppVariant()); 40 } 41 42 void set_fallback_method_enabled(bool state) { 43 BindFallbackMethod(state ? 44 &CppBindingExampleWithOptionalFallback::fallbackMethod 45 : NULL); 46 } 47 48 // The fallback method does nothing, but because of it the JavaScript keeps 49 // running when a nonexistent method is called on an object. 50 void fallbackMethod(const CppArgumentList& args, CppVariant* result) { 51 } 52 53 private: 54 CppBindingExampleSubObject sub_object_; 55 }; 56 57 class ExampleTestShell : public TestShell { 58 public: 59 60 ExampleTestShell(bool use_fallback_method) { 61 example_bound_class_.set_fallback_method_enabled(use_fallback_method); 62 } 63 64 // When called by WebViewDelegate::WindowObjectCleared method, this binds a 65 // CppExampleObject to window.example. 66 virtual void BindJSObjectsToWindow(WebFrame* frame) { 67 example_bound_class_.BindToJavascript(frame, "example"); 68 // We use the layoutTestController binding for notifyDone. 69 TestShell::BindJSObjectsToWindow(frame); 70 } 71 72 // This is a public interface to TestShell's protected method, so it 73 // can be called by our CreateEmptyWindow. 74 bool PublicInitialize(const std::string& starting_url) { 75 return Initialize(GURL(starting_url)); 76 } 77 78 CppBindingExampleWithOptionalFallback example_bound_class_; 79 }; 80 81 class CppBoundClassTest : public TestShellTest { 82 protected: 83 // Adapted from TestShell::CreateNewWindow, this creates an 84 // ExampleTestShellWindow rather than a regular TestShell. 85 virtual void CreateEmptyWindow() { 86 ExampleTestShell* host = new ExampleTestShell(useFallback()); 87 ASSERT_TRUE(host != NULL); 88 bool rv = host->PublicInitialize("about:blank"); 89 if (rv) { 90 test_shell_ = host; 91 TestShell::windowList()->push_back(host->mainWnd()); 92 webframe_ = test_shell_->webView()->mainFrame(); 93 ASSERT_TRUE(webframe_ != NULL); 94 } else { 95 delete host; 96 } 97 } 98 99 // Wraps the given JavaScript snippet in <html><body><script> tags, then 100 // loads it into a webframe so it is executed. 101 void ExecuteJavaScript(const std::string& javascript) { 102 std::string html = "<html><body>"; 103 html.append(TestShellTest::kJavascriptDelayExitScript); 104 html.append("<script>"); 105 html.append(javascript); 106 html.append("</script></body></html>"); 107 // The base URL doesn't matter. 108 webframe_->loadHTMLString(html, GURL("about:blank")); 109 110 test_shell_->WaitTestFinished(); 111 } 112 113 // Executes the specified JavaScript and checks to be sure that the resulting 114 // document text is exactly "SUCCESS". 115 void CheckJavaScriptSuccess(const std::string& javascript) { 116 ExecuteJavaScript(javascript); 117 EXPECT_EQ("SUCCESS", 118 UTF16ToASCII(webkit_glue::DumpDocumentText(webframe_))); 119 } 120 121 // Executes the specified JavaScript and checks that the resulting document 122 // text is empty. 123 void CheckJavaScriptFailure(const std::string& javascript) { 124 ExecuteJavaScript(javascript); 125 EXPECT_EQ("", UTF16ToASCII(webkit_glue::DumpDocumentText(webframe_))); 126 } 127 128 // Constructs a JavaScript snippet that evaluates and compares the left and 129 // right expressions, printing "SUCCESS" to the page if they are equal and 130 // printing their actual values if they are not. Any strings in the 131 // expressions should be enclosed in single quotes, and no double quotes 132 // should appear in either expression (even if escaped). (If a test case 133 // is added that needs fancier quoting, Json::valueToQuotedString could be 134 // used here. For now, it's not worth adding the dependency.) 135 std::string BuildJSCondition(std::string left, std::string right) { 136 return "var leftval = " + left + ";" + 137 "var rightval = " + right + ";" + 138 "if (leftval == rightval) {" + 139 " document.writeln('SUCCESS');" + 140 "} else {" + 141 " document.writeln(\"" + 142 left + " [\" + leftval + \"] != " + 143 right + " [\" + rightval + \"]\");" + 144 "}"; 145 } 146 147 protected: 148 virtual bool useFallback() { 149 return false; 150 } 151 152 private: 153 WebFrame* webframe_; 154 }; 155 156 class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest { 157 protected: 158 virtual bool useFallback() { 159 return true; 160 } 161 }; 162 163 // Ensures that the example object has been bound to JS. 164 TEST_F(CppBoundClassTest, ObjectExists) { 165 std::string js = BuildJSCondition("typeof window.example", "'object'"); 166 CheckJavaScriptSuccess(js); 167 168 // An additional check to test our test. 169 js = BuildJSCondition("typeof window.invalid_object", "'undefined'"); 170 CheckJavaScriptSuccess(js); 171 } 172 173 TEST_F(CppBoundClassTest, PropertiesAreInitialized) { 174 std::string js = BuildJSCondition("example.my_value", "10"); 175 CheckJavaScriptSuccess(js); 176 177 js = BuildJSCondition("example.my_other_value", "'Reinitialized!'"); 178 CheckJavaScriptSuccess(js); 179 } 180 181 TEST_F(CppBoundClassTest, SubOject) { 182 std::string js = BuildJSCondition("typeof window.example.sub_object", 183 "'object'"); 184 CheckJavaScriptSuccess(js); 185 186 js = BuildJSCondition("example.sub_object.sub_value", "'sub!'"); 187 CheckJavaScriptSuccess(js); 188 } 189 190 TEST_F(CppBoundClassTest, SetAndGetProperties) { 191 // The property on the left will be set to the value on the right, then 192 // checked to make sure it holds that same value. 193 static const std::string tests[] = { 194 "example.my_value", "7", 195 "example.my_value", "'test'", 196 "example.my_other_value", "3.14", 197 "example.my_other_value", "false", 198 "" // Array end marker: insert additional test pairs before this. 199 }; 200 201 for (int i = 0; tests[i] != ""; i += 2) { 202 std::string left = tests[i]; 203 std::string right = tests[i + 1]; 204 // left = right; 205 std::string js = left; 206 js.append(" = "); 207 js.append(right); 208 js.append(";"); 209 js.append(BuildJSCondition(left, right)); 210 CheckJavaScriptSuccess(js); 211 } 212 } 213 214 TEST_F(CppBoundClassTest, SetAndGetPropertiesWithCallbacks) { 215 // TODO(dglazkov): fix NPObject issues around failing property setters and 216 // getters and add tests for situations when GetProperty or SetProperty fail. 217 std::string js = "var result = 'SUCCESS';\n" 218 "example.my_value_with_callback = 10;\n" 219 "if (example.my_value_with_callback != 10)\n" 220 " result = 'FAIL: unable to set property.';\n" 221 "example.my_value_with_callback = 11;\n" 222 "if (example.my_value_with_callback != 11)\n" 223 " result = 'FAIL: unable to set property again';\n" 224 "if (example.same != 42)\n" 225 " result = 'FAIL: same property should always be 42';\n" 226 "example.same = 24;\n" 227 "if (example.same != 42)\n" 228 " result = 'FAIL: same property should always be 42';\n" 229 "document.writeln(result);\n"; 230 CheckJavaScriptSuccess(js); 231 } 232 233 TEST_F(CppBoundClassTest, InvokeMethods) { 234 // The expression on the left is expected to return the value on the right. 235 static const std::string tests[] = { 236 "example.echoValue(true)", "true", 237 "example.echoValue(13)", "13", 238 "example.echoValue(2.718)", "2.718", 239 "example.echoValue('yes')", "'yes'", 240 "example.echoValue()", "null", // Too few arguments 241 242 "example.echoType(false)", "true", 243 "example.echoType(19)", "3.14159", 244 "example.echoType(9.876)", "3.14159", 245 "example.echoType('test string')", "'Success!'", 246 "example.echoType()", "null", // Too few arguments 247 248 // Comparing floats that aren't integer-valued is usually problematic due 249 // to rounding, but exact powers of 2 should also be safe. 250 "example.plus(2.5, 18.0)", "20.5", 251 "example.plus(2, 3.25)", "5.25", 252 "example.plus(2, 3)", "5", 253 "example.plus()", "null", // Too few arguments 254 "example.plus(1)", "null", // Too few arguments 255 "example.plus(1, 'test')", "null", // Wrong argument type 256 "example.plus('test', 2)", "null", // Wrong argument type 257 "example.plus('one', 'two')", "null", // Wrong argument type 258 "" // Array end marker: insert additional test pairs before this. 259 }; 260 261 for (int i = 0; tests[i] != ""; i+= 2) { 262 std::string left = tests[i]; 263 std::string right = tests[i + 1]; 264 std::string js = BuildJSCondition(left, right); 265 CheckJavaScriptSuccess(js); 266 } 267 268 std::string js = "example.my_value = 3.25; example.my_other_value = 1.25;"; 269 js.append(BuildJSCondition( 270 "example.plus(example.my_value, example.my_other_value)", "4.5")); 271 CheckJavaScriptSuccess(js); 272 } 273 274 // Tests that invoking a nonexistent method with no fallback method stops the 275 // script's execution 276 TEST_F(CppBoundClassTest, 277 InvokeNonexistentMethodNoFallback) { 278 std::string js = "example.nonExistentMethod();document.writeln('SUCCESS');"; 279 CheckJavaScriptFailure(js); 280 } 281 282 // Ensures existent methods can be invoked successfully when the fallback method 283 // is used 284 TEST_F(CppBoundClassWithFallbackMethodTest, 285 InvokeExistentMethodsWithFallback) { 286 std::string js = BuildJSCondition("example.echoValue(34)", "34"); 287 CheckJavaScriptSuccess(js); 288 } 289 290 } // namespace 291