Chromium Code Reviews| Index: mash/browser/browser.cc |
| diff --git a/mash/browser/browser.cc b/mash/browser/browser.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..9e8fda06a56380110177bcb0cb2788d3c09762d5 |
| --- /dev/null |
| +++ b/mash/browser/browser.cc |
| @@ -0,0 +1,394 @@ |
| +// Copyright 2016 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 "mash/browser/browser.h" |
| + |
| +#include "base/macros.h" |
| +#include "base/memory/weak_ptr.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/strings/string16.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "base/timer/timer.h" |
| +#include "components/mus/public/cpp/window.h" |
| +#include "components/mus/public/cpp/window_tree_connection.h" |
| +#include "mash/public/interfaces/launchable.mojom.h" |
| +#include "mojo/converters/geometry/geometry_type_converters.h" |
| +#include "mojo/public/c/system/main.h" |
| +#include "services/navigation/public/interfaces/view.mojom.h" |
| +#include "services/shell/public/cpp/application_runner.h" |
| +#include "services/shell/public/cpp/connector.h" |
| +#include "services/shell/public/cpp/shell_client.h" |
| +#include "services/tracing/public/cpp/tracing_impl.h" |
| +#include "ui/aura/mus/mus_util.h" |
| +#include "ui/gfx/canvas.h" |
| +#include "ui/gfx/paint_throbber.h" |
| +#include "ui/gfx/text_constants.h" |
| +#include "ui/native_theme/native_theme.h" |
| +#include "ui/views/background.h" |
| +#include "ui/views/controls/button/label_button.h" |
| +#include "ui/views/controls/textfield/textfield.h" |
| +#include "ui/views/controls/textfield/textfield_controller.h" |
| +#include "ui/views/mus/aura_init.h" |
| +#include "ui/views/mus/window_manager_connection.h" |
| +#include "ui/views/widget/widget_delegate.h" |
| +#include "url/gurl.h" |
| + |
| +namespace views { |
| +class AuraInit; |
|
sky
2016/05/16 15:31:58
You shouldn't need to forward declare this as you
|
| +} |
| + |
| +namespace mash { |
| +namespace browser { |
| + |
| +void EnableButton(views::CustomButton* button, bool enabled) { |
| + button->SetState(enabled ? views::Button::STATE_NORMAL |
| + : views::Button::STATE_DISABLED); |
| +} |
| + |
| +class ProgressBar : public views::View { |
|
sky
2016/05/16 15:31:58
Did you try using the ProgressBar in views?
|
| + public: |
| + ProgressBar() {} |
| + ~ProgressBar() override {} |
| + |
| + void SetProgress(double progress) { |
| + progress_ = progress; |
| + SchedulePaint(); |
| + } |
| + |
| + private: |
| + void OnPaint(gfx::Canvas* canvas) override { |
| + if (progress_ == 0.f) { |
| + gfx::Rect progress_rect = GetLocalBounds(); |
| + progress_rect.set_y(progress_rect.bottom() - 1); |
| + progress_rect.set_height(1); |
| + canvas->FillRect(progress_rect, SK_ColorGRAY); |
| + } else { |
| + gfx::Rect progress_rect = GetLocalBounds(); |
| + progress_rect.set_width(progress_rect.width() * progress_); |
| + canvas->FillRect(progress_rect, SK_ColorRED); |
| + } |
| + } |
| + |
| + double progress_ = 0.f; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ProgressBar); |
| +}; |
| + |
| +class Throbber : public views::View { |
| + public: |
| + Throbber() : timer_(false, true), weak_factory_(this) {} |
| + ~Throbber() override {} |
| + |
| + void Throb() { |
|
sky
2016/05/16 15:31:58
nit: you have a Stop(), I would be inclined to cal
|
| + throbbing_ = true; |
|
sky
2016/05/16 15:31:58
DCHECK(!throbbing_), or perhaps early out if alrea
|
| + start_time_ = base::TimeTicks(); |
|
sky
2016/05/16 15:31:58
base::TimeTicks::Now()?
|
| + SchedulePaint(); |
| + timer_.Start( |
| + FROM_HERE, base::TimeDelta::FromMilliseconds(16), |
|
sky
2016/05/16 15:31:58
You sure you want 16? We changed chrome to 30 a wh
|
| + base::Bind(&Throbber::SchedulePaint, |
| + weak_factory_.GetWeakPtr())); |
| + } |
| + |
| + void Stop() { |
| + throbbing_ = false; |
| + if (timer_.IsRunning()) |
| + timer_.Stop(); |
| + SchedulePaint(); |
| + } |
| + |
| + private: |
| + void OnPaint(gfx::Canvas* canvas) override { |
| + if (!throbbing_) |
| + return; |
| + |
| + if (start_time_ == base::TimeTicks()) |
|
sky
2016/05/16 15:31:58
How come you set this here and not in Throb?
|
| + start_time_ = base::TimeTicks::Now(); |
| + |
| + gfx::PaintThrobberSpinning( |
| + canvas, GetLocalBounds(), |
| + GetNativeTheme()->GetSystemColor( |
| + ui::NativeTheme::kColorId_ThrobberSpinningColor), |
| + base::TimeTicks::Now() - start_time_); |
| + } |
| + |
| + bool throbbing_ = false; |
| + base::TimeTicks start_time_; |
| + base::Timer timer_; |
| + base::WeakPtrFactory<Throbber> weak_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(Throbber); |
| +}; |
| + |
| +class UI : public views::WidgetDelegateView, |
| + public views::ButtonListener, |
| + public views::TextfieldController, |
| + public navigation::mojom::ViewClient { |
| + public: |
| + enum class Type { |
| + WINDOW, |
| + POPUP |
| + }; |
| + |
| + UI(Browser* browser, |
| + Type type, |
| + navigation::mojom::ViewPtr view, |
| + navigation::mojom::ViewClientRequest request) |
| + : browser_(browser), |
| + type_(type), |
| + back_button_(new views::LabelButton(this, L"Back")), |
| + forward_button_(new views::LabelButton(this, L"Forward")), |
| + reload_button_(new views::LabelButton(this, L"Reload")), |
| + prompt_(new views::Textfield), |
| + throbber_(new Throbber), |
| + progress_bar_(new ProgressBar), |
| + view_(std::move(view)), |
| + view_client_binding_(this, std::move(request)) { |
| + set_background(views::Background::CreateStandardPanelBackground()); |
| + prompt_->set_controller(this); |
| + back_button_->set_request_focus_on_press(false); |
| + forward_button_->set_request_focus_on_press(false); |
| + reload_button_->set_request_focus_on_press(false); |
| + AddChildView(back_button_); |
| + AddChildView(forward_button_); |
| + AddChildView(reload_button_); |
| + AddChildView(prompt_); |
| + AddChildView(throbber_); |
| + AddChildView(progress_bar_); |
| + } |
| + ~UI() override { |
| + browser_->RemoveWindow(GetWidget()); |
| + } |
| + |
| + void NavigateTo(const GURL& url) { |
| + view_->NavigateTo(url); |
| + } |
| + |
| + private: |
| + // Overridden from views::WidgetDelegate: |
| + views::View* GetContentsView() override { return this; } |
| + base::string16 GetWindowTitle() const override { |
| + // TODO(beng): use resources. |
| + if (current_title_.empty()) |
| + return base::ASCIIToUTF16("Browser"); |
| + base::string16 format = base::ASCIIToUTF16("%s - Browser"); |
| + base::ReplaceFirstSubstringAfterOffset( |
| + &format, 0, base::ASCIIToUTF16("%s"), current_title_); |
| + return format; |
| + } |
| + bool CanResize() const override { return true; } |
| + bool CanMaximize() const override { return true; } |
| + bool CanMinimize() const override { return true; } |
| + |
| + // views::ButtonListener: |
| + void ButtonPressed(views::Button* sender, |
| + const ui::Event& event) override { |
| + if (sender == back_button_) { |
| + view_->GoBack(); |
| + } else if (sender == forward_button_) { |
| + view_->GoForward(); |
| + } else if (sender == reload_button_) { |
| + if (is_loading_) |
| + view_->Stop(); |
| + else |
| + view_->Reload(false); |
| + } |
| + } |
| + |
| + // Overridden from views::View: |
| + void Layout() override { |
| + gfx::Rect local_bounds = GetLocalBounds(); |
| + gfx::Rect bounds = local_bounds; |
| + bounds.Inset(5, 5); |
| + |
| + gfx::Size ps = back_button_->GetPreferredSize(); |
| + back_button_->SetBoundsRect( |
| + gfx::Rect(bounds.x(), bounds.y(), ps.width(), ps.height())); |
|
sky
2016/05/16 15:31:58
nit: run git cl format
|
| + ps = forward_button_->GetPreferredSize(); |
| + forward_button_->SetBoundsRect( |
| + gfx::Rect(back_button_->bounds().right() + 5, bounds.y(), |
| + ps.width(), ps.height())); |
| + ps = reload_button_->GetPreferredSize(); |
| + reload_button_->SetBoundsRect( |
| + gfx::Rect(forward_button_->bounds().right() + 5, bounds.y(), |
| + ps.width(), ps.height())); |
| + |
| + ps = prompt_->GetPreferredSize(); |
| + int prompt_y = |
| + bounds.y() + (reload_button_->bounds().height() - ps.height()) / 2; |
| + int width = |
| + bounds.width() - reload_button_->bounds().right() - ps.height() - 10; |
| + prompt_->SetBoundsRect( |
| + gfx::Rect(reload_button_->bounds().right() + 5, prompt_y, width, |
| + ps.height())); |
| + throbber_->SetBoundsRect( |
| + gfx::Rect(prompt_->bounds().right() + 5, prompt_->bounds().y(), |
| + ps.height(), ps.height())); |
| + |
| + gfx::Rect progress_bar_rect( |
| + local_bounds.x(), back_button_->bounds().bottom() + 5, |
| + local_bounds.width(), 2); |
| + progress_bar_->SetBoundsRect(progress_bar_rect); |
| + |
| + if (content_area_) { |
| + int x = local_bounds.x(); |
| + int y = type_ == Type::POPUP ? 0 : progress_bar_->bounds().bottom(); |
| + gfx::Point offset(x, y); |
| + ConvertPointToWidget(this, &offset); |
| + int width = local_bounds.width(); |
| + int height = local_bounds.height() - y; |
| + content_area_->SetBounds( |
| + gfx::Rect(offset.x(), offset.y(), width, height)); |
| + } |
| + } |
| + void ViewHierarchyChanged( |
| + const views::View::ViewHierarchyChangedDetails& details) override { |
| + if (details.is_add && GetWidget() && !content_area_) { |
| + mus::Window* window = aura::GetMusWindow(GetWidget()->GetNativeWindow()); |
| + content_area_ = window->connection()->NewWindow(nullptr); |
| + window->AddChild(content_area_); |
| + |
| + mus::mojom::WindowTreeClientPtr client; |
| + view_->GetWindowTreeClient(GetProxy(&client)); |
| + content_area_->Embed(std::move(client)); |
| + } |
| + } |
| + |
| + // Overridden from views::TextFieldController: |
| + bool HandleKeyEvent(views::Textfield* sender, |
| + const ui::KeyEvent& key_event) override { |
| + switch (key_event.key_code()) { |
| + case ui::VKEY_RETURN: { |
| + view_->NavigateTo(GURL(prompt_->text())); |
| + } break; |
| + default: |
| + break; |
| + } |
| + return false; |
| + } |
| + |
| + // navigation::mojom::ViewClient: |
| + void LoadingStateChanged(bool is_loading) override { |
| + is_loading_ = is_loading; |
| + if (is_loading_) { |
| + //reload_button_->SetText(L"Stop"); |
|
sky
2016/05/16 15:31:58
?
|
| + throbber_->Throb(); |
| + } else { |
| + reload_button_->SetText(L"Reload"); |
| + throbber_->Stop(); |
| + progress_bar_->SetProgress(0.f); |
| + } |
| + } |
| + void NavigationStateChanged(const GURL& url, |
| + const mojo::String& title, |
| + bool can_go_back, |
| + bool can_go_forward) override { |
| + EnableButton(back_button_, can_go_back); |
| + EnableButton(forward_button_, can_go_forward); |
| + prompt_->SetText(base::UTF8ToUTF16(url.spec())); |
| + current_title_ = base::UTF8ToUTF16(title.get()); |
| + GetWidget()->UpdateWindowTitle(); |
| + } |
| + void LoadProgressChanged(double progress) override { |
| + progress_bar_->SetProgress(progress); |
| + } |
| + void ViewCreated(navigation::mojom::ViewPtr view, |
| + navigation::mojom::ViewClientRequest request, |
| + bool is_popup, |
| + mojo::RectPtr initial_rect, |
| + bool user_gesture) override { |
| + views::Widget* window = views::Widget::CreateWindowWithContextAndBounds( |
| + new UI(browser_, is_popup ? UI::Type::POPUP : UI::Type::WINDOW, |
| + std::move(view), std::move(request)), nullptr, |
| + initial_rect.To<gfx::Rect>()); |
| + window->Show(); |
| + browser_->AddWindow(window); |
| + } |
| + void Close() override { |
| + GetWidget()->Close(); |
| + } |
| + |
| + Browser* browser_; |
| + |
| + Type type_; |
| + |
| + views::LabelButton* back_button_; |
| + views::LabelButton* forward_button_; |
| + views::LabelButton* reload_button_; |
| + views::Textfield* prompt_; |
| + Throbber* throbber_; |
| + ProgressBar* progress_bar_; |
| + |
| + mus::Window* content_area_ = nullptr; |
| + |
| + navigation::mojom::ViewPtr view_; |
| + mojo::Binding<navigation::mojom::ViewClient> view_client_binding_; |
| + |
| + bool is_loading_ = false; |
| + base::string16 current_title_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(UI); |
| +}; |
| + |
| +Browser::Browser() {} |
| +Browser::~Browser() {} |
| + |
| +void Browser::AddWindow(views::Widget* window) { |
| + windows_.push_back(window); |
| +} |
| + |
| +void Browser::RemoveWindow(views::Widget* window) { |
| + auto it = std::find(windows_.begin(), windows_.end(), window); |
| + DCHECK(it != windows_.end()); |
| + windows_.erase(it); |
| + if (windows_.empty()) |
| + base::MessageLoop::current()->QuitWhenIdle(); |
| +} |
| + |
| +void Browser::Initialize(shell::Connector* connector, |
| + const shell::Identity& identity, |
| + uint32_t id) { |
| + connector_ = connector; |
| + tracing_.Initialize(connector, identity.name()); |
| + |
| + aura_init_.reset(new views::AuraInit(connector, "views_mus_resources.pak")); |
| + views::WindowManagerConnection::Create(connector, identity); |
| +} |
| + |
| +bool Browser::AcceptConnection(shell::Connection* connection) { |
| + connection->AddInterface<mojom::Launchable>(this); |
| + return true; |
| +} |
| + |
| +void Browser::Launch(uint32_t what, mojom::LaunchMode how) { |
| + bool reuse = how == mojom::LaunchMode::REUSE || |
| + how == mojom::LaunchMode::DEFAULT; |
| + if (reuse && !windows_.empty()) { |
| + windows_.back()->Activate(); |
| + return; |
| + } |
| + |
| + navigation::mojom::ViewFactoryPtr view_factory; |
| + connector_->ConnectToInterface("exe:navigation", &view_factory); |
| + navigation::mojom::ViewPtr view; |
| + navigation::mojom::ViewClientPtr view_client; |
| + navigation::mojom::ViewClientRequest view_client_request = |
| + GetProxy(&view_client); |
| + view_factory->CreateView(std::move(view_client), GetProxy(&view)); |
| + UI* ui = new UI(this, UI::Type::WINDOW, std::move(view), |
| + std::move(view_client_request)); |
| + views::Widget* window = views::Widget::CreateWindowWithContextAndBounds( |
| + ui, nullptr, gfx::Rect(10, 10, 1024, 600)); |
| + ui->NavigateTo(GURL("http://www.google.com/")); |
| + window->Show(); |
| + AddWindow(window); |
| +} |
| + |
| +void Browser::Create(shell::Connection* connection, |
| + mojom::LaunchableRequest request) { |
| + bindings_.AddBinding(this, std::move(request)); |
| +} |
| + |
| +} // namespace browser |
| +} // namespace mash |