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 "config.h" | |
30 #include "core/editing/Serialization.h" | |
31 | |
32 #include "bindings/core/v8/ExceptionState.h" | |
33 #include "core/CSSValueKeywords.h" | |
34 #include "core/HTMLNames.h" | |
35 #include "core/css/CSSPrimitiveValue.h" | |
36 #include "core/css/CSSValue.h" | |
37 #include "core/css/StylePropertySet.h" | |
38 #include "core/dom/CDATASection.h" | |
39 #include "core/dom/ChildListMutationScope.h" | |
40 #include "core/dom/Comment.h" | |
41 #include "core/dom/ContextFeatures.h" | |
42 #include "core/dom/DocumentFragment.h" | |
43 #include "core/dom/ElementTraversal.h" | |
44 #include "core/dom/ExceptionCode.h" | |
45 #include "core/dom/NodeTraversal.h" | |
46 #include "core/dom/Range.h" | |
47 #include "core/editing/EditingStrategy.h" | |
48 #include "core/editing/EditingUtilities.h" | |
49 #include "core/editing/Editor.h" | |
50 #include "core/editing/MarkupAccumulator.h" | |
51 #include "core/editing/StyledMarkupSerializer.h" | |
52 #include "core/editing/VisibleSelection.h" | |
53 #include "core/editing/VisibleUnits.h" | |
54 #include "core/editing/iterators/TextIterator.h" | |
55 #include "core/frame/LocalFrame.h" | |
56 #include "core/html/HTMLAnchorElement.h" | |
57 #include "core/html/HTMLBRElement.h" | |
58 #include "core/html/HTMLBodyElement.h" | |
59 #include "core/html/HTMLDivElement.h" | |
60 #include "core/html/HTMLElement.h" | |
61 #include "core/html/HTMLQuoteElement.h" | |
62 #include "core/html/HTMLSpanElement.h" | |
63 #include "core/html/HTMLTableCellElement.h" | |
64 #include "core/html/HTMLTableElement.h" | |
65 #include "core/html/HTMLTextFormControlElement.h" | |
66 #include "core/layout/LayoutObject.h" | |
67 #include "platform/weborigin/KURL.h" | |
68 #include "wtf/StdLibExtras.h" | |
69 #include "wtf/text/StringBuilder.h" | |
70 | |
71 namespace blink { | |
72 | |
73 using namespace HTMLNames; | |
74 | |
75 class AttributeChange { | |
76 ALLOW_ONLY_INLINE_ALLOCATION(); | |
77 public: | |
78 AttributeChange() | |
79 : m_name(nullAtom, nullAtom, nullAtom) | |
80 { | |
81 } | |
82 | |
83 AttributeChange(PassRefPtrWillBeRawPtr<Element> element, const QualifiedName
& name, const String& value) | |
84 : m_element(element), m_name(name), m_value(value) | |
85 { | |
86 } | |
87 | |
88 void apply() | |
89 { | |
90 m_element->setAttribute(m_name, AtomicString(m_value)); | |
91 } | |
92 | |
93 DEFINE_INLINE_TRACE() | |
94 { | |
95 visitor->trace(m_element); | |
96 } | |
97 | |
98 private: | |
99 RefPtrWillBeMember<Element> m_element; | |
100 QualifiedName m_name; | |
101 String m_value; | |
102 }; | |
103 | |
104 } // namespace blink | |
105 | |
106 WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::AttributeChange); | |
107 | |
108 namespace blink { | |
109 | |
110 static void completeURLs(DocumentFragment& fragment, const String& baseURL) | |
111 { | |
112 WillBeHeapVector<AttributeChange> changes; | |
113 | |
114 KURL parsedBaseURL(ParsedURLString, baseURL); | |
115 | |
116 for (Element& element : ElementTraversal::descendantsOf(fragment)) { | |
117 AttributeCollection attributes = element.attributes(); | |
118 // AttributeCollection::iterator end = attributes.end(); | |
119 for (const auto& attribute : attributes) { | |
120 if (element.isURLAttribute(attribute) && !attribute.value().isEmpty(
)) | |
121 changes.append(AttributeChange(&element, attribute.name(), KURL(
parsedBaseURL, attribute.value()).string())); | |
122 } | |
123 } | |
124 | |
125 for (auto& change : changes) | |
126 change.apply(); | |
127 } | |
128 | |
129 static bool isHTMLBlockElement(const Node* node) | |
130 { | |
131 ASSERT(node); | |
132 return isHTMLTableCellElement(*node) | |
133 || isNonTableCellHTMLBlockElement(node); | |
134 } | |
135 | |
136 static HTMLElement* ancestorToRetainStructureAndAppearanceForBlock(Element* comm
onAncestorBlock) | |
137 { | |
138 if (!commonAncestorBlock) | |
139 return 0; | |
140 | |
141 if (commonAncestorBlock->hasTagName(tbodyTag) || isHTMLTableRowElement(*comm
onAncestorBlock)) | |
142 return Traversal<HTMLTableElement>::firstAncestor(*commonAncestorBlock); | |
143 | |
144 if (isNonTableCellHTMLBlockElement(commonAncestorBlock)) | |
145 return toHTMLElement(commonAncestorBlock); | |
146 | |
147 return 0; | |
148 } | |
149 | |
150 static inline HTMLElement* ancestorToRetainStructureAndAppearance(Node* commonAn
cestor) | |
151 { | |
152 return ancestorToRetainStructureAndAppearanceForBlock(enclosingBlock(commonA
ncestor)); | |
153 } | |
154 | |
155 static inline HTMLElement* ancestorToRetainStructureAndAppearanceWithNoLayoutObj
ect(Node* commonAncestor) | |
156 { | |
157 HTMLElement* commonAncestorBlock = toHTMLElement(enclosingNodeOfType(firstPo
sitionInOrBeforeNode(commonAncestor), isHTMLBlockElement)); | |
158 return ancestorToRetainStructureAndAppearanceForBlock(commonAncestorBlock); | |
159 } | |
160 | |
161 bool propertyMissingOrEqualToNone(StylePropertySet* style, CSSPropertyID propert
yID) | |
162 { | |
163 if (!style) | |
164 return false; | |
165 RefPtrWillBeRawPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); | |
166 if (!value) | |
167 return true; | |
168 if (!value->isPrimitiveValue()) | |
169 return false; | |
170 return toCSSPrimitiveValue(value.get())->getValueID() == CSSValueNone; | |
171 } | |
172 | |
173 static bool isPresentationalHTMLElement(const Node* node) | |
174 { | |
175 if (!node->isHTMLElement()) | |
176 return false; | |
177 | |
178 const HTMLElement& element = toHTMLElement(*node); | |
179 return element.hasTagName(uTag) || element.hasTagName(sTag) || element.hasTa
gName(strikeTag) | |
180 || element.hasTagName(iTag) || element.hasTagName(emTag) || element.hasT
agName(bTag) || element.hasTagName(strongTag); | |
181 } | |
182 | |
183 template<typename Strategy> | |
184 static HTMLElement* highestAncestorToWrapMarkup(const PositionAlgorithm<Strategy
>& startPosition, const PositionAlgorithm<Strategy>& endPosition, EAnnotateForIn
terchange shouldAnnotate, Node* constrainingAncestor) | |
185 { | |
186 Node* firstNode = startPosition.nodeAsRangeFirstNode(); | |
187 // For compatibility reason, we use container node of start and end | |
188 // positions rather than first node and last node in selection. | |
189 Node* commonAncestor = Strategy::commonAncestor(*startPosition.computeContai
nerNode(), *endPosition.computeContainerNode()); | |
190 ASSERT(commonAncestor); | |
191 HTMLElement* specialCommonAncestor = nullptr; | |
192 if (shouldAnnotate == AnnotateForInterchange) { | |
193 // Include ancestors that aren't completely inside the range but are req
uired to retain | |
194 // the structure and appearance of the copied markup. | |
195 specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAnc
estor); | |
196 if (Node* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNo
de(firstNode), isListItem)) { | |
197 EphemeralRangeTemplate<Strategy> markupRange = EphemeralRangeTemplat
e<Strategy>(startPosition, endPosition); | |
198 EphemeralRangeTemplate<Strategy> nodeRange = VisibleSelection::norma
lizeRange(EphemeralRangeTemplate<Strategy>::rangeOfContents(*parentListNode)); | |
199 if (nodeRange == markupRange) { | |
200 ContainerNode* ancestor = parentListNode->parentNode(); | |
201 while (ancestor && !isHTMLListElement(ancestor)) | |
202 ancestor = ancestor->parentNode(); | |
203 specialCommonAncestor = toHTMLElement(ancestor); | |
204 } | |
205 } | |
206 | |
207 // Retain the Mail quote level by including all ancestor mail block quot
es. | |
208 if (HTMLQuoteElement* highestMailBlockquote = toHTMLQuoteElement(highest
EnclosingNodeOfType(firstPositionInOrBeforeNode(firstNode), isMailHTMLBlockquote
Element, CanCrossEditingBoundary))) | |
209 specialCommonAncestor = highestMailBlockquote; | |
210 } | |
211 | |
212 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : common
Ancestor; | |
213 if (checkAncestor->layoutObject()) { | |
214 HTMLElement* newSpecialCommonAncestor = toHTMLElement(highestEnclosingNo
deOfType(firstPositionInNode(checkAncestor), &isPresentationalHTMLElement, CanCr
ossEditingBoundary, constrainingAncestor)); | |
215 if (newSpecialCommonAncestor) | |
216 specialCommonAncestor = newSpecialCommonAncestor; | |
217 } | |
218 | |
219 // If a single tab is selected, commonAncestor will be a text node inside a
tab span. | |
220 // If two or more tabs are selected, commonAncestor will be the tab span. | |
221 // In either case, if there is a specialCommonAncestor already, it will nece
ssarily be above | |
222 // any tab span that needs to be included. | |
223 if (!specialCommonAncestor && isTabHTMLSpanElementTextNode(commonAncestor)) | |
224 specialCommonAncestor = toHTMLSpanElement(Strategy::parent(*commonAncest
or)); | |
225 if (!specialCommonAncestor && isTabHTMLSpanElement(commonAncestor)) | |
226 specialCommonAncestor = toHTMLSpanElement(commonAncestor); | |
227 | |
228 if (HTMLAnchorElement* enclosingAnchor = toHTMLAnchorElement(enclosingElemen
tWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : com
monAncestor), aTag))) | |
229 specialCommonAncestor = enclosingAnchor; | |
230 | |
231 return specialCommonAncestor; | |
232 } | |
233 | |
234 template <typename Strategy> | |
235 class CreateMarkupAlgorithm { | |
236 public: | |
237 static String createMarkup(const PositionAlgorithm<Strategy>& startPosition,
const PositionAlgorithm<Strategy>& endPosition, EAnnotateForInterchange shouldA
nnotate = DoNotAnnotateForInterchange, ConvertBlocksToInlines = ConvertBlocksToI
nlines::NotConvert, EAbsoluteURLs shouldResolveURLs = DoNotResolveURLs, Node* co
nstrainingAncestor = nullptr); | |
238 }; | |
239 | |
240 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForIntercha
nge? | |
241 // FIXME: At least, annotation and style info should probably not be included in
range.markupString() | |
242 template <typename Strategy> | |
243 String CreateMarkupAlgorithm<Strategy>::createMarkup(const PositionAlgorithm<Str
ategy>& startPosition, const PositionAlgorithm<Strategy>& endPosition, | |
244 EAnnotateForInterchange shouldAnnotate, ConvertBlocksToInlines convertBlocks
ToInlines, EAbsoluteURLs shouldResolveURLs, Node* constrainingAncestor) | |
245 { | |
246 ASSERT(startPosition.isNotNull()); | |
247 ASSERT(endPosition.isNotNull()); | |
248 ASSERT(startPosition.compareTo(endPosition) <= 0); | |
249 | |
250 bool collapsed = startPosition == endPosition; | |
251 if (collapsed) | |
252 return emptyString(); | |
253 Node* commonAncestor = Strategy::commonAncestor(*startPosition.computeContai
nerNode(), *endPosition.computeContainerNode()); | |
254 if (!commonAncestor) | |
255 return emptyString(); | |
256 | |
257 Document* document = startPosition.document(); | |
258 document->updateLayoutIgnorePendingStylesheets(); | |
259 | |
260 HTMLElement* specialCommonAncestor = highestAncestorToWrapMarkup<Strategy>(s
tartPosition, endPosition, shouldAnnotate, constrainingAncestor); | |
261 StyledMarkupSerializer<Strategy> serializer(shouldResolveURLs, shouldAnnotat
e, startPosition, endPosition, specialCommonAncestor, convertBlocksToInlines); | |
262 return serializer.createMarkup(); | |
263 } | |
264 | |
265 String createMarkup(const Position& startPosition, const Position& endPosition,
EAnnotateForInterchange shouldAnnotate, ConvertBlocksToInlines convertBlocksToIn
lines, EAbsoluteURLs shouldResolveURLs, Node* constrainingAncestor) | |
266 { | |
267 ASSERT(startPosition.compareTo(endPosition) <= 0); | |
268 return CreateMarkupAlgorithm<EditingStrategy>::createMarkup(startPosition, e
ndPosition, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs, constrain
ingAncestor); | |
269 } | |
270 | |
271 String createMarkup(const PositionInComposedTree& startPosition, const PositionI
nComposedTree& endPosition, EAnnotateForInterchange shouldAnnotate, ConvertBlock
sToInlines convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs, Node* constr
ainingAncestor) | |
272 { | |
273 ASSERT(startPosition.compareTo(endPosition) <= 0); | |
274 return CreateMarkupAlgorithm<EditingInComposedTreeStrategy>::createMarkup(st
artPosition, endPosition, shouldAnnotate, convertBlocksToInlines, shouldResolveU
RLs, constrainingAncestor); | |
275 } | |
276 | |
277 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentFromMarkup(Document& docu
ment, const String& markup, const String& baseURL, ParserContentPolicy parserCon
tentPolicy) | |
278 { | |
279 // We use a fake body element here to trick the HTML parser to using the InB
ody insertion mode. | |
280 RefPtrWillBeRawPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(docum
ent); | |
281 RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(doc
ument); | |
282 | |
283 fragment->parseHTML(markup, fakeBody.get(), parserContentPolicy); | |
284 | |
285 if (!baseURL.isEmpty() && baseURL != blankURL() && baseURL != document.baseU
RL()) | |
286 completeURLs(*fragment, baseURL); | |
287 | |
288 return fragment.release(); | |
289 } | |
290 | |
291 static const char fragmentMarkerTag[] = "webkit-fragment-marker"; | |
292 | |
293 static bool findNodesSurroundingContext(DocumentFragment* fragment, RefPtrWillBe
RawPtr<Comment>& nodeBeforeContext, RefPtrWillBeRawPtr<Comment>& nodeAfterContex
t) | |
294 { | |
295 for (Node& node : NodeTraversal::startsAt(fragment->firstChild())) { | |
296 if (node.nodeType() == Node::COMMENT_NODE && toComment(node).data() == f
ragmentMarkerTag) { | |
297 if (!nodeBeforeContext) { | |
298 nodeBeforeContext = &toComment(node); | |
299 } else { | |
300 nodeAfterContext = &toComment(node); | |
301 return true; | |
302 } | |
303 } | |
304 } | |
305 return false; | |
306 } | |
307 | |
308 static void trimFragment(DocumentFragment* fragment, Comment* nodeBeforeContext,
Comment* nodeAfterContext) | |
309 { | |
310 RefPtrWillBeRawPtr<Node> next = nullptr; | |
311 for (RefPtrWillBeRawPtr<Node> node = fragment->firstChild(); node; node = ne
xt) { | |
312 if (nodeBeforeContext->isDescendantOf(node.get())) { | |
313 next = NodeTraversal::next(*node); | |
314 continue; | |
315 } | |
316 next = NodeTraversal::nextSkippingChildren(*node); | |
317 ASSERT(!node->contains(nodeAfterContext)); | |
318 node->parentNode()->removeChild(node.get(), ASSERT_NO_EXCEPTION); | |
319 if (nodeBeforeContext == node) | |
320 break; | |
321 } | |
322 | |
323 ASSERT(nodeAfterContext->parentNode()); | |
324 for (RefPtrWillBeRawPtr<Node> node = nodeAfterContext; node; node = next) { | |
325 next = NodeTraversal::nextSkippingChildren(*node); | |
326 node->parentNode()->removeChild(node.get(), ASSERT_NO_EXCEPTION); | |
327 } | |
328 } | |
329 | |
330 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentFromMarkupWithContext(Doc
ument& document, const String& markup, unsigned fragmentStart, unsigned fragment
End, | |
331 const String& baseURL, ParserContentPolicy parserContentPolicy) | |
332 { | |
333 // FIXME: Need to handle the case where the markup already contains these ma
rkers. | |
334 | |
335 StringBuilder taggedMarkup; | |
336 taggedMarkup.append(markup.left(fragmentStart)); | |
337 MarkupFormatter::appendComment(taggedMarkup, fragmentMarkerTag); | |
338 taggedMarkup.append(markup.substring(fragmentStart, fragmentEnd - fragmentSt
art)); | |
339 MarkupFormatter::appendComment(taggedMarkup, fragmentMarkerTag); | |
340 taggedMarkup.append(markup.substring(fragmentEnd)); | |
341 | |
342 RefPtrWillBeRawPtr<DocumentFragment> taggedFragment = createFragmentFromMark
up(document, taggedMarkup.toString(), baseURL, parserContentPolicy); | |
343 | |
344 RefPtrWillBeRawPtr<Comment> nodeBeforeContext = nullptr; | |
345 RefPtrWillBeRawPtr<Comment> nodeAfterContext = nullptr; | |
346 if (!findNodesSurroundingContext(taggedFragment.get(), nodeBeforeContext, no
deAfterContext)) | |
347 return nullptr; | |
348 | |
349 RefPtrWillBeRawPtr<Document> taggedDocument = Document::create(); | |
350 taggedDocument->setContextFeatures(document.contextFeatures()); | |
351 | |
352 RefPtrWillBeRawPtr<Element> root = Element::create(QualifiedName::null(), ta
ggedDocument.get()); | |
353 root->appendChild(taggedFragment.get()); | |
354 taggedDocument->appendChild(root); | |
355 | |
356 RefPtrWillBeRawPtr<Range> range = Range::create(*taggedDocument.get(), | |
357 positionAfterNode(nodeBeforeContext.get()).parentAnchoredEquivalent(), | |
358 positionBeforeNode(nodeAfterContext.get()).parentAnchoredEquivalent()); | |
359 | |
360 Node* commonAncestor = range->commonAncestorContainer(); | |
361 HTMLElement* specialCommonAncestor = ancestorToRetainStructureAndAppearanceW
ithNoLayoutObject(commonAncestor); | |
362 | |
363 // When there's a special common ancestor outside of the fragment, we must i
nclude it as well to | |
364 // preserve the structure and appearance of the fragment. For example, if th
e fragment contains | |
365 // TD, we need to include the enclosing TABLE tag as well. | |
366 RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(doc
ument); | |
367 if (specialCommonAncestor) | |
368 fragment->appendChild(specialCommonAncestor); | |
369 else | |
370 fragment->parserTakeAllChildrenFrom(toContainerNode(*commonAncestor)); | |
371 | |
372 trimFragment(fragment.get(), nodeBeforeContext.get(), nodeAfterContext.get()
); | |
373 | |
374 return fragment; | |
375 } | |
376 | |
377 String createMarkup(const Node* node, EChildrenOnly childrenOnly, EAbsoluteURLs
shouldResolveURLs) | |
378 { | |
379 if (!node) | |
380 return ""; | |
381 | |
382 MarkupAccumulator accumulator(shouldResolveURLs); | |
383 return serializeNodes<EditingStrategy>(accumulator, const_cast<Node&>(*node)
, childrenOnly); | |
384 } | |
385 | |
386 static void fillContainerFromString(ContainerNode* paragraph, const String& stri
ng) | |
387 { | |
388 Document& document = paragraph->document(); | |
389 | |
390 if (string.isEmpty()) { | |
391 paragraph->appendChild(createBlockPlaceholderElement(document)); | |
392 return; | |
393 } | |
394 | |
395 ASSERT(string.find('\n') == kNotFound); | |
396 | |
397 Vector<String> tabList; | |
398 string.split('\t', true, tabList); | |
399 StringBuilder tabText; | |
400 bool first = true; | |
401 size_t numEntries = tabList.size(); | |
402 for (size_t i = 0; i < numEntries; ++i) { | |
403 const String& s = tabList[i]; | |
404 | |
405 // append the non-tab textual part | |
406 if (!s.isEmpty()) { | |
407 if (!tabText.isEmpty()) { | |
408 paragraph->appendChild(createTabSpanElement(document, tabText.to
String())); | |
409 tabText.clear(); | |
410 } | |
411 RefPtrWillBeRawPtr<Text> textNode = document.createTextNode(stringWi
thRebalancedWhitespace(s, first, i + 1 == numEntries)); | |
412 paragraph->appendChild(textNode.release()); | |
413 } | |
414 | |
415 // there is a tab after every entry, except the last entry | |
416 // (if the last character is a tab, the list gets an extra empty entry) | |
417 if (i + 1 != numEntries) | |
418 tabText.append('\t'); | |
419 else if (!tabText.isEmpty()) | |
420 paragraph->appendChild(createTabSpanElement(document, tabText.toStri
ng())); | |
421 | |
422 first = false; | |
423 } | |
424 } | |
425 | |
426 bool isPlainTextMarkup(Node* node) | |
427 { | |
428 ASSERT(node); | |
429 if (!isHTMLDivElement(*node)) | |
430 return false; | |
431 | |
432 HTMLDivElement& element = toHTMLDivElement(*node); | |
433 if (!element.hasAttributes()) | |
434 return false; | |
435 | |
436 if (element.hasOneChild()) | |
437 return element.firstChild()->isTextNode() || element.firstChild()->hasCh
ildren(); | |
438 | |
439 return element.hasChildCount(2) && isTabHTMLSpanElementTextNode(element.firs
tChild()->firstChild()) && element.lastChild()->isTextNode(); | |
440 } | |
441 | |
442 static bool shouldPreserveNewline(const EphemeralRange& range) | |
443 { | |
444 if (Node* node = range.startPosition().nodeAsRangeFirstNode()) { | |
445 if (LayoutObject* layoutObject = node->layoutObject()) | |
446 return layoutObject->style()->preserveNewline(); | |
447 } | |
448 | |
449 if (Node* node = range.startPosition().anchorNode()) { | |
450 if (LayoutObject* layoutObject = node->layoutObject()) | |
451 return layoutObject->style()->preserveNewline(); | |
452 } | |
453 | |
454 return false; | |
455 } | |
456 | |
457 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentFromText(Range* context,
const String& text) | |
458 { | |
459 return createFragmentFromText(EphemeralRange(context), text); | |
460 } | |
461 | |
462 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentFromText(const EphemeralR
ange& context, const String& text) | |
463 { | |
464 if (context.isNull()) | |
465 return nullptr; | |
466 | |
467 Document& document = context.document(); | |
468 RefPtrWillBeRawPtr<DocumentFragment> fragment = document.createDocumentFragm
ent(); | |
469 | |
470 if (text.isEmpty()) | |
471 return fragment.release(); | |
472 | |
473 String string = text; | |
474 string.replace("\r\n", "\n"); | |
475 string.replace('\r', '\n'); | |
476 | |
477 if (shouldPreserveNewline(context)) { | |
478 fragment->appendChild(document.createTextNode(string)); | |
479 if (string.endsWith('\n')) { | |
480 RefPtrWillBeRawPtr<HTMLBRElement> element = createBreakElement(docum
ent); | |
481 element->setAttribute(classAttr, AppleInterchangeNewline); | |
482 fragment->appendChild(element.release()); | |
483 } | |
484 return fragment.release(); | |
485 } | |
486 | |
487 // A string with no newlines gets added inline, rather than being put into a
paragraph. | |
488 if (string.find('\n') == kNotFound) { | |
489 fillContainerFromString(fragment.get(), string); | |
490 return fragment.release(); | |
491 } | |
492 | |
493 // Break string into paragraphs. Extra line breaks turn into empty paragraph
s. | |
494 Element* block = enclosingBlock(context.startPosition().nodeAsRangeFirstNode
()); | |
495 bool useClonesOfEnclosingBlock = block | |
496 && !isHTMLBodyElement(*block) | |
497 && !isHTMLHtmlElement(*block) | |
498 && block != editableRootForPosition(context.startPosition()); | |
499 bool useLineBreak = enclosingTextFormControl(context.startPosition()); | |
500 | |
501 Vector<String> list; | |
502 string.split('\n', true, list); // true gets us empty strings in the list | |
503 size_t numLines = list.size(); | |
504 for (size_t i = 0; i < numLines; ++i) { | |
505 const String& s = list[i]; | |
506 | |
507 RefPtrWillBeRawPtr<Element> element = nullptr; | |
508 if (s.isEmpty() && i + 1 == numLines) { | |
509 // For last line, use the "magic BR" rather than a P. | |
510 element = createBreakElement(document); | |
511 element->setAttribute(classAttr, AppleInterchangeNewline); | |
512 } else if (useLineBreak) { | |
513 element = createBreakElement(document); | |
514 fillContainerFromString(fragment.get(), s); | |
515 } else { | |
516 if (useClonesOfEnclosingBlock) | |
517 element = block->cloneElementWithoutChildren(); | |
518 else | |
519 element = createDefaultParagraphElement(document); | |
520 fillContainerFromString(element.get(), s); | |
521 } | |
522 fragment->appendChild(element.release()); | |
523 } | |
524 return fragment.release(); | |
525 } | |
526 | |
527 String urlToMarkup(const KURL& url, const String& title) | |
528 { | |
529 StringBuilder markup; | |
530 markup.appendLiteral("<a href=\""); | |
531 markup.append(url.string()); | |
532 markup.appendLiteral("\">"); | |
533 MarkupFormatter::appendCharactersReplacingEntities(markup, title, 0, title.l
ength(), EntityMaskInPCDATA); | |
534 markup.appendLiteral("</a>"); | |
535 return markup.toString(); | |
536 } | |
537 | |
538 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentForInnerOuterHTML(const S
tring& markup, Element* contextElement, ParserContentPolicy parserContentPolicy,
const char* method, ExceptionState& exceptionState) | |
539 { | |
540 ASSERT(contextElement); | |
541 Document& document = isHTMLTemplateElement(*contextElement) ? contextElement
->document().ensureTemplateDocument() : contextElement->document(); | |
542 RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(doc
ument); | |
543 | |
544 if (document.isHTMLDocument()) { | |
545 fragment->parseHTML(markup, contextElement, parserContentPolicy); | |
546 return fragment; | |
547 } | |
548 | |
549 bool wasValid = fragment->parseXML(markup, contextElement, parserContentPoli
cy); | |
550 if (!wasValid) { | |
551 exceptionState.throwDOMException(SyntaxError, "The provided markup is in
valid XML, and therefore cannot be inserted into an XML document."); | |
552 return nullptr; | |
553 } | |
554 return fragment.release(); | |
555 } | |
556 | |
557 PassRefPtrWillBeRawPtr<DocumentFragment> createFragmentForTransformToFragment(co
nst String& sourceString, const String& sourceMIMEType, Document& outputDoc) | |
558 { | |
559 RefPtrWillBeRawPtr<DocumentFragment> fragment = outputDoc.createDocumentFrag
ment(); | |
560 | |
561 if (sourceMIMEType == "text/html") { | |
562 // As far as I can tell, there isn't a spec for how transformToFragment
is supposed to work. | |
563 // Based on the documentation I can find, it looks like we want to start
parsing the fragment in the InBody insertion mode. | |
564 // Unfortunately, that's an implementation detail of the parser. | |
565 // We achieve that effect here by passing in a fake body element as cont
ext for the fragment. | |
566 RefPtrWillBeRawPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(o
utputDoc); | |
567 fragment->parseHTML(sourceString, fakeBody.get()); | |
568 } else if (sourceMIMEType == "text/plain") { | |
569 fragment->parserAppendChild(Text::create(outputDoc, sourceString)); | |
570 } else { | |
571 bool successfulParse = fragment->parseXML(sourceString, 0); | |
572 if (!successfulParse) | |
573 return nullptr; | |
574 } | |
575 | |
576 // FIXME: Do we need to mess with URLs here? | |
577 | |
578 return fragment.release(); | |
579 } | |
580 | |
581 static inline void removeElementPreservingChildren(PassRefPtrWillBeRawPtr<Docume
ntFragment> fragment, HTMLElement* element) | |
582 { | |
583 RefPtrWillBeRawPtr<Node> nextChild = nullptr; | |
584 for (RefPtrWillBeRawPtr<Node> child = element->firstChild(); child; child =
nextChild) { | |
585 nextChild = child->nextSibling(); | |
586 element->removeChild(child.get()); | |
587 fragment->insertBefore(child, element); | |
588 } | |
589 fragment->removeChild(element); | |
590 } | |
591 | |
592 static inline bool isSupportedContainer(Element* element) | |
593 { | |
594 ASSERT(element); | |
595 if (!element->isHTMLElement()) | |
596 return true; | |
597 | |
598 HTMLElement& htmlElement = toHTMLElement(*element); | |
599 if (htmlElement.hasTagName(colTag) || htmlElement.hasTagName(colgroupTag) ||
htmlElement.hasTagName(framesetTag) | |
600 || htmlElement.hasTagName(headTag) || htmlElement.hasTagName(styleTag) |
| htmlElement.hasTagName(titleTag)) { | |
601 return false; | |
602 } | |
603 return !htmlElement.ieForbidsInsertHTML(); | |
604 } | |
605 | |
606 PassRefPtrWillBeRawPtr<DocumentFragment> createContextualFragment(const String&
markup, Element* element, ParserContentPolicy parserContentPolicy, ExceptionStat
e& exceptionState) | |
607 { | |
608 ASSERT(element); | |
609 if (!isSupportedContainer(element)) { | |
610 exceptionState.throwDOMException(NotSupportedError, "The range's contain
er is '" + element->localName() + "', which is not supported."); | |
611 return nullptr; | |
612 } | |
613 | |
614 RefPtrWillBeRawPtr<DocumentFragment> fragment = createFragmentForInnerOuterH
TML(markup, element, parserContentPolicy, "createContextualFragment", exceptionS
tate); | |
615 if (!fragment) | |
616 return nullptr; | |
617 | |
618 // We need to pop <html> and <body> elements and remove <head> to | |
619 // accommodate folks passing complete HTML documents to make the | |
620 // child of an element. | |
621 | |
622 RefPtrWillBeRawPtr<Node> nextNode = nullptr; | |
623 for (RefPtrWillBeRawPtr<Node> node = fragment->firstChild(); node; node = ne
xtNode) { | |
624 nextNode = node->nextSibling(); | |
625 if (isHTMLHtmlElement(*node) || isHTMLHeadElement(*node) || isHTMLBodyEl
ement(*node)) { | |
626 HTMLElement* element = toHTMLElement(node); | |
627 if (Node* firstChild = element->firstChild()) | |
628 nextNode = firstChild; | |
629 removeElementPreservingChildren(fragment, element); | |
630 } | |
631 } | |
632 return fragment.release(); | |
633 } | |
634 | |
635 void replaceChildrenWithFragment(ContainerNode* container, PassRefPtrWillBeRawPt
r<DocumentFragment> fragment, ExceptionState& exceptionState) | |
636 { | |
637 ASSERT(container); | |
638 RefPtrWillBeRawPtr<ContainerNode> containerNode(container); | |
639 | |
640 ChildListMutationScope mutation(*containerNode); | |
641 | |
642 if (!fragment->firstChild()) { | |
643 containerNode->removeChildren(); | |
644 return; | |
645 } | |
646 | |
647 // FIXME: This is wrong if containerNode->firstChild() has more than one ref
! | |
648 if (containerNode->hasOneTextChild() && fragment->hasOneTextChild()) { | |
649 toText(containerNode->firstChild())->setData(toText(fragment->firstChild
())->data()); | |
650 return; | |
651 } | |
652 | |
653 // FIXME: No need to replace the child it is a text node and its contents ar
e already == text. | |
654 if (containerNode->hasOneChild()) { | |
655 containerNode->replaceChild(fragment, containerNode->firstChild(), excep
tionState); | |
656 return; | |
657 } | |
658 | |
659 containerNode->removeChildren(); | |
660 containerNode->appendChild(fragment, exceptionState); | |
661 } | |
662 | |
663 void replaceChildrenWithText(ContainerNode* container, const String& text, Excep
tionState& exceptionState) | |
664 { | |
665 ASSERT(container); | |
666 RefPtrWillBeRawPtr<ContainerNode> containerNode(container); | |
667 | |
668 ChildListMutationScope mutation(*containerNode); | |
669 | |
670 // FIXME: This is wrong if containerNode->firstChild() has more than one ref
! Example: | |
671 // <div>foo</div> | |
672 // <script> | |
673 // var oldText = div.firstChild; | |
674 // console.log(oldText.data); // foo | |
675 // div.innerText = "bar"; | |
676 // console.log(oldText.data); // bar!?! | |
677 // </script> | |
678 // I believe this is an intentional benchmark cheat from years ago. | |
679 // We should re-visit if we actually want this still. | |
680 if (containerNode->hasOneTextChild()) { | |
681 toText(containerNode->firstChild())->setData(text); | |
682 return; | |
683 } | |
684 | |
685 // NOTE: This method currently always creates a text node, even if that text
node will be empty. | |
686 RefPtrWillBeRawPtr<Text> textNode = Text::create(containerNode->document(),
text); | |
687 | |
688 // FIXME: No need to replace the child it is a text node and its contents ar
e already == text. | |
689 if (containerNode->hasOneChild()) { | |
690 containerNode->replaceChild(textNode.release(), containerNode->firstChil
d(), exceptionState); | |
691 return; | |
692 } | |
693 | |
694 containerNode->removeChildren(); | |
695 containerNode->appendChild(textNode.release(), exceptionState); | |
696 } | |
697 | |
698 void mergeWithNextTextNode(Text* textNode, ExceptionState& exceptionState) | |
699 { | |
700 ASSERT(textNode); | |
701 Node* next = textNode->nextSibling(); | |
702 if (!next || !next->isTextNode()) | |
703 return; | |
704 | |
705 RefPtrWillBeRawPtr<Text> textNext = toText(next); | |
706 textNode->appendData(textNext->data()); | |
707 if (textNext->parentNode()) // Might have been removed by mutation event. | |
708 textNext->remove(exceptionState); | |
709 } | |
710 | |
711 template class CORE_TEMPLATE_EXPORT CreateMarkupAlgorithm<EditingStrategy>; | |
712 template class CORE_TEMPLATE_EXPORT CreateMarkupAlgorithm<EditingInComposedTreeS
trategy>; | |
713 | |
714 } | |
OLD | NEW |