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
21using WebKit::WebFrame;
22
23namespace {
24
25class 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
36class 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
57class 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
81class 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
147protected:
148  virtual bool useFallback() {
149    return false;
150  }
151
152private:
153  WebFrame* webframe_;
154};
155
156class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest {
157protected:
158  virtual bool useFallback() {
159    return true;
160  }
161};
162
163// Ensures that the example object has been bound to JS.
164TEST_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
173TEST_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
181TEST_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
190TEST_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
214TEST_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
233TEST_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
276TEST_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
284TEST_F(CppBoundClassWithFallbackMethodTest,
285       InvokeExistentMethodsWithFallback) {
286  std::string js = BuildJSCondition("example.echoValue(34)", "34");
287  CheckJavaScriptSuccess(js);
288}
289
290}  // namespace
291