blob: a78e60eca22a7c79a28199c165b76aaddbab1d66 [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 "mandoline/ui/desktop_ui/browser_window.h"
#include "base/command_line.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/mus/public/cpp/event_matcher.h"
#include "components/mus/public/cpp/scoped_window_ptr.h"
#include "components/mus/public/cpp/window_tree_host_factory.h"
#include "mandoline/ui/desktop_ui/browser_commands.h"
#include "mandoline/ui/desktop_ui/browser_manager.h"
#include "mandoline/ui/desktop_ui/find_bar_view.h"
#include "mandoline/ui/desktop_ui/public/interfaces/omnibox.mojom.h"
#include "mandoline/ui/desktop_ui/toolbar_view.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/converters/geometry/geometry_type_converters.h"
#include "mojo/services/tracing/public/cpp/switches.h"
#include "mojo/services/tracing/public/interfaces/tracing.mojom.h"
#include "ui/gfx/canvas.h"
#include "ui/mojo/init/ui_init.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/mus/aura_init.h"
#include "ui/views/mus/display_converter.h"
#include "ui/views/mus/native_widget_mus.h"
#include "ui/views/widget/widget_delegate.h"
namespace mandoline {
class ProgressView : public views::View {
public:
ProgressView() : progress_(0.f), loading_(false) {}
~ProgressView() override {}
void SetProgress(double progress) {
progress_ = progress;
SchedulePaint();
}
void SetIsLoading(bool loading) {
if (loading == loading_)
return;
loading_ = loading;
progress_ = 0.f;
SchedulePaint();
}
private:
void OnPaint(gfx::Canvas* canvas) override {
if (loading_) {
canvas->FillRect(GetLocalBounds(), SK_ColorGREEN);
gfx::Rect progress_rect = GetLocalBounds();
progress_rect.set_width(progress_rect.width() * progress_);
canvas->FillRect(progress_rect, SK_ColorRED);
} else {
canvas->FillRect(GetLocalBounds(), SK_ColorGRAY);
}
}
double progress_;
bool loading_;
DISALLOW_COPY_AND_ASSIGN(ProgressView);
};
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, public:
BrowserWindow::BrowserWindow(mojo::ApplicationImpl* app,
mus::mojom::WindowTreeHostFactory* host_factory,
BrowserManager* manager)
: app_(app),
host_client_binding_(this),
manager_(manager),
toolbar_view_(nullptr),
progress_bar_(nullptr),
find_bar_view_(nullptr),
root_(nullptr),
content_(nullptr),
omnibox_view_(nullptr),
find_active_(0),
find_count_(0),
web_view_(this) {
mus::mojom::WindowTreeHostClientPtr host_client;
host_client_binding_.Bind(GetProxy(&host_client));
mus::CreateWindowTreeHost(host_factory, host_client.Pass(), this, &host_,
nullptr, nullptr);
}
void BrowserWindow::LoadURL(const GURL& url) {
// Haven't been embedded yet, can't embed.
// TODO(beng): remove this.
if (!root_) {
default_url_ = url;
return;
}
if (!url.is_valid()) {
ShowOmnibox();
return;
}
mojo::URLRequestPtr request(mojo::URLRequest::New());
request->url = url.spec();
Embed(request.Pass());
}
void BrowserWindow::Close() {
if (root_)
mus::ScopedWindowPtr::DeleteWindowOrWindowManager(root_);
else
delete this;
}
void BrowserWindow::ShowOmnibox() {
TRACE_EVENT0("desktop_ui", "BrowserWindow::ShowOmnibox");
if (!omnibox_.get()) {
mojo::URLRequestPtr request(mojo::URLRequest::New());
request->url = mojo::String::From("mojo:omnibox");
omnibox_connection_ = app_->ConnectToApplication(request.Pass());
omnibox_connection_->AddService<ViewEmbedder>(this);
omnibox_connection_->ConnectToService(&omnibox_);
omnibox_connection_->SetRemoteServiceProviderConnectionErrorHandler(
[this]() {
// This will cause the connection to be re-established the next time
// we come through this codepath.
omnibox_.reset();
});
}
omnibox_->ShowForURL(mojo::String::From(current_url_.spec()));
}
void BrowserWindow::ShowFind() {
TRACE_EVENT0("desktop_ui", "BrowserWindow::ShowFind");
toolbar_view_->SetVisible(false);
find_bar_view_->Show();
}
void BrowserWindow::GoBack() {
TRACE_EVENT0("desktop_ui", "BrowserWindow::GoBack");
web_view_.web_view()->GoBack();
}
void BrowserWindow::GoForward() {
TRACE_EVENT0("desktop_ui", "BrowserWindow::GoForward");
web_view_.web_view()->GoForward();
}
BrowserWindow::~BrowserWindow() {
DCHECK(!root_);
manager_->BrowserWindowClosed(this);
}
float BrowserWindow::DIPSToPixels(float value) const {
return value * root_->viewport_metrics().device_pixel_ratio;
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, mus::ViewTreeDelegate implementation:
void BrowserWindow::OnEmbed(mus::Window* root) {
TRACE_EVENT0("desktop_ui", "BrowserWindow::OnEmbed");
// BrowserWindow does not support being embedded more than once.
CHECK(!root_);
// Record when the browser window was displayed, used for performance testing.
const base::TimeTicks display_ticks = base::TimeTicks::Now();
root_ = root;
host_->SetTitle("Mandoline");
content_ = root_->connection()->NewWindow();
Init(root_);
host_->SetSize(mojo::Size::From(gfx::Size(1280, 800)));
root_->AddChild(content_);
content_->SetVisible(true);
web_view_.Init(app_, content_);
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::CLOSE),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_W,
mus::mojom::EVENT_FLAGS_CONTROL_DOWN));
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::FOCUS_OMNIBOX),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_L,
mus::mojom::EVENT_FLAGS_CONTROL_DOWN));
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::NEW_WINDOW),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_N,
mus::mojom::EVENT_FLAGS_CONTROL_DOWN));
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::SHOW_FIND),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_F,
mus::mojom::EVENT_FLAGS_CONTROL_DOWN));
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::GO_BACK),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_LEFT,
mus::mojom::EVENT_FLAGS_ALT_DOWN));
host_->AddAccelerator(
static_cast<uint32_t>(BrowserCommand::GO_FORWARD),
mus::CreateKeyMatcher(mus::mojom::KEYBOARD_CODE_RIGHT,
mus::mojom::EVENT_FLAGS_ALT_DOWN));
// Now that we're ready, load the default url.
LoadURL(default_url_);
// Record the time spent opening initial tabs, used for performance testing.
const base::TimeDelta open_tabs_delta =
base::TimeTicks::Now() - display_ticks;
// Record the browser startup time metrics, used for performance testing.
static bool recorded_browser_startup_metrics = false;
if (!recorded_browser_startup_metrics &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
tracing::kEnableStatsCollectionBindings)) {
mojo::URLRequestPtr request(mojo::URLRequest::New());
request->url = mojo::String::From("mojo:tracing");
tracing::StartupPerformanceDataCollectorPtr collector;
app_->ConnectToService(request.Pass(), &collector);
collector->SetBrowserWindowDisplayTicks(display_ticks.ToInternalValue());
collector->SetBrowserOpenTabsTimeDelta(open_tabs_delta.ToInternalValue());
collector->SetBrowserMessageLoopStartTicks(
manager_->startup_ticks().ToInternalValue());
recorded_browser_startup_metrics = true;
}
}
void BrowserWindow::OnConnectionLost(mus::WindowTreeConnection* connection) {
root_ = nullptr;
delete this;
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, mus::ViewTreeHostClient implementation:
void BrowserWindow::OnAccelerator(uint32_t id, mus::mojom::EventPtr event) {
switch (static_cast<BrowserCommand>(id)) {
case BrowserCommand::CLOSE:
Close();
break;
case BrowserCommand::NEW_WINDOW:
manager_->CreateBrowser(GURL());
break;
case BrowserCommand::FOCUS_OMNIBOX:
ShowOmnibox();
break;
case BrowserCommand::SHOW_FIND:
ShowFind();
break;
case BrowserCommand::GO_BACK:
GoBack();
break;
case BrowserCommand::GO_FORWARD:
GoForward();
break;
default:
NOTREACHED();
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, web_view::mojom::WebViewClient implementation:
void BrowserWindow::TopLevelNavigateRequest(mojo::URLRequestPtr request) {
OnHideFindBar();
Embed(request.Pass());
}
void BrowserWindow::TopLevelNavigationStarted(const mojo::String& url) {
GURL gurl(url);
bool changed = current_url_ != gurl;
current_url_ = gurl;
if (changed)
toolbar_view_->SetOmniboxText(base::UTF8ToUTF16(current_url_.spec()));
}
void BrowserWindow::LoadingStateChanged(bool is_loading, double progress) {
progress_bar_->SetIsLoading(is_loading);
progress_bar_->SetProgress(progress);
}
void BrowserWindow::BackForwardChanged(
web_view::mojom::ButtonState back_button,
web_view::mojom::ButtonState forward_button) {
toolbar_view_->SetBackForwardEnabled(
back_button == web_view::mojom::ButtonState::BUTTON_STATE_ENABLED,
forward_button == web_view::mojom::ButtonState::BUTTON_STATE_ENABLED);
}
void BrowserWindow::TitleChanged(const mojo::String& title) {
base::string16 formatted =
title.is_null() ? base::ASCIIToUTF16("Untitled")
: title.To<base::string16>() +
base::ASCIIToUTF16(" - Mandoline");
host_->SetTitle(mojo::String::From(formatted));
}
void BrowserWindow::FindInPageMatchCountUpdated(int32_t request_id,
int32_t count,
bool final_update) {
find_count_ = count;
find_bar_view_->SetMatchLabel(find_active_, find_count_);
}
void BrowserWindow::FindInPageSelectionUpdated(int32_t request_id,
int32_t active_match_ordinal) {
find_active_ = active_match_ordinal;
find_bar_view_->SetMatchLabel(find_active_, find_count_);
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, ViewEmbedder implementation:
void BrowserWindow::Embed(mojo::URLRequestPtr request) {
const std::string string_url = request->url.To<std::string>();
if (string_url == "mojo:omnibox") {
EmbedOmnibox();
return;
}
web_view_.web_view()->LoadRequest(request.Pass());
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, mojo::InterfaceFactory<ViewEmbedder> implementation:
void BrowserWindow::Create(mojo::ApplicationConnection* connection,
mojo::InterfaceRequest<ViewEmbedder> request) {
view_embedder_bindings_.AddBinding(this, request.Pass());
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, views::LayoutManager implementation:
gfx::Size BrowserWindow::GetPreferredSize(const views::View* view) const {
return gfx::Size();
}
void BrowserWindow::Layout(views::View* host) {
// TODO(fsamuel): All bounds should be in physical pixels.
gfx::Rect bounds_in_physical_pixels(host->bounds());
float inverse_device_pixel_ratio =
1.0f / root_->viewport_metrics().device_pixel_ratio;
gfx::Rect toolbar_bounds = gfx::ScaleToEnclosingRect(
bounds_in_physical_pixels, inverse_device_pixel_ratio);
toolbar_bounds.Inset(10, 10, 10, toolbar_bounds.height() - 40);
toolbar_view_->SetBoundsRect(toolbar_bounds);
find_bar_view_->SetBoundsRect(toolbar_bounds);
gfx::Rect progress_bar_bounds(toolbar_bounds.x(), toolbar_bounds.bottom() + 2,
toolbar_bounds.width(), 5);
// The content view bounds are in physical pixels.
gfx::Rect content_bounds(DIPSToPixels(progress_bar_bounds.x()),
DIPSToPixels(progress_bar_bounds.bottom() + 10), 0,
0);
content_bounds.set_width(DIPSToPixels(progress_bar_bounds.width()));
content_bounds.set_height(host->bounds().height() - content_bounds.y() -
DIPSToPixels(10));
content_->SetBounds(content_bounds);
// The omnibox view bounds are in physical pixels.
omnibox_view_->SetBounds(bounds_in_physical_pixels);
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, FindBarDelegate implementation:
void BrowserWindow::OnDoFind(const std::string& find, bool forward) {
web_view_.web_view()->Find(mojo::String::From(find), forward);
}
void BrowserWindow::OnHideFindBar() {
toolbar_view_->SetVisible(true);
find_bar_view_->Hide();
web_view_.web_view()->StopFinding();
}
////////////////////////////////////////////////////////////////////////////////
// BrowserWindow, private:
void BrowserWindow::Init(mus::Window* root) {
DCHECK_GT(root->viewport_metrics().device_pixel_ratio, 0);
if (!aura_init_) {
ui_init_.reset(new ui::mojo::UIInit(views::GetDisplaysFromWindow(root)));
aura_init_.reset(new views::AuraInit(app_, "mandoline_ui.pak"));
}
root_ = root;
omnibox_view_ = root_->connection()->NewWindow();
root_->AddChild(omnibox_view_);
views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView;
widget_delegate->GetContentsView()->set_background(
views::Background::CreateSolidBackground(0xFFDDDDDD));
toolbar_view_ = new ToolbarView(this);
progress_bar_ = new ProgressView;
widget_delegate->GetContentsView()->AddChildView(toolbar_view_);
widget_delegate->GetContentsView()->AddChildView(progress_bar_);
widget_delegate->GetContentsView()->SetLayoutManager(this);
find_bar_view_ = new FindBarView(this);
widget_delegate->GetContentsView()->AddChildView(find_bar_view_);
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = new views::NativeWidgetMus(
widget, app_->shell(), root, mus::mojom::SURFACE_TYPE_DEFAULT);
params.delegate = widget_delegate;
params.bounds = root_->bounds();
widget->Init(params);
widget->Show();
root_->SetFocus();
}
void BrowserWindow::EmbedOmnibox() {
mus::mojom::WindowTreeClientPtr view_tree_client;
omnibox_->GetWindowTreeClient(GetProxy(&view_tree_client));
omnibox_view_->Embed(view_tree_client.Pass());
// TODO(beng): This should be handled sufficiently by
// OmniboxImpl::ShowWindow() but unfortunately view manager policy
// currently prevents the embedded app from changing window z for
// its own window.
omnibox_view_->MoveToFront();
}
} // namespace mandoline