blob: 32cfcd52bdb11113fa5b4913800b6e4e1a77d5aa [file] [log] [blame]
// Copyright 2015 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 "blimp/engine/session/blimp_engine_session.h"
#include <string>
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "blimp/common/create_blimp_message.h"
#include "blimp/common/proto/tab_control.pb.h"
#include "blimp/engine/app/blimp_engine_config.h"
#include "blimp/engine/app/settings_manager.h"
#include "blimp/engine/app/ui/blimp_layout_manager.h"
#include "blimp/engine/app/ui/blimp_screen.h"
#include "blimp/engine/app/ui/blimp_window_tree_client.h"
#include "blimp/engine/app/ui/blimp_window_tree_host.h"
#include "blimp/engine/common/blimp_browser_context.h"
#include "blimp/net/blimp_connection.h"
#include "blimp/net/blimp_message_multiplexer.h"
#include "blimp/net/blimp_message_thread_pipe.h"
#include "blimp/net/browser_connection_handler.h"
#include "blimp/net/common.h"
#include "blimp/net/engine_authentication_handler.h"
#include "blimp/net/engine_connection_manager.h"
#include "blimp/net/null_blimp_message_processor.h"
#include "blimp/net/tcp_engine_transport.h"
#include "blimp/net/thread_pipe_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "ui/aura/client/default_capture_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/gfx/geometry/size.h"
#include "ui/wm/core/base_focus_rules.h"
#include "ui/wm/core/default_activation_client.h"
#include "ui/wm/core/focus_controller.h"
namespace blimp {
namespace engine {
namespace {
const int kDummyTabId = 0;
const float kDefaultScaleFactor = 1.f;
const int kDefaultDisplayWidth = 800;
const int kDefaultDisplayHeight = 600;
const uint16_t kDefaultPort = 25467;
// Focus rules that support activating an child window.
class FocusRulesImpl : public wm::BaseFocusRules {
public:
FocusRulesImpl() {}
~FocusRulesImpl() override {}
bool SupportsChildActivation(aura::Window* window) const override {
return true;
}
private:
DISALLOW_COPY_AND_ASSIGN(FocusRulesImpl);
};
net::IPAddress GetIPv4AnyAddress() {
return net::IPAddress(0, 0, 0, 0);
}
// Proxies calls to TaskRunner::PostTask while stripping the return value,
// which provides a suitable function prototype for binding a base::Closure.
void PostTask(const scoped_refptr<base::TaskRunner>& task_runner,
const base::Closure& closure) {
task_runner->PostTask(FROM_HERE, closure);
}
// Returns a closure that quits the current (bind-time) MessageLoop.
base::Closure QuitCurrentMessageLoopClosure() {
return base::Bind(&PostTask, base::ThreadTaskRunnerHandle::Get(),
base::MessageLoop::QuitWhenIdleClosure());
}
} // namespace
// EngineNetworkComponents is created by the BlimpEngineSession on the UI
// thread, and then used and destroyed on the IO thread.
class EngineNetworkComponents : public ConnectionHandler,
public ConnectionErrorObserver {
public:
// |net_log|: The log to use for network-related events.
// |quit_closure|: A closure which will terminate the engine when
// invoked.
EngineNetworkComponents(net::NetLog* net_log,
const base::Closure& quit_closure);
~EngineNetworkComponents() override;
// Sets up network components and starts listening for incoming connection.
// This should be called after all features have been registered so that
// received messages can be properly handled.
void Initialize(const std::string& client_token);
BrowserConnectionHandler* GetBrowserConnectionHandler();
private:
// ConnectionHandler implementation.
void HandleConnection(std::unique_ptr<BlimpConnection> connection) override;
// ConnectionErrorObserver implementation.
// Signals the engine session that an authenticated connection was
// terminated.
void OnConnectionError(int error) override;
net::NetLog* net_log_;
base::Closure quit_closure_;
std::unique_ptr<BrowserConnectionHandler> connection_handler_;
std::unique_ptr<EngineAuthenticationHandler> authentication_handler_;
std::unique_ptr<EngineConnectionManager> connection_manager_;
DISALLOW_COPY_AND_ASSIGN(EngineNetworkComponents);
};
EngineNetworkComponents::EngineNetworkComponents(
net::NetLog* net_log,
const base::Closure& quit_closure)
: net_log_(net_log),
quit_closure_(quit_closure),
connection_handler_(new BrowserConnectionHandler) {}
EngineNetworkComponents::~EngineNetworkComponents() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
void EngineNetworkComponents::Initialize(const std::string& client_token) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!connection_manager_);
// Plumb authenticated connections from the authentication handler
// to |this| (which will then pass it to |connection_handler_|.
authentication_handler_ =
base::WrapUnique(new EngineAuthenticationHandler(this, client_token));
// Plumb unauthenticated connections to |authentication_handler_|.
connection_manager_ = base::WrapUnique(
new EngineConnectionManager(authentication_handler_.get()));
// Adds BlimpTransports to connection_manager_.
net::IPEndPoint address(GetIPv4AnyAddress(), kDefaultPort);
connection_manager_->AddTransport(
base::WrapUnique(new TCPEngineTransport(address, net_log_)));
}
void EngineNetworkComponents::HandleConnection(
std::unique_ptr<BlimpConnection> connection) {
// Observe |connection| for disconnection events.
connection->AddConnectionErrorObserver(this);
connection_handler_->HandleConnection(std::move(connection));
}
void EngineNetworkComponents::OnConnectionError(int error) {
DVLOG(1) << "EngineNetworkComponents::OnConnectionError(" << error << ")";
quit_closure_.Run();
}
BrowserConnectionHandler*
EngineNetworkComponents::GetBrowserConnectionHandler() {
return connection_handler_.get();
}
BlimpEngineSession::BlimpEngineSession(
std::unique_ptr<BlimpBrowserContext> browser_context,
net::NetLog* net_log,
BlimpEngineConfig* engine_config,
SettingsManager* settings_manager)
: browser_context_(std::move(browser_context)),
engine_config_(engine_config),
screen_(new BlimpScreen),
settings_manager_(settings_manager),
settings_feature_(settings_manager_),
render_widget_feature_(settings_manager_),
net_components_(
new EngineNetworkComponents(net_log,
QuitCurrentMessageLoopClosure())) {
DCHECK(engine_config_);
DCHECK(settings_manager_);
screen_->UpdateDisplayScaleAndSize(kDefaultScaleFactor,
gfx::Size(kDefaultDisplayWidth,
kDefaultDisplayHeight));
render_widget_feature_.SetDelegate(kDummyTabId, this);
}
BlimpEngineSession::~BlimpEngineSession() {
render_widget_feature_.RemoveDelegate(kDummyTabId);
window_tree_host_->GetInputMethod()->RemoveObserver(this);
// Ensure that all WebContents are torn down first, since teardown will
// trigger RenderViewDeleted callbacks to their observers.
web_contents_.reset();
// Safely delete network components on the IO thread.
content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
net_components_.release());
}
void BlimpEngineSession::Initialize() {
DCHECK(!gfx::Screen::GetScreen());
gfx::Screen::SetScreenInstance(screen_.get());
window_tree_host_.reset(new BlimpWindowTreeHost());
screen_->set_window_tree_host(window_tree_host_.get());
window_tree_host_->InitHost();
window_tree_host_->window()->SetLayoutManager(
new BlimpLayoutManager(window_tree_host_->window()));
focus_client_.reset(new wm::FocusController(new FocusRulesImpl));
aura::client::SetFocusClient(window_tree_host_->window(),
focus_client_.get());
aura::client::SetActivationClient(window_tree_host_->window(),
focus_client_.get());
capture_client_.reset(
new aura::client::DefaultCaptureClient(window_tree_host_->window()));
window_tree_client_.reset(
new BlimpWindowTreeClient(window_tree_host_->window()));
window_tree_host_->GetInputMethod()->AddObserver(this);
window_tree_host_->SetBounds(gfx::Rect(screen_->GetPrimaryDisplay().size()));
RegisterFeatures();
// Initialize must only be posted after the RegisterFeature calls have
// completed.
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&EngineNetworkComponents::Initialize,
base::Unretained(net_components_.get()),
engine_config_->client_token()));
}
void BlimpEngineSession::RegisterFeatures() {
thread_pipe_manager_.reset(new ThreadPipeManager(
content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::IO),
content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::UI),
net_components_->GetBrowserConnectionHandler()));
// Register features' message senders and receivers.
tab_control_message_sender_ =
thread_pipe_manager_->RegisterFeature(BlimpMessage::TAB_CONTROL, this);
navigation_message_sender_ =
thread_pipe_manager_->RegisterFeature(BlimpMessage::NAVIGATION, this);
render_widget_feature_.set_render_widget_message_sender(
thread_pipe_manager_->RegisterFeature(BlimpMessage::RENDER_WIDGET,
&render_widget_feature_));
render_widget_feature_.set_input_message_sender(
thread_pipe_manager_->RegisterFeature(BlimpMessage::INPUT,
&render_widget_feature_));
render_widget_feature_.set_compositor_message_sender(
thread_pipe_manager_->RegisterFeature(BlimpMessage::COMPOSITOR,
&render_widget_feature_));
render_widget_feature_.set_ime_message_sender(
thread_pipe_manager_->RegisterFeature(BlimpMessage::IME,
&render_widget_feature_));
// The Settings feature does not need an outgoing message processor, since we
// don't send any messages to the client right now.
thread_pipe_manager_->RegisterFeature(BlimpMessage::SETTINGS,
&settings_feature_);
}
bool BlimpEngineSession::CreateWebContents(const int target_tab_id) {
DVLOG(1) << "Create tab " << target_tab_id;
// TODO(haibinlu): Support more than one active WebContents (crbug/547231).
if (web_contents_) {
DLOG(WARNING) << "Tab " << target_tab_id << " already existed";
return false;
}
content::WebContents::CreateParams create_params(browser_context_.get(),
nullptr);
std::unique_ptr<content::WebContents> new_contents =
base::WrapUnique(content::WebContents::Create(create_params));
PlatformSetContents(std::move(new_contents));
return true;
}
void BlimpEngineSession::CloseWebContents(const int target_tab_id) {
DVLOG(1) << "Close tab " << target_tab_id;
DCHECK(web_contents_);
web_contents_->Close();
}
void BlimpEngineSession::HandleResize(float device_pixel_ratio,
const gfx::Size& size) {
DVLOG(1) << "Resize to " << size.ToString() << ", " << device_pixel_ratio;
screen_->UpdateDisplayScaleAndSize(device_pixel_ratio, size);
window_tree_host_->SetBounds(gfx::Rect(size));
if (web_contents_) {
const gfx::Size size_in_dips = screen_->GetPrimaryDisplay().bounds().size();
web_contents_->GetNativeView()->SetBounds(gfx::Rect(size_in_dips));
}
if (web_contents_ && web_contents_->GetRenderViewHost() &&
web_contents_->GetRenderViewHost()->GetWidget()) {
web_contents_->GetRenderViewHost()->GetWidget()->WasResized();
}
}
void BlimpEngineSession::LoadUrl(const int target_tab_id, const GURL& url) {
DVLOG(1) << "Load URL " << url << " in tab " << target_tab_id;
if (url.is_empty()) {
return;
}
// TODO(dtrainor, haibinlu): Fix up the URL with url_fixer.h. If that doesn't
// produce a valid spec() then try to build a search query?
content::NavigationController::LoadURLParams params(url);
params.transition_type = ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
web_contents_->GetController().LoadURLWithParams(params);
web_contents_->Focus();
}
void BlimpEngineSession::GoBack(const int target_tab_id) {
DVLOG(1) << "Back in tab " << target_tab_id;
web_contents_->GetController().GoBack();
}
void BlimpEngineSession::GoForward(const int target_tab_id) {
DVLOG(1) << "Forward in tab " << target_tab_id;
web_contents_->GetController().GoForward();
}
void BlimpEngineSession::Reload(const int target_tab_id) {
DVLOG(1) << "Reload in tab " << target_tab_id;
web_contents_->GetController().Reload(true);
}
void BlimpEngineSession::OnWebGestureEvent(
content::RenderWidgetHost* render_widget_host,
std::unique_ptr<blink::WebGestureEvent> event) {
render_widget_host->ForwardGestureEvent(*event);
}
void BlimpEngineSession::OnCompositorMessageReceived(
content::RenderWidgetHost* render_widget_host,
const std::vector<uint8_t>& message) {
render_widget_host->HandleCompositorProto(message);
}
void BlimpEngineSession::OnTextInputTypeChanged(
const ui::TextInputClient* client) {}
void BlimpEngineSession::OnFocus() {}
void BlimpEngineSession::OnBlur() {}
void BlimpEngineSession::OnCaretBoundsChanged(
const ui::TextInputClient* client) {}
// Called when either:
// - the TextInputClient is changed (e.g. by a change of focus)
// - the TextInputType of the TextInputClient changes
void BlimpEngineSession::OnTextInputStateChanged(
const ui::TextInputClient* client) {
if (!web_contents_->GetRenderWidgetHostView())
return;
ui::TextInputType type =
client ? client->GetTextInputType() : ui::TEXT_INPUT_TYPE_NONE;
// TODO(shaktisahu): Propagate the new type to the client.
// Hide IME, when text input is out of focus, i.e. if the text input type
// changes to ui::TEXT_INPUT_TYPE_NONE. For other text input types,
// OnShowImeIfNeeded is used instead to send show IME request to client.
if (type == ui::TEXT_INPUT_TYPE_NONE)
render_widget_feature_.SendHideImeRequest(
kDummyTabId,
web_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost());
}
void BlimpEngineSession::OnInputMethodDestroyed(
const ui::InputMethod* input_method) {}
// Called when a user input should trigger showing the IME.
void BlimpEngineSession::OnShowImeIfNeeded() {
if (!web_contents_->GetRenderWidgetHostView() ||
!window_tree_host_->GetInputMethod()->GetTextInputClient())
return;
render_widget_feature_.SendShowImeRequest(
kDummyTabId,
web_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost(),
window_tree_host_->GetInputMethod()->GetTextInputClient());
}
void BlimpEngineSession::ProcessMessage(
std::unique_ptr<BlimpMessage> message,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
DCHECK(message->type() == BlimpMessage::TAB_CONTROL ||
message->type() == BlimpMessage::NAVIGATION);
net::Error result = net::OK;
if (message->type() == BlimpMessage::TAB_CONTROL) {
switch (message->tab_control().type()) {
case TabControlMessage::CREATE_TAB:
if (!CreateWebContents(message->target_tab_id()))
result = net::ERR_FAILED;
break;
case TabControlMessage::CLOSE_TAB:
CloseWebContents(message->target_tab_id());
case TabControlMessage::SIZE:
HandleResize(message->tab_control().size().device_pixel_ratio(),
gfx::Size(message->tab_control().size().width(),
message->tab_control().size().height()));
break;
default:
NOTIMPLEMENTED();
result = net::ERR_NOT_IMPLEMENTED;
}
} else if (message->type() == BlimpMessage::NAVIGATION && web_contents_) {
switch (message->navigation().type()) {
case NavigationMessage::LOAD_URL:
LoadUrl(message->target_tab_id(),
GURL(message->navigation().load_url().url()));
break;
case NavigationMessage::GO_BACK:
GoBack(message->target_tab_id());
break;
case NavigationMessage::GO_FORWARD:
GoForward(message->target_tab_id());
break;
case NavigationMessage::RELOAD:
Reload(message->target_tab_id());
break;
default:
NOTIMPLEMENTED();
result = net::ERR_NOT_IMPLEMENTED;
}
} else {
DVLOG(1) << "No WebContents for navigation control";
result = net::ERR_FAILED;
}
callback.Run(result);
}
void BlimpEngineSession::AddNewContents(content::WebContents* source,
content::WebContents* new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) {
NOTIMPLEMENTED();
}
content::WebContents* BlimpEngineSession::OpenURLFromTab(
content::WebContents* source,
const content::OpenURLParams& params) {
// CURRENT_TAB is the only one we implement for now.
if (params.disposition != CURRENT_TAB) {
NOTIMPLEMENTED();
return nullptr;
}
// TODO(haibinlu): Add helper method to get LoadURLParams from OpenURLParams.
content::NavigationController::LoadURLParams load_url_params(params.url);
load_url_params.source_site_instance = params.source_site_instance;
load_url_params.referrer = params.referrer;
load_url_params.frame_tree_node_id = params.frame_tree_node_id;
load_url_params.transition_type = params.transition;
load_url_params.extra_headers = params.extra_headers;
load_url_params.should_replace_current_entry =
params.should_replace_current_entry;
load_url_params.is_renderer_initiated = params.is_renderer_initiated;
source->GetController().LoadURLWithParams(load_url_params);
return source;
}
void BlimpEngineSession::RequestToLockMouse(content::WebContents* web_contents,
bool user_gesture,
bool last_unlocked_by_target) {
web_contents->GotResponseToLockMouseRequest(true);
}
void BlimpEngineSession::CloseContents(content::WebContents* source) {
if (source == web_contents_.get()) {
Observe(nullptr);
web_contents_.reset();
}
}
void BlimpEngineSession::ActivateContents(content::WebContents* contents) {
contents->GetRenderViewHost()->GetWidget()->Focus();
}
void BlimpEngineSession::ForwardCompositorProto(
content::RenderWidgetHost* render_widget_host,
const std::vector<uint8_t>& proto) {
render_widget_feature_.SendCompositorMessage(kDummyTabId, render_widget_host,
proto);
}
void BlimpEngineSession::NavigationStateChanged(
content::WebContents* source,
content::InvalidateTypes changed_flags) {
if (source != web_contents_.get() || !changed_flags)
return;
NavigationMessage* navigation_message;
std::unique_ptr<BlimpMessage> message =
CreateBlimpMessage(&navigation_message, kDummyTabId);
navigation_message->set_type(NavigationMessage::NAVIGATION_STATE_CHANGED);
NavigationStateChangeMessage* details =
navigation_message->mutable_navigation_state_changed();
if (changed_flags & content::InvalidateTypes::INVALIDATE_TYPE_URL)
details->set_url(source->GetURL().spec());
if (changed_flags & content::InvalidateTypes::INVALIDATE_TYPE_TAB) {
// TODO(dtrainor): Serialize the favicon? crbug.com/597094.
DVLOG(3) << "Tab favicon changed";
}
if (changed_flags & content::InvalidateTypes::INVALIDATE_TYPE_TITLE)
details->set_title(base::UTF16ToUTF8(source->GetTitle()));
if (changed_flags & content::InvalidateTypes::INVALIDATE_TYPE_LOAD)
details->set_loading(source->IsLoading());
navigation_message_sender_->ProcessMessage(std::move(message),
net::CompletionCallback());
}
void BlimpEngineSession::LoadProgressChanged(
content::WebContents* source, double progress) {
if (source != web_contents_.get())
return;
bool page_load_completed = (progress == 1.0);
// If the client has been notified of a page load completed change, avoid
// sending another message. For the first navigation, the initial value used
// by the client is already false.
if (last_page_load_completed_value_ == page_load_completed)
return;
NavigationMessage* navigation_message = nullptr;
std::unique_ptr<BlimpMessage> message =
CreateBlimpMessage(&navigation_message, kDummyTabId);
navigation_message->set_type(NavigationMessage::NAVIGATION_STATE_CHANGED);
NavigationStateChangeMessage* details =
navigation_message->mutable_navigation_state_changed();
details->set_page_load_completed(page_load_completed);
navigation_message_sender_->ProcessMessage(std::move(message),
net::CompletionCallback());
last_page_load_completed_value_ = page_load_completed;
}
void BlimpEngineSession::RenderViewCreated(
content::RenderViewHost* render_view_host) {
render_widget_feature_.OnRenderWidgetCreated(kDummyTabId,
render_view_host->GetWidget());
}
void BlimpEngineSession::RenderViewHostChanged(
content::RenderViewHost* old_host,
content::RenderViewHost* new_host) {
// Informs client that WebContents swaps its visible RenderViewHost with
// another one.
render_widget_feature_.OnRenderWidgetInitialized(kDummyTabId,
new_host->GetWidget());
}
void BlimpEngineSession::RenderViewDeleted(
content::RenderViewHost* render_view_host) {
render_widget_feature_.OnRenderWidgetDeleted(kDummyTabId,
render_view_host->GetWidget());
}
void BlimpEngineSession::PlatformSetContents(
std::unique_ptr<content::WebContents> new_contents) {
new_contents->SetDelegate(this);
Observe(new_contents.get());
web_contents_ = std::move(new_contents);
aura::Window* parent = window_tree_host_->window();
aura::Window* content = web_contents_->GetNativeView();
if (!parent->Contains(content))
parent->AddChild(content);
content->Show();
}
} // namespace engine
} // namespace blimp