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