chromium/android_webview/browser/renderer_host/aw_render_view_host_ext.cc

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

#include "android_webview/browser/renderer_host/aw_render_view_host_ext.h"

#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/aw_contents.h"
#include "android_webview/browser/aw_contents_client_bridge.h"
#include "android_webview/common/aw_features.h"
#include "base/android/scoped_java_ref.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "net/http/http_request_headers.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"

namespace android_webview {

// static
void AwRenderViewHostExt::BindFrameHost(
    mojo::PendingAssociatedReceiver<mojom::FrameHost> receiver,
    content::RenderFrameHost* rfh) {
  auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
  if (!web_contents)
    return;
  auto* aw_contents = AwContents::FromWebContents(web_contents);
  if (!aw_contents)
    return;
  auto* aw_rvh_ext = aw_contents->render_view_host_ext();
  if (!aw_rvh_ext)
    return;
  aw_rvh_ext->frame_host_receivers_.Bind(rfh, std::move(receiver));
}

AwRenderViewHostExt::AwRenderViewHostExt(AwRenderViewHostExtClient* client,
                                         content::WebContents* contents)
    : content::WebContentsObserver(contents),
      client_(client),
      frame_host_receivers_(contents, this) {
  DCHECK(client_);
}

AwRenderViewHostExt::~AwRenderViewHostExt() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

void AwRenderViewHostExt::DocumentHasImages(DocumentHasImagesResult result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!web_contents()->GetRenderViewHost()) {
    std::move(result).Run(false);
    return;
  }

  if (auto* local_main_frame_remote = GetLocalMainFrameRemote()) {
    local_main_frame_remote->DocumentHasImage(std::move(result));
  } else {
    // Still have to respond to the API call WebView#docuemntHasImages.
    // Otherwise the listener of the response may be starved.
    std::move(result).Run(false);
  }
}

void AwRenderViewHostExt::RequestNewHitTestDataAt(
    const gfx::PointF& touch_center,
    const gfx::SizeF& touch_area) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // If the new hit testing approach for touch start is enabled we just early
  // return.
  if (base::FeatureList::IsEnabled(
          features::kWebViewHitTestInBlinkOnTouchStart)) {
    return;
  }

  // The following code is broken for OOPIF and fenced frames. The hit testing
  // feature for touch start replaces this code and works correctly in those
  // scenarios. For mitigating risk we've put the old code behind a feature
  // flag.
  //
  // We only need to get blink::WebView on the renderer side to invoke the
  // blink hit test Mojo method, so sending this message via LocalMainFrame
  // interface is enough.
  if (auto* local_main_frame_remote = GetLocalMainFrameRemote())
    local_main_frame_remote->HitTest(touch_center, touch_area);
}

mojom::HitTestDataPtr AwRenderViewHostExt::TakeLastHitTestData() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  return std::move(last_hit_test_data_);
}

void AwRenderViewHostExt::SetTextZoomFactor(float factor) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (auto* local_main_frame_remote = GetLocalMainFrameRemote())
    local_main_frame_remote->SetTextZoomFactor(factor);
}

void AwRenderViewHostExt::ResetScrollAndScaleState() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (auto* local_main_frame_remote = GetLocalMainFrameRemote())
    local_main_frame_remote->ResetScrollAndScaleState();
}

void AwRenderViewHostExt::SetInitialPageScale(double page_scale_factor) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (auto* local_main_frame_remote = GetLocalMainFrameRemote())
    local_main_frame_remote->SetInitialPageScale(page_scale_factor);
}

void AwRenderViewHostExt::SetWillSuppressErrorPage(bool suppress) {
  will_suppress_error_page_ = suppress;
}

void AwRenderViewHostExt::SmoothScroll(int target_x,
                                       int target_y,
                                       base::TimeDelta duration) {
  if (auto* local_main_frame_remote = GetLocalMainFrameRemote())
    local_main_frame_remote->SmoothScroll(target_x, target_y, duration);
}

