chromium/chrome/browser/ui/views/lens/lens_side_panel_navigation_helper.cc

// Copyright 2022 The Chromium Authors
// 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/lens/lens_side_panel_navigation_helper.h"

#include <string_view>

#include "chrome/browser/ui/browser.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "ui/base/page_transition_types.h"

namespace {

// List of domains that are safe to happen in the background. To be in the list,
// the site must not be reachable by user navigation.
static constexpr std::string_view BACKGROUND_THROTTLE_EXCEPTIONS[] = {
    "https://feedback.googleusercontent.com"};

bool IsSameSite(const GURL& url1, const GURL& url2) {
  return url1.SchemeIs(url2.scheme()) &&
         net::registry_controlled_domains::SameDomainOrHost(
             url1, url2,
             net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}

// Helper that returns if the given URL is in the exception set
bool HasThrottleException(const GURL& url) {
  for (std::string_view safe_site : BACKGROUND_THROTTLE_EXCEPTIONS) {
    if (IsSameSite(url, GURL(safe_site)))
      return true;
  }
  return false;
}

class LensSidePanelNavigationThrottle : public content::NavigationThrottle {
 public:
  explicit LensSidePanelNavigationThrottle(
      content::NavigationHandle* navigation_handle)
      : NavigationThrottle(navigation_handle) {}

  // NavigationThrottle overrides.
  ThrottleCheckResult WillStartRequest() override {
    return HandleSidePanelRequest();
  }

  ThrottleCheckResult WillRedirectRequest() override {
    return HandleSidePanelRequest();
  }

  const char* GetNameForLogging() override {
    return "LensSidePanelNavigationThrottle";
  }

 private:
  ThrottleCheckResult HandleSidePanelRequest() {
    const auto& url = navigation_handle()->GetURL();

    // All navigations on a separate subdomain open in a new tab
    auto* navigation_helper =
        lens::LensSidePanelNavigationHelper::FromWebContents(
            navigation_handle()->GetWebContents());
    DCHECK(navigation_helper);

    // Allow navigations that belong to the same site as the image search
    // engine or those that have an exception.
    if (IsSameSite(navigation_helper->GetOriginUrl(), url) ||
        HasThrottleException(url))
      return content::NavigationThrottle::PROCEED;

    auto params =
        content::OpenURLParams::FromNavigationHandle(navigation_handle());

    // All user clicks to a destination otuside of the origin URL should open in
    // a new tab
    if (ui::PageTransitionCoreTypeIs(params.transition,
                                     ui::PAGE_TRANSITION_LINK)) {
      navigation_helper->OpenInNewTab(params);
    }
    return content::NavigationThrottle::CANCEL;
  }
};

}  // namespace

namespace lens {
LensSidePanelNavigationHelper::LensSidePanelNavigationHelper(
    content::WebContents* web_contents,
    Browser* browser,
    const std::string& origin_url)
    : content::WebContentsUserData<LensSidePanelNavigationHelper>(
          *web_contents),
      origin_url_(origin_url) {
  // If Lens side panel every becomes contextual (i.e. not tied to one browser
  // window) this implementation needs to be revisited to update the browser
  // pointer when Lens side panel changes windows.
  browser_ = browser;
}

LensSidePanelNavigationHelper::~LensSidePanelNavigationHelper() = default;

void LensSidePanelNavigationHelper::OpenInNewTab(
    content::OpenURLParams params) {
  if (browser_ == nullptr)
    return;
  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  browser_->OpenURL(params, /*navigation_handle_callback=*/{});
}

const GURL& LensSidePanelNavigationHelper::GetOriginUrl() {
  return origin_url_;
}

std::unique_ptr<content::NavigationThrottle>
LensSidePanelNavigationHelper::MaybeCreateThrottleFor(
    content::NavigationHandle* handle) {
  // The helper will only return an address if a Lens side panel has been
  // initialized
  auto* helper =
      LensSidePanelNavigationHelper::FromWebContents(handle->GetWebContents());
  if (!helper)
    return nullptr;

  return std::make_unique<LensSidePanelNavigationThrottle>(handle);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(LensSidePanelNavigationHelper);
}  // namespace lens