1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156

content / browser / display_cutout / safe_area_insets_host_impl.cc [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/display_cutout/safe_area_insets_host_impl.h"

#include "content/browser/display_cutout/display_cutout_constants.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/navigation_handle.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"

namespace content {

// DocumentUserData stored inside each RenderFrameHost indicating the
// viewport-fit value for that document.
class CONTENT_EXPORT SafeAreaUserData
    : public DocumentUserData<SafeAreaUserData> {
 public:
  ~SafeAreaUserData() override = default;

  void set_viewport_fit(blink::mojom::ViewportFit value) { value_ = value; }
  blink::mojom::ViewportFit viewport_fit() { return value_; }

 private:
  explicit SafeAreaUserData(RenderFrameHost* rfh)
      : DocumentUserData<SafeAreaUserData>(rfh) {}

  // The viewport-fit value known by blink, or kAuto.
  blink::mojom::ViewportFit value_ = blink::mojom::ViewportFit::kAuto;

  // NOTE: Do not add data members without updating SetViewportFitValue.
  // This is because data does not need to be stored if it consists purely
  // of default values. If new data is added then SetViewportFitValue must
  // check if the new data also has a default value before skipping storage.

  friend DocumentUserData;
  DOCUMENT_USER_DATA_KEY_DECL();
};

DOCUMENT_USER_DATA_KEY_IMPL(SafeAreaUserData);

SafeAreaInsetsHostImpl::SafeAreaInsetsHostImpl(WebContentsImpl* web_contents)
    : SafeAreaInsetsHost(web_contents) {}

SafeAreaInsetsHostImpl::~SafeAreaInsetsHostImpl() = default;

void SafeAreaInsetsHostImpl::DidAcquireFullscreen(RenderFrameHost* rfh) {
  fullscreen_rfh_ = static_cast<RenderFrameHostImpl*>(rfh)->GetWeakPtr();
  MaybeActiveRenderFrameHostChanged();
}

void SafeAreaInsetsHostImpl::DidExitFullscreen() {
  fullscreen_rfh_.reset();
  MaybeActiveRenderFrameHostChanged();
}

void SafeAreaInsetsHostImpl::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  // If the navigation is committed and we are not a same-document
  // navigation then set the current Render Frame Host and its value.
  if (navigation_handle->HasCommitted() &&
      !navigation_handle->IsSameDocument() &&
      navigation_handle->IsInPrimaryMainFrame()) {
    RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost();
    DCHECK(rfh);
    current_rfh_ = static_cast<RenderFrameHostImpl*>(rfh)->GetWeakPtr();
    MaybeActiveRenderFrameHostChanged();
  }
}

void SafeAreaInsetsHostImpl::SetDisplayCutoutSafeArea(gfx::Insets insets) {
  RenderFrameHostImpl* rfh = ActiveRenderFrameHost();
  if (rfh) {
    // Skip sending the safe area to frame if the values match the latest sent
    // values.
    if (insets != insets_) {
      base::UmaHistogramBoolean(
          "Android.SafeAreaInsets.SendSafeAreaToFrame.Optimized", false);
      SendSafeAreaToFrame(rfh, insets);
    } else {
      base::UmaHistogramBoolean(
          "Android.SafeAreaInsets.SendSafeAreaToFrame.Optimized", true);
    }
  }
  insets_ = insets;
}

void SafeAreaInsetsHostImpl::ViewportFitChangedForFrame(
    RenderFrameHost* rfh,
    blink::mojom::ViewportFit value) {
  DCHECK(rfh);
  SetViewportFitValue(rfh, value);

  // If we are the active `RenderFrameHost` frame then notify
  // WebContentsObservers about the new value.
  if (rfh == ActiveRenderFrameHost()) {
    MaybeActiveRenderFrameHostChanged();
  }
}

void SafeAreaInsetsHostImpl::MaybeActiveRenderFrameHostChanged() {
  base::WeakPtr<RenderFrameHostImpl> new_active_rfh =
      fullscreen_rfh_ ? fullscreen_rfh_ : current_rfh_;

  if (active_rfh_.get() && new_active_rfh.get() != active_rfh_.get()) {
    // Reset the SAI for the previous active frame.
    SendSafeAreaToFrame(active_rfh_.get(), gfx::Insets());
  }
  active_rfh_ = new_active_rfh;

  blink::mojom::ViewportFit new_value =
      GetValueOrDefault(ActiveRenderFrameHost());
  if (new_value != active_value_) {
    active_value_ = new_value;
    web_contents_impl_->NotifyViewportFitChanged(new_value);
  }
  // Update Blink so its document displays with the current insets.
  if (new_active_rfh) {
    SendSafeAreaToFrame(new_active_rfh.get(), insets_);
  }
}

RenderFrameHostImpl* SafeAreaInsetsHostImpl::ActiveRenderFrameHost() {
  return active_rfh_.get();
}

blink::mojom::ViewportFit SafeAreaInsetsHostImpl::GetValueOrDefault(
    RenderFrameHost* rfh) const {
  // The active RenderFrameHost can be null in some cases, such as if fullscreen
  // mode is exited before the navigation finishes.
  if (rfh) {
    SafeAreaUserData* data = SafeAreaUserData::GetForCurrentDocument(rfh);
    if (data) {
      return data->viewport_fit();
    }
  }
  return blink::mojom::ViewportFit::kAuto;
}

void SafeAreaInsetsHostImpl::SetViewportFitValue(
    RenderFrameHost* rfh,
    blink::mojom::ViewportFit value) {
  if (value == blink::mojom::ViewportFit::kAuto) {
    // We don't need to store UserData when it only contains the default
    // value(s).
    if (SafeAreaUserData::GetForCurrentDocument(rfh)) {
      SafeAreaUserData::DeleteForCurrentDocument(rfh);
    }
  } else {
    SafeAreaUserData::GetOrCreateForCurrentDocument(rfh)->set_viewport_fit(
        value);
  }
}

}  // namespace content