641 lines
23 KiB
C++
641 lines
23 KiB
C++
/*
|
|
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
|
|
* (C) 1999 Antti Koivisto (koivisto@kde.org)
|
|
* (C) 2000 Simon Hausmann <hausmann@kde.org>
|
|
* Copyright (C) 2003-2016 Apple Inc. All rights reserved.
|
|
* (C) 2006 Graham Dennis (graham.dennis@gmail.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "HTMLAnchorElement.h"
|
|
|
|
#include "Chrome.h"
|
|
#include "ChromeClient.h"
|
|
#include "DOMTokenList.h"
|
|
#include "ElementIterator.h"
|
|
#include "EventHandler.h"
|
|
#include "EventNames.h"
|
|
#include "Frame.h"
|
|
#include "FrameLoader.h"
|
|
#include "FrameLoaderClient.h"
|
|
#include "FrameLoaderTypes.h"
|
|
#include "FrameSelection.h"
|
|
#include "HTMLCanvasElement.h"
|
|
#include "HTMLImageElement.h"
|
|
#include "HTMLParserIdioms.h"
|
|
#include "HTMLPictureElement.h"
|
|
#include "KeyboardEvent.h"
|
|
#include "MouseEvent.h"
|
|
#include "PingLoader.h"
|
|
#include "PlatformMouseEvent.h"
|
|
#include "PrivateClickMeasurement.h"
|
|
#include "RegistrableDomain.h"
|
|
#include "RenderImage.h"
|
|
#include "ResourceRequest.h"
|
|
#include "RuntimeEnabledFeatures.h"
|
|
#include "SVGImage.h"
|
|
#include "ScriptController.h"
|
|
#include "SecurityOrigin.h"
|
|
#include "SecurityPolicy.h"
|
|
#include "Settings.h"
|
|
#include "UserGestureIndicator.h"
|
|
#include <wtf/IsoMallocInlines.h>
|
|
#include <wtf/WeakHashMap.h>
|
|
#include <wtf/text/StringBuilder.h>
|
|
#include <wtf/text/StringConcatenateNumbers.h>
|
|
|
|
#if PLATFORM(COCOA)
|
|
#include "DataDetection.h"
|
|
#endif
|
|
|
|
namespace WebCore {
|
|
|
|
WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAnchorElement);
|
|
|
|
using namespace HTMLNames;
|
|
|
|
HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
|
|
: HTMLElement(tagName, document)
|
|
{
|
|
}
|
|
|
|
Ref<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
|
|
{
|
|
return adoptRef(*new HTMLAnchorElement(aTag, document));
|
|
}
|
|
|
|
Ref<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
|
|
{
|
|
return adoptRef(*new HTMLAnchorElement(tagName, document));
|
|
}
|
|
|
|
HTMLAnchorElement::~HTMLAnchorElement()
|
|
{
|
|
clearRootEditableElementForSelectionOnMouseDown();
|
|
}
|
|
|
|
bool HTMLAnchorElement::supportsFocus() const
|
|
{
|
|
if (hasEditableStyle())
|
|
return HTMLElement::supportsFocus();
|
|
// If not a link we should still be able to focus the element if it has tabIndex.
|
|
return isLink() || HTMLElement::supportsFocus();
|
|
}
|
|
|
|
bool HTMLAnchorElement::isMouseFocusable() const
|
|
{
|
|
// Only allow links with tabIndex or contentEditable to be mouse focusable.
|
|
if (isLink())
|
|
return HTMLElement::supportsFocus();
|
|
|
|
return HTMLElement::isMouseFocusable();
|
|
}
|
|
|
|
bool HTMLAnchorElement::isInteractiveContent() const
|
|
{
|
|
return isLink();
|
|
}
|
|
|
|
static bool hasNonEmptyBox(RenderBoxModelObject* renderer)
|
|
{
|
|
if (!renderer)
|
|
return false;
|
|
|
|
// Before calling absoluteRects, check for the common case where borderBoundingBox
|
|
// is non-empty, since this is a faster check and almost always returns true.
|
|
// FIXME: Why do we need to call absoluteRects at all?
|
|
if (!renderer->borderBoundingBox().isEmpty())
|
|
return true;
|
|
|
|
// FIXME: Since all we are checking is whether the rects are empty, could we just
|
|
// pass in 0,0 for the layout point instead of calling localToAbsolute?
|
|
Vector<IntRect> rects;
|
|
renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute()));
|
|
for (auto& rect : rects) {
|
|
if (!rect.isEmpty())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
|
|
{
|
|
if (!isLink())
|
|
return HTMLElement::isKeyboardFocusable(event);
|
|
|
|
if (!isFocusable())
|
|
return false;
|
|
|
|
if (!document().frame())
|
|
return false;
|
|
|
|
if (!document().frame()->eventHandler().tabsToLinks(event))
|
|
return false;
|
|
|
|
if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
|
|
return true;
|
|
|
|
return hasNonEmptyBox(renderBoxModelObject());
|
|
}
|
|
|
|
static void appendServerMapMousePosition(StringBuilder& url, Event& event)
|
|
{
|
|
if (!is<MouseEvent>(event))
|
|
return;
|
|
auto& mouseEvent = downcast<MouseEvent>(event);
|
|
|
|
if (!is<HTMLImageElement>(mouseEvent.target()))
|
|
return;
|
|
|
|
auto& imageElement = downcast<HTMLImageElement>(*mouseEvent.target());
|
|
if (!imageElement.isServerMap())
|
|
return;
|
|
|
|
auto* renderer = imageElement.renderer();
|
|
if (!is<RenderImage>(renderer))
|
|
return;
|
|
|
|
// FIXME: This should probably pass UseTransforms in the OptionSet<MapCoordinatesMode>.
|
|
auto absolutePosition = downcast<RenderImage>(*renderer).absoluteToLocal(FloatPoint(mouseEvent.pageX(), mouseEvent.pageY()));
|
|
url.append('?', std::lround(absolutePosition.x()), ',', std::lround(absolutePosition.y()));
|
|
}
|
|
|
|
void HTMLAnchorElement::defaultEventHandler(Event& event)
|
|
{
|
|
if (isLink()) {
|
|
if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
|
|
event.setDefaultHandled();
|
|
dispatchSimulatedClick(&event);
|
|
return;
|
|
}
|
|
|
|
if (MouseEvent::canTriggerActivationBehavior(event) && treatLinkAsLiveForEventType(eventType(event))) {
|
|
handleClick(event);
|
|
return;
|
|
}
|
|
|
|
if (hasEditableStyle()) {
|
|
// This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
|
|
// for the LiveWhenNotFocused editable link behavior
|
|
if (event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() != RightButton && document().frame()) {
|
|
setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().selection().rootEditableElement());
|
|
m_wasShiftKeyDownOnMouseDown = downcast<MouseEvent>(event).shiftKey();
|
|
} else if (event.type() == eventNames().mouseoverEvent) {
|
|
// These are cleared on mouseover and not mouseout because their values are needed for drag events,
|
|
// but drag events happen after mouse out events.
|
|
clearRootEditableElementForSelectionOnMouseDown();
|
|
m_wasShiftKeyDownOnMouseDown = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
HTMLElement::defaultEventHandler(event);
|
|
}
|
|
|
|
void HTMLAnchorElement::setActive(bool down, bool pause, Style::InvalidationScope invalidationScope)
|
|
{
|
|
if (hasEditableStyle()) {
|
|
switch (document().settings().editableLinkBehavior()) {
|
|
case EditableLinkBehavior::Default:
|
|
case EditableLinkBehavior::AlwaysLive:
|
|
break;
|
|
|
|
// Don't set the link to be active if the current selection is in the same editable block as this link.
|
|
case EditableLinkBehavior::LiveWhenNotFocused:
|
|
if (down && document().frame() && document().frame()->selection().selection().rootEditableElement() == rootEditableElement())
|
|
return;
|
|
break;
|
|
|
|
case EditableLinkBehavior::NeverLive:
|
|
case EditableLinkBehavior::OnlyLiveWithShiftKey:
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
HTMLElement::setActive(down, pause, invalidationScope);
|
|
}
|
|
|
|
void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomString& value)
|
|
{
|
|
if (name == hrefAttr) {
|
|
bool wasLink = isLink();
|
|
setIsLink(!value.isNull() && !shouldProhibitLinks(this));
|
|
if (wasLink != isLink())
|
|
invalidateStyleForSubtree();
|
|
if (isLink()) {
|
|
String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
|
|
if (document().isDNSPrefetchEnabled() && document().frame()) {
|
|
if (protocolIsInHTTPFamily(parsedURL) || parsedURL.startsWith("//"))
|
|
document().frame()->loader().client().prefetchDNS(document().completeURL(parsedURL).host().toString());
|
|
}
|
|
}
|
|
} else if (name == nameAttr || name == titleAttr) {
|
|
// Do nothing.
|
|
} else if (name == relAttr) {
|
|
// Update HTMLAnchorElement::relList() if more rel attributes values are supported.
|
|
static MainThreadNeverDestroyed<const AtomString> noReferrer("noreferrer", AtomString::ConstructFromLiteral);
|
|
static MainThreadNeverDestroyed<const AtomString> noOpener("noopener", AtomString::ConstructFromLiteral);
|
|
static MainThreadNeverDestroyed<const AtomString> opener("opener", AtomString::ConstructFromLiteral);
|
|
const bool shouldFoldCase = true;
|
|
SpaceSplitString relValue(value, shouldFoldCase);
|
|
if (relValue.contains(noReferrer))
|
|
m_linkRelations.add(Relation::NoReferrer);
|
|
if (relValue.contains(noOpener))
|
|
m_linkRelations.add(Relation::NoOpener);
|
|
if (relValue.contains(opener))
|
|
m_linkRelations.add(Relation::Opener);
|
|
if (m_relList)
|
|
m_relList->associatedAttributeValueChanged(value);
|
|
}
|
|
else
|
|
HTMLElement::parseAttribute(name, value);
|
|
}
|
|
|
|
bool HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
|
|
{
|
|
return dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
|
|
}
|
|
|
|
bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
|
|
{
|
|
return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
|
|
}
|
|
|
|
bool HTMLAnchorElement::canStartSelection() const
|
|
{
|
|
if (!isLink())
|
|
return HTMLElement::canStartSelection();
|
|
return hasEditableStyle();
|
|
}
|
|
|
|
bool HTMLAnchorElement::draggable() const
|
|
{
|
|
const AtomString& value = attributeWithoutSynchronization(draggableAttr);
|
|
if (equalLettersIgnoringASCIICase(value, "true"))
|
|
return true;
|
|
if (equalLettersIgnoringASCIICase(value, "false"))
|
|
return false;
|
|
return hasAttributeWithoutSynchronization(hrefAttr);
|
|
}
|
|
|
|
URL HTMLAnchorElement::href() const
|
|
{
|
|
return document().completeURL(attributeWithoutSynchronization(hrefAttr));
|
|
}
|
|
|
|
void HTMLAnchorElement::setHref(const AtomString& value)
|
|
{
|
|
setAttributeWithoutSynchronization(hrefAttr, value);
|
|
}
|
|
|
|
bool HTMLAnchorElement::hasRel(Relation relation) const
|
|
{
|
|
return m_linkRelations.contains(relation);
|
|
}
|
|
|
|
DOMTokenList& HTMLAnchorElement::relList()
|
|
{
|
|
if (!m_relList) {
|
|
m_relList = makeUnique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document&, StringView token) {
|
|
#if USE(SYSTEM_PREVIEW)
|
|
if (equalIgnoringASCIICase(token, "ar"))
|
|
return true;
|
|
#endif
|
|
return equalIgnoringASCIICase(token, "noreferrer") || equalIgnoringASCIICase(token, "noopener");
|
|
});
|
|
}
|
|
return *m_relList;
|
|
}
|
|
|
|
const AtomString& HTMLAnchorElement::name() const
|
|
{
|
|
return getNameAttribute();
|
|
}
|
|
|
|
int HTMLAnchorElement::defaultTabIndex() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
String HTMLAnchorElement::target() const
|
|
{
|
|
return attributeWithoutSynchronization(targetAttr);
|
|
}
|
|
|
|
String HTMLAnchorElement::origin() const
|
|
{
|
|
return SecurityOrigin::create(href()).get().toString();
|
|
}
|
|
|
|
String HTMLAnchorElement::text()
|
|
{
|
|
return textContent();
|
|
}
|
|
|
|
void HTMLAnchorElement::setText(const String& text)
|
|
{
|
|
setTextContent(text);
|
|
}
|
|
|
|
bool HTMLAnchorElement::isLiveLink() const
|
|
{
|
|
return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
|
|
}
|
|
|
|
void HTMLAnchorElement::sendPings(const URL& destinationURL)
|
|
{
|
|
if (!document().frame())
|
|
return;
|
|
|
|
if (!hasAttributeWithoutSynchronization(pingAttr) || !document().settings().hyperlinkAuditingEnabled())
|
|
return;
|
|
|
|
SpaceSplitString pingURLs(attributeWithoutSynchronization(pingAttr), false);
|
|
for (unsigned i = 0; i < pingURLs.size(); i++)
|
|
PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
|
|
}
|
|
|
|
#if USE(SYSTEM_PREVIEW)
|
|
bool HTMLAnchorElement::isSystemPreviewLink()
|
|
{
|
|
if (!document().settings().systemPreviewEnabled())
|
|
return false;
|
|
|
|
static MainThreadNeverDestroyed<const AtomString> systemPreviewRelValue("ar", AtomString::ConstructFromLiteral);
|
|
|
|
if (!relList().contains(systemPreviewRelValue))
|
|
return false;
|
|
|
|
if (auto* child = firstElementChild()) {
|
|
if (is<HTMLImageElement>(child) || is<HTMLPictureElement>(child)) {
|
|
auto numChildren = childElementCount();
|
|
// FIXME: We've documented that it should be the only child, but some early demos have two children.
|
|
return numChildren == 1 || numChildren == 2;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
std::optional<PrivateClickMeasurement> HTMLAnchorElement::parsePrivateClickMeasurement() const
|
|
{
|
|
using SourceID = PrivateClickMeasurement::SourceID;
|
|
using SourceSite = PrivateClickMeasurement::SourceSite;
|
|
using AttributionDestinationSite = PrivateClickMeasurement::AttributionDestinationSite;
|
|
|
|
auto* page = document().page();
|
|
if (!page || page->sessionID().isEphemeral()
|
|
|| !document().settings().privateClickMeasurementEnabled()
|
|
|| !UserGestureIndicator::processingUserGesture())
|
|
return std::nullopt;
|
|
|
|
auto hasAttributionSourceIDAttr = hasAttributeWithoutSynchronization(attributionsourceidAttr);
|
|
auto hasAttributionDestinationAttr = hasAttributeWithoutSynchronization(attributiondestinationAttr);
|
|
if (!hasAttributionSourceIDAttr && !hasAttributionDestinationAttr)
|
|
return std::nullopt;
|
|
|
|
auto attributionSourceIDAttr = attributeWithoutSynchronization(attributionsourceidAttr);
|
|
auto attributionDestinationAttr = attributeWithoutSynchronization(attributiondestinationAttr);
|
|
|
|
if (!hasAttributionSourceIDAttr || !hasAttributionDestinationAttr || attributionSourceIDAttr.isEmpty() || attributionDestinationAttr.isEmpty()) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "Both attributionsourceid and attributiondestination need to be set for Private Click Measurement to work."_s);
|
|
return std::nullopt;
|
|
}
|
|
|
|
RefPtr<Frame> frame = document().frame();
|
|
if (!frame || !frame->isMainFrame()) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "Private Click Measurement is only supported in the main frame."_s);
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto attributionSourceID = parseHTMLNonNegativeInteger(attributionSourceIDAttr);
|
|
if (!attributionSourceID) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "attributionsourceid is not a non-negative integer which is required for Private Click Measurement."_s);
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (attributionSourceID.value() > PrivateClickMeasurement::SourceID::MaxEntropy) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, makeString("attributionsourceid must have a non-negative value less than or equal to ", PrivateClickMeasurement::SourceID::MaxEntropy, " for Private Click Measurement."));
|
|
return std::nullopt;
|
|
}
|
|
|
|
URL destinationURL { URL(), attributionDestinationAttr };
|
|
if (!destinationURL.isValid() || !destinationURL.protocolIsInHTTPFamily()) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "attributiondestination could not be converted to a valid HTTP-family URL."_s);
|
|
return std::nullopt;
|
|
}
|
|
|
|
RegistrableDomain documentRegistrableDomain { document().url() };
|
|
if (documentRegistrableDomain.matches(destinationURL)) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "attributiondestination can not be the same site as the current website."_s);
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto privateClickMeasurement = PrivateClickMeasurement { SourceID(attributionSourceID.value()), SourceSite(WTFMove(documentRegistrableDomain)), AttributionDestinationSite(destinationURL) };
|
|
|
|
auto attributionSourceNonceAttr = attributeWithoutSynchronization(attributionsourcenonceAttr);
|
|
if (!attributionSourceNonceAttr.isEmpty()) {
|
|
auto ephemeralNonce = PrivateClickMeasurement::EphemeralSourceNonce { attributionSourceNonceAttr };
|
|
if (!ephemeralNonce.isValid()) {
|
|
document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "attributionsourcenonce was not valid."_s);
|
|
return std::nullopt;
|
|
}
|
|
privateClickMeasurement.setEphemeralSourceNonce(WTFMove(ephemeralNonce));
|
|
}
|
|
|
|
return privateClickMeasurement;
|
|
}
|
|
|
|
void HTMLAnchorElement::handleClick(Event& event)
|
|
{
|
|
event.setDefaultHandled();
|
|
|
|
RefPtr<Frame> frame = document().frame();
|
|
if (!frame)
|
|
return;
|
|
|
|
if (!hasTagName(aTag) && !isConnected())
|
|
return;
|
|
|
|
StringBuilder url;
|
|
url.append(stripLeadingAndTrailingHTMLSpaces(attributeWithoutSynchronization(hrefAttr)));
|
|
appendServerMapMousePosition(url, event);
|
|
URL completedURL = document().completeURL(url.toString());
|
|
|
|
#if ENABLE(DATA_DETECTION) && PLATFORM(IOS_FAMILY)
|
|
if (DataDetection::isDataDetectorLink(*this) && DataDetection::canPresentDataDetectorsUIForElement(*this)) {
|
|
if (auto* page = document().page()) {
|
|
if (page->chrome().client().showDataDetectorsUIForElement(*this, event))
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
String downloadAttribute;
|
|
#if ENABLE(DOWNLOAD_ATTRIBUTE)
|
|
if (document().settings().downloadAttributeEnabled()) {
|
|
// Ignore the download attribute completely if the href URL is cross origin.
|
|
bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin().canRequest(completedURL);
|
|
if (isSameOrigin)
|
|
downloadAttribute = ResourceResponse::sanitizeSuggestedFilename(attributeWithoutSynchronization(downloadAttr));
|
|
else if (hasAttributeWithoutSynchronization(downloadAttr))
|
|
document().addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "The download attribute on anchor was ignored because its href URL has a different security origin.");
|
|
}
|
|
#endif
|
|
|
|
SystemPreviewInfo systemPreviewInfo;
|
|
#if USE(SYSTEM_PREVIEW)
|
|
systemPreviewInfo.isPreview = isSystemPreviewLink() && document().settings().systemPreviewEnabled();
|
|
|
|
if (systemPreviewInfo.isPreview) {
|
|
systemPreviewInfo.element.elementIdentifier = document().identifierForElement(*this);
|
|
systemPreviewInfo.element.documentIdentifier = document().identifier();
|
|
systemPreviewInfo.element.webPageIdentifier = document().frame()->loader().pageID().value_or(PageIdentifier { });
|
|
if (auto* child = firstElementChild())
|
|
systemPreviewInfo.previewRect = child->boundsInRootViewSpace();
|
|
}
|
|
#endif
|
|
|
|
auto referrerPolicy = hasRel(Relation::NoReferrer) ? ReferrerPolicy::NoReferrer : this->referrerPolicy();
|
|
|
|
auto effectiveTarget = this->effectiveTarget();
|
|
NewFrameOpenerPolicy newFrameOpenerPolicy = NewFrameOpenerPolicy::Allow;
|
|
if (hasRel(Relation::NoOpener) || hasRel(Relation::NoReferrer) || (!hasRel(Relation::Opener) && document().settings().blankAnchorTargetImpliesNoOpenerEnabled() && equalIgnoringASCIICase(effectiveTarget, "_blank")))
|
|
newFrameOpenerPolicy = NewFrameOpenerPolicy::Suppress;
|
|
|
|
auto privateClickMeasurement = parsePrivateClickMeasurement();
|
|
// A matching triggering event needs to happen before an attribution report can be sent.
|
|
// Thus, URLs should be empty for now.
|
|
ASSERT(!privateClickMeasurement || (privateClickMeasurement->attributionReportSourceURL().isNull() && privateClickMeasurement->attributionReportAttributeOnURL().isNull()));
|
|
|
|
frame->loader().changeLocation(completedURL, effectiveTarget, &event, referrerPolicy, document().shouldOpenExternalURLsPolicyToPropagate(), newFrameOpenerPolicy, downloadAttribute, systemPreviewInfo, WTFMove(privateClickMeasurement));
|
|
|
|
sendPings(completedURL);
|
|
}
|
|
|
|
// Falls back to using <base> element's target if the anchor does not have one.
|
|
String HTMLAnchorElement::effectiveTarget() const
|
|
{
|
|
auto effectiveTarget = target();
|
|
if (effectiveTarget.isEmpty())
|
|
effectiveTarget = document().baseTarget();
|
|
return effectiveTarget;
|
|
}
|
|
|
|
HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event& event)
|
|
{
|
|
if (!is<MouseEvent>(event))
|
|
return NonMouseEvent;
|
|
return downcast<MouseEvent>(event).shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
|
|
}
|
|
|
|
bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
|
|
{
|
|
if (!hasEditableStyle())
|
|
return true;
|
|
|
|
switch (document().settings().editableLinkBehavior()) {
|
|
case EditableLinkBehavior::Default:
|
|
case EditableLinkBehavior::AlwaysLive:
|
|
return true;
|
|
|
|
case EditableLinkBehavior::NeverLive:
|
|
return false;
|
|
|
|
// If the selection prior to clicking on this link resided in the same editable block as this link,
|
|
// and the shift key isn't pressed, we don't want to follow the link.
|
|
case EditableLinkBehavior::LiveWhenNotFocused:
|
|
return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
|
|
|
|
case EditableLinkBehavior::OnlyLiveWithShiftKey:
|
|
return eventType == MouseEventWithShiftKey;
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
return false;
|
|
}
|
|
|
|
bool isEnterKeyKeydownEvent(Event& event)
|
|
{
|
|
return event.type() == eventNames().keydownEvent && is<KeyboardEvent>(event) && downcast<KeyboardEvent>(event).keyIdentifier() == "Enter";
|
|
}
|
|
|
|
bool shouldProhibitLinks(Element* element)
|
|
{
|
|
return isInSVGImage(element);
|
|
}
|
|
|
|
bool HTMLAnchorElement::willRespondToMouseClickEvents()
|
|
{
|
|
return isLink() || HTMLElement::willRespondToMouseClickEvents();
|
|
}
|
|
|
|
static auto& rootEditableElementMap()
|
|
{
|
|
static NeverDestroyed<WeakHashMap<HTMLAnchorElement, WeakPtr<Element>>> map;
|
|
return map.get();
|
|
}
|
|
|
|
Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
|
|
{
|
|
if (!m_hasRootEditableElementForSelectionOnMouseDown)
|
|
return 0;
|
|
return rootEditableElementMap().get(*this).get();
|
|
}
|
|
|
|
void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
|
|
{
|
|
if (!m_hasRootEditableElementForSelectionOnMouseDown)
|
|
return;
|
|
rootEditableElementMap().remove(*this);
|
|
m_hasRootEditableElementForSelectionOnMouseDown = false;
|
|
}
|
|
|
|
void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
|
|
{
|
|
if (!element) {
|
|
clearRootEditableElementForSelectionOnMouseDown();
|
|
return;
|
|
}
|
|
|
|
rootEditableElementMap().set(*this, makeWeakPtr(element));
|
|
m_hasRootEditableElementForSelectionOnMouseDown = true;
|
|
}
|
|
|
|
void HTMLAnchorElement::setReferrerPolicyForBindings(const AtomString& value)
|
|
{
|
|
setAttributeWithoutSynchronization(referrerpolicyAttr, value);
|
|
}
|
|
|
|
String HTMLAnchorElement::referrerPolicyForBindings() const
|
|
{
|
|
return referrerPolicyToString(referrerPolicy());
|
|
}
|
|
|
|
ReferrerPolicy HTMLAnchorElement::referrerPolicy() const
|
|
{
|
|
if (document().settings().referrerPolicyAttributeEnabled())
|
|
return parseReferrerPolicy(attributeWithoutSynchronization(referrerpolicyAttr), ReferrerPolicySource::ReferrerPolicyAttribute).value_or(ReferrerPolicy::EmptyString);
|
|
return ReferrerPolicy::EmptyString;
|
|
}
|
|
|
|
}
|