| // 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 |