| // Copyright 2017 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. |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/json/json_reader.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "content/public/test/browser_test.h" |
| #include "headless/grit/headless_browsertest_resources.h" |
| #include "headless/public/devtools/domains/runtime.h" |
| #include "headless/public/headless_browser.h" |
| #include "headless/public/headless_devtools_client.h" |
| #include "headless/public/headless_tab_socket.h" |
| #include "headless/public/headless_web_contents.h" |
| #include "headless/test/headless_browser_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| namespace headless { |
| |
| class HeadlessJsBindingsTest |
| : public HeadlessAsyncDevTooledBrowserTest, |
| public HeadlessTabSocket::Listener, |
| public HeadlessDevToolsClient::RawProtocolListener { |
| public: |
| void SetUp() override { |
| options()->mojo_service_names.insert("headless::TabSocket"); |
| HeadlessAsyncDevTooledBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| base::ThreadRestrictions::SetIOAllowed(true); |
| base::FilePath pak_path; |
| ASSERT_TRUE(PathService::Get(base::DIR_MODULE, &pak_path)); |
| pak_path = pak_path.AppendASCII("headless_browser_tests.pak"); |
| ResourceBundle::GetSharedInstance().AddDataPackFromPath( |
| pak_path, ui::SCALE_FACTOR_NONE); |
| } |
| |
| void RunDevTooledTest() override { |
| devtools_client_->SetRawProtocolListener(this); |
| headless_tab_socket_ = web_contents_->GetHeadlessTabSocket(); |
| DCHECK(headless_tab_socket_); |
| headless_tab_socket_->SetListener(this); |
| PrepareToRunJsBindingsTest(); |
| } |
| |
| void PrepareToRunJsBindingsTest() { |
| devtools_client_->GetRuntime()->Evaluate( |
| ResourceBundle::GetSharedInstance() |
| .GetRawDataResource(DEVTOOLS_BINDINGS_TEST) |
| .as_string(), |
| base::Bind(&HeadlessJsBindingsTest::OnEvaluateResult, |
| base::Unretained(this))); |
| } |
| |
| virtual void RunJsBindingsTest() = 0; |
| virtual std::string GetExpectedResult() = 0; |
| |
| void OnEvaluateResult(std::unique_ptr<runtime::EvaluateResult> result) { |
| if (!result->HasExceptionDetails()) { |
| RunJsBindingsTest(); |
| } else { |
| FailOnJsEvaluateException(std::move(result)); |
| } |
| } |
| |
| void FailOnJsEvaluateException( |
| std::unique_ptr<runtime::EvaluateResult> result) { |
| if (!result->HasExceptionDetails()) |
| return; |
| |
| FinishAsynchronousTest(); |
| |
| const runtime::ExceptionDetails* exception_details = |
| result->GetExceptionDetails(); |
| FAIL() << exception_details->GetText() |
| << (exception_details->HasException() |
| ? exception_details->GetException()->GetDescription().c_str() |
| : ""); |
| } |
| |
| void OnMessageFromTab(const std::string& json_message) override { |
| std::unique_ptr<base::Value> message = |
| base::JSONReader::Read(json_message, base::JSON_PARSE_RFC); |
| const base::DictionaryValue* message_dict; |
| const base::DictionaryValue* params_dict; |
| std::string method; |
| int id; |
| if (!message || !message->GetAsDictionary(&message_dict) || |
| !message_dict->GetString("method", &method) || |
| !message_dict->GetDictionary("params", ¶ms_dict) || |
| !message_dict->GetInteger("id", &id)) { |
| FinishAsynchronousTest(); |
| FAIL() << "Badly formed message " << json_message; |
| return; |
| } |
| |
| if (method == "__Result") { |
| std::string result; |
| params_dict->GetString("result", &result); |
| EXPECT_EQ(GetExpectedResult(), result); |
| FinishAsynchronousTest(); |
| return; |
| } |
| |
| devtools_client_->SendRawDevToolsMessage(json_message); |
| } |
| |
| HeadlessWebContents::Builder::TabSocketType GetTabSocketType() override { |
| return HeadlessWebContents::Builder::TabSocketType::MAIN_WORLD; |
| } |
| |
| bool OnProtocolMessage(const std::string& devtools_agent_host_id, |
| const std::string& json_message, |
| const base::DictionaryValue& parsed_message) override { |
| int id; |
| // If |parsed_message| contains an id we know this is a message reply. |
| if (parsed_message.GetInteger("id", &id)) { |
| // We are only interested in message replies (ones with an id) where the |
| // id is odd. The reason is HeadlessDevToolsClientImpl uses even/oddness |
| // to distinguish between commands send from the C++ bindings and those |
| // via HeadlessDevToolsClientImpl::SendRawDevToolsMessage. |
| if ((id % 2) == 0) |
| return false; |
| |
| headless_tab_socket_->SendMessageToTab(json_message); |
| return true; |
| } |
| |
| std::string method; |
| if (!parsed_message.GetString("method", &method)) |
| return false; |
| |
| headless_tab_socket_->SendMessageToTab(json_message); |
| |
| // Check which domain the event belongs to, if it's the DOM domain then |
| // assume js handled it. |
| std::vector<base::StringPiece> sections = SplitStringPiece( |
| method, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| return sections[0] == "DOM" || sections[0] == "Runtime"; |
| } |
| |
| private: |
| HeadlessTabSocket* headless_tab_socket_; |
| }; |
| |
| class SimpleCommandJsBindingsTest : public HeadlessJsBindingsTest { |
| public: |
| void RunJsBindingsTest() override { |
| devtools_client_->GetRuntime()->Evaluate( |
| "new chromium.BindingsTest().evalOneAddOne();", |
| base::Bind(&HeadlessJsBindingsTest::FailOnJsEvaluateException, |
| base::Unretained(this))); |
| } |
| |
| std::string GetExpectedResult() override { return "2"; } |
| }; |
| |
| HEADLESS_ASYNC_DEVTOOLED_TEST_F(SimpleCommandJsBindingsTest); |
| |
| class ExperimentalCommandJsBindingsTest : public HeadlessJsBindingsTest { |
| public: |
| void RunJsBindingsTest() override { |
| devtools_client_->GetRuntime()->Evaluate( |
| "new chromium.BindingsTest().getIsolatedWorldName();", |
| base::Bind(&HeadlessJsBindingsTest::FailOnJsEvaluateException, |
| base::Unretained(this))); |
| } |
| |
| std::string GetExpectedResult() override { |
| return "Created Test Isolated World"; |
| } |
| }; |
| |
| HEADLESS_ASYNC_DEVTOOLED_TEST_F(ExperimentalCommandJsBindingsTest); |
| |
| class SimpleEventJsBindingsTest : public HeadlessJsBindingsTest { |
| public: |
| void RunJsBindingsTest() override { |
| devtools_client_->GetRuntime()->Evaluate( |
| "new chromium.BindingsTest().listenForChildNodeCountUpdated();", |
| base::Bind(&HeadlessJsBindingsTest::FailOnJsEvaluateException, |
| base::Unretained(this))); |
| } |
| |
| std::string GetExpectedResult() override { |
| return "{\"nodeId\":4,\"childNodeCount\":1}"; |
| } |
| }; |
| |
| HEADLESS_ASYNC_DEVTOOLED_TEST_F(SimpleEventJsBindingsTest); |
| |
| } // namespace headless |