// Copyright (c) 2009 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #include "include/cef.h" #include "include/cef_runnable.h" #include "testing/gtest/include/gtest/gtest.h" #include "test_handler.h" namespace { bool g_V8TestV8HandlerExecuteCalled; bool g_V8TestV8HandlerExecute2Called; class V8TestV8Handler : public CefThreadSafeBase { public: V8TestV8Handler(bool bindingTest) { binding_test_ = bindingTest; } virtual bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { TestExecute(name, object, arguments, retval, exception); return true; } void TestExecute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { if(name == "execute") { g_V8TestV8HandlerExecuteCalled = true; ASSERT_EQ((size_t)8, arguments.size()); int argct = 0; // basic types ASSERT_TRUE(arguments[argct]->IsInt()); ASSERT_EQ(5, arguments[argct]->GetIntValue()); argct++; ASSERT_TRUE(arguments[argct]->IsDouble()); ASSERT_EQ(6.543, arguments[argct]->GetDoubleValue()); argct++; ASSERT_TRUE(arguments[argct]->IsBool()); ASSERT_EQ(true, arguments[argct]->GetBoolValue()); argct++; ASSERT_TRUE(arguments[argct]->IsString()); ASSERT_EQ(arguments[argct]->GetStringValue(), "test string"); argct++; CefRefPtr value; // array ASSERT_TRUE(arguments[argct]->IsArray()); ASSERT_EQ(4, arguments[argct]->GetArrayLength()); { int subargct = 0; value = arguments[argct]->GetValue(subargct); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(7, value->GetIntValue()); subargct++; value = arguments[argct]->GetValue(subargct); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsDouble()); ASSERT_EQ(5.432, value->GetDoubleValue()); subargct++; value = arguments[argct]->GetValue(subargct); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsBool()); ASSERT_EQ(false, value->GetBoolValue()); subargct++; value = arguments[argct]->GetValue(subargct); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsString()); ASSERT_EQ(value->GetStringValue(), "another string"); subargct++; } argct++; // object ASSERT_TRUE(arguments[argct]->IsObject()); { value = arguments[argct]->GetValue("arg0"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(2, value->GetIntValue()); value = arguments[argct]->GetValue("arg1"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsDouble()); ASSERT_EQ(3.433, value->GetDoubleValue()); value = arguments[argct]->GetValue("arg2"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsBool()); ASSERT_EQ(true, value->GetBoolValue()); value = arguments[argct]->GetValue("arg3"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsString()); ASSERT_EQ(value->GetStringValue(), "some string"); } argct++; // function that returns a value ASSERT_TRUE(arguments[argct]->IsFunction()); { CefV8ValueList args; args.push_back(CefV8Value::CreateInt(5)); args.push_back(CefV8Value::CreateDouble(3.5)); args.push_back(CefV8Value::CreateBool(true)); args.push_back(CefV8Value::CreateString("10")); CefRefPtr rv; CefString exception; ASSERT_TRUE(arguments[argct]->ExecuteFunction( arguments[argct], args, rv, exception)); ASSERT_TRUE(rv.get() != NULL); ASSERT_TRUE(rv->IsDouble()); ASSERT_EQ(19.5, rv->GetDoubleValue()); } argct++; // function that throws an exception ASSERT_TRUE(arguments[argct]->IsFunction()); { CefV8ValueList args; args.push_back(CefV8Value::CreateDouble(5)); args.push_back(CefV8Value::CreateDouble(0)); CefRefPtr rv; CefString exception; ASSERT_TRUE(arguments[argct]->ExecuteFunction( arguments[argct], args, rv, exception)); ASSERT_EQ(exception, "Uncaught My Exception"); } argct++; if(binding_test_) { // values value = object->GetValue("intVal"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(12, value->GetIntValue()); value = object->GetValue("doubleVal"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsDouble()); ASSERT_EQ(5.432, value->GetDoubleValue()); value = object->GetValue("boolVal"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsBool()); ASSERT_EQ(true, value->GetBoolValue()); value = object->GetValue("stringVal"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsString()); ASSERT_EQ(value->GetStringValue(), "the string"); value = object->GetValue("arrayVal"); ASSERT_TRUE(value.get() != NULL); ASSERT_TRUE(value->IsArray()); { CefRefPtr value2; int subargct = 0; value2 = value->GetValue(subargct); ASSERT_TRUE(value2.get() != NULL); ASSERT_TRUE(value2->IsInt()); ASSERT_EQ(4, value2->GetIntValue()); subargct++; value2 = value->GetValue(subargct); ASSERT_TRUE(value2.get() != NULL); ASSERT_TRUE(value2->IsDouble()); ASSERT_EQ(120.43, value2->GetDoubleValue()); subargct++; value2 = value->GetValue(subargct); ASSERT_TRUE(value2.get() != NULL); ASSERT_TRUE(value2->IsBool()); ASSERT_EQ(true, value2->GetBoolValue()); subargct++; value2 = value->GetValue(subargct); ASSERT_TRUE(value2.get() != NULL); ASSERT_TRUE(value2->IsString()); ASSERT_EQ(value2->GetStringValue(), "a string"); subargct++; } } retval = CefV8Value::CreateInt(5); } else if(name == "execute2") { g_V8TestV8HandlerExecute2Called = true; // check the result of calling the "execute" function ASSERT_EQ((size_t)1, arguments.size()); ASSERT_TRUE(arguments[0]->IsInt()); ASSERT_EQ(5, arguments[0]->GetIntValue()); } } bool binding_test_; }; class V8TestHandler : public TestHandler { public: V8TestHandler(bool bindingTest) { binding_test_ = bindingTest; } virtual void RunTest() { std::string object; if(binding_test_) { // binding uses the window object object = "window.test"; } else { // extension uses a global object object = "test"; } std::stringstream testHtml; testHtml << "" "" ""; AddResource("http://tests/run.html", testHtml.str(), "text/html"); CreateBrowser("http://tests/run.html"); } virtual RetVal HandleLoadEnd(CefRefPtr browser, CefRefPtr frame, bool isMainContent, int httpStatusCode) { if(!browser->IsPopup() && !frame.get()) DestroyTest(); return RV_CONTINUE; } virtual RetVal HandleJSBinding(CefRefPtr browser, CefRefPtr frame, CefRefPtr object) { if(binding_test_) { TestHandleJSBinding(browser, frame, object); return RV_HANDLED; } return RV_CONTINUE; } void TestHandleJSBinding(CefRefPtr browser, CefRefPtr frame, CefRefPtr object) { // Create the new V8 object CefRefPtr testObj = CefV8Value::CreateObject(NULL); ASSERT_TRUE(testObj.get() != NULL); ASSERT_TRUE(object->SetValue("test", testObj)); // Create an instance of V8ExecuteV8Handler CefRefPtr testHandler(new V8TestV8Handler(true)); ASSERT_TRUE(testHandler.get() != NULL); // Add the functions CefRefPtr testFunc; testFunc = CefV8Value::CreateFunction("execute", testHandler); ASSERT_TRUE(testFunc.get() != NULL); ASSERT_TRUE(testObj->SetValue("execute", testFunc)); testFunc = CefV8Value::CreateFunction("execute2", testHandler); ASSERT_TRUE(testFunc.get() != NULL); ASSERT_TRUE(testObj->SetValue("execute2", testFunc)); // Add the values ASSERT_TRUE(testObj->SetValue("intVal", CefV8Value::CreateInt(12))); ASSERT_TRUE(testObj->SetValue("doubleVal", CefV8Value::CreateDouble(5.432))); ASSERT_TRUE(testObj->SetValue("boolVal", CefV8Value::CreateBool(true))); ASSERT_TRUE(testObj->SetValue("stringVal", CefV8Value::CreateString("the string"))); CefRefPtr testArray(CefV8Value::CreateArray()); ASSERT_TRUE(testArray.get() != NULL); ASSERT_TRUE(testObj->SetValue("arrayVal", testArray)); ASSERT_TRUE(testArray->SetValue(0, CefV8Value::CreateInt(4))); ASSERT_TRUE(testArray->SetValue(1, CefV8Value::CreateDouble(120.43))); ASSERT_TRUE(testArray->SetValue(2, CefV8Value::CreateBool(true))); ASSERT_TRUE(testArray->SetValue(3, CefV8Value::CreateString("a string"))); } bool binding_test_; }; } // namespace // Verify window binding TEST(V8Test, Binding) { g_V8TestV8HandlerExecuteCalled = false; g_V8TestV8HandlerExecute2Called = false; CefRefPtr handler = new V8TestHandler(true); handler->ExecuteTest(); ASSERT_TRUE(g_V8TestV8HandlerExecuteCalled); ASSERT_TRUE(g_V8TestV8HandlerExecute2Called); } // Verify extensions TEST(V8Test, Extension) { g_V8TestV8HandlerExecuteCalled = false; g_V8TestV8HandlerExecute2Called = false; std::string extensionCode = "var test;" "if (!test)" " test = {};" "(function() {" " test.execute = function(a,b,c,d,e,f,g,h) {" " native function execute();" " return execute(a,b,c,d,e,f,g,h);" " };" " test.execute2 = function(a) {" " native function execute2();" " return execute2(a);" " };" "})();"; CefRegisterExtension("v8/test", extensionCode, new V8TestV8Handler(false)); CefRefPtr handler = new V8TestHandler(false); handler->ExecuteTest(); ASSERT_TRUE(g_V8TestV8HandlerExecuteCalled); ASSERT_TRUE(g_V8TestV8HandlerExecute2Called); } namespace { // Using a delegate so that the code below can remain inline. class CefV8HandlerDelegate { public: virtual bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) = 0; }; class DelegatingV8Handler : public CefThreadSafeBase { public: DelegatingV8Handler(CefV8HandlerDelegate *delegate): delegate_(delegate) { } ~DelegatingV8Handler() { } bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { return delegate_->Execute(name, object, arguments, retval, exception); } private: CefV8HandlerDelegate *delegate_; }; class TestContextHandler: public TestHandler, public CefV8HandlerDelegate { public: TestContextHandler() {} virtual void RunTest() { // Test Flow: // load main.html. // 1. main.html calls hello("main", callIFrame) in the execute handler. // The excute handler checks that "main" was called and saves // the callIFrame function, context, and receiver object. // 2. iframe.html calls hello("iframe") in the execute handler. // The execute handler checks that "iframe" was called. if both main // and iframe were called, it calls CallIFrame() // 3. CallIFrame calls "callIFrame" in main.html // 4. which calls iframe.html "calledFromMain()". // 5. which calls "fromIFrame()" in execute handler. // The execute handler checks that the entered and current urls are // what we expect: "main.html" and "iframe.html", respectively // 6. It then posts a task to call AsyncTestContext // you can validate the entered and current context are still the // same here, but it is not checked by this test case. // 7. AsyncTestContext tests to make sure that no context is set at // this point and loads "begin.html" // 8. begin.html calls "begin(func1, func2)" in the execute handler // The execute handler posts a tasks to call both of those functions // when no context is defined. Both should work with the specified // context. AsyncTestException should run first, followed by // AsyncTestNavigate() which calls the func2 to do a document.location // based loading of "end.html". // 9. end.html calls "end()" in the execute handler. // which concludes the test. std::stringstream mainHtml; mainHtml << "" "

