Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(832)

Side by Side Diff: Source/core/editing/StyledMarkupSerializer.cpp

Issue 1295073002: Move serializer related files in core/editing/ related files into core/editing/serializers/ (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: 2015-08-17T15:24:31 Rebase for Source/web/WebPageSerializerImpl.cpp Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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/StyledMarkupSerializer.h"
31
32 #include "core/css/StylePropertySet.h"
33 #include "core/dom/Document.h"
34 #include "core/dom/Element.h"
35 #include "core/dom/Text.h"
36 #include "core/dom/shadow/ElementShadow.h"
37 #include "core/editing/EditingStyle.h"
38 #include "core/editing/EditingUtilities.h"
39 #include "core/editing/Serialization.h"
40 #include "core/editing/VisibleSelection.h"
41 #include "core/editing/VisibleUnits.h"
42 #include "core/editing/iterators/TextIterator.h"
43 #include "core/html/HTMLBodyElement.h"
44 #include "core/html/HTMLElement.h"
45 #include "wtf/text/StringBuilder.h"
46
47 namespace blink {
48
49 namespace {
50
51 template<typename Strategy>
52 TextOffset toTextOffset(const PositionAlgorithm<Strategy>& position)
53 {
54 if (position.isNull())
55 return TextOffset();
56
57 if (!position.computeContainerNode()->isTextNode())
58 return TextOffset();
59
60 return TextOffset(toText(position.computeContainerNode()), position.offsetIn ContainerNode());
61 }
62
63 template<typename EditingStrategy>
64 static bool handleSelectionBoundary(const Node&);
65
66 template<>
67 bool handleSelectionBoundary<EditingStrategy>(const Node&)
68 {
69 return false;
70 }
71
72 template<>
73 bool handleSelectionBoundary<EditingInComposedTreeStrategy>(const Node& node)
74 {
75 if (!node.isElementNode())
76 return false;
77 ElementShadow* shadow = toElement(node).shadow();
78 if (!shadow)
79 return false;
80 return shadow->youngestShadowRoot()->type() == ShadowRootType::UserAgent;
81 }
82
83 } // namespace
84
85 using namespace HTMLNames;
86
87 template<typename Strategy>
88 class StyledMarkupTraverser {
89 WTF_MAKE_NONCOPYABLE(StyledMarkupTraverser);
90 STACK_ALLOCATED();
91 public:
92 StyledMarkupTraverser();
93 StyledMarkupTraverser(StyledMarkupAccumulator*, Node*);
94
95 Node* traverse(Node*, Node*);
96 void wrapWithNode(ContainerNode&, PassRefPtrWillBeRawPtr<EditingStyle>);
97 RefPtrWillBeRawPtr<EditingStyle> createInlineStyleIfNeeded(Node&);
98
99 private:
100 bool shouldAnnotate() const;
101 bool convertBlocksToInlines() const;
102 void appendStartMarkup(Node&);
103 void appendEndMarkup(Node&);
104 RefPtrWillBeRawPtr<EditingStyle> createInlineStyle(Element&);
105 bool needsInlineStyle(const Element&);
106 bool shouldApplyWrappingStyle(const Node&) const;
107
108 StyledMarkupAccumulator* m_accumulator;
109 RefPtrWillBeMember<Node> m_lastClosed;
110 RefPtrWillBeMember<EditingStyle> m_wrappingStyle;
111 };
112
113 template<typename Strategy>
114 bool StyledMarkupTraverser<Strategy>::shouldAnnotate() const
115 {
116 return m_accumulator->shouldAnnotate();
117 }
118
119 template<typename Strategy>
120 bool StyledMarkupTraverser<Strategy>::convertBlocksToInlines() const
121 {
122 return m_accumulator->convertBlocksToInlines();
123 }
124
125 template<typename Strategy>
126 StyledMarkupSerializer<Strategy>::StyledMarkupSerializer(EAbsoluteURLs shouldRes olveURLs, EAnnotateForInterchange shouldAnnotate, const PositionAlgorithm<Strate gy>& start, const PositionAlgorithm<Strategy>& end, Node* highestNodeToBeSeriali zed, ConvertBlocksToInlines convertBlocksToInlines)
127 : m_start(start)
128 , m_end(end)
129 , m_shouldResolveURLs(shouldResolveURLs)
130 , m_shouldAnnotate(shouldAnnotate)
131 , m_highestNodeToBeSerialized(highestNodeToBeSerialized)
132 , m_convertBlocksToInlines(convertBlocksToInlines)
133 , m_lastClosed(highestNodeToBeSerialized)
134 {
135 }
136
137 static bool needInterchangeNewlineAfter(const VisiblePosition& v)
138 {
139 VisiblePosition next = v.next();
140 Node* upstreamNode = next.deepEquivalent().upstream().anchorNode();
141 Node* downstreamNode = v.deepEquivalent().downstream().anchorNode();
142 // Add an interchange newline if a paragraph break is selected and a br won' t already be added to the markup to represent it.
143 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(isHTMLBRElement( *upstreamNode) && upstreamNode == downstreamNode);
144 }
145
146 static bool needInterchangeNewlineAt(const VisiblePosition& v)
147 {
148 // FIXME: |v.previous()| works on a DOM tree. We need to fix this to work on
149 // a composed tree.
150 return needInterchangeNewlineAfter(v.previous());
151 }
152
153 template<typename Strategy>
154 static bool areSameRanges(Node* node, const PositionAlgorithm<Strategy>& startPo sition, const PositionAlgorithm<Strategy>& endPosition)
155 {
156 ASSERT(node);
157 const EphemeralRange range = VisibleSelection::selectionFromContentsOfNode(n ode).toNormalizedEphemeralRange();
158 return toPositionInDOMTree(startPosition) == range.startPosition() && toPosi tionInDOMTree(endPosition) == range.endPosition();
159 }
160
161 static PassRefPtrWillBeRawPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(c onst HTMLElement* element)
162 {
163 RefPtrWillBeRawPtr<EditingStyle> style = EditingStyle::create(element->inlin eStyle());
164 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work t o untangle
165 // the non-const-ness of styleFromMatchedRulesForElement.
166 style->mergeStyleFromRules(const_cast<HTMLElement*>(element));
167 return style.release();
168 }
169
170 template<typename Strategy>
171 String StyledMarkupSerializer<Strategy>::createMarkup()
172 {
173 StyledMarkupAccumulator markupAccumulator(m_shouldResolveURLs, toTextOffset( m_start.parentAnchoredEquivalent()), toTextOffset(m_end.parentAnchoredEquivalent ()), m_start.document(), m_shouldAnnotate, m_convertBlocksToInlines);
174
175 Node* pastEnd = m_end.nodeAsRangePastLastNode();
176
177 Node* firstNode = m_start.nodeAsRangeFirstNode();
178 VisiblePosition visibleStart(m_start);
179 VisiblePosition visibleEnd(m_end);
180 if (shouldAnnotate() && needInterchangeNewlineAfter(visibleStart)) {
181 markupAccumulator.appendInterchangeNewline();
182 if (visibleStart.deepEquivalent() == visibleEnd.previous().deepEquivalen t())
183 return markupAccumulator.takeResults();
184
185 firstNode = visibleStart.next().deepEquivalent().anchorNode();
186
187 if (pastEnd && PositionAlgorithm<Strategy>::beforeNode(firstNode).compar eTo(PositionAlgorithm<Strategy>::beforeNode(pastEnd)) >= 0) {
188 // This condition hits in editing/pasteboard/copy-display-none.html.
189 return markupAccumulator.takeResults();
190 }
191 }
192
193 if (!m_lastClosed)
194 m_lastClosed = StyledMarkupTraverser<Strategy>().traverse(firstNode, pas tEnd);
195 StyledMarkupTraverser<Strategy> traverser(&markupAccumulator, m_lastClosed);
196 Node* lastClosed = traverser.traverse(firstNode, pastEnd);
197
198 if (m_highestNodeToBeSerialized && lastClosed) {
199 // TODO(hajimehoshi): This is calculated at createMarkupInternal too.
200 Node* commonAncestor = Strategy::commonAncestor(*m_start.computeContaine rNode(), *m_end.computeContainerNode());
201 ASSERT(commonAncestor);
202 HTMLBodyElement* body = toHTMLBodyElement(enclosingElementWithTag(firstP ositionInNode(commonAncestor), bodyTag));
203 HTMLBodyElement* fullySelectedRoot = nullptr;
204 // FIXME: Do this for all fully selected blocks, not just the body.
205 if (body && areSameRanges(body, m_start, m_end))
206 fullySelectedRoot = body;
207
208 // Also include all of the ancestors of lastClosed up to this special an cestor.
209 // FIXME: What is ancestor?
210 for (ContainerNode* ancestor = Strategy::parent(*lastClosed); ancestor; ancestor = Strategy::parent(*ancestor)) {
211 if (ancestor == fullySelectedRoot && !markupAccumulator.convertBlock sToInlines()) {
212 RefPtrWillBeRawPtr<EditingStyle> fullySelectedRootStyle = styleF romMatchedRulesAndInlineDecl(fullySelectedRoot);
213
214 // Bring the background attribute over, but not as an attribute because a background attribute on a div
215 // appears to have no effect.
216 if ((!fullySelectedRootStyle || !fullySelectedRootStyle->style() || !fullySelectedRootStyle->style()->getPropertyCSSValue(CSSPropertyBackgroundI mage))
217 && fullySelectedRoot->hasAttribute(backgroundAttr))
218 fullySelectedRootStyle->style()->setProperty(CSSPropertyBack groundImage, "url('" + fullySelectedRoot->getAttribute(backgroundAttr) + "')");
219
220 if (fullySelectedRootStyle->style()) {
221 // Reset the CSS properties to avoid an assertion error in a ddStyleMarkup().
222 // This assertion is caused at least when we select all text of a <body> element whose
223 // 'text-decoration' property is "inherit", and copy it.
224 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->st yle(), CSSPropertyTextDecoration))
225 fullySelectedRootStyle->style()->setProperty(CSSProperty TextDecoration, CSSValueNone);
226 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->st yle(), CSSPropertyWebkitTextDecorationsInEffect))
227 fullySelectedRootStyle->style()->setProperty(CSSProperty WebkitTextDecorationsInEffect, CSSValueNone);
228 markupAccumulator.wrapWithStyleNode(fullySelectedRootStyle-> style());
229 }
230 } else {
231 RefPtrWillBeRawPtr<EditingStyle> style = traverser.createInlineS tyleIfNeeded(*ancestor);
232 // Since this node and all the other ancestors are not in the se lection we want
233 // styles that affect the exterior of the node not to be not inc luded.
234 // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
235 // only the ones that affect it and the nodes within it.
236 if (style && style->style())
237 style->style()->removeProperty(CSSPropertyFloat);
238 traverser.wrapWithNode(*ancestor, style);
239 }
240
241 if (ancestor == m_highestNodeToBeSerialized)
242 break;
243 }
244 }
245
246 // FIXME: The interchange newline should be placed in the block that it's in , not after all of the content, unconditionally.
247 if (shouldAnnotate() && needInterchangeNewlineAt(visibleEnd))
248 markupAccumulator.appendInterchangeNewline();
249
250 return markupAccumulator.takeResults();
251 }
252
253 template<typename Strategy>
254 StyledMarkupTraverser<Strategy>::StyledMarkupTraverser()
255 : StyledMarkupTraverser(nullptr, nullptr)
256 {
257 }
258
259 template<typename Strategy>
260 StyledMarkupTraverser<Strategy>::StyledMarkupTraverser(StyledMarkupAccumulator* accumulator, Node* lastClosed)
261 : m_accumulator(accumulator)
262 , m_lastClosed(lastClosed)
263 , m_wrappingStyle(nullptr)
264 {
265 if (!m_accumulator) {
266 ASSERT(!m_lastClosed);
267 return;
268 }
269 if (!m_lastClosed)
270 return;
271 ContainerNode* parent = Strategy::parent(*m_lastClosed);
272 if (!parent)
273 return;
274 if (shouldAnnotate()) {
275 m_wrappingStyle = EditingStyle::wrappingStyleForAnnotatedSerialization(p arent);
276 return;
277 }
278 m_wrappingStyle = EditingStyle::wrappingStyleForSerialization(parent);
279 }
280
281 template<typename Strategy>
282 Node* StyledMarkupTraverser<Strategy>::traverse(Node* startNode, Node* pastEnd)
283 {
284 WillBeHeapVector<RawPtrWillBeMember<ContainerNode>> ancestorsToClose;
285 Node* next;
286 Node* lastClosed = nullptr;
287 for (Node* n = startNode; n && n != pastEnd; n = next) {
288 // If |n| is a selection boundary such as <input>, traverse the child
289 // nodes in the DOM tree instead of the composed tree.
290 if (handleSelectionBoundary<Strategy>(*n)) {
291 lastClosed = StyledMarkupTraverser<EditingStrategy>(m_accumulator, m _lastClosed.get()).traverse(n, EditingStrategy::nextSkippingChildren(*n));
292 next = EditingInComposedTreeStrategy::nextSkippingChildren(*n);
293 } else {
294 next = Strategy::next(*n);
295 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) {
296 // Don't write out empty block containers that aren't fully sele cted.
297 continue;
298 }
299
300 if (!n->layoutObject() && !enclosingElementWithTag(firstPositionInOr BeforeNode(n), selectTag)) {
301 next = Strategy::nextSkippingChildren(*n);
302 // Don't skip over pastEnd.
303 if (pastEnd && Strategy::isDescendantOf(*pastEnd, *n))
304 next = pastEnd;
305 } else {
306 // Add the node to the markup if we're not skipping the descenda nts
307 appendStartMarkup(*n);
308
309 // If node has no children, close the tag now.
310 if (Strategy::hasChildren(*n)) {
311 ancestorsToClose.append(toContainerNode(n));
312 continue;
313 }
314 appendEndMarkup(*n);
315 lastClosed = n;
316 }
317 }
318
319 // 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.
320 // FIXME: What happens if we just inserted open tag and reached the end?
321 if (Strategy::nextSibling(*n) && next != pastEnd)
322 continue;
323
324 // Close up the ancestors.
325 while (!ancestorsToClose.isEmpty()) {
326 ContainerNode* ancestor = ancestorsToClose.last();
327 ASSERT(ancestor);
328 if (next && next != pastEnd && Strategy::isDescendantOf(*next, *ance stor))
329 break;
330 // Not at the end of the range, close ancestors up to sibling of nex t node.
331 appendEndMarkup(*ancestor);
332 lastClosed = ancestor;
333 ancestorsToClose.removeLast();
334 }
335
336 // Surround the currently accumulated markup with markup for ancestors w e never opened as we leave the subtree(s) rooted at those ancestors.
337 ContainerNode* nextParent = next ? Strategy::parent(*next) : nullptr;
338 if (next == pastEnd || n == nextParent)
339 continue;
340
341 ASSERT(n);
342 Node* lastAncestorClosedOrSelf = (lastClosed && Strategy::isDescendantOf (*n, *lastClosed)) ? lastClosed : n;
343 for (ContainerNode* parent = Strategy::parent(*lastAncestorClosedOrSelf) ; parent && parent != nextParent; parent = Strategy::parent(*parent)) {
344 // All ancestors that aren't in the ancestorsToClose list should eit her be a) unrendered:
345 if (!parent->layoutObject())
346 continue;
347 // or b) ancestors that we never encountered during a pre-order trav ersal starting at startNode:
348 ASSERT(startNode);
349 ASSERT(Strategy::isDescendantOf(*startNode, *parent));
350 RefPtrWillBeRawPtr<EditingStyle> style = createInlineStyleIfNeeded(* parent);
351 wrapWithNode(*parent, style);
352 lastClosed = parent;
353 }
354 }
355
356 return lastClosed;
357 }
358
359 template<typename Strategy>
360 bool StyledMarkupTraverser<Strategy>::needsInlineStyle(const Element& element)
361 {
362 if (!element.isHTMLElement())
363 return false;
364 if (shouldAnnotate())
365 return true;
366 return convertBlocksToInlines() && isBlock(&element);
367 }
368
369 template<typename Strategy>
370 void StyledMarkupTraverser<Strategy>::wrapWithNode(ContainerNode& node, PassRefP trWillBeRawPtr<EditingStyle> style)
371 {
372 if (!m_accumulator)
373 return;
374 StringBuilder markup;
375 if (node.isDocumentNode()) {
376 MarkupFormatter::appendXMLDeclaration(markup, toDocument(node));
377 m_accumulator->pushMarkup(markup.toString());
378 return;
379 }
380 if (!node.isElementNode())
381 return;
382 Element& element = toElement(node);
383 if (shouldApplyWrappingStyle(element) || needsInlineStyle(element))
384 m_accumulator->appendElementWithInlineStyle(markup, element, style);
385 else
386 m_accumulator->appendElement(markup, element);
387 m_accumulator->pushMarkup(markup.toString());
388 m_accumulator->appendEndTag(toElement(node));
389 }
390
391 template<typename Strategy>
392 RefPtrWillBeRawPtr<EditingStyle> StyledMarkupTraverser<Strategy>::createInlineSt yleIfNeeded(Node& node)
393 {
394 if (!m_accumulator)
395 return nullptr;
396 if (!node.isElementNode())
397 return nullptr;
398 RefPtrWillBeRawPtr<EditingStyle> inlineStyle = createInlineStyle(toElement(n ode));
399 if (convertBlocksToInlines() && isBlock(&node))
400 inlineStyle->forceInline();
401 return inlineStyle;
402 }
403
404 template<typename Strategy>
405 void StyledMarkupTraverser<Strategy>::appendStartMarkup(Node& node)
406 {
407 if (!m_accumulator)
408 return;
409 switch (node.nodeType()) {
410 case Node::TEXT_NODE: {
411 Text& text = toText(node);
412 if (text.parentElement() && isHTMLTextAreaElement(text.parentElement())) {
413 m_accumulator->appendText(text);
414 break;
415 }
416 RefPtrWillBeRawPtr<EditingStyle> inlineStyle = nullptr;
417 if (shouldApplyWrappingStyle(text)) {
418 inlineStyle = m_wrappingStyle->copy();
419 // FIXME: <rdar://problem/5371536> Style rules that match pasted con tent can change it's appearance
420 // Make sure spans are inline style in paste side e.g. span { displa y: block }.
421 inlineStyle->forceInline();
422 // FIXME: Should this be included in forceInline?
423 inlineStyle->style()->setProperty(CSSPropertyFloat, CSSValueNone);
424 }
425 m_accumulator->appendTextWithInlineStyle(text, inlineStyle);
426 break;
427 }
428 case Node::ELEMENT_NODE: {
429 Element& element = toElement(node);
430 if ((element.isHTMLElement() && shouldAnnotate()) || shouldApplyWrapping Style(element)) {
431 RefPtrWillBeRawPtr<EditingStyle> inlineStyle = createInlineStyle(ele ment);
432 m_accumulator->appendElementWithInlineStyle(element, inlineStyle);
433 break;
434 }
435 m_accumulator->appendElement(element);
436 break;
437 }
438 default:
439 m_accumulator->appendStartMarkup(node);
440 break;
441 }
442 }
443
444 template<typename Strategy>
445 void StyledMarkupTraverser<Strategy>::appendEndMarkup(Node& node)
446 {
447 if (!m_accumulator || !node.isElementNode())
448 return;
449 m_accumulator->appendEndTag(toElement(node));
450 }
451
452 template<typename Strategy>
453 bool StyledMarkupTraverser<Strategy>::shouldApplyWrappingStyle(const Node& node) const
454 {
455 return m_lastClosed && Strategy::parent(*m_lastClosed) == Strategy::parent(n ode)
456 && m_wrappingStyle && m_wrappingStyle->style();
457 }
458
459 template<typename Strategy>
460 RefPtrWillBeRawPtr<EditingStyle> StyledMarkupTraverser<Strategy>::createInlineSt yle(Element& element)
461 {
462 RefPtrWillBeRawPtr<EditingStyle> inlineStyle = nullptr;
463
464 if (shouldApplyWrappingStyle(element)) {
465 inlineStyle = m_wrappingStyle->copy();
466 inlineStyle->removePropertiesInElementDefaultStyle(&element);
467 inlineStyle->removeStyleConflictingWithStyleOfElement(&element);
468 } else {
469 inlineStyle = EditingStyle::create();
470 }
471
472 if (element.isStyledElement() && element.inlineStyle())
473 inlineStyle->overrideWithStyle(element.inlineStyle());
474
475 if (element.isHTMLElement() && shouldAnnotate())
476 inlineStyle->mergeStyleFromRulesForSerialization(&toHTMLElement(element) );
477
478 return inlineStyle;
479 }
480
481 template class StyledMarkupSerializer<EditingStrategy>;
482 template class StyledMarkupSerializer<EditingInComposedTreeStrategy>;
483
484 } // namespace blink
OLDNEW
« no previous file with comments | « Source/core/editing/StyledMarkupSerializer.h ('k') | Source/core/editing/StyledMarkupSerializerTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698