| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserv
ed. | |
| 3 * Copyright (C) 2008, 2009, 2010, 2011 Google Inc. All rights reserved. | |
| 4 * Copyright (C) 2011 Igalia S.L. | |
| 5 * Copyright (C) 2011 Motorola Mobility. All rights reserved. | |
| 6 * | |
| 7 * Redistribution and use in source and binary forms, with or without | |
| 8 * modification, are permitted provided that the following conditions | |
| 9 * are met: | |
| 10 * 1. Redistributions of source code must retain the above copyright | |
| 11 * notice, this list of conditions and the following disclaimer. | |
| 12 * 2. Redistributions in binary form must reproduce the above copyright | |
| 13 * notice, this list of conditions and the following disclaimer in the | |
| 14 * documentation and/or other materials provided with the distribution. | |
| 15 * | |
| 16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
| 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
| 20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 */ | |
| 28 | |
| 29 #include "sky/engine/config.h" | |
| 30 #include "sky/engine/core/editing/markup.h" | |
| 31 | |
| 32 #include "gen/sky/core/CSSPropertyNames.h" | |
| 33 #include "gen/sky/core/CSSValueKeywords.h" | |
| 34 #include "gen/sky/core/HTMLNames.h" | |
| 35 #include "sky/engine/bindings/core/v8/ExceptionState.h" | |
| 36 #include "sky/engine/core/css/CSSPrimitiveValue.h" | |
| 37 #include "sky/engine/core/css/CSSValue.h" | |
| 38 #include "sky/engine/core/css/StylePropertySet.h" | |
| 39 #include "sky/engine/core/dom/ChildListMutationScope.h" | |
| 40 #include "sky/engine/core/dom/DocumentFragment.h" | |
| 41 #include "sky/engine/core/dom/ElementTraversal.h" | |
| 42 #include "sky/engine/core/dom/ExceptionCode.h" | |
| 43 #include "sky/engine/core/dom/NodeTraversal.h" | |
| 44 #include "sky/engine/core/dom/Range.h" | |
| 45 #include "sky/engine/core/dom/Text.h" | |
| 46 #include "sky/engine/core/editing/Editor.h" | |
| 47 #include "sky/engine/core/editing/MarkupAccumulator.h" | |
| 48 #include "sky/engine/core/editing/TextIterator.h" | |
| 49 #include "sky/engine/core/editing/VisibleSelection.h" | |
| 50 #include "sky/engine/core/editing/VisibleUnits.h" | |
| 51 #include "sky/engine/core/editing/htmlediting.h" | |
| 52 #include "sky/engine/core/frame/LocalFrame.h" | |
| 53 #include "sky/engine/core/html/HTMLAnchorElement.h" | |
| 54 #include "sky/engine/core/html/HTMLElement.h" | |
| 55 #include "sky/engine/core/rendering/RenderObject.h" | |
| 56 #include "sky/engine/platform/weborigin/KURL.h" | |
| 57 #include "sky/engine/wtf/StdLibExtras.h" | |
| 58 #include "sky/engine/wtf/text/StringBuilder.h" | |
| 59 | |
| 60 namespace blink { | |
| 61 | |
| 62 static bool propertyMissingOrEqualToNone(StylePropertySet*, CSSPropertyID); | |
| 63 | |
| 64 class AttributeChange { | |
| 65 ALLOW_ONLY_INLINE_ALLOCATION(); | |
| 66 public: | |
| 67 AttributeChange() | |
| 68 : m_name(nullAtom) | |
| 69 { | |
| 70 } | |
| 71 | |
| 72 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, cons
t String& value) | |
| 73 : m_element(element), m_name(name), m_value(value) | |
| 74 { | |
| 75 } | |
| 76 | |
| 77 void apply() | |
| 78 { | |
| 79 m_element->setAttribute(m_name, AtomicString(m_value)); | |
| 80 } | |
| 81 | |
| 82 private: | |
| 83 RefPtr<Element> m_element; | |
| 84 QualifiedName m_name; | |
| 85 String m_value; | |
| 86 }; | |
| 87 | |
| 88 } // namespace blink | |
| 89 | |
| 90 WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::AttributeChange); | |
| 91 | |
| 92 namespace blink { | |
| 93 | |
| 94 class StyledMarkupAccumulator final : public MarkupAccumulator { | |
| 95 public: | |
| 96 StyledMarkupAccumulator(Vector<RawPtr<Node> >* nodes, EAbsoluteURLs, EAnnota
teForInterchange, RawPtr<const Range>, Node* highestNodeToBeSerialized = 0); | |
| 97 Node* serializeNodes(Node* startNode, Node* pastEnd); | |
| 98 void appendString(const String& s) { return MarkupAccumulator::appendString(
s); } | |
| 99 void wrapWithNode(ContainerNode&, bool convertBlocksToInlines = false); | |
| 100 void wrapWithStyleNode(StylePropertySet*, const Document&, bool isBlock = fa
lse); | |
| 101 String takeResults(); | |
| 102 | |
| 103 private: | |
| 104 void appendStyleNodeOpenTag(StringBuilder&, StylePropertySet*, const Documen
t&, bool isBlock = false); | |
| 105 const String& styleNodeCloseTag(bool isBlock = false); | |
| 106 virtual void appendText(StringBuilder& out, Text&) override; | |
| 107 String renderedText(Node&, const Range*); | |
| 108 String stringValueForRange(const Node&, const Range*); | |
| 109 void appendElement(StringBuilder& out, Element&, bool addDisplayInline); | |
| 110 virtual void appendElement(StringBuilder& out, Element& element, Namespaces*
) override { appendElement(out, element, false); } | |
| 111 | |
| 112 enum NodeTraversalMode { EmitString, DoNotEmitString }; | |
| 113 Node* traverseNodesForSerialization(Node* startNode, Node* pastEnd, NodeTrav
ersalMode); | |
| 114 | |
| 115 bool shouldAnnotate() const { return m_shouldAnnotate == AnnotateForIntercha
nge || m_shouldAnnotate == AnnotateForNavigationTransition; } | |
| 116 bool shouldApplyWrappingStyle(const Node& node) const | |
| 117 { | |
| 118 return m_highestNodeToBeSerialized && m_highestNodeToBeSerialized->paren
tNode() == node.parentNode() | |
| 119 && m_wrappingStyle && m_wrappingStyle->style(); | |
| 120 } | |
| 121 | |
| 122 Vector<String> m_reversedPrecedingMarkup; | |
| 123 const EAnnotateForInterchange m_shouldAnnotate; | |
| 124 RawPtr<Node> m_highestNodeToBeSerialized; | |
| 125 RefPtr<EditingStyle> m_wrappingStyle; | |
| 126 }; | |
| 127 | |
| 128 inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<RawPtr<Node> >* n
odes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, R
awPtr<const Range> range, Node* highestNodeToBeSerialized) | |
| 129 : MarkupAccumulator(nodes, shouldResolveURLs, range) | |
| 130 , m_shouldAnnotate(shouldAnnotate) | |
| 131 , m_highestNodeToBeSerialized(highestNodeToBeSerialized) | |
| 132 { | |
| 133 } | |
| 134 | |
| 135 void StyledMarkupAccumulator::wrapWithNode(ContainerNode& node, bool convertBloc
ksToInlines) | |
| 136 { | |
| 137 StringBuilder markup; | |
| 138 if (node.isElementNode()) | |
| 139 appendElement(markup, toElement(node), convertBlocksToInlines && isBlock
(&node)); | |
| 140 else | |
| 141 appendStartMarkup(markup, node, 0); | |
| 142 m_reversedPrecedingMarkup.append(markup.toString()); | |
| 143 if (node.isElementNode()) | |
| 144 appendEndTag(toElement(node)); | |
| 145 if (m_nodes) | |
| 146 m_nodes->append(&node); | |
| 147 } | |
| 148 | |
| 149 void StyledMarkupAccumulator::wrapWithStyleNode(StylePropertySet* style, const D
ocument& document, bool isBlock) | |
| 150 { | |
| 151 StringBuilder openTag; | |
| 152 appendStyleNodeOpenTag(openTag, style, document, isBlock); | |
| 153 m_reversedPrecedingMarkup.append(openTag.toString()); | |
| 154 appendString(styleNodeCloseTag(isBlock)); | |
| 155 } | |
| 156 | |
| 157 void StyledMarkupAccumulator::appendStyleNodeOpenTag(StringBuilder& out, StylePr
opertySet* style, const Document& document, bool isBlock) | |
| 158 { | |
| 159 // wrappingStyleForSerialization should have removed -webkit-text-decoration
s-in-effect | |
| 160 ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsI
nEffect)); | |
| 161 if (isBlock) | |
| 162 out.appendLiteral("<div style=\""); | |
| 163 else | |
| 164 out.appendLiteral("<span style=\""); | |
| 165 appendAttributeValue(out, style->asText(), document.isHTMLDocument()); | |
| 166 out.appendLiteral("\">"); | |
| 167 } | |
| 168 | |
| 169 const String& StyledMarkupAccumulator::styleNodeCloseTag(bool isBlock) | |
| 170 { | |
| 171 DEFINE_STATIC_LOCAL(const String, divClose, ("</div>")); | |
| 172 DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>")); | |
| 173 return isBlock ? divClose : styleSpanClose; | |
| 174 } | |
| 175 | |
| 176 String StyledMarkupAccumulator::takeResults() | |
| 177 { | |
| 178 StringBuilder result; | |
| 179 result.reserveCapacity(totalLength(m_reversedPrecedingMarkup) + length()); | |
| 180 | |
| 181 for (size_t i = m_reversedPrecedingMarkup.size(); i > 0; --i) | |
| 182 result.append(m_reversedPrecedingMarkup[i - 1]); | |
| 183 | |
| 184 concatenateMarkup(result); | |
| 185 | |
| 186 // We remove '\0' characters because they are not visibly rendered to the us
er. | |
| 187 return result.toString().replace(0, ""); | |
| 188 } | |
| 189 | |
| 190 void StyledMarkupAccumulator::appendText(StringBuilder& out, Text& text) | |
| 191 { | |
| 192 } | |
| 193 | |
| 194 String StyledMarkupAccumulator::renderedText(Node& node, const Range* range) | |
| 195 { | |
| 196 if (!node.isTextNode()) | |
| 197 return String(); | |
| 198 | |
| 199 Text& textNode = toText(node); | |
| 200 unsigned startOffset = 0; | |
| 201 unsigned endOffset = textNode.length(); | |
| 202 | |
| 203 if (range && textNode == range->startContainer()) | |
| 204 startOffset = range->startOffset(); | |
| 205 if (range && textNode == range->endContainer()) | |
| 206 endOffset = range->endOffset(); | |
| 207 | |
| 208 Position start = createLegacyEditingPosition(&textNode, startOffset); | |
| 209 Position end = createLegacyEditingPosition(&textNode, endOffset); | |
| 210 return plainText(Range::create(textNode.document(), start, end).get()); | |
| 211 } | |
| 212 | |
| 213 String StyledMarkupAccumulator::stringValueForRange(const Node& node, const Rang
e* range) | |
| 214 { | |
| 215 if (!node.isTextNode()) | |
| 216 return emptyString(); | |
| 217 String text = toText(node).data(); | |
| 218 if (!range) | |
| 219 return text; | |
| 220 if (node == range->endContainer()) | |
| 221 text.truncate(range->endOffset()); | |
| 222 if (node == range->startContainer()) | |
| 223 text.remove(0, range->startOffset()); | |
| 224 return text; | |
| 225 } | |
| 226 | |
| 227 void StyledMarkupAccumulator::appendElement(StringBuilder& out, Element& element
, bool addDisplayInline) | |
| 228 { | |
| 229 const bool documentIsHTML = element.document().isHTMLDocument(); | |
| 230 appendOpenTag(out, element, 0); | |
| 231 | |
| 232 const bool shouldAnnotateOrForceInline = element.isHTMLElement() && (shouldA
nnotate() || addDisplayInline); | |
| 233 const bool shouldOverrideStyleAttr = shouldAnnotateOrForceInline || shouldAp
plyWrappingStyle(element); | |
| 234 | |
| 235 AttributeCollection attributes = element.attributes(); | |
| 236 AttributeCollection::iterator end = attributes.end(); | |
| 237 for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it)
{ | |
| 238 // We'll handle the style attribute separately, below. | |
| 239 if (it->name() == HTMLNames::styleAttr && shouldOverrideStyleAttr) | |
| 240 continue; | |
| 241 appendAttribute(out, element, *it, 0); | |
| 242 } | |
| 243 | |
| 244 if (shouldOverrideStyleAttr) { | |
| 245 RefPtr<EditingStyle> newInlineStyle = nullptr; | |
| 246 | |
| 247 if (shouldApplyWrappingStyle(element)) { | |
| 248 newInlineStyle = m_wrappingStyle->copy(); | |
| 249 newInlineStyle->removePropertiesInElementDefaultStyle(&element); | |
| 250 newInlineStyle->removeStyleConflictingWithStyleOfElement(&element); | |
| 251 } else | |
| 252 newInlineStyle = EditingStyle::create(); | |
| 253 | |
| 254 if (element.isStyledElement() && element.inlineStyle()) | |
| 255 newInlineStyle->overrideWithStyle(element.inlineStyle()); | |
| 256 | |
| 257 if (shouldAnnotateOrForceInline) { | |
| 258 if (shouldAnnotate()) | |
| 259 newInlineStyle->mergeStyleFromRulesForSerialization(&toHTMLEleme
nt(element)); | |
| 260 | |
| 261 if (&element == m_highestNodeToBeSerialized && m_shouldAnnotate == A
nnotateForNavigationTransition) | |
| 262 newInlineStyle->addAbsolutePositioningFromElement(element); | |
| 263 | |
| 264 if (addDisplayInline) | |
| 265 newInlineStyle->forceInline(); | |
| 266 } | |
| 267 | |
| 268 if (!newInlineStyle->isEmpty()) { | |
| 269 out.appendLiteral(" style=\""); | |
| 270 appendAttributeValue(out, newInlineStyle->style()->asText(), documen
tIsHTML); | |
| 271 out.append('\"'); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 appendCloseTag(out, element); | |
| 276 } | |
| 277 | |
| 278 Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) | |
| 279 { | |
| 280 if (!m_highestNodeToBeSerialized) { | |
| 281 Node* lastClosed = traverseNodesForSerialization(startNode, pastEnd, DoN
otEmitString); | |
| 282 m_highestNodeToBeSerialized = lastClosed; | |
| 283 } | |
| 284 | |
| 285 if (m_highestNodeToBeSerialized && m_highestNodeToBeSerialized->parentNode()
) { | |
| 286 m_wrappingStyle = EditingStyle::wrappingStyleForSerialization(m_highestN
odeToBeSerialized->parentNode(), shouldAnnotate()); | |
| 287 if (m_shouldAnnotate == AnnotateForNavigationTransition) { | |
| 288 m_wrappingStyle->style()->removeProperty(CSSPropertyBackgroundColor)
; | |
| 289 m_wrappingStyle->style()->removeProperty(CSSPropertyBackgroundImage)
; | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 | |
| 294 return traverseNodesForSerialization(startNode, pastEnd, EmitString); | |
| 295 } | |
| 296 | |
| 297 Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
de* pastEnd, NodeTraversalMode traversalMode) | |
| 298 { | |
| 299 const bool shouldEmit = traversalMode == EmitString; | |
| 300 Vector<RawPtr<ContainerNode> > ancestorsToClose; | |
| 301 Node* next; | |
| 302 Node* lastClosed = 0; | |
| 303 for (Node* n = startNode; n != pastEnd; n = next) { | |
| 304 // According to <rdar://problem/5730668>, it is possible for n to blow | |
| 305 // past pastEnd and become null here. This shouldn't be possible. | |
| 306 // This null check will prevent crashes (but create too much markup) | |
| 307 // and the ASSERT will hopefully lead us to understanding the problem. | |
| 308 ASSERT(n); | |
| 309 if (!n) | |
| 310 break; | |
| 311 | |
| 312 next = NodeTraversal::next(*n); | |
| 313 bool openedTag = false; | |
| 314 | |
| 315 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) | |
| 316 // Don't write out empty block containers that aren't fully selected
. | |
| 317 continue; | |
| 318 | |
| 319 if (!n->renderer() && m_shouldAnnotate != AnnotateForNavigationTransitio
n) { | |
| 320 next = NodeTraversal::nextSkippingChildren(*n); | |
| 321 // Don't skip over pastEnd. | |
| 322 if (pastEnd && pastEnd->isDescendantOf(n)) | |
| 323 next = pastEnd; | |
| 324 } else { | |
| 325 // Add the node to the markup if we're not skipping the descendants | |
| 326 if (shouldEmit) | |
| 327 appendStartTag(*n); | |
| 328 | |
| 329 // If node has no children, close the tag now. | |
| 330 if (n->isContainerNode() && toContainerNode(n)->hasChildren()) { | |
| 331 openedTag = true; | |
| 332 ancestorsToClose.append(toContainerNode(n)); | |
| 333 } else { | |
| 334 if (shouldEmit && n->isElementNode()) | |
| 335 appendEndTag(toElement(*n)); | |
| 336 lastClosed = n; | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 // If we didn't insert open tag and there's no more siblings or we're at
the end of the traversal, take care of ancestors. | |
| 341 // FIXME: What happens if we just inserted open tag and reached the end? | |
| 342 if (!openedTag && (!n->nextSibling() || next == pastEnd)) { | |
| 343 // Close up the ancestors. | |
| 344 while (!ancestorsToClose.isEmpty()) { | |
| 345 ContainerNode* ancestor = ancestorsToClose.last(); | |
| 346 ASSERT(ancestor); | |
| 347 if (next != pastEnd && next->isDescendantOf(ancestor)) | |
| 348 break; | |
| 349 // Not at the end of the range, close ancestors up to sibling of
next node. | |
| 350 if (shouldEmit && ancestor->isElementNode()) | |
| 351 appendEndTag(toElement(*ancestor)); | |
| 352 lastClosed = ancestor; | |
| 353 ancestorsToClose.removeLast(); | |
| 354 } | |
| 355 | |
| 356 // Surround the currently accumulated markup with markup for ancesto
rs we never opened as we leave the subtree(s) rooted at those ancestors. | |
| 357 ContainerNode* nextParent = next ? next->parentNode() : 0; | |
| 358 if (next != pastEnd && n != nextParent) { | |
| 359 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ?
lastClosed : n; | |
| 360 for (ContainerNode* parent = lastAncestorClosedOrSelf->parentNod
e(); parent && parent != nextParent; parent = parent->parentNode()) { | |
| 361 // All ancestors that aren't in the ancestorsToClose list sh
ould either be a) unrendered: | |
| 362 if (!parent->renderer()) | |
| 363 continue; | |
| 364 // or b) ancestors that we never encountered during a pre-or
der traversal starting at startNode: | |
| 365 ASSERT(startNode->isDescendantOf(parent)); | |
| 366 if (shouldEmit) | |
| 367 wrapWithNode(*parent); | |
| 368 lastClosed = parent; | |
| 369 } | |
| 370 } | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 return lastClosed; | |
| 375 } | |
| 376 | |
| 377 static bool propertyMissingOrEqualToNone(StylePropertySet* style, CSSPropertyID
propertyID) | |
| 378 { | |
| 379 if (!style) | |
| 380 return false; | |
| 381 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); | |
| 382 if (!value) | |
| 383 return true; | |
| 384 if (!value->isPrimitiveValue()) | |
| 385 return false; | |
| 386 return toCSSPrimitiveValue(value.get())->getValueID() == CSSValueNone; | |
| 387 } | |
| 388 | |
| 389 static bool needInterchangeNewlineAfter(const VisiblePosition& v) | |
| 390 { | |
| 391 return isEndOfParagraph(v) && isStartOfParagraph(v.next()); | |
| 392 } | |
| 393 | |
| 394 static PassRefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const HTMLEle
ment* element) | |
| 395 { | |
| 396 RefPtr<EditingStyle> style = EditingStyle::create(element->inlineStyle()); | |
| 397 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work t
o untangle | |
| 398 // the non-const-ness of styleFromMatchedRulesForElement. | |
| 399 style->mergeStyleFromRules(const_cast<HTMLElement*>(element)); | |
| 400 return style.release(); | |
| 401 } | |
| 402 | |
| 403 static bool isPresentationalHTMLElement(const Node* node) | |
| 404 { | |
| 405 return false; | |
| 406 } | |
| 407 | |
| 408 static HTMLElement* highestAncestorToWrapMarkup(const Range* range, EAnnotateFor
Interchange shouldAnnotate, Node* constrainingAncestor) | |
| 409 { | |
| 410 Node* commonAncestor = range->commonAncestorContainer(); | |
| 411 ASSERT(commonAncestor); | |
| 412 HTMLElement* specialCommonAncestor = 0; | |
| 413 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : common
Ancestor; | |
| 414 if (checkAncestor->renderer()) { | |
| 415 HTMLElement* newSpecialCommonAncestor = toHTMLElement(highestEnclosingNo
deOfType(firstPositionInNode(checkAncestor), &isPresentationalHTMLElement, CanCr
ossEditingBoundary, constrainingAncestor)); | |
| 416 if (newSpecialCommonAncestor) | |
| 417 specialCommonAncestor = newSpecialCommonAncestor; | |
| 418 } | |
| 419 | |
| 420 if (HTMLAnchorElement* enclosingAnchor = toHTMLAnchorElement(enclosingElemen
tWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : com
monAncestor), HTMLNames::aTag))) | |
| 421 specialCommonAncestor = enclosingAnchor; | |
| 422 | |
| 423 return specialCommonAncestor; | |
| 424 } | |
| 425 | |
| 426 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForIntercha
nge? | |
| 427 // FIXME: At least, annotation and style info should probably not be included in
range.markupString() | |
| 428 static String createMarkupInternal(Document& document, const Range* range, const
Range* updatedRange, Vector<RawPtr<Node> >* nodes, | |
| 429 EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsolu
teURLs shouldResolveURLs, Node* constrainingAncestor) | |
| 430 { | |
| 431 ASSERT(range); | |
| 432 ASSERT(updatedRange); | |
| 433 DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\""
AppleInterchangeNewline "\">")); | |
| 434 | |
| 435 bool collapsed = updatedRange->collapsed(); | |
| 436 if (collapsed) | |
| 437 return emptyString(); | |
| 438 Node* commonAncestor = updatedRange->commonAncestorContainer(); | |
| 439 if (!commonAncestor) | |
| 440 return emptyString(); | |
| 441 | |
| 442 document.updateLayoutIgnorePendingStylesheets(); | |
| 443 | |
| 444 // FIXME(sky): Remove this variable. | |
| 445 HTMLElement* fullySelectedRoot = 0; | |
| 446 // FIXME: Do this for all fully selected blocks, not just the body. | |
| 447 | |
| 448 HTMLElement* specialCommonAncestor = highestAncestorToWrapMarkup(updatedRang
e, shouldAnnotate, constrainingAncestor); | |
| 449 StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate
, updatedRange, specialCommonAncestor); | |
| 450 Node* pastEnd = updatedRange->pastLastNode(); | |
| 451 | |
| 452 Node* startNode = updatedRange->firstNode(); | |
| 453 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFIN
ITY); | |
| 454 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY)
; | |
| 455 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(
visibleStart)) { | |
| 456 if (visibleStart == visibleEnd.previous()) | |
| 457 return interchangeNewlineString; | |
| 458 | |
| 459 accumulator.appendString(interchangeNewlineString); | |
| 460 startNode = visibleStart.next().deepEquivalent().deprecatedNode(); | |
| 461 | |
| 462 if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, AS
SERT_NO_EXCEPTION) >= 0) | |
| 463 return interchangeNewlineString; | |
| 464 } | |
| 465 | |
| 466 Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); | |
| 467 | |
| 468 if (specialCommonAncestor && lastClosed) { | |
| 469 // Also include all of the ancestors of lastClosed up to this special an
cestor. | |
| 470 for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ances
tor = ancestor->parentNode()) { | |
| 471 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { | |
| 472 RefPtr<EditingStyle> fullySelectedRootStyle = styleFromMatchedRu
lesAndInlineDecl(fullySelectedRoot); | |
| 473 | |
| 474 if (fullySelectedRootStyle->style()) { | |
| 475 // Reset the CSS properties to avoid an assertion error in a
ddStyleMarkup(). | |
| 476 // This assertion is caused at least when we select all text
of a <body> element whose | |
| 477 // 'text-decoration' property is "inherit", and copy it. | |
| 478 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->st
yle(), CSSPropertyTextDecoration)) | |
| 479 fullySelectedRootStyle->style()->setProperty(CSSProperty
TextDecoration, CSSValueNone); | |
| 480 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->st
yle(), CSSPropertyWebkitTextDecorationsInEffect)) | |
| 481 fullySelectedRootStyle->style()->setProperty(CSSProperty
WebkitTextDecorationsInEffect, CSSValueNone); | |
| 482 accumulator.wrapWithStyleNode(fullySelectedRootStyle->style(
), document, true); | |
| 483 } | |
| 484 } else { | |
| 485 accumulator.wrapWithNode(*ancestor, convertBlocksToInlines); | |
| 486 } | |
| 487 if (nodes) | |
| 488 nodes->append(ancestor); | |
| 489 | |
| 490 if (ancestor == specialCommonAncestor) | |
| 491 break; | |
| 492 } | |
| 493 } | |
| 494 | |
| 495 // FIXME: The interchange newline should be placed in the block that it's in
, not after all of the content, unconditionally. | |
| 496 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(
visibleEnd.previous())) | |
| 497 accumulator.appendString(interchangeNewlineString); | |
| 498 | |
| 499 return accumulator.takeResults(); | |
| 500 } | |
| 501 | |
| 502 String createMarkup(const Range* range, Vector<RawPtr<Node> >* nodes, EAnnotateF
orInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldR
esolveURLs, Node* constrainingAncestor) | |
| 503 { | |
| 504 if (!range) | |
| 505 return emptyString(); | |
| 506 | |
| 507 Document& document = range->ownerDocument(); | |
| 508 const Range* updatedRange = range; | |
| 509 | |
| 510 return createMarkupInternal(document, range, updatedRange, nodes, shouldAnno
tate, convertBlocksToInlines, shouldResolveURLs, constrainingAncestor); | |
| 511 } | |
| 512 | |
| 513 String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<RawPtr<
Node> >* nodes, EAbsoluteURLs shouldResolveURLs, Vector<QualifiedName>* tagNames
ToSkip) | |
| 514 { | |
| 515 if (!node) | |
| 516 return ""; | |
| 517 | |
| 518 MarkupAccumulator accumulator(nodes, shouldResolveURLs); | |
| 519 return accumulator.serializeNodes(const_cast<Node&>(*node), childrenOnly, ta
gNamesToSkip); | |
| 520 } | |
| 521 | |
| 522 void replaceChildrenWithFragment(ContainerNode* container, PassRefPtr<DocumentFr
agment> fragment, ExceptionState& exceptionState) | |
| 523 { | |
| 524 ASSERT(container); | |
| 525 RefPtr<ContainerNode> containerNode(container); | |
| 526 | |
| 527 ChildListMutationScope mutation(*containerNode); | |
| 528 | |
| 529 if (!fragment->firstChild()) { | |
| 530 containerNode->removeChildren(); | |
| 531 return; | |
| 532 } | |
| 533 | |
| 534 // FIXME: This is wrong if containerNode->firstChild() has more than one ref
! | |
| 535 if (containerNode->hasOneTextChild() && fragment->hasOneTextChild()) { | |
| 536 toText(containerNode->firstChild())->setData(toText(fragment->firstChild
())->data()); | |
| 537 return; | |
| 538 } | |
| 539 | |
| 540 // FIXME: No need to replace the child it is a text node and its contents ar
e already == text. | |
| 541 if (containerNode->hasOneChild()) { | |
| 542 containerNode->replaceChild(fragment, containerNode->firstChild(), excep
tionState); | |
| 543 return; | |
| 544 } | |
| 545 | |
| 546 containerNode->removeChildren(); | |
| 547 containerNode->appendChild(fragment, exceptionState); | |
| 548 } | |
| 549 | |
| 550 void mergeWithNextTextNode(Text* textNode, ExceptionState& exceptionState) | |
| 551 { | |
| 552 ASSERT(textNode); | |
| 553 Node* next = textNode->nextSibling(); | |
| 554 if (!next || !next->isTextNode()) | |
| 555 return; | |
| 556 | |
| 557 RefPtr<Text> textNext = toText(next); | |
| 558 textNode->appendData(textNext->data()); | |
| 559 if (textNext->parentNode()) // Might have been removed by mutation event. | |
| 560 textNext->remove(exceptionState); | |
| 561 } | |
| 562 | |
| 563 } | |
| OLD | NEW |