910e62b5创建于 1月15日历史提交
// Copyright 2024 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/renderer/web_link_preview_triggerer_impl.h"

#include "base/functional/bind.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.h"

namespace {

constexpr base::TimeDelta kHoverThreshold = base::Milliseconds(800);
// TODO(crbug.com/330196622):
//   Use ui::GestureConfiguration()->long_press_time_ms() here.
constexpr base::TimeDelta kLongPressThreshold = base::Milliseconds(800);

blink::WebURL GetURL(blink::WebElement& anchor_element) {
  if (anchor_element.IsNull()) {
    return blink::WebURL();
  }

  blink::WebString href = anchor_element.GetAttribute("href");
  if (href.IsNull()) {
    return blink::WebURL();
  }

  // TODO(b:325558426): Mimic HTMLAnchorElement::Href and
  // StripLeadingAndTrailingHTMLSpaces.
  blink::WebURL url = anchor_element.GetDocument().CompleteURL(href);

  if (!url.IsValid()) {
    return blink::WebURL();
  }

  return url;
}

blink::WebElement GetMostInnerAnchorElement(blink::WebElement element) {
  blink::WebElement most_inner_anchor_element;
  for (blink::WebNode curr = element; !curr.IsNull() && curr.IsElementNode();
       curr = curr.ParentNode()) {
    blink::WebElement curr_as_element = curr.To<blink::WebElement>();
    if (curr_as_element.HasHTMLTagName("a")) {
      most_inner_anchor_element = curr_as_element;
      break;
    }
  }

  return most_inner_anchor_element;
}

}  // namespace

std::unique_ptr<blink::WebLinkPreviewTriggerer>
CreateWebLinkPreviewTriggerer() {
  if (!base::FeatureList::IsEnabled(blink::features::kLinkPreview)) {
    return nullptr;
  }

  switch (blink::features::kLinkPreviewTriggerType.Get()) {
    // Alt+click is handled by navigation policy.
    case blink::features::LinkPreviewTriggerType::kAltClick:
      return nullptr;
    case blink::features::LinkPreviewTriggerType::kAltHover:
      return std::make_unique<WebLinkPreviewTriggererAltHover>();
    case blink::features::LinkPreviewTriggerType::kLongPress:
      return std::make_unique<WebLinkPreviewTriggererLongPress>();
  }
}

WebLinkPreviewTriggererAltHover::WebLinkPreviewTriggererAltHover()
    : timer_(std::make_unique<base::OneShotTimer>()) {}

WebLinkPreviewTriggererAltHover::~WebLinkPreviewTriggererAltHover() = default;

WebLinkPreviewTriggererAltHover::WebLinkPreviewTriggererAltHover(
    WebLinkPreviewTriggererAltHover&& other) = default;

WebLinkPreviewTriggererAltHover& WebLinkPreviewTriggererAltHover::operator=(
    WebLinkPreviewTriggererAltHover&& other) = default;

void WebLinkPreviewTriggererAltHover::MaybeChangedKeyEventModifier(
    int modifiers) {
  bool is_alt_on = (modifiers & blink::WebInputEvent::kAltKey) != 0;
  UpdateState(is_alt_on, anchor_element_);
}

void WebLinkPreviewTriggererAltHover::DidChangeHoverElement(
    blink::WebElement element) {
  UpdateState(is_alt_on_, GetMostInnerAnchorElement(element));
}

void WebLinkPreviewTriggererAltHover::UpdateState(
    bool is_alt_on,
    blink::WebElement anchor_element) {
  if (is_alt_on_ == is_alt_on && anchor_element_ == anchor_element) {
    return;
  }

  is_alt_on_ = is_alt_on;
  anchor_element_ = anchor_element;

  if (is_alt_on_ && !GetURL(anchor_element).IsNull()) {
    timer_->Start(
        FROM_HERE, kHoverThreshold,
        base::BindOnce(&WebLinkPreviewTriggererAltHover::InitiatePreview,
                       // base::Unretained() is safe since `this` owns `timer_`.
                       base::Unretained(this)));
  } else {
    timer_->Stop();
  }
}

void WebLinkPreviewTriggererAltHover::InitiatePreview() {
  blink::WebDocument document = anchor_element_.GetDocument();
  if (document.IsNull()) {
    return;
  }

  blink::WebURL url = GetURL(anchor_element_);
  if (url.IsNull()) {
    return;
  }

  document.InitiatePreview(url);
}

WebLinkPreviewTriggererLongPress::WebLinkPreviewTriggererLongPress()
    : timer_(std::make_unique<base::OneShotTimer>()) {}

WebLinkPreviewTriggererLongPress::~WebLinkPreviewTriggererLongPress() = default;

WebLinkPreviewTriggererLongPress::WebLinkPreviewTriggererLongPress(
    WebLinkPreviewTriggererLongPress&& other) = default;

WebLinkPreviewTriggererLongPress& WebLinkPreviewTriggererLongPress::operator=(
    WebLinkPreviewTriggererLongPress&& other) = default;

void WebLinkPreviewTriggererLongPress::DidChangeHoverElement(
    blink::WebElement element) {
  CHECK(!timer_->IsRunning() || !anchor_element_.IsNull());
  if (GetMostInnerAnchorElement(element) != anchor_element_) {
    timer_->Stop();
    anchor_element_.Reset();
  }
}

void WebLinkPreviewTriggererLongPress::DidAnchorElementReceiveMouseDownEvent(
    blink::WebElement anchor_element,
    blink::WebMouseEvent::Button button,
    int click_count) {
  CHECK(!timer_->IsRunning() || !anchor_element_.IsNull());
  timer_->Stop();
  anchor_element_.Reset();
  if (button == blink::WebMouseEvent::Button::kLeft && click_count == 1) {
    anchor_element_ = anchor_element;
    timer_->Start(
        FROM_HERE, kLongPressThreshold,
        base::BindOnce(&WebLinkPreviewTriggererLongPress::InitiatePreview,
                       // base::Unretained() is safe since `this` owns `timer_`.
                       base::Unretained(this)));
  }
}

void WebLinkPreviewTriggererLongPress::DidAnchorElementReceiveMouseUpEvent(
    blink::WebElement anchor_element,
    blink::WebMouseEvent::Button button,
    int click_count) {
  CHECK(!timer_->IsRunning() || !anchor_element_.IsNull());
  timer_->Stop();
  anchor_element_.Reset();
}

void WebLinkPreviewTriggererLongPress::InitiatePreview() {
  blink::WebDocument document = anchor_element_.GetDocument();
  blink::WebURL url = GetURL(anchor_element_);
  anchor_element_.Reset();
  if (document.IsNull() || url.IsNull()) {
    return;
  }
  document.InitiatePreview(url);
}