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:
CppBindingExampleSubObject()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:
CppBindingExampleWithOptionalFallback()38 CppBindingExampleWithOptionalFallback() {
39 BindProperty("sub_object", sub_object_.GetAsCppVariant());
40 }
41
set_fallback_method_enabled(bool state)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.
fallbackMethod(const CppArgumentList & args,CppVariant * result)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
ExampleTestShell(bool use_fallback_method)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.
BindJSObjectsToWindow(WebFrame * frame)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.
PublicInitialize(const std::string & starting_url)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.
CreateEmptyWindow()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.
ExecuteJavaScript(const std::string & javascript)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".
CheckJavaScriptSuccess(const std::string & javascript)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.
CheckJavaScriptFailure(const std::string & javascript)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.)
BuildJSCondition(std::string left,std::string right)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:
useFallback()148 virtual bool useFallback() {
149 return false;
150 }
151
152 private:
153 WebFrame* webframe_;
154 };
155
156 class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest {
157 protected:
useFallback()158 virtual bool useFallback() {
159 return true;
160 }
161 };
162
163 // Ensures that the example object has been bound to JS.
TEST_F(CppBoundClassTest,ObjectExists)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
TEST_F(CppBoundClassTest,PropertiesAreInitialized)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
TEST_F(CppBoundClassTest,SubOject)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
TEST_F(CppBoundClassTest,SetAndGetProperties)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
TEST_F(CppBoundClassTest,SetAndGetPropertiesWithCallbacks)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
TEST_F(CppBoundClassTest,InvokeMethods)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
TEST_F(CppBoundClassTest,InvokeNonexistentMethodNoFallback)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
TEST_F(CppBoundClassWithFallbackMethodTest,InvokeExistentMethodsWithFallback)284 TEST_F(CppBoundClassWithFallbackMethodTest,
285 InvokeExistentMethodsWithFallback) {
286 std::string js = BuildJSCondition("example.echoValue(34)", "34");
287 CheckJavaScriptSuccess(js);
288 }
289
290 } // namespace
291