blob: 49e42c9e02236ba3b8d2dbf9e04e8490169aab18 [file] [log] [blame]
// Copyright 2018 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 "ash/assistant/ui/assistant_web_view.h"
#include <algorithm>
#include <utility>
#include "ash/assistant/model/assistant_ui_model.h"
#include "ash/assistant/ui/assistant_ui_constants.h"
#include "ash/assistant/ui/assistant_view_delegate.h"
#include "ash/assistant/util/deep_link_util.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/bind.h"
#include "base/callback.h"
#include "services/content/public/cpp/navigable_contents_view.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/canvas.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
namespace ash {
// AssistantWebView ------------------------------------------------------------
AssistantWebView::AssistantWebView(AssistantViewDelegate* delegate)
: delegate_(delegate) {
InitLayout();
delegate_->AddObserver(this);
delegate_->AddUiModelObserver(this);
}
AssistantWebView::~AssistantWebView() {
delegate_->RemoveUiModelObserver(this);
delegate_->RemoveObserver(this);
}
const char* AssistantWebView::GetClassName() const {
return "AssistantWebView";
}
gfx::Size AssistantWebView::CalculatePreferredSize() const {
return gfx::Size(kPreferredWidthDip, GetHeightForWidth(kPreferredWidthDip));
}
int AssistantWebView::GetHeightForWidth(int width) const {
if (app_list_features::IsEmbeddedAssistantUIEnabled())
return kMaxHeightEmbeddedDip;
// |height| <= |kMaxHeightDip|.
// |height| should not exceed the height of the usable work area.
const gfx::Rect usable_work_area =
delegate_->GetUiModel()->usable_work_area();
return std::min(kMaxHeightDip, usable_work_area.height());
}
void AssistantWebView::ChildPreferredSizeChanged(views::View* child) {
// Because AssistantWebView has a fixed size, it does not re-layout its
// children when their preferred size changes. To address this, we need to
// explicitly request a layout pass.
Layout();
SchedulePaint();
}
void AssistantWebView::OnFocus() {
if (contents_)
contents_->Focus();
}
void AssistantWebView::AboutToRequestFocusFromTabTraversal(bool reverse) {
if (contents_)
contents_->FocusThroughTabTraversal(reverse);
}
void AssistantWebView::InitLayout() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
// Caption bar.
caption_bar_ = new CaptionBar();
caption_bar_->set_delegate(this);
caption_bar_->SetButtonVisible(AssistantButtonId::kMinimize, false);
if (app_list_features::IsEmbeddedAssistantUIEnabled())
caption_bar_->SetButtonVisible(AssistantButtonId::kClose, false);
AddChildView(caption_bar_);
}
bool AssistantWebView::OnCaptionButtonPressed(AssistantButtonId id) {
// We need special handling of the back button. When possible, the back button
// should navigate backwards in the web contents' history stack.
if (id == AssistantButtonId::kBack && contents_) {
contents_->GoBack(base::BindOnce(
[](const base::WeakPtr<AssistantWebView>& assistant_web_view,
bool success) {
// If we can't navigate back in the web contents' history stack we
// defer back to our primary caption button delegate.
if (!success && assistant_web_view) {
assistant_web_view->delegate_->GetCaptionBarDelegate()
->OnCaptionButtonPressed(AssistantButtonId::kBack);
}
},
weak_factory_.GetWeakPtr()));
return true;
}
// For all other buttons we defer to our primary caption button delegate.
return delegate_->GetCaptionBarDelegate()->OnCaptionButtonPressed(id);
}
void AssistantWebView::OnDeepLinkReceived(
assistant::util::DeepLinkType type,
const std::map<std::string, std::string>& params) {
if (!assistant::util::IsWebDeepLinkType(type, params))
return;
RemoveContents();
if (!contents_factory_.is_bound()) {
delegate_->GetNavigableContentsFactoryForView(
contents_factory_.BindNewPipeAndPassReceiver());
}
auto contents_params = content::mojom::NavigableContentsParams::New();
contents_params->suppress_navigations = true;
contents_ = std::make_unique<content::NavigableContents>(
contents_factory_.get(), std::move(contents_params));
// We observe |contents_| so that we can handle events from the underlying
// web contents.
contents_->AddObserver(this);
// Navigate to the url associated with the received deep link.
contents_->Navigate(assistant::util::GetWebUrl(type, params).value());
}
void AssistantWebView::DidStopLoading() {
// We should only respond to the |DidStopLoading| event the first time, to add
// the view for the navigable contents to our view hierarchy and perform other
// one-time view initializations.
if (contents_view_initialized_)
return;
contents_view_initialized_ = true;
UpdateContentSize();
AddChildView(contents_->GetView()->view());
SetFocusBehavior(FocusBehavior::ALWAYS);
// We need to clip the corners of our web contents to match our container.
contents_->GetView()->native_view()->layer()->SetRoundedCornerRadius(
{/*top_left=*/0, /*top_right=*/0, /*bottom_right=*/kCornerRadiusDip,
/*bottom_left=*/kCornerRadiusDip});
}
void AssistantWebView::DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) {
if (!from_user_gesture)
return;
// Deep links are always handled by AssistantViewDelegate. If the
// |disposition| indicates a desire to open a new foreground tab, we also
// defer to the AssistantViewDelegate so that it can open the |url| in the
// browser.
if (assistant::util::IsDeepLinkUrl(url) ||
disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB) {
delegate_->OpenUrlFromView(url);
return;
}
// Otherwise we'll allow our web contents to navigate freely.
contents_->Navigate(url);
}
void AssistantWebView::OnUiVisibilityChanged(
AssistantVisibility new_visibility,
AssistantVisibility old_visibility,
base::Optional<AssistantEntryPoint> entry_point,
base::Optional<AssistantExitPoint> exit_point) {
// When the Assistant UI is closed we need to clear the |contents_| in order
// to free the memory.
if (new_visibility == AssistantVisibility::kClosed)
RemoveContents();
}
void AssistantWebView::OnUsableWorkAreaChanged(
const gfx::Rect& usable_work_area) {
UpdateContentSize();
}
void AssistantWebView::RemoveContents() {
if (!contents_)
return;
views::View* view = contents_->GetView()->view();
if (view)
RemoveChildView(view);
SetFocusBehavior(FocusBehavior::NEVER);
contents_->RemoveObserver(this);
contents_.reset();
contents_view_initialized_ = false;
}
void AssistantWebView::UpdateContentSize() {
if (!contents_ || !contents_view_initialized_)
return;
const gfx::Size preferred_size = gfx::Size(
kPreferredWidthDip, GetHeightForWidth(kPreferredWidthDip) -
caption_bar_->GetPreferredSize().height());
contents_->GetView()->view()->SetPreferredSize(preferred_size);
}
} // namespace ash