blob: 401ec7acec0e7eb7846903bbe7e2f5d3a83dbfd1 [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 "base/bind.h"
#include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ssl/security_state_tab_helper.h"
#include "chrome/browser/ui/ash/tablet_mode_client.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/common/chrome_render_frame.mojom.h"
#include "chrome/common/url_constants.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/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.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 "content/public/common/browser_controls_state.h"
#include "extensions/common/constants.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/aura/window.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/controls/native/native_view_host.h"
namespace {
bool IsTabletModeEnabled() {
return TabletModeClient::Get() &&
TabletModeClient::Get()->tablet_mode_enabled();
}
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).
content::BrowserControlsState GetBrowserControlsStateConstraints(
content::WebContents* contents) {
DCHECK(contents);
if (!IsTabletModeEnabled() || contents->IsFullscreen() ||
contents->IsFocusedElementEditable() ||
contents->ShowingInterstitialPage() || contents->IsBeingDestroyed() ||
contents->IsCrashed() || IsSpokenFeedbackEnabled()) {
return content::BROWSER_CONTROLS_STATE_SHOWN;
}
content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
if (!entry || entry->GetPageType() != content::PAGE_TYPE_NORMAL)
return content::BROWSER_CONTROLS_STATE_SHOWN;
const GURL& url = entry->GetURL();
if (url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(chrome::kChromeNativeScheme) ||
url.SchemeIs(extensions::kExtensionScheme)) {
return content::BROWSER_CONTROLS_STATE_SHOWN;
}
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
if (profile && search::IsNTPOrRelatedURL(url, profile))
return content::BROWSER_CONTROLS_STATE_SHOWN;
auto* helper = SecurityStateTabHelper::FromWebContents(contents);
switch (helper->GetSecurityLevel()) {
case security_state::HTTP_SHOW_WARNING:
case security_state::DANGEROUS:
return content::BROWSER_CONTROLS_STATE_SHOWN;
// Force compiler failure if new security level types were added without
// this being updated.
case security_state::NONE:
case security_state::EV_SECURE:
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 =
PermissionRequestManager::FromWebContents(contents);
if (permission_manager && permission_manager->IsBubbleVisible())
return content::BROWSER_CONTROLS_STATE_SHOWN;
return content::BROWSER_CONTROLS_STATE_BOTH;
}
// Instructs the renderer of |web_contents| to show the top controls, and also
// updates its shown state constraints based on the current status of
// |web_contents| (see GetBrowserControlsStateConstraints() above).
void UpdateBrowserControlsStateShown(content::WebContents* web_contents,
bool animate) {
DCHECK(web_contents);
content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
if (!main_frame)
return;
chrome::mojom::ChromeRenderFrameAssociatedPtr renderer;
main_frame->GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
if (!renderer)
return;
const content::BrowserControlsState constraints_state =
GetBrowserControlsStateConstraints(web_contents);
const content::BrowserControlsState current_state =
content::BROWSER_CONTROLS_STATE_SHOWN;
renderer->UpdateBrowserControlsState(constraints_state, current_state,
animate);
}
// 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 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 =
PermissionRequestManager::FromWebContents(web_contents);
if (permission_manager)
permission_manager->AddObserver(this);
}
~TopControlsSlideTabObserver() override {
auto* permission_manager =
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(false /* animate */);
owner_->SetShownRatio(web_contents(), 1.f);
}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->IsInMainFrame() && navigation_handle->HasCommitted())
UpdateBrowserControlsStateShown(true /* animate */);
}
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override {
if (render_frame_host->IsCurrent() &&
(render_frame_host == web_contents()->GetMainFrame())) {
UpdateBrowserControlsStateShown(true /* animate */);
}
}
void DidChangeVisibleSecurityState() override {
UpdateBrowserControlsStateShown(true /* animate */);
}
void DidAttachInterstitialPage() override {
UpdateBrowserControlsStateShown(true /* animate */);
}
void DidDetachInterstitialPage() override {
UpdateBrowserControlsStateShown(true /* animate */);
}
// PermissionRequestManager::Observer:
void OnBubbleAdded() override {
UpdateBrowserControlsStateShown(true /* animate */);
}
void OnBubbleRemoved() override {
// This will update the shown constraints.
UpdateBrowserControlsStateShown(false /* animate */);
}
private:
void UpdateBrowserControlsStateShown(bool animate) {
::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());
registrar_.Add(this, content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
content::NotificationService::AllSources());
if (TabletModeClient::Get())
TabletModeClient::Get()->AddObserver(this);
browser_view_->browser()->tab_strip_model()->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);
browser_view_->browser()->tab_strip_model()->RemoveObserver(this);
if (TabletModeClient::Get())
TabletModeClient::Get()->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);
// 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;
}
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) {
is_gesture_scrolling_in_progress_ = in_progress;
// Gesture scrolling may end before we reach 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.
if (is_gesture_scrolling_in_progress_ || !is_sliding_in_progress_)
return;
// Also, it may end when we are already at a terminal value of the
// |shown_ratio_| (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). Calling refresh in this case will take care of ending the
// sliding state (if we are in it).
Refresh();
}
bool TopControlsSlideControllerChromeOS::IsTopControlsGestureScrollInProgress()
const {
return is_gesture_scrolling_in_progress_;
}
void TopControlsSlideControllerChromeOS::OnTabletModeToggled(
bool tablet_mode_enabled) {
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, true /* animate */);
}
void TopControlsSlideControllerChromeOS::SetTabNeedsAttentionAt(
int index,
bool attention) {
UpdateBrowserControlsStateShown(browser_view_->GetActiveWebContents(),
true /* animate */);
}
void TopControlsSlideControllerChromeOS::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
// TODO(afakhry): It would be nice to add a WebContentsObserver method that
// broadcasts this event.
if (type != content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE)
return;
// Make sure this notification is meant for us.
content::WebContents* active_contents = browser_view_->GetActiveWebContents();
content::RenderViewHost* render_view_host =
content::Source<content::RenderViewHost>(source).ptr();
if (!active_contents || content::WebContents::FromRenderViewHost(
render_view_host) != active_contents) {
return;
}
content::FocusedNodeDetails* node_details =
content::Details<content::FocusedNodeDetails>(details).ptr();
// If a non-editable node gets focused and 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 (node_details->is_editable_node || shown_ratio_ == 1.f)
UpdateBrowserControlsStateShown(active_contents, true /* 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;
}
content::WebContents* active_contents = browser_view_->GetActiveWebContents();
if (active_contents)
UpdateBrowserControlsStateShown(active_contents, true /* animate */);
}
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(active_contents, false /* animate */);
}
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);
// We need to transform webcontents native view's container rather than the
// webcontents native view itself. That's because 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.
DCHECK(browser_view_->contents_web_view()->holder()->GetNativeViewContainer())
<< "The web contents' native view didn't attach yet!";
ui::Layer* contents_container_layer = browser_view_->contents_web_view()
->holder()
->GetNativeViewContainer()
->layer();
ui::Layer* root_layer = browser_view_->frame()->GetRootView()->layer();
std::vector<ui::Layer*> layers = {root_layer, contents_container_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 webcontents NativeViewHostAura's
// clipping window's layer's transform to identity. From now on, the views
// layout takes care of where everything is.
DCHECK(browser_view_->contents_web_view()->holder()->GetNativeViewContainer())
<< "The web contents' native view didn't attach yet!";
ui::Layer* contents_container_layer = browser_view_->contents_web_view()
->holder()
->GetNativeViewContainer()
->layer();
gfx::Transform transform;
contents_container_layer->SetTransform(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();
}