Index: mash/browser/browser.cc |
diff --git a/mash/browser/browser.cc b/mash/browser/browser.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..01c4ad33d01a286892000952fd835a2da34a79cd |
--- /dev/null |
+++ b/mash/browser/browser.cc |
@@ -0,0 +1,376 @@ |
+// 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 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 { |
+ public: |
+ ProgressBar() {} |
+ ~ProgressBar() override {} |
+ |
+ void SetProgress(double progress) { |
+ progress_ = progress; |
+ SchedulePaint(); |
+ } |
+ |
+ private: |
+ void OnPaint(gfx::Canvas* canvas) override { |
+ gfx::Rect stroke_rect = GetLocalBounds(); |
+ stroke_rect.set_y(stroke_rect.bottom() - 1); |
+ stroke_rect.set_height(1); |
+ canvas->FillRect(stroke_rect, SK_ColorGRAY); |
+ if (progress_ != 0.f) { |
+ 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 Start() { |
+ throbbing_ = true; |
+ start_time_ = base::TimeTicks::Now(); |
+ SchedulePaint(); |
+ timer_.Start( |
+ FROM_HERE, base::TimeDelta::FromMilliseconds(30), |
+ 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; |
+ |
+ 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, base::ASCIIToUTF16("Back"))), |
+ forward_button_( |
+ new views::LabelButton(this, base::ASCIIToUTF16("Forward"))), |
+ reload_button_( |
+ new views::LabelButton(this, base::ASCIIToUTF16("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())); |
+ 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(base::ASCIIToUTF16("Stop")); |
+ throbber_->Start(); |
+ } else { |
+ reload_button_->SetText(base::ASCIIToUTF16("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 |