summaryrefslogtreecommitdiffstats
path: root/dom/xml/nsXMLPrettyPrinter.cpp
blob: 4a386e92fbecf8e48aef4882c449c071fc8b8f5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsXMLPrettyPrinter.h"
#include "nsContentUtils.h"
#include "nsICSSDeclaration.h"
#include "nsSyncLoadService.h"
#include "nsPIDOMWindow.h"
#include "nsNetUtil.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Document.h"
#include "nsVariant.h"
#include "mozilla/dom/CustomEvent.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentL10n.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/txMozillaXSLTProcessor.h"

using namespace mozilla;
using namespace mozilla::dom;

NS_IMPL_ISUPPORTS(nsXMLPrettyPrinter, nsIDocumentObserver, nsIMutationObserver)

nsXMLPrettyPrinter::nsXMLPrettyPrinter()
    : mDocument(nullptr), mUnhookPending(false) {}

nsXMLPrettyPrinter::~nsXMLPrettyPrinter() {
  NS_ASSERTION(!mDocument, "we shouldn't be referencing the document still");
}

nsresult nsXMLPrettyPrinter::PrettyPrint(Document* aDocument,
                                         bool* aDidPrettyPrint) {
  *aDidPrettyPrint = false;

  // check the pref
  if (!Preferences::GetBool("layout.xml.prettyprint", true)) {
    return NS_OK;
  }

  // Find the root element
  RefPtr<Element> rootElement = aDocument->GetRootElement();
  NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);

  // nsXMLContentSink should not ask us to pretty print an XML doc that comes
  // with a CanAttachShadowDOM() == true root element, but just in case:
  if (rootElement->CanAttachShadowDOM()) {
    MOZ_DIAGNOSTIC_ASSERT(false, "We shouldn't be getting this root element");
    return NS_ERROR_UNEXPECTED;
  }

  // Ok, we should prettyprint. Let's do it!
  *aDidPrettyPrint = true;
  nsresult rv = NS_OK;

  // Load the XSLT
  nsCOMPtr<nsIURI> xslUri;
  rv = NS_NewURI(getter_AddRefs(xslUri),
                 "chrome://global/content/xml/XMLPrettyPrint.xsl"_ns);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<Document> xslDocument;
  rv = nsSyncLoadService::LoadDocument(
      xslUri, nsIContentPolicy::TYPE_XSLT, nsContentUtils::GetSystemPrincipal(),
      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nullptr,
      aDocument->CookieJarSettings(), true, ReferrerPolicy::_empty,
      getter_AddRefs(xslDocument));
  NS_ENSURE_SUCCESS(rv, rv);

  // Transform the document
  RefPtr<txMozillaXSLTProcessor> transformer = new txMozillaXSLTProcessor();
  ErrorResult err;
  transformer->ImportStylesheet(*xslDocument, err);
  if (NS_WARN_IF(err.Failed())) {
    return err.StealNSResult();
  }

  RefPtr<DocumentFragment> resultFragment =
      transformer->TransformToFragment(*aDocument, *aDocument, err);
  if (NS_WARN_IF(err.Failed())) {
    return err.StealNSResult();
  }

  // Attach an UA Widget Shadow Root on it.
  rootElement->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::No);
  RefPtr<ShadowRoot> shadowRoot = rootElement->GetShadowRoot();
  MOZ_RELEASE_ASSERT(shadowRoot && shadowRoot->IsUAWidget(),
                     "There should be a UA Shadow Root here.");

  // Append the document fragment to the shadow dom.
  shadowRoot->AppendChild(*resultFragment, err);
  if (NS_WARN_IF(err.Failed())) {
    return err.StealNSResult();
  }

  // Create a DocumentL10n, as the XML document is not allowed to have one.
  // Make it sync so that the test for bug 590812 does not require a setTimeout.
  RefPtr<DocumentL10n> l10n = DocumentL10n::Create(aDocument, true);
  NS_ENSURE_TRUE(l10n, NS_ERROR_UNEXPECTED);
  l10n->AddResourceId("dom/XMLPrettyPrint.ftl"_ns);

  // Localize the shadow DOM header
  Element* l10nRoot = shadowRoot->GetElementById(u"header"_ns);
  NS_ENSURE_TRUE(l10nRoot, NS_ERROR_UNEXPECTED);
  l10n->SetRootInfo(l10nRoot);
  l10n->ConnectRoot(*l10nRoot, true, err);
  if (NS_WARN_IF(err.Failed())) {
    return err.StealNSResult();
  }
  RefPtr<Promise> promise = l10n->TranslateRoots(err);
  if (NS_WARN_IF(err.Failed())) {
    return err.StealNSResult();
  }

  // Observe the document so we know when to switch to "normal" view
  aDocument->AddObserver(this);
  mDocument = aDocument;

  NS_ADDREF_THIS();

  return NS_OK;
}

void nsXMLPrettyPrinter::MaybeUnhook(nsIContent* aContent) {
  // If aContent is null, the document-node was modified.
  // If it is not null but in the shadow tree or the <scrollbar> NACs,
  // the change was in the generated content, and it should be ignored.
  bool isGeneratedContent =
      aContent &&
      (aContent->IsInNativeAnonymousSubtree() || aContent->IsInShadowTree());

  if (!isGeneratedContent && !mUnhookPending) {
    // Can't blindly to mUnhookPending after AddScriptRunner,
    // since AddScriptRunner _could_ in theory run us
    // synchronously
    mUnhookPending = true;
    nsContentUtils::AddScriptRunner(NewRunnableMethod(
        "nsXMLPrettyPrinter::Unhook", this, &nsXMLPrettyPrinter::Unhook));
  }
}

void nsXMLPrettyPrinter::Unhook() {
  mDocument->RemoveObserver(this);
  nsCOMPtr<Element> element = mDocument->GetDocumentElement();

  if (element) {
    // Remove the shadow root
    element->UnattachShadow();
  }

  mDocument = nullptr;

  NS_RELEASE_THIS();
}

void nsXMLPrettyPrinter::AttributeChanged(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aOldValue) {
  MaybeUnhook(aElement);
}

void nsXMLPrettyPrinter::ContentAppended(nsIContent* aFirstNewContent) {
  MaybeUnhook(aFirstNewContent->GetParent());
}

void nsXMLPrettyPrinter::ContentInserted(nsIContent* aChild) {
  MaybeUnhook(aChild->GetParent());
}

void nsXMLPrettyPrinter::ContentRemoved(nsIContent* aChild,
                                        nsIContent* aPreviousSibling) {
  MaybeUnhook(aChild->GetParent());
}

void nsXMLPrettyPrinter::NodeWillBeDestroyed(nsINode* aNode) {
  mDocument = nullptr;
  NS_RELEASE_THIS();
}

nsresult NS_NewXMLPrettyPrinter(nsXMLPrettyPrinter** aPrinter) {
  *aPrinter = new nsXMLPrettyPrinter;
  NS_ADDREF(*aPrinter);
  return NS_OK;
}