Hello From Main Frame

" "" "" ""; AddResource("http://tests/main.html", mainHtml.str(), "text/html"); std::stringstream iframeHtml; iframeHtml << "" "

Hello From IFRAME

" "" ""; AddResource("http://tests/iframe.html", iframeHtml.str(), "text/html"); std::stringstream beginHtml; beginHtml << "" "

V8 Context Test

" "" ""; AddResource("http://tests/begin.html", beginHtml.str(), "text/html"); std::stringstream endHtml; endHtml << "" "

Navigation Succeeded!

" "" ""; AddResource("http://tests/end.html", endHtml.str(), "text/html"); CreateBrowser("http://tests/main.html"); } virtual RetVal HandleLoadEnd(CefRefPtr browser, CefRefPtr frame, bool isMainContent, int httpStatusCode) { return RV_CONTINUE; } virtual RetVal HandleJSBinding(CefRefPtr browser, CefRefPtr frame, CefRefPtr object) { CefRefPtr cc = CefV8Context::GetCurrentContext(); CefRefPtr currentBrowser = cc->GetBrowser(); CefRefPtr currentFrame = cc->GetFrame(); CefString currentURL = currentFrame->GetURL(); CefRefPtr ec = CefV8Context::GetEnteredContext(); CefRefPtr enteredBrowser = ec->GetBrowser(); CefRefPtr enteredFrame = ec->GetFrame(); CefString enteredURL = enteredFrame->GetURL(); CefRefPtr funcHandler(new DelegatingV8Handler(this)); CefRefPtr helloFunc = CefV8Value::CreateFunction("hello", funcHandler); object->SetValue("hello", helloFunc); CefRefPtr fromIFrameFunc = CefV8Value::CreateFunction("fromIFrame", funcHandler); object->SetValue("fromIFrame", fromIFrameFunc); CefRefPtr goFunc = CefV8Value::CreateFunction("begin", funcHandler); object->SetValue("begin", goFunc); CefRefPtr doneFunc = CefV8Value::CreateFunction("end", funcHandler); object->SetValue("end", doneFunc); return RV_HANDLED; } void CallIFrame() { CefV8ValueList args; CefRefPtr rv; CefString exception; CefRefPtr empty; ASSERT_TRUE(funcIFrame_->ExecuteFunctionWithContext(contextIFrame_, empty, args, rv, exception)); } void AsyncTestContext(CefRefPtr ec, CefRefPtr cc) { // we should not be in a context in this call. CefRefPtr noContext = CefV8Context::GetCurrentContext(); if (!noContext.get()) got_no_context_.yes(); CefRefPtr enteredBrowser = ec->GetBrowser(); CefRefPtr enteredFrame = ec->GetFrame(); CefString enteredURL = enteredFrame->GetURL(); CefString enteredName = enteredFrame->GetName(); CefRefPtr enteredMainFrame = enteredBrowser->GetMainFrame(); CefString enteredMainURL = enteredMainFrame->GetURL(); CefString enteredMainName = enteredMainFrame->GetName(); CefRefPtr currentBrowser = cc->GetBrowser(); CefRefPtr currentFrame = cc->GetFrame(); CefString currentURL = currentFrame->GetURL(); CefString currentName = currentFrame->GetName(); CefRefPtr currentMainFrame = currentBrowser->GetMainFrame(); CefString currentMainURL = currentMainFrame->GetURL(); CefString currentMainName = currentMainFrame->GetName(); CefRefPtr copyFromMainFrame = currentMainFrame->GetBrowser(); currentMainFrame->LoadURL("http://tests/begin.html"); } void AsyncTestException(CefRefPtr context, CefRefPtr func) { CefV8ValueList args; CefRefPtr rv; CefString exception; CefRefPtr empty; ASSERT_TRUE(func->ExecuteFunctionWithContext(context, empty, args, rv, exception)); if(exception == "Uncaught My Exception") got_exception_.yes(); } void AsyncTestNavigation(CefRefPtr context, CefRefPtr func) { CefV8ValueList args; args.push_back(CefV8Value::CreateString("http://tests/end.html")); CefRefPtr rv; CefString exception; CefRefPtr global = context->GetGlobal(); ASSERT_TRUE(func->ExecuteFunctionWithContext(context, global, args, rv, exception)); if(exception.empty()) got_navigation_.yes(); } bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { CefRefPtr cc = CefV8Context::GetCurrentContext(); CefRefPtr ec = CefV8Context::GetEnteredContext(); CefRefPtr enteredBrowser = ec->GetBrowser(); CefRefPtr enteredFrame = ec->GetFrame(); CefString enteredURL = enteredFrame->GetURL(); CefString enteredName = enteredFrame->GetName(); CefRefPtr enteredMainFrame = enteredBrowser->GetMainFrame(); CefString enteredMainURL = enteredMainFrame->GetURL(); CefString enteredMainName = enteredMainFrame->GetName(); CefRefPtr currentBrowser = cc->GetBrowser(); CefRefPtr currentFrame = cc->GetFrame(); CefString currentURL = currentFrame->GetURL(); CefString currentName = currentFrame->GetName(); CefRefPtr currentMainFrame = currentBrowser->GetMainFrame(); CefString currentMainURL = currentMainFrame->GetURL(); CefString currentMainName = currentMainFrame->GetName(); if (name == "hello") { if(arguments.size() == 2 && arguments[0]->IsString() && arguments[1]->IsFunction()) { CefString msg = arguments[0]->GetStringValue(); if(msg == "main") { got_hello_main_.yes(); contextIFrame_ = cc; funcIFrame_ = arguments[1]; } } else if(arguments.size() == 1 && arguments[0]->IsString()) { CefString msg = arguments[0]->GetStringValue(); if(msg == "iframe") got_hello_iframe_.yes(); } else return false; if(got_hello_main_ && got_hello_iframe_ && funcIFrame_->IsFunction()) { // NB: At this point, enteredURL == http://tests/iframe.html which is // expected since the iframe made the call on its own. The unexpected // behavior is that in the call to fromIFrame (below) the enteredURL // == http://tests/main.html even though the iframe.html context was // entered first. // -- Perhaps WebKit does something other than look at the bottom // of stack for the entered context. if(enteredURL == "http://tests/iframe.html") got_iframe_as_entered_url_.yes(); CallIFrame(); } return true; } else if(name == "fromIFrame") { if(enteredURL == "http://tests/main.html") got_correct_entered_url_.yes(); if(currentURL == "http://tests/iframe.html") got_correct_current_url_.yes(); CefPostTask(TID_UI, NewCefRunnableMethod(this, &TestContextHandler::AsyncTestContext, ec, cc)); return true; } else if(name == "begin") { if(arguments.size() == 2 && arguments[0]->IsFunction() && arguments[1]->IsFunction()) { CefRefPtr funcException = arguments[0]; CefRefPtr funcNavigate = arguments[1]; CefPostTask(TID_UI, NewCefRunnableMethod(this, &TestContextHandler::AsyncTestException, cc, funcException)); CefPostTask(TID_UI, NewCefRunnableMethod(this, &TestContextHandler::AsyncTestNavigation, cc, funcNavigate)); return true; } } else if (name == "end") { got_testcomplete_.yes(); DestroyTest(); return true; } return false; } // This function we will be called later to make it call into the // IFRAME, which then calls "fromIFrame" so that we can check the // entered vs current contexts are working as expected. CefRefPtr contextIFrame_; CefRefPtr funcIFrame_; TrackCallback got_hello_main_; TrackCallback got_hello_iframe_; TrackCallback got_correct_entered_url_; TrackCallback got_correct_current_url_; TrackCallback got_iframe_as_entered_url_; TrackCallback got_no_context_; TrackCallback got_exception_; TrackCallback got_navigation_; TrackCallback got_testcomplete_; }; } // namespace // Verify context works to allow async v8 callbacks TEST(V8Test, Context) { CefRefPtr handler = new TestContextHandler(); handler->ExecuteTest(); EXPECT_TRUE(handler->got_hello_main_); EXPECT_TRUE(handler->got_hello_iframe_); EXPECT_TRUE(handler->got_no_context_); EXPECT_TRUE(handler->got_iframe_as_entered_url_); EXPECT_TRUE(handler->got_correct_entered_url_); EXPECT_TRUE(handler->got_correct_current_url_); EXPECT_TRUE(handler->got_exception_); EXPECT_TRUE(handler->got_navigation_); EXPECT_TRUE(handler->got_testcomplete_); }