blob: 9fa588fc4dc0e75489daadc96cda32df8180e670 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/test/chromedriver/chrome/chrome_impl.h"
#include <stddef.h>
#include <utility>
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/devtools_client.h"
#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
#include "chrome/test/chromedriver/chrome/devtools_http_client.h"
#include "chrome/test/chromedriver/chrome/page_load_strategy.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/web_view_impl.h"
ChromeImpl::~ChromeImpl() {
}
Status ChromeImpl::GetAsDesktop(ChromeDesktopImpl** desktop) {
return Status(kUnknownError, "operation unsupported");
}
const BrowserInfo* ChromeImpl::GetBrowserInfo() const {
return devtools_http_client_->browser_info();
}
bool ChromeImpl::HasCrashedWebView() {
for (const auto& view : web_views_) {
if (view->WasCrashed())
return true;
}
return false;
}
Status ChromeImpl::GetWebViewIdForFirstTab(std::string* web_view_id,
bool w3c_compliant) {
WebViewsInfo views_info;
Status status = devtools_http_client_->GetWebViewsInfo(&views_info);
if (status.IsError())
return status;
UpdateWebViews(views_info, w3c_compliant);
for (size_t i = 0; i < views_info.GetSize(); ++i) {
const WebViewInfo& view = views_info.Get(i);
if (view.type == WebViewInfo::kPage) {
*web_view_id = view.id;
return Status(kOk);
}
}
return Status(kUnknownError, "unable to discover open window in chrome");
}
Status ChromeImpl::GetWebViewIds(std::list<std::string>* web_view_ids,
bool w3c_compliant) {
WebViewsInfo views_info;
Status status = devtools_http_client_->GetWebViewsInfo(&views_info);
if (status.IsError())
return status;
UpdateWebViews(views_info, w3c_compliant);
std::list<std::string> web_view_ids_tmp;
for (const auto& view : web_views_)
web_view_ids_tmp.push_back(view->GetId());
web_view_ids->swap(web_view_ids_tmp);
return Status(kOk);
}
void ChromeImpl::UpdateWebViews(const WebViewsInfo& views_info,
bool w3c_compliant) {
// Check if some web views are closed (or in the case of background pages,
// become inactive).
auto it = web_views_.begin();
while (it != web_views_.end()) {
const WebViewInfo* view = views_info.GetForId((*it)->GetId());
if (!view || view->IsInactiveBackgroundPage()) {
it = web_views_.erase(it);
} else {
++it;
}
}
// Check for newly-opened web views.
for (size_t i = 0; i < views_info.GetSize(); ++i) {
const WebViewInfo& view = views_info.Get(i);
if (devtools_http_client_->IsBrowserWindow(view) &&
!view.IsInactiveBackgroundPage()) {
bool found = false;
for (const auto& web_view : web_views_) {
if (web_view->GetId() == view.id) {
found = true;
break;
}
}
if (!found) {
std::unique_ptr<DevToolsClient> client(
devtools_http_client_->CreateClient(view.id));
for (const auto& listener : devtools_event_listeners_)
client->AddListener(listener.get());
// OnConnected will fire when DevToolsClient connects later.
CHECK(!page_load_strategy_.empty());
web_views_.push_back(std::make_unique<WebViewImpl>(
view.id, w3c_compliant, devtools_http_client_->browser_info(),
std::move(client), devtools_http_client_->device_metrics(),
page_load_strategy_));
}
}
}
}
Status ChromeImpl::GetWebViewById(const std::string& id, WebView** web_view) {
for (const auto& view : web_views_) {
if (view->GetId() == id) {
*web_view = view.get();
return Status(kOk);
}
}
return Status(kUnknownError, "web view not found");
}
Status ChromeImpl::GetWindow(const std::string& target_id, Window* window) {
Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError())
return status;
base::DictionaryValue params;
params.SetString("targetId", target_id);
std::unique_ptr<base::DictionaryValue> result;
status = devtools_websocket_client_->SendCommandAndGetResult(
"Browser.getWindowForTarget", params, &result);
if (status.IsError())
return status;
return ParseWindow(std::move(result), window);
}
Status ChromeImpl::GetWindowPosition(const std::string& target_id,
int* x,
int* y) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
*x = window.left;
*y = window.top;
return Status(kOk);
}
Status ChromeImpl::SetWindowPosition(const std::string& target_id,
int x,
int y) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
if (window.state != "normal") {
// restore window to normal first to allow position change.
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetInteger("left", x);
bounds->SetInteger("top", y);
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::MaximizeWindow(const std::string& target_id) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
if (window.state == "maximized")
return Status(kOk);
if (window.state != "normal") {
// always restore window to normal first, since chrome ui doesn't allow
// maximizing a minimized or fullscreen window.
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "maximized");
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::MinimizeWindow(const std::string& target_id) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
if (window.state == "minimized")
return Status(kOk);
if (window.state != "normal") {
// restore window to normal first
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "minimized");
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::FullScreenWindow(const std::string& target_id) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
if (window.state == "fullscreen")
return Status(kOk);
if (window.state != "normal") {
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "fullscreen");
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::SetWindowRect(const std::string& target_id,
const base::DictionaryValue& params) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
auto bounds = std::make_unique<base::DictionaryValue>();
// fully exit fullscreen
if (window.state != "normal") {
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
// window position
int x = 0;
int y = 0;
if (params.GetInteger("x", &x) && params.GetInteger("y", &y)) {
bounds->SetInteger("left", x);
bounds->SetInteger("top", y);
}
// window size
int width = 0;
int height = 0;
if (params.GetInteger("width", &width) &&
params.GetInteger("height", &height)) {
bounds->SetInteger("width", width);
bounds->SetInteger("height", height);
}
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::GetWindowSize(const std::string& target_id,
int* width,
int* height) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
*width = window.width;
*height = window.height;
return Status(kOk);
}
Status ChromeImpl::GetWindowBounds(int window_id, Window* window) {
Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError())
return status;
base::DictionaryValue params;
params.SetInteger("windowId", window_id);
std::unique_ptr<base::DictionaryValue> result;
status = devtools_websocket_client_->SendCommandAndGetResult(
"Browser.getWindowBounds", params, &result);
if (status.IsError())
return status;
return ParseWindowBounds(std::move(result), window);
}
Status ChromeImpl::SetWindowBounds(
int window_id,
std::unique_ptr<base::DictionaryValue> bounds) {
Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError())
return status;
base::DictionaryValue params;
params.SetInteger("windowId", window_id);
params.Set("bounds", bounds->CreateDeepCopy());
status = devtools_websocket_client_->SendCommand("Browser.setWindowBounds",
params);
if (status.IsError())
return status;
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
std::string state;
if (!bounds->GetString("windowState", &state))
return Status(kOk);
Window window;
status = GetWindowBounds(window_id, &window);
if (status.IsError())
return status;
if (window.state == state)
return Status(kOk);
if (state == "maximized" && window.state == "normal") {
// Maximize window is not supported in some environment, such as Mac Chrome
// version 70 and above, or Linux without a window manager.
// In these cases, we simulate window maximization by setting window size
// to equal to screen size. This is accordance with the W3C spec at
// https://www.w3.org/TR/webdriver1/#dfn-maximize-the-window.
// Get a WebView, then use it to send JavaScript to query screen size.
if (web_views_.size() == 0)
return Status(kUnknownError, "no WebView");
WebView* web_view = web_views_.begin()->get();
std::unique_ptr<base::Value> result;
status = web_view->EvaluateScript(
std::string(),
"({width: screen.availWidth, height: screen.availHeight})", &result);
if (status.IsError())
return Status(kUnknownError, "JavaScript code failed", status);
const base::Value* width =
result->FindKeyOfType("width", base::Value::Type::INTEGER);
const base::Value* height =
result->FindKeyOfType("height", base::Value::Type::INTEGER);
if (width == nullptr || height == nullptr)
return Status(kUnknownError, "unexpected JavaScript result");
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetInteger("width", width->GetInt());
bounds->SetInteger("height", height->GetInt());
bounds->SetInteger("left", 0);
bounds->SetInteger("top", 0);
params.Set("bounds", bounds->CreateDeepCopy());
return devtools_websocket_client_->SendCommand("Browser.setWindowBounds",
params);
} else {
return Status(kUnknownError, "failed to change window state to " + state +
", current state is " + window.state);
}
}
Status ChromeImpl::SetWindowSize(const std::string& target_id,
int width,
int height) {
Window window;
Status status = GetWindow(target_id, &window);
if (status.IsError())
return status;
if (window.state != "normal") {
// restore window to normal first to allow size change.
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetString("windowState", "normal");
status = SetWindowBounds(window.id, std::move(bounds));
if (status.IsError())
return status;
}
auto bounds = std::make_unique<base::DictionaryValue>();
bounds->SetInteger("width", width);
bounds->SetInteger("height", height);
return SetWindowBounds(window.id, std::move(bounds));
}
Status ChromeImpl::ParseWindow(std::unique_ptr<base::DictionaryValue> params,
Window* window) {
if (!params->GetInteger("windowId", &window->id))
return Status(kUnknownError, "no window id in response");
return ParseWindowBounds(std::move(params), window);
}
Status ChromeImpl::ParseWindowBounds(
std::unique_ptr<base::DictionaryValue> params,
Window* window) {
const base::Value* value = nullptr;
const base::DictionaryValue* bounds_dict = nullptr;
if (!params->Get("bounds", &value) || !value->GetAsDictionary(&bounds_dict))
return Status(kUnknownError, "no window bounds in response");
if (!bounds_dict->GetString("windowState", &window->state))
return Status(kUnknownError, "no window state in window bounds");
if (!bounds_dict->GetInteger("left", &window->left))
return Status(kUnknownError, "no left offset in window bounds");
if (!bounds_dict->GetInteger("top", &window->top))
return Status(kUnknownError, "no top offset in window bounds");
if (!bounds_dict->GetInteger("width", &window->width))
return Status(kUnknownError, "no width in window bounds");
if (!bounds_dict->GetInteger("height", &window->height))
return Status(kUnknownError, "no height in window bounds");
return Status(kOk);
}
Status ChromeImpl::CloseWebView(const std::string& id) {
Status status = devtools_http_client_->CloseWebView(id);
if (status.IsError())
return status;
for (auto iter = web_views_.begin(); iter != web_views_.end(); ++iter) {
if ((*iter)->GetId() == id) {
web_views_.erase(iter);
break;
}
}
return Status(kOk);
}
Status ChromeImpl::ActivateWebView(const std::string& id) {
return devtools_http_client_->ActivateWebView(id);
}
Status ChromeImpl::SetAcceptInsecureCerts() {
Status status = devtools_websocket_client_->ConnectIfNecessary();
if (status.IsError())
return status;
base::DictionaryValue params;
params.SetBoolean("ignore", true);
// We ignore the status returned by this command - If it is an error, the
// target likely doesn't yet support the command. In that case, we'll fall
// back to --ignore-certificate-errors.
// TODO(eseckler): Handle status once we remove support for
// --ignore-certificate-errors.
devtools_websocket_client_->SendCommand("Security.setIgnoreCertificateErrors",
params);
return Status(kOk);
}
bool ChromeImpl::IsMobileEmulationEnabled() const {
return false;
}
bool ChromeImpl::HasTouchScreen() const {
return false;
}
std::string ChromeImpl::page_load_strategy() const {
CHECK(!page_load_strategy_.empty());
return page_load_strategy_;
}
Status ChromeImpl::Quit() {
Status status = QuitImpl();
if (status.IsOk())
quit_ = true;
return status;
}
ChromeImpl::ChromeImpl(std::unique_ptr<DevToolsHttpClient> http_client,
std::unique_ptr<DevToolsClient> websocket_client,
std::vector<std::unique_ptr<DevToolsEventListener>>
devtools_event_listeners,
std::string page_load_strategy)
: quit_(false),
devtools_http_client_(std::move(http_client)),
devtools_websocket_client_(std::move(websocket_client)),
devtools_event_listeners_(std::move(devtools_event_listeners)),
page_load_strategy_(page_load_strategy) {}