| // 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 |