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