blob: 762a9a6167d2566b4ab38fbd87d2357257134e27 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/highlight_painting_utils.h"
#include "components/shared_highlighting/core/common/text_fragments_constants.h"
#include "third_party/blink/renderer/core/css/pseudo_style_request.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/page/focus_controller.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/text_paint_style.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
bool NodeIsSelectable(const ComputedStyle& style, Node* node) {
return !node->IsInert() && !(style.UserSelect() == EUserSelect::kNone &&
style.UserModify() == EUserModify::kReadOnly);
}
bool NodeIsReplaced(Node* node) {
return node && node->GetLayoutObject() &&
node->GetLayoutObject()->IsLayoutReplaced();
}
Color ForcedSystemForegroundColor(PseudoId pseudo_id,
mojom::blink::ColorScheme color_scheme) {
CSSValueID keyword = CSSValueID::kHighlighttext;
switch (pseudo_id) {
case kPseudoIdTargetText:
// TODO(futhark): According to the spec, the UA style should use Marktext.
keyword = CSSValueID::kHighlighttext;
break;
case kPseudoIdSelection:
keyword = CSSValueID::kHighlighttext;
break;
default:
NOTREACHED();
break;
}
return LayoutTheme::GetTheme().SystemColor(keyword, color_scheme);
}
Color ForcedSystemBackgroundColor(PseudoId pseudo_id,
mojom::blink::ColorScheme color_scheme) {
CSSValueID keyword = CSSValueID::kHighlight;
switch (pseudo_id) {
case kPseudoIdTargetText:
// TODO(futhark): According to the spec, the UA style should use Mark.
keyword = CSSValueID::kHighlight;
break;
case kPseudoIdSelection:
keyword = CSSValueID::kHighlight;
break;
default:
NOTREACHED();
break;
}
return LayoutTheme::GetTheme().SystemColor(keyword, color_scheme);
}
Color HighlightThemeForegroundColor(const Document& document,
const ComputedStyle& style,
const CSSProperty& color_property,
PseudoId pseudo_id) {
switch (pseudo_id) {
case kPseudoIdSelection:
if (!LayoutTheme::GetTheme().SupportsSelectionForegroundColors())
return style.VisitedDependentColor(color_property);
if (document.GetFrame()->Selection().FrameIsFocusedAndActive()) {
return LayoutTheme::GetTheme().ActiveSelectionForegroundColor(
style.UsedColorScheme());
}
return LayoutTheme::GetTheme().InactiveSelectionForegroundColor(
style.UsedColorScheme());
case kPseudoIdTargetText:
return LayoutTheme::GetTheme().PlatformTextSearchColor(
false /* active match */, document.InForcedColorsMode(),
style.UsedColorScheme());
default:
NOTREACHED();
return Color();
}
}
Color HighlightThemeBackgroundColor(const Document& document,
const ComputedStyle& style,
PseudoId pseudo_id) {
switch (pseudo_id) {
case kPseudoIdSelection:
return document.GetFrame()->Selection().FrameIsFocusedAndActive()
? LayoutTheme::GetTheme().ActiveSelectionBackgroundColor(
style.UsedColorScheme())
: LayoutTheme::GetTheme().InactiveSelectionBackgroundColor(
style.UsedColorScheme());
case kPseudoIdTargetText:
if (RuntimeEnabledFeatures::TextFragmentColorChangeEnabled())
return Color(shared_highlighting::kFragmentTextBackgroundColorARGB);
return LayoutTheme::GetTheme().PlatformTextSearchHighlightColor(
false /* active match */, document.InForcedColorsMode(),
style.UsedColorScheme());
default:
NOTREACHED();
return Color();
}
}
scoped_refptr<const ComputedStyle> HighlightPseudoStyle(Node* node,
PseudoId pseudo) {
if (!node)
return nullptr;
Element* element = nullptr;
// In Blink, highlight pseudo style only applies to direct children of the
// element on which the highligh pseudo is matched. In order to be able to
// style highlight inside elements implemented with a UA shadow tree, like
// input::selection, we calculate highlight style on the shadow host for
// elements inside the UA shadow.
ShadowRoot* root = node->ContainingShadowRoot();
if (root && root->IsUserAgent())
element = node->OwnerShadowHost();
// If we request highlight style for LayoutText, query highlight style on the
// parent element instead, as that is the node for which the highligh pseudo
// matches. This should most likely have used FlatTreeTraversal, but since we
// don't implement inheritance of highlight styles, it would probably break
// cases where you style a shadow host with a highlight pseudo and expect
// light tree text children to be affected by that style.
if (!element)
element = Traversal<Element>::FirstAncestorOrSelf(*node);
if (!element || element->IsPseudoElement())
return nullptr;
PseudoElementStyleRequest request(pseudo);
if (pseudo == kPseudoIdSelection &&
element->GetDocument().GetStyleEngine().UsesWindowInactiveSelector() &&
!element->GetDocument().GetPage()->GetFocusController().IsActive()) {
// ::selection and ::selection:window-inactive styles may be different. Only
// cache the styles for ::selection if there are no :window-inactive
// selector, or if the page is active.
return element->UncachedStyleForPseudoElement(request,
element->GetComputedStyle());
}
return element->CachedStyleForPseudoElement(request);
}
Color HighlightColor(const Document& document,
const ComputedStyle& style,
Node* node,
PseudoId pseudo,
const CSSProperty& color_property,
const GlobalPaintFlags global_paint_flags) {
if (pseudo == kPseudoIdSelection) {
// If the element is unselectable, or we are only painting the selection,
// don't override the foreground color with the selection foreground color.
if ((node && !NodeIsSelectable(style, node)) ||
(global_paint_flags & kGlobalPaintSelectionDragImageOnly)) {
return style.VisitedDependentColor(color_property);
}
}
scoped_refptr<const ComputedStyle> pseudo_style =
HighlightPseudoStyle(node, pseudo);
mojom::blink::ColorScheme color_scheme = style.UsedColorScheme();
if (pseudo_style) {
if (!document.InForcedColorsMode() ||
pseudo_style->ForcedColorAdjust() == EForcedColorAdjust::kNone) {
return pseudo_style->VisitedDependentColor(color_property);
}
color_scheme = pseudo_style->UsedColorScheme();
}
if (document.InForcedColorsMode())
return ForcedSystemForegroundColor(pseudo, color_scheme);
return HighlightThemeForegroundColor(document, style, color_property, pseudo);
}
} // anonymous namespace
Color HighlightPaintingUtils::HighlightBackgroundColor(
const Document& document,
const ComputedStyle& style,
Node* node,
PseudoId pseudo) {
if (pseudo == kPseudoIdSelection) {
if (node && !NodeIsSelectable(style, node))
return Color::kTransparent;
}
mojom::blink::ColorScheme color_scheme = style.UsedColorScheme();
if (scoped_refptr<const ComputedStyle> pseudo_style =
HighlightPseudoStyle(node, pseudo)) {
if (!document.InForcedColorsMode() ||
pseudo_style->ForcedColorAdjust() == EForcedColorAdjust::kNone) {
Color highlight_color =
pseudo_style->VisitedDependentColor(GetCSSPropertyBackgroundColor());
if (pseudo == kPseudoIdSelection && NodeIsReplaced(node)) {
// Avoid that ::selection full obscures selected replaced elements like
// images.
return highlight_color.BlendWithWhite();
}
return highlight_color;
}
color_scheme = pseudo_style->UsedColorScheme();
}
if (document.InForcedColorsMode())
return ForcedSystemBackgroundColor(pseudo, color_scheme);
return HighlightThemeBackgroundColor(document, style, pseudo);
}
base::Optional<AppliedTextDecoration>
HighlightPaintingUtils::HighlightTextDecoration(
const ComputedStyle& style,
const ComputedStyle& pseudo_style) {
const Vector<AppliedTextDecoration>& style_decorations =
style.AppliedTextDecorations();
const Vector<AppliedTextDecoration>& pseudo_style_decorations =
pseudo_style.AppliedTextDecorations();
if (style_decorations.IsEmpty())
return base::nullopt;
base::Optional<AppliedTextDecoration> highlight_text_decoration =
style_decorations.back();
if (pseudo_style_decorations.size() &&
style_decorations.back().Lines() ==
pseudo_style_decorations.back().Lines()) {
highlight_text_decoration = pseudo_style_decorations.back();
}
highlight_text_decoration.value().SetColor(
pseudo_style.VisitedDependentColor(GetCSSPropertyTextDecorationColor()));
return highlight_text_decoration;
}
Color HighlightPaintingUtils::HighlightForegroundColor(
const Document& document,
const ComputedStyle& style,
Node* node,
PseudoId pseudo,
const GlobalPaintFlags global_paint_flags) {
return HighlightColor(document, style, node, pseudo,
GetCSSPropertyWebkitTextFillColor(),
global_paint_flags);
}
Color HighlightPaintingUtils::HighlightEmphasisMarkColor(
const Document& document,
const ComputedStyle& style,
Node* node,
PseudoId pseudo,
const GlobalPaintFlags global_paint_flags) {
return HighlightColor(document, style, node, pseudo,
GetCSSPropertyWebkitTextEmphasisColor(),
global_paint_flags);
}
TextPaintStyle HighlightPaintingUtils::HighlightPaintingStyle(
const Document& document,
const ComputedStyle& style,
Node* node,
PseudoId pseudo,
const TextPaintStyle& text_style,
const PaintInfo& paint_info) {
TextPaintStyle highlight_style = text_style;
bool uses_text_as_clip = paint_info.phase == PaintPhase::kTextClip;
const GlobalPaintFlags global_paint_flags = paint_info.GetGlobalPaintFlags();
// Each highlight overlay’s shadows are completely independent of any shadows
// specified on the originating element (or the other highlight overlays).
highlight_style.shadow = nullptr;
if (!uses_text_as_clip) {
highlight_style.fill_color = HighlightForegroundColor(
document, style, node, pseudo, global_paint_flags);
highlight_style.emphasis_mark_color = HighlightEmphasisMarkColor(
document, style, node, pseudo, global_paint_flags);
}
if (scoped_refptr<const ComputedStyle> pseudo_style =
HighlightPseudoStyle(node, pseudo)) {
highlight_style.stroke_color =
uses_text_as_clip ? Color::kBlack
: pseudo_style->VisitedDependentColor(
GetCSSPropertyWebkitTextStrokeColor());
highlight_style.stroke_width = pseudo_style->TextStrokeWidth();
highlight_style.shadow =
uses_text_as_clip ? nullptr : pseudo_style->TextShadow();
highlight_style.selection_text_decoration =
HighlightTextDecoration(style, *pseudo_style);
}
// Text shadows are disabled when printing. http://crbug.com/258321
if (document.Printing())
highlight_style.shadow = nullptr;
return highlight_style;
}
} // namespace blink