| /* |
| * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc. |
| * Copyright (C) 2010, 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/editing/EditingStyleUtilities.h" |
| |
| #include "core/CSSPropertyNames.h" |
| #include "core/css/CSSColorValue.h" |
| #include "core/css/CSSComputedStyleDeclaration.h" |
| #include "core/css/CSSIdentifierValue.h" |
| #include "core/css/CSSPropertyValueSet.h" |
| #include "core/css/parser/CSSParser.h" |
| #include "core/editing/EditingStyle.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/EphemeralRange.h" |
| #include "core/editing/VisiblePosition.h" |
| #include "core/editing/VisibleSelection.h" |
| |
| namespace blink { |
| |
| using namespace cssvalue; |
| |
| namespace { |
| |
| Position AdjustedSelectionStartForStyleComputation(const Position& position) { |
| // This function is used by range style computations to avoid bugs like: |
| // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a |
| // selection starting from end of line once |
| // It is important to skip certain irrelevant content at the start of the |
| // selection, so we do not wind up with a spurious "mixed" style. |
| |
| VisiblePosition visible_position = CreateVisiblePosition(position); |
| if (visible_position.IsNull()) |
| return Position(); |
| |
| // if the selection starts just before a paragraph break, skip over it |
| if (IsEndOfParagraph(visible_position)) { |
| return MostForwardCaretPosition( |
| NextPositionOf(visible_position).DeepEquivalent()); |
| } |
| |
| // otherwise, make sure to be at the start of the first selected node, |
| // instead of possibly at the end of the last node before the selection |
| return MostForwardCaretPosition(visible_position.DeepEquivalent()); |
| } |
| |
| } // anonymous namespace |
| |
| bool EditingStyleUtilities::HasAncestorVerticalAlignStyle(Node& node, |
| CSSValueID value) { |
| for (Node& runner : NodeTraversal::InclusiveAncestorsOf(node)) { |
| CSSComputedStyleDeclaration* ancestor_style = |
| CSSComputedStyleDeclaration::Create(&runner); |
| if (GetIdentifierValue(ancestor_style, CSSPropertyVerticalAlign) == value) |
| return true; |
| } |
| return false; |
| } |
| |
| EditingStyle* |
| EditingStyleUtilities::CreateWrappingStyleForAnnotatedSerialization( |
| ContainerNode* context) { |
| // TODO(editing-dev): Change this function to take |const ContainerNode&|. |
| // Tracking bug for this is crbug.com/766448. |
| DCHECK(context); |
| EditingStyle* wrapping_style = |
| EditingStyle::Create(context, EditingStyle::kEditingPropertiesInEffect); |
| |
| // Styles that Mail blockquotes contribute should only be placed on the Mail |
| // blockquote, to help us differentiate those styles from ones that the user |
| // has applied. This helps us get the color of content pasted into |
| // blockquotes right. |
| wrapping_style->RemoveStyleAddedByElement(ToHTMLElement(EnclosingNodeOfType( |
| FirstPositionInOrBeforeNode(*context), IsMailHTMLBlockquoteElement, |
| kCanCrossEditingBoundary))); |
| |
| // Call collapseTextDecorationProperties first or otherwise it'll copy the |
| // value over from in-effect to text-decorations. |
| wrapping_style->CollapseTextDecorationProperties( |
| context->GetDocument().GetSecureContextMode()); |
| |
| return wrapping_style; |
| } |
| |
| EditingStyle* EditingStyleUtilities::CreateWrappingStyleForSerialization( |
| ContainerNode* context) { |
| DCHECK(context); |
| EditingStyle* wrapping_style = EditingStyle::Create(); |
| |
| // When not annotating for interchange, we only preserve inline style |
| // declarations. |
| for (Node& node : NodeTraversal::InclusiveAncestorsOf(*context)) { |
| if (node.IsDocumentNode()) |
| break; |
| if (node.IsStyledElement() && !IsMailHTMLBlockquoteElement(&node)) { |
| wrapping_style->MergeInlineAndImplicitStyleOfElement( |
| ToElement(&node), EditingStyle::kDoNotOverrideValues, |
| EditingStyle::kEditingPropertiesInEffect); |
| } |
| } |
| |
| return wrapping_style; |
| } |
| |
| EditingStyle* EditingStyleUtilities::CreateStyleAtSelectionStart( |
| const VisibleSelection& selection, |
| bool should_use_background_color_in_effect, |
| MutableCSSPropertyValueSet* style_to_check) { |
| if (selection.IsNone()) |
| return nullptr; |
| |
| Document& document = *selection.Start().GetDocument(); |
| |
| DCHECK(!document.NeedsLayoutTreeUpdate()); |
| DocumentLifecycle::DisallowTransitionScope disallow_transition( |
| document.Lifecycle()); |
| |
| // TODO(editing-dev): We should make |position| to |const Position&| by |
| // integrating this expression and if-statement below. |
| Position position = |
| selection.IsCaret() |
| ? CreateVisiblePosition(selection.Start()).DeepEquivalent() |
| : AdjustedSelectionStartForStyleComputation(selection.Start()); |
| |
| // If the pos is at the end of a text node, then this node is not fully |
| // selected. Move it to the next deep equivalent position to avoid removing |
| // the style from this node. |
| // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we |
| // want Position("world", 0) instead. |
| // We only do this for range because caret at Position("hello", 5) in |
| // <b>hello</b>world should give you font-weight: bold. |
| Node* position_node = position.ComputeContainerNode(); |
| if (selection.IsRange() && position_node && position_node->IsTextNode() && |
| position.ComputeOffsetInContainerNode() == |
| position_node->MaxCharacterOffset()) |
| position = NextVisuallyDistinctCandidate(position); |
| |
| Element* element = AssociatedElementOf(position); |
| if (!element) |
| return nullptr; |
| |
| EditingStyle* style = |
| EditingStyle::Create(element, EditingStyle::kAllProperties); |
| style->MergeTypingStyle(&element->GetDocument()); |
| |
| // If |element| has <sub> or <sup> ancestor element, apply the corresponding |
| // style(vertical-align) to it so that document.queryCommandState() works with |
| // the style. See bug http://crbug.com/582225. |
| CSSValueID value_id = |
| GetIdentifierValue(style_to_check, CSSPropertyVerticalAlign); |
| if (value_id == CSSValueSub || value_id == CSSValueSuper) { |
| CSSComputedStyleDeclaration* element_style = |
| CSSComputedStyleDeclaration::Create(element); |
| // Find the ancestor that has CSSValueSub or CSSValueSuper as the value of |
| // CSS vertical-align property. |
| if (GetIdentifierValue(element_style, CSSPropertyVerticalAlign) == |
| CSSValueBaseline && |
| HasAncestorVerticalAlignStyle(*element, value_id)) |
| style->Style()->SetProperty(CSSPropertyVerticalAlign, value_id); |
| } |
| |
| // If background color is transparent, traverse parent nodes until we hit a |
| // different value or document root Also, if the selection is a range, ignore |
| // the background color at the start of selection, and find the background |
| // color of the common ancestor. |
| if (should_use_background_color_in_effect && |
| (selection.IsRange() || HasTransparentBackgroundColor(style->Style()))) { |
| const EphemeralRange range(selection.ToNormalizedEphemeralRange()); |
| if (const CSSValue* value = |
| BackgroundColorValueInEffect(range.CommonAncestorContainer())) { |
| style->SetProperty(CSSPropertyBackgroundColor, value->CssText(), |
| /* important */ false, |
| document.GetSecureContextMode()); |
| } |
| } |
| |
| return style; |
| } |
| |
| bool EditingStyleUtilities::IsTransparentColorValue(const CSSValue* css_value) { |
| if (!css_value) |
| return true; |
| if (css_value->IsColorValue()) |
| return !ToCSSColorValue(css_value)->Value().Alpha(); |
| if (!css_value->IsIdentifierValue()) |
| return false; |
| return ToCSSIdentifierValue(css_value)->GetValueID() == CSSValueTransparent; |
| } |
| |
| bool EditingStyleUtilities::HasTransparentBackgroundColor( |
| CSSStyleDeclaration* style) { |
| const CSSValue* css_value = |
| style->GetPropertyCSSValueInternal(CSSPropertyBackgroundColor); |
| return IsTransparentColorValue(css_value); |
| } |
| |
| bool EditingStyleUtilities::HasTransparentBackgroundColor( |
| CSSPropertyValueSet* style) { |
| const CSSValue* css_value = |
| style->GetPropertyCSSValue(CSSPropertyBackgroundColor); |
| return IsTransparentColorValue(css_value); |
| } |
| |
| const CSSValue* EditingStyleUtilities::BackgroundColorValueInEffect( |
| Node* node) { |
| for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { |
| CSSComputedStyleDeclaration* ancestor_style = |
| CSSComputedStyleDeclaration::Create(ancestor); |
| if (!HasTransparentBackgroundColor(ancestor_style)) { |
| return ancestor_style->GetPropertyCSSValue( |
| GetCSSPropertyBackgroundColor()); |
| } |
| } |
| return nullptr; |
| } |
| |
| } // namespace blink |