void AwRenderViewHostExt::DidStartNavigation(
    content::NavigationHandle* navigation_handle) {
  if (will_suppress_error_page_)
    navigation_handle->SetSilentlyIgnoreErrors();
}

void AwRenderViewHostExt::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!navigation_handle->HasCommitted())
    return;

  // Only record a visit if the navigation affects user-facing session history
  // (i.e. it occurs in the primary frame tree).
  if (navigation_handle->IsInPrimaryMainFrame() ||
      (navigation_handle->GetParentFrame() &&
       navigation_handle->GetParentFrame()->GetPage().IsPrimary() &&
       navigation_handle->HasSubframeNavigationEntryCommitted())) {
    AwBrowserContext::FromWebContents(web_contents())
        ->AddVisitedURLs(navigation_handle->GetRedirectChain());
  }
}

void AwRenderViewHostExt::OnPageScaleFactorChanged(float page_scale_factor) {
  client_->OnWebLayoutPageScaleFactorChanged(page_scale_factor);
}

void AwRenderViewHostExt::UpdateHitTestData(
    mojom::HitTestDataPtr hit_test_data) {
  content::RenderFrameHost* render_frame_host =
      frame_host_receivers_.GetCurrentTargetFrame();
  // Make sense from any frame of the active frame tree, because a focused
  // node could be in either the mainframe or a subframe.
  if (!render_frame_host->IsActive())
    return;

  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  last_hit_test_data_ = std::move(hit_test_data);
}

void AwRenderViewHostExt::ContentsSizeChanged(const gfx::Size& contents_size) {
  content::RenderFrameHost* render_frame_host =
      frame_host_receivers_.GetCurrentTargetFrame();

  // Only makes sense coming from the main frame of the current frame tree.
  if (!render_frame_host->IsInPrimaryMainFrame())
    return;

  client_->OnWebLayoutContentsSizeChanged(contents_size);
}

void AwRenderViewHostExt::ShouldOverrideUrlLoading(
    const std::u16string& url,
    bool has_user_gesture,
    bool is_redirect,
    bool is_main_frame,
    ShouldOverrideUrlLoadingCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  bool ignore_navigation = false;
  AwContentsClientBridge* client =
      AwContentsClientBridge::FromWebContents(web_contents());
  if (client) {
    if (!client->ShouldOverrideUrlLoading(
            url, has_user_gesture, is_redirect, is_main_frame,
            net::HttpRequestHeaders(), &ignore_navigation)) {
      // If the shouldOverrideUrlLoading call caused a java exception we should
      // always return immediately here!
      return;
    }
  } else {
    LOG(WARNING) << "Failed to find the associated render view host for url: "
                 << url;
  }

  std::move(callback).Run(ignore_navigation);
}

mojom::LocalMainFrame* AwRenderViewHostExt::GetLocalMainFrameRemote() {
  // Validate the local main frame matches what we have stored for the current
  // main frame. Previously `local_main_frame_remote_` was adjusted in
  // RenderFrameCreated/RenderFrameHostChanged events but the timings of when
  // this class gets called vs others using this class might cause a TOU
  // problem, so we validate it each time before use.
  content::RenderFrameHost* main_frame = web_contents()->GetPrimaryMainFrame();
  content::GlobalRenderFrameHostId main_frame_id = main_frame->GetGlobalId();
  if (main_frame_global_id_ == main_frame_id) {
    return local_main_frame_remote_.get();
  }

  local_main_frame_remote_.reset();

  // Avoid accessing GetRemoteAssociatedInterfaces until the renderer is
  // created.
  if (!main_frame->IsRenderFrameLive()) {
    main_frame_global_id_ = content::GlobalRenderFrameHostId();
    return nullptr;
  }

  main_frame_global_id_ = main_frame_id;
  main_frame->GetRemoteAssociatedInterfaces()->GetInterface(
      local_main_frame_remote_.BindNewEndpointAndPassReceiver());
  return local_main_frame_remote_.get();
}

}  // namespace android_webview