[go: nahoru, domu]

blob: b78328a13a6063801960b2a52076a10eeb5f544e [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 "chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h"
#include <vector>
#include "ash/public/cpp/tablet_mode.h"
#include "base/auto_reset.h"
#include "base/bind.h"
#include "cc/input/browser_controls_state.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ssl/security_state_tab_helper.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/common/url_constants.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/focused_node_details.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/common/constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/views/controls/native/native_view_host.h"
namespace {
bool IsTabletModeEnabled() {
return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
}
bool IsSpokenFeedbackEnabled() {
chromeos::AccessibilityManager* accessibility_manager =
chromeos::AccessibilityManager::Get();
return accessibility_manager &&
accessibility_manager->IsSpokenFeedbackEnabled();
}
// Based on the current status of |contents|, returns the browser top controls
// shown state constraints, which specifies if the top controls are allowed to
// be only shown, or either shown or hidden.
// This function is mostly similar to its corresponding Android one in Java code
// (See TabStateBrowserControlsVisibilityDelegate#canAutoHideBrowserControls()
// in TabStateBrowserControlsVisibilityDelegate.java).
cc::BrowserControlsState GetBrowserControlsStateConstraints(
content::WebContents* contents) {
DCHECK(contents);
if (!IsTabletModeEnabled() || contents->IsFullscreen() ||
contents->IsFocusedElementEditable() || contents->IsBeingDestroyed() ||
contents->IsCrashed() || IsSpokenFeedbackEnabled()) {
return cc::BrowserControlsState::kShown;
}
content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
if (!entry || entry->GetPageType() != content::PAGE_TYPE_NORMAL)
return cc::BrowserControlsState::kShown;
const GURL& url = entry->GetURL();
if (url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(chrome::kChromeNativeScheme) ||
url.SchemeIs(extensions::kExtensionScheme)) {
return cc::BrowserControlsState::kShown;
}
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
if (profile && search::IsNTPOrRelatedURL(url, profile))
return cc::BrowserControlsState::kShown;
auto* helper = SecurityStateTabHelper::FromWebContents(contents);
switch (helper->GetSecurityLevel()) {
case security_state::WARNING:
case security_state::DANGEROUS:
return cc::BrowserControlsState::kShown;
// Force compiler failure if new security level types were added without
// this being updated.
case security_state::NONE:
case security_state::SECURE:
case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
case security_state::SECURITY_LEVEL_COUNT:
break;
}
// Keep top-chrome visible while a permission bubble is visible.
auto* permission_manager =
permissions::PermissionRequestManager::FromWebContents(contents);
if (permission_manager && permission_manager->IsRequestInProgress())
return cc::BrowserControlsState::kShown;
return cc::BrowserControlsState::kBoth;
}
// Triggers a visual properties synchrnoization event on |contents|' main
// frame's view's widget.
void SynchronizeVisualProperties(content::WebContents* contents) {
DCHECK(contents);
content::RenderFrameHost* main_frame = contents->GetMainFrame();
if (!main_frame)
return;
auto* rvh = main_frame->GetRenderViewHost();
if (!rvh)
return;
auto* widget = rvh->GetWidget();
if (!widget)
return;
widget->SynchronizeVisualProperties();
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// TopControlsSlideTabObserver:
// Pushes updates of the browser top controls state constraints to the renderer
// when certain events happen on the webcontents. It also keeps track of the
// current top controls shown ratio for this tab so that it stays in sync with
// the corresponding value that the tab's renderer has.
class TopControlsSlideTabObserver
: public content::WebContentsObserver,
public permissions::PermissionRequestManager::Observer {
public:
TopControlsSlideTabObserver(content::WebContents* web_contents,
TopControlsSlideControllerChromeOS* owner)
: content::WebContentsObserver(web_contents), owner_(owner) {
// This object is constructed when |web_contents| is attached to the
// browser's tabstrip, meaning that Browser is now the delegate of
// |web_contents|. Updating the visual properties will now sync the correct
// top chrome height in the renderer.
SynchronizeVisualProperties(web_contents);
auto* permission_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (permission_manager)
permission_manager->AddObserver(this);
}
~TopControlsSlideTabObserver() override {
auto* permission_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents());
if (permission_manager)
permission_manager->RemoveObserver(this);
}
float shown_ratio() const { return shown_ratio_; }
bool shrink_renderer_size() const { return shrink_renderer_size_; }
void SetShownRatio(float ratio, bool sliding_or_scrolling_in_progress) {
shown_ratio_ = ratio;
if (!sliding_or_scrolling_in_progress)
UpdateDoBrowserControlsShrinkRendererSize();
}
void UpdateDoBrowserControlsShrinkRendererSize() {
shrink_renderer_size_ = shown_ratio_ == 1.f;
}
// content::WebContentsObserver:
void RenderProcessGone(base::TerminationStatus status) override {
// There is no renderer to communicate with, so just ensure top-chrome
// is shown. Also the render may have crashed before resetting the gesture
// in progress bit.
owner_->SetTopControlsGestureScrollInProgress(false);
owner_->SetShownRatio(web_contents(), 1.f);
}
void OnRendererUnresponsive(
content::RenderProcessHost* render_process_host) override {
// The render process might respond shortly, so instruct the renderer to
// show top-chrome, and show it manually immediately.
UpdateBrowserControlsStateShown(/*animate=*/false);
owner_->SetShownRatio(web_contents(), 1.f);
}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->IsInMainFrame() && navigation_handle->HasCommitted())
UpdateBrowserControlsStateShown(/*animate=*/true);
}
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code) override {
if (render_frame_host->IsCurrent() &&
(render_frame_host == web_contents()->GetMainFrame())) {
UpdateBrowserControlsStateShown(/*animate=*/true);
}
}
void DidChangeVisibleSecurityState() override {
UpdateBrowserControlsStateShown(/*animate=*/true);
}
void OnFocusChangedInPage(content::FocusedNodeDetails* details) override {
// Even if a non-editable node gets focused, if top-chrome is fully shown,
// we should also update the browser controls state constraints so that
// top-chrome is able to be hidden again.
if (details->is_editable_node || shown_ratio_ == 1.f)
UpdateBrowserControlsStateShown(/*animate=*/true);
}
// PermissionRequestManager::Observer:
void OnBubbleAdded() override {
UpdateBrowserControlsStateShown(/*animate=*/true);
}
void OnBubbleRemoved() override {
// This will update the shown constraints.
UpdateBrowserControlsStateShown(/*animate=*/false);
}
private:
void UpdateBrowserControlsStateShown(bool animate) {
owner_->UpdateBrowserControlsStateShown(web_contents(), animate);
}
TopControlsSlideControllerChromeOS* const owner_;
// Tracks the current shown ratio of this tab as synchronized with its
// renderer. This is needed because when switching tabs, we must restore the
// shown ratio of the newly-activated tab manually, not just ask the renderer
// to animate it to shown. The renderer may never animate anything to fully
// shown. Here's an example:
//
// Assume we have two tabs:
//
// +-------+-------+
// | Tab 1 | Tab 2 |
// +-------+-------+
//
// - User scrolls and hides top-chrome for tab 1.
// - User presses Ctrl + Tab to switch to tab 2.
// - We *just* ask the renderer to show top-chrome for tab 2.
// - Tab 2's renderer thinks that shown ratio is already 1 and top-chrome is
// already shown.
// - Renderer doesn't call us, and top-chrome remains hidden even though it
// should be shown.
float shown_ratio_ = 1.f;
// Indicates whether the renderer's viewport size should be shrunk by the
// height of the browser's top controls. This value never changes while
// sliding is in progress. It is updated only once right before sliding begins
// and remains unchanged until sliding ends, at which point it is updated
// right before the final layout of the BrowserView.
// https://crbug.com/885223.
bool shrink_renderer_size_ = true;
DISALLOW_COPY_AND_ASSIGN(TopControlsSlideTabObserver);
};
////////////////////////////////////////////////////////////////////////////////
// TopControlsSlideControllerChromeOS:
TopControlsSlideControllerChromeOS::TopControlsSlideControllerChromeOS(
BrowserView* browser_view)
: browser_view_(browser_view) {
DCHECK(browser_view);
DCHECK(browser_view->frame());
DCHECK(browser_view->browser());
DCHECK(browser_view->IsBrowserTypeNormal());
DCHECK(browser_view->browser()->tab_strip_model());
DCHECK(browser_view->GetLocationBarView());
DCHECK(browser_view->GetLocationBarView()->omnibox_view());
observed_omni_box_ = browser_view->GetLocationBarView()->omnibox_view();
observed_omni_box_->AddObserver(this);
if (ash::TabletMode::Get())
ash::TabletMode::Get()->AddObserver(this);
browser_view_->browser()->tab_strip_model()->AddObserver(this);
display::Screen::GetScreen()->AddObserver(this);
chromeos::AccessibilityManager* accessibility_manager =
chromeos::AccessibilityManager::Get();
if (accessibility_manager) {
accessibility_status_subscription_ =
accessibility_manager->RegisterCallback(base::BindRepeating(
&TopControlsSlideControllerChromeOS::OnAccessibilityStatusChanged,
base::Unretained(this)));
}
OnEnabledStateChanged(CanEnable(base::nullopt));
}
TopControlsSlideControllerChromeOS::~TopControlsSlideControllerChromeOS() {
OnEnabledStateChanged(false);
display::Screen::GetScreen()->RemoveObserver(this);
browser_view_->browser()->tab_strip_model()->RemoveObserver(this);
if (ash::TabletMode::Get())
ash::TabletMode::Get()->RemoveObserver(this);
if (observed_omni_box_)
observed_omni_box_->RemoveObserver(this);
}
bool TopControlsSlideControllerChromeOS::IsEnabled() const {
return is_enabled_;
}
float TopControlsSlideControllerChromeOS::GetShownRatio() const {
return shown_ratio_;
}
void TopControlsSlideControllerChromeOS::SetShownRatio(
content::WebContents* contents,
float ratio) {
DCHECK(contents);
if (pause_updates_)
return;
// Make sure the value tracked per tab is always updated even when sliding is
// disabled, so that we're always synchronized with the renderer.
DCHECK(observed_tabs_.count(contents));
// The only times the `DoBrowserControlsShrinkRendererSize` bit is allowed to
// change are:
// 1) Right before we begin sliding the controls, which happens immediately
// after we set a fractional shown ratio.
// 2) As soon as both gesture scrolling has finished and controls reach a
// terminal value (1 or 0). Note that a scroll might finish but controls
// might still be animating. In this case,
// `DoBrowserControlsShrinkRendererSize` is changed when the animation
// finishes.
const bool is_enabled = IsEnabled();
const bool sliding_or_scrolling_in_progress =
is_gesture_scrolling_in_progress_ || is_sliding_in_progress_ ||
(is_enabled && ratio != 0.f && ratio != 1.f);
observed_tabs_[contents]->SetShownRatio(ratio,
sliding_or_scrolling_in_progress);
if (!is_enabled) {
// However, if sliding is disabled, we don't update |shown_ratio_|, which is
// the current value for the entire browser, and it must always be 1.f (i.e.
// the top controls are fully shown).
DCHECK_EQ(shown_ratio_, 1.f);
return;
}
// Skip |shown_ratio_| update if the changes are not from the active
// WebContents.
if (contents != browser_view_->GetActiveWebContents())
return;
if (shown_ratio_ == ratio)
return;
shown_ratio_ = ratio;
Refresh();
// When disabling is deferred, we're waiting for the render to fully show top-
// chrome, so look for a value of 1.f. The renderer may be animating towards
// that value.
if (defer_disabling_ && shown_ratio_ == 1.f) {
defer_disabling_ = false;
// Don't just set |is_enabled_| to false. Make sure it's a correct value.
OnEnabledStateChanged(CanEnable(base::nullopt));
}
}
void TopControlsSlideControllerChromeOS::OnBrowserFullscreenStateWillChange(
bool new_fullscreen_state) {
OnEnabledStateChanged(CanEnable(new_fullscreen_state));
}
bool TopControlsSlideControllerChromeOS::DoBrowserControlsShrinkRendererSize(
const content::WebContents* contents) const {
if (!IsEnabled())
return false;
auto iter = observed_tabs_.find(contents);
if (iter == observed_tabs_.end()) {
// this may be called for a new tab that hasn't attached yet to the tabstrip
return false;
}
return iter->second->shrink_renderer_size();
}
void TopControlsSlideControllerChromeOS::SetTopControlsGestureScrollInProgress(
bool in_progress) {
if (is_gesture_scrolling_in_progress_ == in_progress)
return;
is_gesture_scrolling_in_progress_ = in_progress;
if (update_state_after_gesture_scrolling_ends_) {
DCHECK(!is_gesture_scrolling_in_progress_);
DCHECK(pause_updates_);
OnEnabledStateChanged(CanEnable(base::nullopt));
update_state_after_gesture_scrolling_ends_ = false;
pause_updates_ = false;
}
if (!IsEnabled())
return;
if (is_gesture_scrolling_in_progress_) {
// Once gesture scrolling starts, the renderer is expected to
// SetShownRatio() or at least call back here to reset
// |is_gesture_scrolling_in_progress_| back to false. Nothing needs to be
// done here.
return;
}
// Regardless of the value of |is_sliding_in_progress_|, which may be:
// - True:
// * We haven't reached a terminal value (1.f or 0.f) for the
// |shown_ratio_|. In this case the render should continue by animating
// the top controls towards one side. Therefore we wait for that to
// happen.
// * We are already at a terminal value of the |shown_ratio_| but sliding
// hasn't ended, because gesture scrolling hasn't ended (for example user
// scrolls top-chrome up until it's fully hidden, keeps their finger down
// without movement for a bit, and then releases finger).
//
// - False:
// * In tests, where flings can be very fast that the renderer sets the
// shown ratio from one terminal value to the opposite terminal value
// directly (without fractional values). In this case no sliding happens,
// but we still want to commit the new value of the shown ratio, once
// gesture scrolling ends.
//
// Calling refresh will take care of the above cases.
Refresh();
}
bool TopControlsSlideControllerChromeOS::IsTopControlsGestureScrollInProgress()
const {
return is_gesture_scrolling_in_progress_;
}
bool TopControlsSlideControllerChromeOS::IsTopControlsSlidingInProgress()
const {
return is_sliding_in_progress_;
}
void TopControlsSlideControllerChromeOS::OnTabletModeStarted() {
OnEnabledStateChanged(CanEnable(base::nullopt));
}
void TopControlsSlideControllerChromeOS::OnTabletModeEnded() {
OnEnabledStateChanged(CanEnable(base::nullopt));
}
void TopControlsSlideControllerChromeOS::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (change.type() == TabStripModelChange::kInserted) {
for (const auto& contents : change.GetInsert()->contents) {
observed_tabs_.emplace(contents.contents,
std::make_unique<TopControlsSlideTabObserver>(
contents.contents, this));
}
} else if (change.type() == TabStripModelChange::kRemoved) {
for (const auto& contents : change.GetRemove()->contents)
observed_tabs_.erase(contents.contents);
} else if (change.type() == TabStripModelChange::kReplaced) {
auto* replace = change.GetReplace();
observed_tabs_.erase(replace->old_contents);
DCHECK(!observed_tabs_.count(replace->new_contents));
observed_tabs_.emplace(replace->new_contents,
std::make_unique<TopControlsSlideTabObserver>(
replace->new_contents, this));
}
if (tab_strip_model->empty() || !selection.active_tab_changed())
return;
content::WebContents* new_active_contents = selection.new_contents;
DCHECK(observed_tabs_.count(new_active_contents));
// Restore the newly-activated tab's shown ratio. If this is a newly inserted
// tab, its |shown_ratio_| is 1.0f.
SetShownRatio(new_active_contents,
observed_tabs_[new_active_contents]->shown_ratio());
UpdateBrowserControlsStateShown(new_active_contents, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::SetTabNeedsAttentionAt(
int index,
bool attention) {
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
if (!IsEnabled())
return;
if (!is_sliding_in_progress_ || !is_gesture_scrolling_in_progress_)
return;
// If any of the below display metrics changes while both sliding and gesture
// scrolling are in progress, we force-set the top controls to be fully shown,
// and temporarily disables the state of the top controls sliding feature
// until the user lifts their finger to end gesture scrolling, at which point
// we set it back to its correct value.
// This is necessary, since this way the browser view will layout properly,
// avoiding having a broken page or a broken browser view if one of the below
// changes happen while the top controls are not in a steady state.
constexpr int kCheckedMetrics =
display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR |
display::DisplayObserver::DISPLAY_METRIC_ROTATION |
display::DisplayObserver::DISPLAY_METRIC_PRIMARY |
display::DisplayObserver::DISPLAY_METRIC_MIRROR_STATE;
if ((changed_metrics & kCheckedMetrics) == 0)
return;
if (browser_view_->GetNativeWindow()->GetHost()->GetDisplayId() !=
display.id()) {
return;
}
content::WebContents* active_contents = browser_view_->GetActiveWebContents();
if (!active_contents)
return;
update_state_after_gesture_scrolling_ends_ = true;
{
// Setting |is_gesture_scrolling_in_progress_| to false temporarily will end
// the sliding when we set the shown ratio to a terminal value of 1.f.
base::AutoReset<bool> resetter{&is_gesture_scrolling_in_progress_, false};
SetShownRatio(active_contents, 1.f);
}
pause_updates_ = true;
OnEnabledStateChanged(false);
}
void TopControlsSlideControllerChromeOS::OnViewIsDeleting(
views::View* observed_view) {
DCHECK_EQ(observed_view, observed_omni_box_);
observed_omni_box_ = nullptr;
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::OnViewFocused(
views::View* observed_view) {
DCHECK_EQ(observed_view, observed_omni_box_);
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::OnViewBlurred(
views::View* observed_view) {
DCHECK_EQ(observed_view, observed_omni_box_);
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::UpdateBrowserControlsStateShown(
content::WebContents* web_contents,
bool animate) {
web_contents =
web_contents ? web_contents : browser_view_->GetActiveWebContents();
if (!web_contents)
return;
content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
if (!main_frame)
return;
// If the omnibox is focused, then the top controls should be constrained to
// remain fully shown until the omnibox is blurred.
const cc::BrowserControlsState constraints_state =
observed_omni_box_ && observed_omni_box_->HasFocus()
? cc::BrowserControlsState::kShown
: GetBrowserControlsStateConstraints(web_contents);
const cc::BrowserControlsState current_state =
cc::BrowserControlsState::kShown;
main_frame->UpdateBrowserControlsState(constraints_state, current_state,
animate);
}
bool TopControlsSlideControllerChromeOS::CanEnable(
base::Optional<bool> fullscreen_state) const {
return IsTabletModeEnabled() &&
!(fullscreen_state.value_or(browser_view_->IsFullscreen()));
}
void TopControlsSlideControllerChromeOS::OnAccessibilityStatusChanged(
const chromeos::AccessibilityStatusEventDetails& event_details) {
if (event_details.notification_type !=
chromeos::ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK) {
return;
}
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/true);
}
void TopControlsSlideControllerChromeOS::OnEnabledStateChanged(bool new_state) {
if (new_state == is_enabled_)
return;
is_enabled_ = new_state;
content::WebContents* active_contents = browser_view_->GetActiveWebContents();
if (!active_contents)
return;
if (!new_state && shown_ratio_ < 1.f) {
// We should never set the shown ratio immediately here, rather ask the
// renderer to show top-chrome without animation. Since this will happen
// later asynchronously, we need to defer the enabled status update until
// we get called by the renderer to set the shown ratio to 1.f. Otherwise
// we will layout the page to a smaller height before the renderer gets
// to know that it needs to update the shown ratio to 1.f.
// https://crbug.com/884453.
is_enabled_ = true;
defer_disabling_ = true;
} else {
defer_disabling_ = false;
// Now that the state of this feature is changed, force the renderer to get
// the new top controls height by triggering a visual properties
// synchrnoization event.
SynchronizeVisualProperties(active_contents);
}
// This will also update the browser controls state constraints in the render
// now that the state changed.
UpdateBrowserControlsStateShown(/*web_contents=*/nullptr, /*animate=*/false);
}
void TopControlsSlideControllerChromeOS::Refresh() {
const bool got_a_terminal_shown_ratio =
(shown_ratio_ == 1.f || shown_ratio_ == 0.f);
if (!is_gesture_scrolling_in_progress_ && got_a_terminal_shown_ratio) {
// Reached a terminal value and gesture scrolling is not in progress.
OnEndSliding();
return;
}
if (!is_sliding_in_progress_) {
if (got_a_terminal_shown_ratio) {
// Don't start sliding until we receive a fractional shown ratio.
return;
}
OnBeginSliding();
}
// Using |shown_ratio_|, translate the browser top controls (using the root
// view layer), as well as the layer of page contents native view's container
// (which is the clipping window in the case of a NativeViewHostAura).
// The translation is done in the Y-coordinate by an amount equal to the
// height of the hidden part of the browser top controls.
const int top_container_height = browser_view_->top_container()->height();
const float y_translation = top_container_height * (shown_ratio_ - 1.f);
gfx::Transform trans;
trans.Translate(0, y_translation);
ui::Layer* root_layer = browser_view_->frame()->GetRootView()->layer();
std::vector<ui::Layer*> layers = {root_layer};
// We need to transform all the native views' containers of all the attached
// NativeViewHosts to this BrowserView, rather than the NativeViewHosts
// themselves. The attached NativeViewHosts can be active tab's WebContents,
// and the webui tabstrip (if enabled). This is because for example in the
// case of the tab's WebContents, the container in the case of aura is the
// clipping window. If we translate the WebContents native view the page will
// appear to scroll, but clipping window will act as a static/ view port that
// doesn't move with the top controls.
for (auto* native_view_host :
browser_view_->GetNativeViewHostsForTopControlsSlide()) {
DCHECK(native_view_host->GetNativeViewContainer())
<< "The native view didn't attach yet to the NativeViewHost!";
layers.push_back(native_view_host->GetNativeViewContainer()->layer());
}
for (auto* layer : layers) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(0));
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
layer->SetTransform(trans);
}
}
void TopControlsSlideControllerChromeOS::OnBeginSliding() {
DCHECK(IsEnabled());
// It should never be called again.
DCHECK(!is_sliding_in_progress_);
// Explicitly update the `DoBrowserControlsShrinkRendererSize` bit here before
// we begin sliding, and before we resize the browser view below, which will
// result in changing the bounds of the `BrowserView::contents_web_view_`,
// causing the RednerWidgetHost to request the new value of the
// `DoBrowserControlsShrinkRendererSize` bit, which should be false from now
// on, during and after sliding, until only sliding ends and the top controls
// are fully shown.
UpdateDoBrowserControlsShrinkRendererSize();
is_sliding_in_progress_ = true;
BrowserFrame* browser_frame = browser_view_->frame();
views::View* root_view = browser_frame->GetRootView();
// We paint to layer to be able to efficiently translate the browser
// top-controls without having to adjust the bounds of the views which trigger
// re-layouts and re-paints, which makes scrolling feel laggy.
root_view->SetPaintToLayer();
// We need to make the layer non-opaque as the tabstrip has transparent areas
// (where there are no tabs) which shows the frame header from underneath it.
// Making the root view paint to a layer will always produce garbage and
// artifacts while the layer is being scrolled if it's left to be opaque.
// Making it non-opaque fixes this issue.
root_view->layer()->SetFillsBoundsOpaquely(false);
// We need to fix the order of the layers after making the root view paint to
// layer. Otherwise, the root view's layer will show on top of the contents'
// native view's layer and cover it.
browser_frame->ReorderNativeViews();
ui::Layer* widget_layer = browser_frame->GetLayer();
// OnBeginSliding() means we are in a transient state (i.e. the top controls
// didn't reach its final state of either fully shown or fully hidden). During
// this state, we resize the widget's root view to be bigger in height so the
// contents can take up more space, and slidding top-chrome doesn't result in
// showing clipped web contents.
// This resize will trigger a relayout on the BrowserView which will take care
// of positioning everything correctly (See BrowserViewLayout).
// Note: It's ok to trigger a layout at the beginning and ending of the slide
// but not in-between. Layers transforms handles the in-between.
gfx::Rect root_bounds = root_view->bounds();
const int top_container_height = browser_view_->top_container()->height();
const int new_height = widget_layer->bounds().height() + top_container_height;
root_bounds.set_height(new_height);
root_view->SetBoundsRect(root_bounds);
// Changing the bounds will have triggered an InvalidateLayout() on
// NativeViewHost. InvalidateLayout() results in Layout() being called later,
// after transforms are set. NativeViewHostAura calculates the bounds of the
// window using transforms. By calling LayoutRootViewIfNecessary() we force
// the layout now, before any transforms are installed. To do otherwise
// results in NativeViewHost positioning the WebContents at the wrong
// location.
// TODO(https://crbug.com/950981): this is rather fragile, and the code should
// deal with Layout() being called during the slide.
root_view->GetWidget()->LayoutRootViewIfNecessary();
// We don't want anything to show outside the browser window's bounds.
widget_layer->SetMasksToBounds(true);
}
void TopControlsSlideControllerChromeOS::OnEndSliding() {
DCHECK(IsEnabled());
// This should only be called at terminal values of the |shown_ratio_|.
DCHECK(shown_ratio_ == 1.f || shown_ratio_ == 0.f);
// It should never be called while gesture scrolling is still in progress.
DCHECK(!is_gesture_scrolling_in_progress_);
// If disabling is deferred, sliding should end only when top-chrome is fully
// shown.
DCHECK(!defer_disabling_ || (shown_ratio_ == 1.f));
// It can, however, be called when sliding is not in progress as a result of
// Setting the value directly (for example due to renderer crash), or a direct
// call from the renderer to set the shown ratio to a terminal value.
is_sliding_in_progress_ = false;
// At the end of sliding, we reset the transforms of all the attached
// NativeViewHostAuras' clipping windows' layers to identity. From now on, the
// views layout takes care of where everything is.
const gfx::Transform identity_transform;
for (auto* native_view_host :
browser_view_->GetNativeViewHostsForTopControlsSlide()) {
DCHECK(native_view_host->GetNativeViewContainer())
<< "The native view didn't attach yet to the NativeViewHost!";
native_view_host->GetNativeViewContainer()->layer()->SetTransform(
identity_transform);
}
BrowserFrame* browser_frame = browser_view_->frame();
views::View* root_view = browser_frame->GetRootView();
root_view->DestroyLayer();
ui::Layer* widget_layer = browser_frame->GetLayer();
// Note the difference between the below root view resize, and the
// corresponding one in OnBeginSliding() above. Here we have reached a steady
// terminal (|shown_ratio_| is either 1.f or 0.f) state, which means the
// height of the root view should be restored to the height of the widget.
// Note: It's ok to trigger a layout at the beginning and ending of the slide
// but not in-between. Layers transforms handles the in-between.
auto root_bounds = root_view->bounds();
const int original_height = root_bounds.height();
const int new_height = widget_layer->bounds().height();
// This must be updated here **before** the browser is laid out, since the
// renderer (as a result of the layout) may query this value, and hence it
// should be correct.
UpdateDoBrowserControlsShrinkRendererSize();
// We need to guarantee a browser view re-layout, but want to avoid doing that
// twice.
if (new_height != original_height) {
root_bounds.set_height(new_height);
root_view->SetBoundsRect(root_bounds);
} else {
// This can happen when setting the shown ratio directly from one terminal
// value to the opposite. The height of the root view doesn't change, but
// the browser view must be re-laid out.
browser_view_->Layout();
}
// If the top controls are fully hidden, then the top container is laid out
// such that its bounds are outside the window. The window should continue to
// mask anything outside its bounds.
widget_layer->SetMasksToBounds(shown_ratio_ < 1.f);
}
void TopControlsSlideControllerChromeOS::
UpdateDoBrowserControlsShrinkRendererSize() {
// It should never be called while sliding is in progress.
DCHECK(!is_sliding_in_progress_);
content::WebContents* active_contents = browser_view_->GetActiveWebContents();
if (!active_contents)
return;
DCHECK(observed_tabs_.count(active_contents));
observed_tabs_[active_contents]->UpdateDoBrowserControlsShrinkRendererSize();
}