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