1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * nsIContentSerializer implementation that can be used with an
9  * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML
10  * string that could be parsed into more or less the original DOM.
11  */
12 
13 #include "nsXHTMLContentSerializer.h"
14 
15 #include "mozilla/dom/Element.h"
16 #include "nsIContent.h"
17 #include "mozilla/dom/Document.h"
18 #include "nsElementTable.h"
19 #include "nsNameSpaceManager.h"
20 #include "nsString.h"
21 #include "nsUnicharUtils.h"
22 #include "nsIDocumentEncoder.h"
23 #include "nsGkAtoms.h"
24 #include "nsIURI.h"
25 #include "nsNetUtil.h"
26 #include "nsEscape.h"
27 #include "nsCRT.h"
28 #include "nsContentUtils.h"
29 #include "nsIScriptElement.h"
30 #include "nsStubMutationObserver.h"
31 #include "nsAttrName.h"
32 #include "nsComputedDOMStyle.h"
33 
34 using namespace mozilla;
35 using namespace mozilla::dom;
36 
37 static const int32_t kLongLineLen = 128;
38 
39 #define kXMLNS "xmlns"
40 
NS_NewXHTMLContentSerializer(nsIContentSerializer ** aSerializer)41 nsresult NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer) {
42   RefPtr<nsXHTMLContentSerializer> it = new nsXHTMLContentSerializer();
43   it.forget(aSerializer);
44   return NS_OK;
45 }
46 
nsXHTMLContentSerializer()47 nsXHTMLContentSerializer::nsXHTMLContentSerializer()
48     : mIsHTMLSerializer(false),
49       mIsCopying(false),
50       mDisableEntityEncoding(0),
51       mRewriteEncodingDeclaration(false),
52       mIsFirstChildOfOL(false) {}
53 
~nsXHTMLContentSerializer()54 nsXHTMLContentSerializer::~nsXHTMLContentSerializer() {
55   NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty");
56 }
57 
58 NS_IMETHODIMP
Init(uint32_t aFlags,uint32_t aWrapColumn,const Encoding * aEncoding,bool aIsCopying,bool aRewriteEncodingDeclaration,bool * aNeedsPreformatScanning,nsAString & aOutput)59 nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
60                                const Encoding* aEncoding, bool aIsCopying,
61                                bool aRewriteEncodingDeclaration,
62                                bool* aNeedsPreformatScanning,
63                                nsAString& aOutput) {
64   // The previous version of the HTML serializer did implicit wrapping
65   // when there is no flags, so we keep wrapping in order to keep
66   // compatibility with the existing calling code
67   // XXXLJ perhaps should we remove this default settings later ?
68   if (aFlags & nsIDocumentEncoder::OutputFormatted) {
69     aFlags = aFlags | nsIDocumentEncoder::OutputWrap;
70   }
71 
72   nsresult rv;
73   rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aEncoding, aIsCopying,
74                                     aRewriteEncodingDeclaration,
75                                     aNeedsPreformatScanning, aOutput);
76   NS_ENSURE_SUCCESS(rv, rv);
77 
78   mRewriteEncodingDeclaration = aRewriteEncodingDeclaration;
79   mIsCopying = aIsCopying;
80   mIsFirstChildOfOL = false;
81   mInBody = 0;
82   mDisableEntityEncoding = 0;
83   mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly);
84 
85   return NS_OK;
86 }
87 
88 // See if the string has any lines longer than longLineLen:
89 // if so, we presume formatting is wonky (e.g. the node has been edited)
90 // and we'd better rewrap the whole text node.
HasLongLines(const nsString & text,int32_t & aLastNewlineOffset)91 bool nsXHTMLContentSerializer::HasLongLines(const nsString& text,
92                                             int32_t& aLastNewlineOffset) {
93   uint32_t start = 0;
94   uint32_t theLen = text.Length();
95   bool rv = false;
96   aLastNewlineOffset = kNotFound;
97   for (start = 0; start < theLen;) {
98     int32_t eol = text.FindChar('\n', start);
99     if (eol < 0) {
100       eol = text.Length();
101     } else {
102       aLastNewlineOffset = eol;
103     }
104     if (int32_t(eol - start) > kLongLineLen) rv = true;
105     start = eol + 1;
106   }
107   return rv;
108 }
109 
110 NS_IMETHODIMP
AppendText(nsIContent * aText,int32_t aStartOffset,int32_t aEndOffset)111 nsXHTMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
112                                      int32_t aEndOffset) {
113   NS_ENSURE_ARG(aText);
114   NS_ENSURE_STATE(mOutput);
115 
116   nsAutoString data;
117   nsresult rv;
118 
119   rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
120   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
121 
122   if (mDoRaw || PreLevel() > 0) {
123     NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
124                    NS_ERROR_OUT_OF_MEMORY);
125   } else if (mDoFormat) {
126     NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, *mOutput),
127                    NS_ERROR_OUT_OF_MEMORY);
128   } else if (mDoWrap) {
129     NS_ENSURE_TRUE(AppendToStringWrapped(data, *mOutput),
130                    NS_ERROR_OUT_OF_MEMORY);
131   } else {
132     int32_t lastNewlineOffset = kNotFound;
133     if (HasLongLines(data, lastNewlineOffset)) {
134       // We have long lines, rewrap
135       mDoWrap = true;
136       bool result = AppendToStringWrapped(data, *mOutput);
137       mDoWrap = false;
138       NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
139     } else {
140       NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
141                      NS_ERROR_OUT_OF_MEMORY);
142     }
143   }
144 
145   return NS_OK;
146 }
147 
SerializeAttributes(Element * aElement,Element * aOriginalElement,nsAString & aTagPrefix,const nsAString & aTagNamespaceURI,nsAtom * aTagName,nsAString & aStr,uint32_t aSkipAttr,bool aAddNSAttr)148 bool nsXHTMLContentSerializer::SerializeAttributes(
149     Element* aElement, Element* aOriginalElement, nsAString& aTagPrefix,
150     const nsAString& aTagNamespaceURI, nsAtom* aTagName, nsAString& aStr,
151     uint32_t aSkipAttr, bool aAddNSAttr) {
152   nsresult rv;
153   uint32_t index, count;
154   nsAutoString prefixStr, uriStr, valueStr;
155   nsAutoString xmlnsStr;
156   xmlnsStr.AssignLiteral(kXMLNS);
157 
158   int32_t contentNamespaceID = aElement->GetNameSpaceID();
159 
160   MaybeSerializeIsValue(aElement, aStr);
161 
162   // this method is not called by nsHTMLContentSerializer
163   // so we don't have to check HTML element, just XHTML
164 
165   if (mIsCopying && kNameSpaceID_XHTML == contentNamespaceID) {
166     // Need to keep track of OL and LI elements in order to get ordinal number
167     // for the LI.
168     if (aTagName == nsGkAtoms::ol) {
169       // We are copying and current node is an OL;
170       // Store its start attribute value in olState->startVal.
171       nsAutoString start;
172       int32_t startAttrVal = 0;
173       aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::start, start);
174       if (!start.IsEmpty()) {
175         nsresult rv = NS_OK;
176         startAttrVal = start.ToInteger(&rv);
177         // If OL has "start" attribute, first LI element has to start with that
178         // value Therefore subtracting 1 as all the LI elements are incrementing
179         // it before using it; In failure of ToInteger(), default StartAttrValue
180         // to 0.
181         if (NS_SUCCEEDED(rv))
182           --startAttrVal;
183         else
184           startAttrVal = 0;
185       }
186       olState state(startAttrVal, true);
187       mOLStateStack.AppendElement(state);
188     } else if (aTagName == nsGkAtoms::li) {
189       mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement);
190       if (mIsFirstChildOfOL) {
191         // If OL is parent of this LI, serialize attributes in different manner.
192         NS_ENSURE_TRUE(SerializeLIValueAttribute(aElement, aStr), false);
193       }
194     }
195   }
196 
197   // If we had to add a new namespace declaration, serialize
198   // and push it on the namespace stack
199   if (aAddNSAttr) {
200     if (aTagPrefix.IsEmpty()) {
201       // Serialize default namespace decl
202       NS_ENSURE_TRUE(
203           SerializeAttr(u""_ns, xmlnsStr, aTagNamespaceURI, aStr, true), false);
204     } else {
205       // Serialize namespace decl
206       NS_ENSURE_TRUE(
207           SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true),
208           false);
209     }
210     PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
211   }
212 
213   count = aElement->GetAttrCount();
214 
215   // Now serialize each of the attributes
216   // XXX Unfortunately we need a namespace manager to get
217   // attribute URIs.
218   for (index = 0; index < count; index++) {
219     if (aSkipAttr == index) {
220       continue;
221     }
222 
223     dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(index);
224     const nsAttrName* name = info.mName;
225 
226     int32_t namespaceID = name->NamespaceID();
227     nsAtom* attrName = name->LocalName();
228     nsAtom* attrPrefix = name->GetPrefix();
229 
230     // Filter out any attribute starting with [-|_]moz
231     nsDependentAtomString attrNameStr(attrName);
232     if (StringBeginsWith(attrNameStr, u"_moz"_ns) ||
233         StringBeginsWith(attrNameStr, u"-moz"_ns)) {
234       continue;
235     }
236 
237     if (attrPrefix) {
238       attrPrefix->ToString(prefixStr);
239     } else {
240       prefixStr.Truncate();
241     }
242 
243     bool addNSAttr = false;
244     if (kNameSpaceID_XMLNS != namespaceID) {
245       nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
246       addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
247     }
248 
249     info.mValue->ToString(valueStr);
250 
251     nsDependentAtomString nameStr(attrName);
252     bool isJS = false;
253 
254     if (kNameSpaceID_XHTML == contentNamespaceID) {
255       if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsGkAtoms::li) &&
256           (attrName == nsGkAtoms::value)) {
257         // This is handled separately in SerializeLIValueAttribute()
258         continue;
259       }
260 
261       isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr);
262 
263       if (namespaceID == kNameSpaceID_None &&
264           ((attrName == nsGkAtoms::href) || (attrName == nsGkAtoms::src))) {
265         // Make all links absolute when converting only the selection:
266         if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) {
267           // Would be nice to handle OBJECT tags,
268           // but that gets more complicated since we have to
269           // search the tag list for CODEBASE as well.
270           // For now, just leave them relative.
271           nsIURI* uri = aElement->GetBaseURI();
272           if (uri) {
273             nsAutoString absURI;
274             rv = NS_MakeAbsoluteURI(absURI, valueStr, uri);
275             if (NS_SUCCEEDED(rv)) {
276               valueStr = absURI;
277             }
278           }
279         }
280       }
281 
282       if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta &&
283           attrName == nsGkAtoms::content) {
284         // If we're serializing a <meta http-equiv="content-type">,
285         // use the proper value, rather than what's in the document.
286         nsAutoString header;
287         aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
288         if (header.LowerCaseEqualsLiteral("content-type")) {
289           valueStr =
290               u"text/html; charset="_ns + NS_ConvertASCIItoUTF16(mCharset);
291         }
292       }
293 
294       // Expand shorthand attribute.
295       if (namespaceID == kNameSpaceID_None &&
296           IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) {
297         valueStr = nameStr;
298       }
299     } else {
300       isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr);
301     }
302 
303     NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS),
304                    false);
305 
306     if (addNSAttr) {
307       NS_ASSERTION(!prefixStr.IsEmpty(),
308                    "Namespaced attributes must have a prefix");
309       NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true),
310                      false);
311       PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
312     }
313   }
314 
315   return true;
316 }
317 
AfterElementStart(nsIContent * aContent,nsIContent * aOriginalElement,nsAString & aStr)318 bool nsXHTMLContentSerializer::AfterElementStart(nsIContent* aContent,
319                                                  nsIContent* aOriginalElement,
320                                                  nsAString& aStr) {
321   if (mRewriteEncodingDeclaration && aContent->IsHTMLElement(nsGkAtoms::head)) {
322     // Check if there already are any content-type meta children.
323     // If there are, they will be modified to use the correct charset.
324     // If there aren't, we'll insert one here.
325     bool hasMeta = false;
326     for (nsIContent* child = aContent->GetFirstChild(); child;
327          child = child->GetNextSibling()) {
328       if (child->IsHTMLElement(nsGkAtoms::meta) &&
329           child->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::content)) {
330         nsAutoString header;
331         child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
332                                     header);
333 
334         if (header.LowerCaseEqualsLiteral("content-type")) {
335           hasMeta = true;
336           break;
337         }
338       }
339     }
340 
341     if (!hasMeta) {
342       NS_ENSURE_TRUE(AppendNewLineToString(aStr), false);
343       if (mDoFormat) {
344         NS_ENSURE_TRUE(AppendIndentation(aStr), false);
345       }
346       NS_ENSURE_TRUE(
347           AppendToString(u"<meta http-equiv=\"content-type\""_ns, aStr), false);
348       NS_ENSURE_TRUE(AppendToString(u" content=\"text/html; charset="_ns, aStr),
349                      false);
350       NS_ENSURE_TRUE(AppendToString(NS_ConvertASCIItoUTF16(mCharset), aStr),
351                      false);
352       if (mIsHTMLSerializer) {
353         NS_ENSURE_TRUE(AppendToString(u"\">"_ns, aStr), false);
354       } else {
355         NS_ENSURE_TRUE(AppendToString(u"\" />"_ns, aStr), false);
356       }
357     }
358   }
359 
360   return true;
361 }
362 
AfterElementEnd(nsIContent * aContent,nsAString & aStr)363 void nsXHTMLContentSerializer::AfterElementEnd(nsIContent* aContent,
364                                                nsAString& aStr) {
365   NS_ASSERTION(!mIsHTMLSerializer,
366                "nsHTMLContentSerializer shouldn't call this method !");
367 
368   // this method is not called by nsHTMLContentSerializer
369   // so we don't have to check HTML element, just XHTML
370   if (aContent->IsHTMLElement(nsGkAtoms::body)) {
371     --mInBody;
372   }
373 }
374 
375 NS_IMETHODIMP
AppendDocumentStart(Document * aDocument)376 nsXHTMLContentSerializer::AppendDocumentStart(Document* aDocument) {
377   if (!mBodyOnly) {
378     return nsXMLContentSerializer::AppendDocumentStart(aDocument);
379   }
380 
381   return NS_OK;
382 }
383 
CheckElementStart(Element * aElement,bool & aForceFormat,nsAString & aStr,nsresult & aResult)384 bool nsXHTMLContentSerializer::CheckElementStart(Element* aElement,
385                                                  bool& aForceFormat,
386                                                  nsAString& aStr,
387                                                  nsresult& aResult) {
388   aResult = NS_OK;
389 
390   // The _moz_dirty attribute is emitted by the editor to
391   // indicate that this element should be pretty printed
392   // even if we're not in pretty printing mode
393   aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
394                  aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
395 
396   if (aElement->IsHTMLElement(nsGkAtoms::br) &&
397       (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre) &&
398       PreLevel() > 0) {
399     aResult = AppendNewLineToString(aStr) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
400     return false;
401   }
402 
403   if (aElement->IsHTMLElement(nsGkAtoms::body)) {
404     ++mInBody;
405   }
406 
407   return true;
408 }
409 
CheckElementEnd(Element * aElement,Element * aOriginalElement,bool & aForceFormat,nsAString & aStr)410 bool nsXHTMLContentSerializer::CheckElementEnd(Element* aElement,
411                                                Element* aOriginalElement,
412                                                bool& aForceFormat,
413                                                nsAString& aStr) {
414   NS_ASSERTION(!mIsHTMLSerializer,
415                "nsHTMLContentSerializer shouldn't call this method !");
416 
417   aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
418                  aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
419 
420   if (mIsCopying && aElement->IsHTMLElement(nsGkAtoms::ol)) {
421     NS_ASSERTION((!mOLStateStack.IsEmpty()), "Cannot have an empty OL Stack");
422     /* Though at this point we must always have an state to be deleted as all
423        the OL opening tags are supposed to push an olState object to the stack*/
424     if (!mOLStateStack.IsEmpty()) {
425       mOLStateStack.RemoveLastElement();
426     }
427   }
428 
429   bool dummyFormat;
430   return nsXMLContentSerializer::CheckElementEnd(aElement, aOriginalElement,
431                                                  dummyFormat, aStr);
432 }
433 
AppendAndTranslateEntities(const nsAString & aStr,nsAString & aOutputStr)434 bool nsXHTMLContentSerializer::AppendAndTranslateEntities(
435     const nsAString& aStr, nsAString& aOutputStr) {
436   if (mBodyOnly && !mInBody) {
437     return true;
438   }
439 
440   if (mDisableEntityEncoding) {
441     return aOutputStr.Append(aStr, fallible);
442   }
443 
444   return nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr);
445 }
446 
IsShorthandAttr(const nsAtom * aAttrName,const nsAtom * aElementName)447 bool nsXHTMLContentSerializer::IsShorthandAttr(const nsAtom* aAttrName,
448                                                const nsAtom* aElementName) {
449   // checked
450   if ((aAttrName == nsGkAtoms::checked) && (aElementName == nsGkAtoms::input)) {
451     return true;
452   }
453 
454   // compact
455   if ((aAttrName == nsGkAtoms::compact) &&
456       (aElementName == nsGkAtoms::dir || aElementName == nsGkAtoms::dl ||
457        aElementName == nsGkAtoms::menu || aElementName == nsGkAtoms::ol ||
458        aElementName == nsGkAtoms::ul)) {
459     return true;
460   }
461 
462   // declare
463   if ((aAttrName == nsGkAtoms::declare) &&
464       (aElementName == nsGkAtoms::object)) {
465     return true;
466   }
467 
468   // defer
469   if ((aAttrName == nsGkAtoms::defer) && (aElementName == nsGkAtoms::script)) {
470     return true;
471   }
472 
473   // disabled
474   if ((aAttrName == nsGkAtoms::disabled) &&
475       (aElementName == nsGkAtoms::button || aElementName == nsGkAtoms::input ||
476        aElementName == nsGkAtoms::optgroup ||
477        aElementName == nsGkAtoms::option || aElementName == nsGkAtoms::select ||
478        aElementName == nsGkAtoms::textarea)) {
479     return true;
480   }
481 
482   // ismap
483   if ((aAttrName == nsGkAtoms::ismap) &&
484       (aElementName == nsGkAtoms::img || aElementName == nsGkAtoms::input)) {
485     return true;
486   }
487 
488   // multiple
489   if ((aAttrName == nsGkAtoms::multiple) &&
490       (aElementName == nsGkAtoms::select)) {
491     return true;
492   }
493 
494   // noresize
495   if ((aAttrName == nsGkAtoms::noresize) &&
496       (aElementName == nsGkAtoms::frame)) {
497     return true;
498   }
499 
500   // noshade
501   if ((aAttrName == nsGkAtoms::noshade) && (aElementName == nsGkAtoms::hr)) {
502     return true;
503   }
504 
505   // nowrap
506   if ((aAttrName == nsGkAtoms::nowrap) &&
507       (aElementName == nsGkAtoms::td || aElementName == nsGkAtoms::th)) {
508     return true;
509   }
510 
511   // readonly
512   if ((aAttrName == nsGkAtoms::readonly) &&
513       (aElementName == nsGkAtoms::input ||
514        aElementName == nsGkAtoms::textarea)) {
515     return true;
516   }
517 
518   // selected
519   if ((aAttrName == nsGkAtoms::selected) &&
520       (aElementName == nsGkAtoms::option)) {
521     return true;
522   }
523 
524   // autoplay and controls
525   if ((aElementName == nsGkAtoms::video || aElementName == nsGkAtoms::audio) &&
526       (aAttrName == nsGkAtoms::autoplay || aAttrName == nsGkAtoms::muted ||
527        aAttrName == nsGkAtoms::controls)) {
528     return true;
529   }
530 
531   return false;
532 }
533 
LineBreakBeforeOpen(int32_t aNamespaceID,nsAtom * aName)534 bool nsXHTMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID,
535                                                    nsAtom* aName) {
536   if (aNamespaceID != kNameSpaceID_XHTML) {
537     return mAddSpace;
538   }
539 
540   if (aName == nsGkAtoms::title || aName == nsGkAtoms::meta ||
541       aName == nsGkAtoms::link || aName == nsGkAtoms::style ||
542       aName == nsGkAtoms::select || aName == nsGkAtoms::option ||
543       aName == nsGkAtoms::script || aName == nsGkAtoms::html) {
544     return true;
545   }
546 
547   return nsHTMLElement::IsBlock(nsHTMLTags::CaseSensitiveAtomTagToId(aName));
548 }
549 
LineBreakAfterOpen(int32_t aNamespaceID,nsAtom * aName)550 bool nsXHTMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID,
551                                                   nsAtom* aName) {
552   if (aNamespaceID != kNameSpaceID_XHTML) {
553     return false;
554   }
555 
556   if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) ||
557       (aName == nsGkAtoms::body) || (aName == nsGkAtoms::ul) ||
558       (aName == nsGkAtoms::ol) || (aName == nsGkAtoms::dl) ||
559       (aName == nsGkAtoms::table) || (aName == nsGkAtoms::tbody) ||
560       (aName == nsGkAtoms::tr) || (aName == nsGkAtoms::br) ||
561       (aName == nsGkAtoms::meta) || (aName == nsGkAtoms::link) ||
562       (aName == nsGkAtoms::script) || (aName == nsGkAtoms::select) ||
563       (aName == nsGkAtoms::map) || (aName == nsGkAtoms::area) ||
564       (aName == nsGkAtoms::style)) {
565     return true;
566   }
567 
568   return false;
569 }
570 
LineBreakBeforeClose(int32_t aNamespaceID,nsAtom * aName)571 bool nsXHTMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID,
572                                                     nsAtom* aName) {
573   if (aNamespaceID != kNameSpaceID_XHTML) {
574     return false;
575   }
576 
577   if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) ||
578       (aName == nsGkAtoms::body) || (aName == nsGkAtoms::ul) ||
579       (aName == nsGkAtoms::ol) || (aName == nsGkAtoms::dl) ||
580       (aName == nsGkAtoms::select) || (aName == nsGkAtoms::table) ||
581       (aName == nsGkAtoms::tbody)) {
582     return true;
583   }
584   return false;
585 }
586 
LineBreakAfterClose(int32_t aNamespaceID,nsAtom * aName)587 bool nsXHTMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID,
588                                                    nsAtom* aName) {
589   if (aNamespaceID != kNameSpaceID_XHTML) {
590     return false;
591   }
592 
593   if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) ||
594       (aName == nsGkAtoms::body) || (aName == nsGkAtoms::tr) ||
595       (aName == nsGkAtoms::th) || (aName == nsGkAtoms::td) ||
596       (aName == nsGkAtoms::title) || (aName == nsGkAtoms::dt) ||
597       (aName == nsGkAtoms::dd) || (aName == nsGkAtoms::select) ||
598       (aName == nsGkAtoms::option) || (aName == nsGkAtoms::map)) {
599     return true;
600   }
601 
602   return nsHTMLElement::IsBlock(nsHTMLTags::CaseSensitiveAtomTagToId(aName));
603 }
604 
MaybeEnterInPreContent(nsIContent * aNode)605 void nsXHTMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) {
606   if (!ShouldMaintainPreLevel() || !aNode->IsHTMLElement()) {
607     return;
608   }
609 
610   if (IsElementPreformatted(aNode) ||
611       aNode->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style,
612                                  nsGkAtoms::noscript, nsGkAtoms::noframes)) {
613     PreLevel()++;
614   }
615 }
616 
MaybeLeaveFromPreContent(nsIContent * aNode)617 void nsXHTMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) {
618   if (!ShouldMaintainPreLevel() || !aNode->IsHTMLElement()) {
619     return;
620   }
621 
622   if (IsElementPreformatted(aNode) ||
623       aNode->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style,
624                                  nsGkAtoms::noscript, nsGkAtoms::noframes)) {
625     --PreLevel();
626   }
627 }
628 
IsElementPreformatted(nsIContent * aNode)629 bool nsXHTMLContentSerializer::IsElementPreformatted(nsIContent* aNode) {
630   MOZ_ASSERT(ShouldMaintainPreLevel(),
631              "We should not be calling this needlessly");
632 
633   if (!aNode->IsElement()) {
634     return false;
635   }
636   RefPtr<ComputedStyle> computedStyle =
637       nsComputedDOMStyle::GetComputedStyleNoFlush(aNode->AsElement(), nullptr);
638   if (computedStyle) {
639     const nsStyleText* textStyle = computedStyle->StyleText();
640     return textStyle->WhiteSpaceOrNewlineIsSignificant();
641   }
642   return false;
643 }
644 
SerializeLIValueAttribute(nsIContent * aElement,nsAString & aStr)645 bool nsXHTMLContentSerializer::SerializeLIValueAttribute(nsIContent* aElement,
646                                                          nsAString& aStr) {
647   // We are copying and we are at the "first" LI node of OL in selected range.
648   // It may not be the first LI child of OL but it's first in the selected
649   // range. Note that we get into this condition only once per a OL.
650   bool found = false;
651   nsAutoString valueStr;
652 
653   olState state(0, false);
654 
655   if (!mOLStateStack.IsEmpty()) {
656     state = mOLStateStack[mOLStateStack.Length() - 1];
657     // isFirstListItem should be true only before the serialization of the
658     // first item in the list.
659     state.isFirstListItem = false;
660     mOLStateStack[mOLStateStack.Length() - 1] = state;
661   }
662 
663   int32_t startVal = state.startVal;
664   int32_t offset = 0;
665 
666   // Traverse previous siblings until we find one with "value" attribute.
667   // offset keeps track of how many previous siblings we had to traverse.
668   nsIContent* currNode = aElement;
669   while (currNode && !found) {
670     if (currNode->IsHTMLElement(nsGkAtoms::li)) {
671       currNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
672                                      valueStr);
673       if (valueStr.IsEmpty()) {
674         offset++;
675       } else {
676         found = true;
677         nsresult rv = NS_OK;
678         startVal = valueStr.ToInteger(&rv);
679       }
680     }
681     currNode = currNode->GetPreviousSibling();
682   }
683   // If LI was not having "value", Set the "value" attribute for it.
684   // Note that We are at the first LI in the selected range of OL.
685   if (offset == 0 && found) {
686     // offset = 0 => LI itself has the value attribute and we did not need to
687     // traverse back. Just serialize value attribute like other tags.
688     NS_ENSURE_TRUE(SerializeAttr(u""_ns, u"value"_ns, valueStr, aStr, false),
689                    false);
690   } else if (offset == 1 && !found) {
691     /*(offset = 1 && !found) means either LI is the first child node of OL
692     and LI is not having "value" attribute.
693     In that case we would not like to set "value" attribute to reduce the
694     changes.
695     */
696     // do nothing...
697   } else if (offset > 0) {
698     // Set value attribute.
699     nsAutoString valueStr;
700 
701     // As serializer needs to use this valueAttr we are creating here,
702     valueStr.AppendInt(startVal + offset);
703     NS_ENSURE_TRUE(SerializeAttr(u""_ns, u"value"_ns, valueStr, aStr, false),
704                    false);
705   }
706 
707   return true;
708 }
709 
IsFirstChildOfOL(nsIContent * aElement)710 bool nsXHTMLContentSerializer::IsFirstChildOfOL(nsIContent* aElement) {
711   nsIContent* parent = aElement->GetParent();
712   if (parent && parent->NodeName().LowerCaseEqualsLiteral("ol")) {
713     if (!mOLStateStack.IsEmpty()) {
714       olState state = mOLStateStack[mOLStateStack.Length() - 1];
715       if (state.isFirstListItem) return true;
716     }
717   }
718 
719   return false;
720 }
721 
HasNoChildren(nsIContent * aContent)722 bool nsXHTMLContentSerializer::HasNoChildren(nsIContent* aContent) {
723   for (nsIContent* child = aContent->GetFirstChild(); child;
724        child = child->GetNextSibling()) {
725     if (!child->IsText()) return false;
726 
727     if (child->TextLength()) return false;
728   }
729 
730   return true;
731 }
732