1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "txMozillaXMLOutput.h"
7 
8 #include "mozilla/dom/Document.h"
9 #include "nsIDocShell.h"
10 #include "nsIScriptElement.h"
11 #include "nsCharsetSource.h"
12 #include "nsIRefreshURI.h"
13 #include "nsPIDOMWindow.h"
14 #include "nsIContent.h"
15 #include "nsContentCID.h"
16 #include "nsUnicharUtils.h"
17 #include "nsGkAtoms.h"
18 #include "txLog.h"
19 #include "nsNameSpaceManager.h"
20 #include "txStringUtils.h"
21 #include "txURIUtils.h"
22 #include "nsIDocumentTransformer.h"
23 #include "mozilla/StyleSheetInlines.h"
24 #include "mozilla/css/Loader.h"
25 #include "mozilla/dom/DocumentType.h"
26 #include "mozilla/dom/DocumentFragment.h"
27 #include "mozilla/dom/Element.h"
28 #include "mozilla/dom/ScriptLoader.h"
29 #include "mozilla/Encoding.h"
30 #include "nsContentUtils.h"
31 #include "nsDocElementCreatedNotificationRunner.h"
32 #include "txXMLUtils.h"
33 #include "nsContentSink.h"
34 #include "nsINode.h"
35 #include "nsContentCreatorFunctions.h"
36 #include "nsError.h"
37 #include "nsIFrame.h"
38 #include <algorithm>
39 #include "nsTextNode.h"
40 #include "mozilla/dom/Comment.h"
41 #include "mozilla/dom/ProcessingInstruction.h"
42 
43 using namespace mozilla;
44 using namespace mozilla::dom;
45 
46 #define TX_ENSURE_CURRENTNODE                            \
47   NS_ASSERTION(mCurrentNode, "mCurrentNode is nullptr"); \
48   if (!mCurrentNode) return NS_ERROR_UNEXPECTED
49 
txMozillaXMLOutput(txOutputFormat * aFormat,nsITransformObserver * aObserver)50 txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
51                                        nsITransformObserver* aObserver)
52     : mTreeDepth(0),
53       mBadChildLevel(0),
54       mTableState(NORMAL),
55       mCreatingNewDocument(true),
56       mOpenedElementIsHTML(false),
57       mRootContentCreated(false),
58       mNoFixup(false) {
59   MOZ_COUNT_CTOR(txMozillaXMLOutput);
60   if (aObserver) {
61     mNotifier = new txTransformNotifier();
62     if (mNotifier) {
63       mNotifier->Init(aObserver);
64     }
65   }
66 
67   mOutputFormat.merge(*aFormat);
68   mOutputFormat.setFromDefaults();
69 }
70 
txMozillaXMLOutput(txOutputFormat * aFormat,DocumentFragment * aFragment,bool aNoFixup)71 txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
72                                        DocumentFragment* aFragment,
73                                        bool aNoFixup)
74     : mTreeDepth(0),
75       mBadChildLevel(0),
76       mTableState(NORMAL),
77       mCreatingNewDocument(false),
78       mOpenedElementIsHTML(false),
79       mRootContentCreated(false),
80       mNoFixup(aNoFixup) {
81   MOZ_COUNT_CTOR(txMozillaXMLOutput);
82   mOutputFormat.merge(*aFormat);
83   mOutputFormat.setFromDefaults();
84 
85   mCurrentNode = aFragment;
86   mDocument = mCurrentNode->OwnerDoc();
87   mNodeInfoManager = mDocument->NodeInfoManager();
88 }
89 
~txMozillaXMLOutput()90 txMozillaXMLOutput::~txMozillaXMLOutput() {
91   MOZ_COUNT_DTOR(txMozillaXMLOutput);
92 }
93 
attribute(nsAtom * aPrefix,nsAtom * aLocalName,nsAtom * aLowercaseLocalName,const int32_t aNsID,const nsString & aValue)94 nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
95                                        nsAtom* aLowercaseLocalName,
96                                        const int32_t aNsID,
97                                        const nsString& aValue) {
98   RefPtr<nsAtom> owner;
99   if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
100     if (aLowercaseLocalName) {
101       aLocalName = aLowercaseLocalName;
102     } else {
103       owner = TX_ToLowerCaseAtom(aLocalName);
104       NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
105 
106       aLocalName = owner;
107     }
108   }
109 
110   return attributeInternal(aPrefix, aLocalName, aNsID, aValue);
111 }
112 
attribute(nsAtom * aPrefix,const nsAString & aLocalName,const int32_t aNsID,const nsString & aValue)113 nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix,
114                                        const nsAString& aLocalName,
115                                        const int32_t aNsID,
116                                        const nsString& aValue) {
117   RefPtr<nsAtom> lname;
118 
119   if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
120     nsAutoString lnameStr;
121     nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
122     lname = NS_Atomize(lnameStr);
123   } else {
124     lname = NS_Atomize(aLocalName);
125   }
126 
127   NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
128 
129   // Check that it's a valid name
130   if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
131     // Try without prefix
132     aPrefix = nullptr;
133     if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
134       // Don't return error here since the callers don't deal
135       return NS_OK;
136     }
137   }
138 
139   return attributeInternal(aPrefix, lname, aNsID, aValue);
140 }
141 
attributeInternal(nsAtom * aPrefix,nsAtom * aLocalName,int32_t aNsID,const nsString & aValue)142 nsresult txMozillaXMLOutput::attributeInternal(nsAtom* aPrefix,
143                                                nsAtom* aLocalName,
144                                                int32_t aNsID,
145                                                const nsString& aValue) {
146   if (!mOpenedElement) {
147     // XXX Signal this? (can't add attributes after element closed)
148     return NS_OK;
149   }
150 
151   NS_ASSERTION(!mBadChildLevel, "mBadChildLevel set when element is opened");
152 
153   return mOpenedElement->SetAttr(aNsID, aLocalName, aPrefix, aValue, false);
154 }
155 
characters(const nsAString & aData,bool aDOE)156 nsresult txMozillaXMLOutput::characters(const nsAString& aData, bool aDOE) {
157   nsresult rv = closePrevious(false);
158   NS_ENSURE_SUCCESS(rv, rv);
159 
160   if (!mBadChildLevel) {
161     mText.Append(aData);
162   }
163 
164   return NS_OK;
165 }
166 
comment(const nsString & aData)167 nsresult txMozillaXMLOutput::comment(const nsString& aData) {
168   nsresult rv = closePrevious(true);
169   NS_ENSURE_SUCCESS(rv, rv);
170 
171   if (mBadChildLevel) {
172     return NS_OK;
173   }
174 
175   TX_ENSURE_CURRENTNODE;
176 
177   RefPtr<Comment> comment = new (mNodeInfoManager) Comment(mNodeInfoManager);
178 
179   rv = comment->SetText(aData, false);
180   NS_ENSURE_SUCCESS(rv, rv);
181 
182   ErrorResult error;
183   mCurrentNode->AppendChildTo(comment, true, error);
184   return error.StealNSResult();
185 }
186 
endDocument(nsresult aResult)187 nsresult txMozillaXMLOutput::endDocument(nsresult aResult) {
188   TX_ENSURE_CURRENTNODE;
189 
190   if (NS_FAILED(aResult)) {
191     if (mNotifier) {
192       mNotifier->OnTransformEnd(aResult);
193     }
194 
195     return NS_OK;
196   }
197 
198   nsresult rv = closePrevious(true);
199   if (NS_FAILED(rv)) {
200     if (mNotifier) {
201       mNotifier->OnTransformEnd(rv);
202     }
203 
204     return rv;
205   }
206 
207   if (mCreatingNewDocument) {
208     // This should really be handled by Document::EndLoad
209     MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
210                "Bad readyState");
211     mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
212     if (ScriptLoader* loader = mDocument->ScriptLoader()) {
213       loader->ParsingComplete(false);
214     }
215   }
216 
217   if (mNotifier) {
218     mNotifier->OnTransformEnd();
219   }
220 
221   return NS_OK;
222 }
223 
endElement()224 nsresult txMozillaXMLOutput::endElement() {
225   TX_ENSURE_CURRENTNODE;
226 
227   if (mBadChildLevel) {
228     --mBadChildLevel;
229     MOZ_LOG(txLog::xslt, LogLevel::Debug,
230             ("endElement, mBadChildLevel = %d\n", mBadChildLevel));
231     return NS_OK;
232   }
233 
234   --mTreeDepth;
235 
236   nsresult rv = closePrevious(true);
237   NS_ENSURE_SUCCESS(rv, rv);
238 
239   NS_ASSERTION(mCurrentNode->IsElement(), "borked mCurrentNode");
240   NS_ENSURE_TRUE(mCurrentNode->IsElement(), NS_ERROR_UNEXPECTED);
241 
242   Element* element = mCurrentNode->AsElement();
243 
244   // Handle html-elements
245   if (!mNoFixup) {
246     if (element->IsHTMLElement()) {
247       rv = endHTMLElement(element);
248       NS_ENSURE_SUCCESS(rv, rv);
249     }
250 
251     // Handle elements that are different when parser-created
252     if (nsIContent::RequiresDoneCreatingElement(
253             element->NodeInfo()->NamespaceID(),
254             element->NodeInfo()->NameAtom())) {
255       element->DoneCreatingElement();
256     } else if (element->IsSVGElement(nsGkAtoms::script) ||
257                element->IsHTMLElement(nsGkAtoms::script)) {
258       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element);
259       if (sele) {
260         bool block = sele->AttemptToExecute();
261         // If the act of insertion evaluated the script, we're fine.
262         // Else, add this script element to the array of loading scripts.
263         if (block) {
264           rv = mNotifier->AddScriptElement(sele);
265           NS_ENSURE_SUCCESS(rv, rv);
266         }
267       } else {
268         MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
269                    "Script elements need to implement nsIScriptElement and SVG "
270                    "wasn't disabled.");
271       }
272     } else if (nsIContent::RequiresDoneAddingChildren(
273                    element->NodeInfo()->NamespaceID(),
274                    element->NodeInfo()->NameAtom())) {
275       element->DoneAddingChildren(true);
276     }
277   }
278 
279   if (mCreatingNewDocument) {
280     // Handle all sorts of stylesheets
281     if (auto* linkStyle = LinkStyle::FromNode(*mCurrentNode)) {
282       linkStyle->SetEnableUpdates(true);
283       auto updateOrError = linkStyle->UpdateStyleSheet(mNotifier);
284       if (mNotifier && updateOrError.isOk() &&
285           updateOrError.unwrap().ShouldBlock()) {
286         mNotifier->AddPendingStylesheet();
287       }
288     }
289   }
290 
291   // Add the element to the tree if it wasn't added before and take one step
292   // up the tree
293   uint32_t last = mCurrentNodeStack.Count() - 1;
294   NS_ASSERTION(last != (uint32_t)-1, "empty stack");
295 
296   nsCOMPtr<nsINode> parent = mCurrentNodeStack.SafeObjectAt(last);
297   mCurrentNodeStack.RemoveObjectAt(last);
298 
299   if (mCurrentNode == mNonAddedNode) {
300     if (parent == mDocument) {
301       NS_ASSERTION(!mRootContentCreated,
302                    "Parent to add to shouldn't be a document if we "
303                    "have a root content");
304       mRootContentCreated = true;
305     }
306 
307     // Check to make sure that script hasn't inserted the node somewhere
308     // else in the tree
309     if (!mCurrentNode->GetParentNode()) {
310       parent->AppendChildTo(mNonAddedNode, true, IgnoreErrors());
311     }
312     mNonAddedNode = nullptr;
313   }
314 
315   mCurrentNode = parent;
316 
317   mTableState =
318       static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
319 
320   return NS_OK;
321 }
322 
getOutputDocument(Document ** aDocument)323 void txMozillaXMLOutput::getOutputDocument(Document** aDocument) {
324   NS_IF_ADDREF(*aDocument = mDocument);
325 }
326 
processingInstruction(const nsString & aTarget,const nsString & aData)327 nsresult txMozillaXMLOutput::processingInstruction(const nsString& aTarget,
328                                                    const nsString& aData) {
329   nsresult rv = closePrevious(true);
330   NS_ENSURE_SUCCESS(rv, rv);
331 
332   if (mOutputFormat.mMethod == eHTMLOutput) return NS_OK;
333 
334   TX_ENSURE_CURRENTNODE;
335 
336   rv = nsContentUtils::CheckQName(aTarget, false);
337   NS_ENSURE_SUCCESS(rv, rv);
338 
339   nsCOMPtr<nsIContent> pi =
340       NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
341 
342   LinkStyle* linkStyle = nullptr;
343   if (mCreatingNewDocument) {
344     linkStyle = LinkStyle::FromNode(*pi);
345     if (linkStyle) {
346       linkStyle->SetEnableUpdates(false);
347     }
348   }
349 
350   ErrorResult error;
351   mCurrentNode->AppendChildTo(pi, true, error);
352   if (error.Failed()) {
353     return error.StealNSResult();
354   }
355 
356   if (linkStyle) {
357     linkStyle->SetEnableUpdates(true);
358     auto updateOrError = linkStyle->UpdateStyleSheet(mNotifier);
359     if (mNotifier && updateOrError.isOk() &&
360         updateOrError.unwrap().ShouldBlock()) {
361       mNotifier->AddPendingStylesheet();
362     }
363   }
364 
365   return NS_OK;
366 }
367 
startDocument()368 nsresult txMozillaXMLOutput::startDocument() {
369   if (mNotifier) {
370     mNotifier->OnTransformStart();
371   }
372 
373   if (mCreatingNewDocument) {
374     ScriptLoader* loader = mDocument->ScriptLoader();
375     if (loader) {
376       loader->BeginDeferringScripts();
377     }
378   }
379 
380   return NS_OK;
381 }
382 
startElement(nsAtom * aPrefix,nsAtom * aLocalName,nsAtom * aLowercaseLocalName,const int32_t aNsID)383 nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
384                                           nsAtom* aLowercaseLocalName,
385                                           const int32_t aNsID) {
386   MOZ_ASSERT(aNsID != kNameSpaceID_None || !aPrefix,
387              "Can't have prefix without namespace");
388 
389   if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
390     RefPtr<nsAtom> owner;
391     if (!aLowercaseLocalName) {
392       owner = TX_ToLowerCaseAtom(aLocalName);
393       NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
394 
395       aLowercaseLocalName = owner;
396     }
397     return startElementInternal(nullptr, aLowercaseLocalName,
398                                 kNameSpaceID_XHTML);
399   }
400 
401   return startElementInternal(aPrefix, aLocalName, aNsID);
402 }
403 
startElement(nsAtom * aPrefix,const nsAString & aLocalName,const int32_t aNsID)404 nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix,
405                                           const nsAString& aLocalName,
406                                           const int32_t aNsID) {
407   int32_t nsId = aNsID;
408   RefPtr<nsAtom> lname;
409 
410   if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
411     nsId = kNameSpaceID_XHTML;
412 
413     nsAutoString lnameStr;
414     nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
415     lname = NS_Atomize(lnameStr);
416   } else {
417     lname = NS_Atomize(aLocalName);
418   }
419 
420   // No biggie if we lose the prefix due to OOM
421   NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
422 
423   // Check that it's a valid name
424   if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
425     // Try without prefix
426     aPrefix = nullptr;
427     if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
428       return NS_ERROR_XSLT_BAD_NODE_NAME;
429     }
430   }
431 
432   return startElementInternal(aPrefix, lname, nsId);
433 }
434 
startElementInternal(nsAtom * aPrefix,nsAtom * aLocalName,int32_t aNsID)435 nsresult txMozillaXMLOutput::startElementInternal(nsAtom* aPrefix,
436                                                   nsAtom* aLocalName,
437                                                   int32_t aNsID) {
438   TX_ENSURE_CURRENTNODE;
439 
440   if (mBadChildLevel) {
441     ++mBadChildLevel;
442     MOZ_LOG(txLog::xslt, LogLevel::Debug,
443             ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
444     return NS_OK;
445   }
446 
447   nsresult rv = closePrevious(true);
448   NS_ENSURE_SUCCESS(rv, rv);
449 
450   // Push and init state
451   if (mTreeDepth == MAX_REFLOW_DEPTH) {
452     // eCloseElement couldn't add the parent so we fail as well or we've
453     // reached the limit of the depth of the tree that we allow.
454     ++mBadChildLevel;
455     MOZ_LOG(txLog::xslt, LogLevel::Debug,
456             ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
457     return NS_OK;
458   }
459 
460   ++mTreeDepth;
461 
462   rv = mTableStateStack.push(NS_INT32_TO_PTR(mTableState));
463   NS_ENSURE_SUCCESS(rv, rv);
464 
465   if (!mCurrentNodeStack.AppendObject(mCurrentNode)) {
466     return NS_ERROR_OUT_OF_MEMORY;
467   }
468 
469   mTableState = NORMAL;
470   mOpenedElementIsHTML = false;
471 
472   // Create the element
473   RefPtr<NodeInfo> ni = mNodeInfoManager->GetNodeInfo(
474       aLocalName, aPrefix, aNsID, nsINode::ELEMENT_NODE);
475 
476   NS_NewElement(getter_AddRefs(mOpenedElement), ni.forget(),
477                 mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
478 
479   // Set up the element and adjust state
480   if (!mNoFixup) {
481     if (aNsID == kNameSpaceID_XHTML) {
482       mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput);
483       rv = startHTMLElement(mOpenedElement, mOpenedElementIsHTML);
484       NS_ENSURE_SUCCESS(rv, rv);
485     }
486   }
487 
488   if (mCreatingNewDocument) {
489     // Handle all sorts of stylesheets
490     if (auto* linkStyle = LinkStyle::FromNodeOrNull(mOpenedElement)) {
491       linkStyle->SetEnableUpdates(false);
492     }
493   }
494 
495   return NS_OK;
496 }
497 
closePrevious(bool aFlushText)498 nsresult txMozillaXMLOutput::closePrevious(bool aFlushText) {
499   TX_ENSURE_CURRENTNODE;
500 
501   nsresult rv;
502   if (mOpenedElement) {
503     bool currentIsDoc = mCurrentNode == mDocument;
504     if (currentIsDoc && mRootContentCreated) {
505       // We already have a document element, but the XSLT spec allows this.
506       // As a workaround, create a wrapper object and use that as the
507       // document element.
508 
509       rv = createTxWrapper();
510       NS_ENSURE_SUCCESS(rv, rv);
511     }
512 
513     ErrorResult error;
514     mCurrentNode->AppendChildTo(mOpenedElement, true, error);
515     if (error.Failed()) {
516       return error.StealNSResult();
517     }
518 
519     if (currentIsDoc) {
520       mRootContentCreated = true;
521       nsContentUtils::AddScriptRunner(
522           new nsDocElementCreatedNotificationRunner(mDocument));
523     }
524 
525     mCurrentNode = mOpenedElement;
526     mOpenedElement = nullptr;
527   } else if (aFlushText && !mText.IsEmpty()) {
528     // Text can't appear in the root of a document
529     if (mDocument == mCurrentNode) {
530       if (XMLUtils::isWhitespace(mText)) {
531         mText.Truncate();
532 
533         return NS_OK;
534       }
535 
536       rv = createTxWrapper();
537       NS_ENSURE_SUCCESS(rv, rv);
538     }
539     RefPtr<nsTextNode> text =
540         new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
541 
542     rv = text->SetText(mText, false);
543     NS_ENSURE_SUCCESS(rv, rv);
544 
545     ErrorResult error;
546     mCurrentNode->AppendChildTo(text, true, error);
547     if (error.Failed()) {
548       return error.StealNSResult();
549     }
550 
551     mText.Truncate();
552   }
553 
554   return NS_OK;
555 }
556 
createTxWrapper()557 nsresult txMozillaXMLOutput::createTxWrapper() {
558   NS_ASSERTION(mDocument == mCurrentNode,
559                "creating wrapper when document isn't parent");
560 
561   int32_t namespaceID;
562   nsresult rv = nsContentUtils::NameSpaceManager()->RegisterNameSpace(
563       nsLiteralString(kTXNameSpaceURI), namespaceID);
564   NS_ENSURE_SUCCESS(rv, rv);
565 
566   nsCOMPtr<Element> wrapper =
567       mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
568                             nsGkAtoms::transformiix, namespaceID);
569 
570 #ifdef DEBUG
571   // Keep track of the location of the current documentElement, if there is
572   // one, so we can verify later
573   uint32_t j = 0, rootLocation = 0;
574 #endif
575   for (nsCOMPtr<nsIContent> childContent = mDocument->GetFirstChild();
576        childContent; childContent = childContent->GetNextSibling()) {
577 #ifdef DEBUG
578     if (childContent->IsElement()) {
579       rootLocation = j;
580     }
581 #endif
582 
583     if (childContent->NodeInfo()->NameAtom() ==
584         nsGkAtoms::documentTypeNodeName) {
585 #ifdef DEBUG
586       // The new documentElement should go after the document type.
587       // This is needed for cases when there is no existing
588       // documentElement in the document.
589       rootLocation = std::max(rootLocation, j + 1);
590       ++j;
591 #endif
592     } else {
593       mDocument->RemoveChildNode(childContent, true);
594 
595       ErrorResult error;
596       wrapper->AppendChildTo(childContent, true, error);
597       if (error.Failed()) {
598         return error.StealNSResult();
599       }
600       break;
601     }
602   }
603 
604   if (!mCurrentNodeStack.AppendObject(wrapper)) {
605     return NS_ERROR_OUT_OF_MEMORY;
606   }
607   mCurrentNode = wrapper;
608   mRootContentCreated = true;
609   NS_ASSERTION(rootLocation == mDocument->GetChildCount(),
610                "Incorrect root location");
611   ErrorResult error;
612   mDocument->AppendChildTo(wrapper, true, error);
613   return error.StealNSResult();
614 }
615 
startHTMLElement(nsIContent * aElement,bool aIsHTML)616 nsresult txMozillaXMLOutput::startHTMLElement(nsIContent* aElement,
617                                               bool aIsHTML) {
618   nsresult rv = NS_OK;
619 
620   if ((!aElement->IsHTMLElement(nsGkAtoms::tr) || !aIsHTML) &&
621       NS_PTR_TO_INT32(mTableStateStack.peek()) == ADDED_TBODY) {
622     uint32_t last = mCurrentNodeStack.Count() - 1;
623     NS_ASSERTION(last != (uint32_t)-1, "empty stack");
624 
625     mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
626     mCurrentNodeStack.RemoveObjectAt(last);
627     mTableStateStack.pop();
628   }
629 
630   if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) {
631     mTableState = TABLE;
632   } else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML &&
633              NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) {
634     RefPtr<Element> tbody;
635     rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody));
636     NS_ENSURE_SUCCESS(rv, rv);
637 
638     ErrorResult error;
639     mCurrentNode->AppendChildTo(tbody, true, error);
640     if (error.Failed()) {
641       return error.StealNSResult();
642     }
643 
644     rv = mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY));
645     NS_ENSURE_SUCCESS(rv, rv);
646 
647     if (!mCurrentNodeStack.AppendObject(tbody)) {
648       return NS_ERROR_OUT_OF_MEMORY;
649     }
650 
651     mCurrentNode = tbody;
652   } else if (aElement->IsHTMLElement(nsGkAtoms::head) &&
653              mOutputFormat.mMethod == eHTMLOutput) {
654     // Insert META tag, according to spec, 16.2, like
655     // <META http-equiv="Content-Type" content="text/html; charset=EUC-JP">
656     RefPtr<Element> meta;
657     rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta));
658     NS_ENSURE_SUCCESS(rv, rv);
659 
660     rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
661                        u"Content-Type"_ns, false);
662     NS_ENSURE_SUCCESS(rv, rv);
663 
664     nsAutoString metacontent;
665     metacontent.Append(mOutputFormat.mMediaType);
666     metacontent.AppendLiteral("; charset=");
667     metacontent.Append(mOutputFormat.mEncoding);
668     rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content, metacontent,
669                        false);
670     NS_ENSURE_SUCCESS(rv, rv);
671 
672     // No need to notify since aElement hasn't been inserted yet
673     NS_ASSERTION(!aElement->IsInUncomposedDoc(), "should not be in doc");
674     ErrorResult error;
675     aElement->AppendChildTo(meta, false, error);
676     if (error.Failed()) {
677       return error.StealNSResult();
678     }
679   }
680 
681   return NS_OK;
682 }
683 
endHTMLElement(nsIContent * aElement)684 nsresult txMozillaXMLOutput::endHTMLElement(nsIContent* aElement) {
685   if (mTableState == ADDED_TBODY) {
686     NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody),
687                  "Element flagged as added tbody isn't a tbody");
688     uint32_t last = mCurrentNodeStack.Count() - 1;
689     NS_ASSERTION(last != (uint32_t)-1, "empty stack");
690 
691     mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
692     mCurrentNodeStack.RemoveObjectAt(last);
693     mTableState =
694         static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
695   }
696 
697   return NS_OK;
698 }
699 
createResultDocument(const nsAString & aName,int32_t aNsID,Document * aSourceDocument,bool aLoadedAsData)700 nsresult txMozillaXMLOutput::createResultDocument(const nsAString& aName,
701                                                   int32_t aNsID,
702                                                   Document* aSourceDocument,
703                                                   bool aLoadedAsData) {
704   nsresult rv;
705 
706   // Create the document
707   if (mOutputFormat.mMethod == eHTMLOutput) {
708     rv = NS_NewHTMLDocument(getter_AddRefs(mDocument), aLoadedAsData);
709     NS_ENSURE_SUCCESS(rv, rv);
710   } else {
711     // We should check the root name/namespace here and create the
712     // appropriate document
713     rv = NS_NewXMLDocument(getter_AddRefs(mDocument), aLoadedAsData);
714     NS_ENSURE_SUCCESS(rv, rv);
715   }
716   // This should really be handled by Document::BeginLoad
717   MOZ_ASSERT(
718       mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
719       "Bad readyState");
720   mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING);
721   mDocument->SetMayStartLayout(false);
722   bool hasHadScriptObject = false;
723   nsIScriptGlobalObject* sgo =
724       aSourceDocument->GetScriptHandlingObject(hasHadScriptObject);
725   NS_ENSURE_STATE(sgo || !hasHadScriptObject);
726 
727   mCurrentNode = mDocument;
728   mNodeInfoManager = mDocument->NodeInfoManager();
729 
730   // Reset and set up the document
731   URIUtils::ResetWithSource(mDocument, aSourceDocument);
732 
733   // Make sure we set the script handling object after resetting with the
734   // source, so that we have the right principal.
735   mDocument->SetScriptHandlingObject(sgo);
736 
737   mDocument->SetStateObjectFrom(aSourceDocument);
738 
739   // Set the charset
740   if (!mOutputFormat.mEncoding.IsEmpty()) {
741     const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding);
742     if (encoding) {
743       mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
744       mDocument->SetDocumentCharacterSet(WrapNotNull(encoding));
745     }
746   }
747 
748   // Set the mime-type
749   if (!mOutputFormat.mMediaType.IsEmpty()) {
750     mDocument->SetContentType(mOutputFormat.mMediaType);
751   } else if (mOutputFormat.mMethod == eHTMLOutput) {
752     mDocument->SetContentType(u"text/html"_ns);
753   } else {
754     mDocument->SetContentType(u"application/xml"_ns);
755   }
756 
757   if (mOutputFormat.mMethod == eXMLOutput &&
758       mOutputFormat.mOmitXMLDeclaration != eTrue) {
759     int32_t standalone;
760     if (mOutputFormat.mStandalone == eNotSet) {
761       standalone = -1;
762     } else if (mOutputFormat.mStandalone == eFalse) {
763       standalone = 0;
764     } else {
765       standalone = 1;
766     }
767 
768     // Could use mOutputFormat.mVersion.get() when we support
769     // versions > 1.0.
770     static const char16_t kOneDotZero[] = {'1', '.', '0', '\0'};
771     mDocument->SetXMLDeclaration(kOneDotZero, mOutputFormat.mEncoding.get(),
772                                  standalone);
773   }
774 
775   // Set up script loader of the result document.
776   ScriptLoader* loader = mDocument->ScriptLoader();
777   if (mNotifier) {
778     loader->AddObserver(mNotifier);
779   } else {
780     // Don't load scripts, we can't notify the caller when they're loaded.
781     loader->SetEnabled(false);
782   }
783 
784   if (mNotifier) {
785     rv = mNotifier->SetOutputDocument(mDocument);
786     NS_ENSURE_SUCCESS(rv, rv);
787   }
788 
789   // Do this after calling OnDocumentCreated to ensure that the
790   // PresShell/PresContext has been hooked up and get notified.
791   if (mDocument) {
792     mDocument->SetCompatibilityMode(eCompatibility_FullStandards);
793   }
794 
795   // Add a doc-type if requested
796   if (!mOutputFormat.mSystemId.IsEmpty()) {
797     nsAutoString qName;
798     if (mOutputFormat.mMethod == eHTMLOutput) {
799       qName.AssignLiteral("html");
800     } else {
801       qName.Assign(aName);
802     }
803 
804     nsresult rv = nsContentUtils::CheckQName(qName);
805     if (NS_SUCCEEDED(rv)) {
806       RefPtr<nsAtom> doctypeName = NS_Atomize(qName);
807       if (!doctypeName) {
808         return NS_ERROR_OUT_OF_MEMORY;
809       }
810 
811       // Indicate that there is no internal subset (not just an empty one)
812       RefPtr<DocumentType> documentType = NS_NewDOMDocumentType(
813           mNodeInfoManager, doctypeName, mOutputFormat.mPublicId,
814           mOutputFormat.mSystemId, VoidString());
815 
816       ErrorResult error;
817       mDocument->AppendChildTo(documentType, true, error);
818       if (error.Failed()) {
819         return error.StealNSResult();
820       }
821     }
822   }
823 
824   return NS_OK;
825 }
826 
createHTMLElement(nsAtom * aName,Element ** aResult)827 nsresult txMozillaXMLOutput::createHTMLElement(nsAtom* aName,
828                                                Element** aResult) {
829   NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput,
830                "need to adjust createHTMLElement");
831 
832   *aResult = nullptr;
833 
834   RefPtr<NodeInfo> ni;
835   ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_XHTML,
836                                      nsINode::ELEMENT_NODE);
837 
838   nsCOMPtr<Element> el;
839   nsresult rv = NS_NewHTMLElement(
840       getter_AddRefs(el), ni.forget(),
841       mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
842   el.forget(aResult);
843   return rv;
844 }
845 
txTransformNotifier()846 txTransformNotifier::txTransformNotifier()
847     : mPendingStylesheetCount(0), mInTransform(false) {}
848 
849 txTransformNotifier::~txTransformNotifier() = default;
850 
NS_IMPL_ISUPPORTS(txTransformNotifier,nsIScriptLoaderObserver,nsICSSLoaderObserver)851 NS_IMPL_ISUPPORTS(txTransformNotifier, nsIScriptLoaderObserver,
852                   nsICSSLoaderObserver)
853 
854 NS_IMETHODIMP
855 txTransformNotifier::ScriptAvailable(nsresult aResult,
856                                      nsIScriptElement* aElement,
857                                      bool aIsInlineClassicScript, nsIURI* aURI,
858                                      int32_t aLineNo) {
859   if (NS_FAILED(aResult) && mScriptElements.RemoveObject(aElement)) {
860     SignalTransformEnd();
861   }
862 
863   return NS_OK;
864 }
865 
866 NS_IMETHODIMP
ScriptEvaluated(nsresult aResult,nsIScriptElement * aElement,bool aIsInline)867 txTransformNotifier::ScriptEvaluated(nsresult aResult,
868                                      nsIScriptElement* aElement,
869                                      bool aIsInline) {
870   if (mScriptElements.RemoveObject(aElement)) {
871     SignalTransformEnd();
872   }
873 
874   return NS_OK;
875 }
876 
877 NS_IMETHODIMP
StyleSheetLoaded(StyleSheet * aSheet,bool aWasDeferred,nsresult aStatus)878 txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
879                                       nsresult aStatus) {
880   if (mPendingStylesheetCount == 0) {
881     // We weren't waiting on this stylesheet anyway.  This can happen if
882     // SignalTransformEnd got called with an error aResult.  See
883     // http://bugzilla.mozilla.org/show_bug.cgi?id=215465.
884     return NS_OK;
885   }
886 
887   // We're never waiting for alternate stylesheets
888   if (!aWasDeferred) {
889     --mPendingStylesheetCount;
890     SignalTransformEnd();
891   }
892 
893   return NS_OK;
894 }
895 
Init(nsITransformObserver * aObserver)896 void txTransformNotifier::Init(nsITransformObserver* aObserver) {
897   mObserver = aObserver;
898 }
899 
AddScriptElement(nsIScriptElement * aElement)900 nsresult txTransformNotifier::AddScriptElement(nsIScriptElement* aElement) {
901   return mScriptElements.AppendObject(aElement) ? NS_OK
902                                                 : NS_ERROR_OUT_OF_MEMORY;
903 }
904 
AddPendingStylesheet()905 void txTransformNotifier::AddPendingStylesheet() { ++mPendingStylesheetCount; }
906 
OnTransformEnd(nsresult aResult)907 void txTransformNotifier::OnTransformEnd(nsresult aResult) {
908   mInTransform = false;
909   SignalTransformEnd(aResult);
910 }
911 
OnTransformStart()912 void txTransformNotifier::OnTransformStart() { mInTransform = true; }
913 
SetOutputDocument(Document * aDocument)914 nsresult txTransformNotifier::SetOutputDocument(Document* aDocument) {
915   mDocument = aDocument;
916 
917   // Notify the contentsink that the document is created
918   return mObserver->OnDocumentCreated(mDocument);
919 }
920 
SignalTransformEnd(nsresult aResult)921 void txTransformNotifier::SignalTransformEnd(nsresult aResult) {
922   if (mInTransform ||
923       (NS_SUCCEEDED(aResult) &&
924        (mScriptElements.Count() > 0 || mPendingStylesheetCount > 0))) {
925     return;
926   }
927 
928   // mPendingStylesheetCount is nonzero at this point only if aResult is an
929   // error.  Set it to 0 so we won't reenter this code when we stop the
930   // CSSLoader.
931   mPendingStylesheetCount = 0;
932   mScriptElements.Clear();
933 
934   // Make sure that we don't get deleted while this function is executed and
935   // we remove ourselfs from the scriptloader
936   nsCOMPtr<nsIScriptLoaderObserver> kungFuDeathGrip(this);
937 
938   if (mDocument) {
939     mDocument->ScriptLoader()->DeferCheckpointReached();
940     mDocument->ScriptLoader()->RemoveObserver(this);
941     // XXX Maybe we want to cancel script loads if NS_FAILED(rv)?
942 
943     if (NS_FAILED(aResult)) {
944       mDocument->CSSLoader()->Stop();
945     }
946   }
947 
948   if (NS_SUCCEEDED(aResult)) {
949     mObserver->OnTransformDone(aResult, mDocument);
950   }
951 }
952