1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "nsXMLPrettyPrinter.h"
7 #include "nsContentUtils.h"
8 #include "nsICSSDeclaration.h"
9 #include "nsIObserver.h"
10 #include "nsSyncLoadService.h"
11 #include "nsPIDOMWindow.h"
12 #include "nsIDOMElement.h"
13 #include "nsIServiceManager.h"
14 #include "nsNetUtil.h"
15 #include "mozilla/dom/Element.h"
16 #include "nsBindingManager.h"
17 #include "nsXBLService.h"
18 #include "nsIScriptSecurityManager.h"
19 #include "mozilla/Preferences.h"
20 #include "nsIDocument.h"
21 #include "nsVariant.h"
22 #include "mozilla/dom/CustomEvent.h"
23 #include "mozilla/dom/DocumentFragment.h"
24 #include "mozilla/dom/txMozillaXSLTProcessor.h"
25
26 using namespace mozilla;
27 using namespace mozilla::dom;
28
NS_IMPL_ISUPPORTS(nsXMLPrettyPrinter,nsIDocumentObserver,nsIMutationObserver)29 NS_IMPL_ISUPPORTS(nsXMLPrettyPrinter, nsIDocumentObserver, nsIMutationObserver)
30
31 nsXMLPrettyPrinter::nsXMLPrettyPrinter()
32 : mDocument(nullptr), mUnhookPending(false) {}
33
~nsXMLPrettyPrinter()34 nsXMLPrettyPrinter::~nsXMLPrettyPrinter() {
35 NS_ASSERTION(!mDocument, "we shouldn't be referencing the document still");
36 }
37
PrettyPrint(nsIDocument * aDocument,bool * aDidPrettyPrint)38 nsresult nsXMLPrettyPrinter::PrettyPrint(nsIDocument* aDocument,
39 bool* aDidPrettyPrint) {
40 *aDidPrettyPrint = false;
41
42 // Check for iframe with display:none. Such iframes don't have presshells
43 nsCOMPtr<nsIPresShell> shell = aDocument->GetShell();
44 if (!shell) {
45 return NS_OK;
46 }
47
48 // check if we're in an invisible iframe
49 nsPIDOMWindowOuter* internalWin = aDocument->GetWindow();
50 nsCOMPtr<Element> frameElem;
51 if (internalWin) {
52 frameElem = internalWin->GetFrameElementInternal();
53 }
54
55 if (frameElem) {
56 nsCOMPtr<nsICSSDeclaration> computedStyle;
57 if (nsIDocument* frameOwnerDoc = frameElem->OwnerDoc()) {
58 nsPIDOMWindowOuter* window = frameOwnerDoc->GetDefaultView();
59 if (window) {
60 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
61 window->GetCurrentInnerWindow();
62
63 ErrorResult dummy;
64 computedStyle =
65 innerWindow->GetComputedStyle(*frameElem, EmptyString(), dummy);
66 dummy.SuppressException();
67 }
68 }
69
70 if (computedStyle) {
71 nsAutoString visibility;
72 computedStyle->GetPropertyValue(NS_LITERAL_STRING("visibility"),
73 visibility);
74 if (!visibility.EqualsLiteral("visible")) {
75 return NS_OK;
76 }
77 }
78 }
79
80 // check the pref
81 if (!Preferences::GetBool("layout.xml.prettyprint", true)) {
82 return NS_OK;
83 }
84
85 // Ok, we should prettyprint. Let's do it!
86 *aDidPrettyPrint = true;
87 nsresult rv = NS_OK;
88
89 // Load the XSLT
90 nsCOMPtr<nsIURI> xslUri;
91 rv = NS_NewURI(
92 getter_AddRefs(xslUri),
93 NS_LITERAL_CSTRING("chrome://global/content/xml/XMLPrettyPrint.xsl"));
94 NS_ENSURE_SUCCESS(rv, rv);
95
96 nsCOMPtr<nsIDOMDocument> xslDocument;
97 rv = nsSyncLoadService::LoadDocument(
98 xslUri, nsIContentPolicy::TYPE_XSLT, nsContentUtils::GetSystemPrincipal(),
99 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nullptr, true,
100 mozilla::net::RP_Unset, getter_AddRefs(xslDocument));
101 NS_ENSURE_SUCCESS(rv, rv);
102
103 // Transform the document
104 RefPtr<txMozillaXSLTProcessor> transformer = new txMozillaXSLTProcessor();
105 ErrorResult err;
106 nsCOMPtr<nsIDocument> xslDoc = do_QueryInterface(xslDocument);
107 transformer->ImportStylesheet(*xslDoc, err);
108 if (NS_WARN_IF(err.Failed())) {
109 return err.StealNSResult();
110 }
111
112 RefPtr<DocumentFragment> resultFragment =
113 transformer->TransformToFragment(*aDocument, *aDocument, err);
114 if (NS_WARN_IF(err.Failed())) {
115 return err.StealNSResult();
116 }
117
118 //
119 // Apply the prettprint XBL binding.
120 //
121 // We take some shortcuts here. In particular, we don't bother invoking the
122 // contstructor (since the binding has no constructor), and we don't bother
123 // calling LoadBindingDocument because it's a chrome:// URI and thus will get
124 // sync loaded no matter what.
125 //
126
127 // Grab the XBL service.
128 nsXBLService* xblService = nsXBLService::GetInstance();
129 NS_ENSURE_TRUE(xblService, NS_ERROR_NOT_AVAILABLE);
130
131 // Compute the binding URI.
132 nsCOMPtr<nsIURI> bindingUri;
133 rv = NS_NewURI(
134 getter_AddRefs(bindingUri),
135 NS_LITERAL_STRING(
136 "chrome://global/content/xml/XMLPrettyPrint.xml#prettyprint"));
137 NS_ENSURE_SUCCESS(rv, rv);
138
139 // Compute the bound element.
140 RefPtr<Element> rootElement = aDocument->GetRootElement();
141 NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
142
143 // Grab the system principal.
144 nsCOMPtr<nsIPrincipal> sysPrincipal;
145 nsContentUtils::GetSecurityManager()->GetSystemPrincipal(
146 getter_AddRefs(sysPrincipal));
147
148 // Destroy any existing frames before we unbind anonymous content.
149 // Note that the shell might be Destroy'ed by now (see bug 1415541).
150 if (!shell->IsDestroying()) {
151 shell->DestroyFramesForAndRestyle(rootElement);
152 }
153
154 // Load the bindings.
155 RefPtr<nsXBLBinding> unused;
156 bool ignored;
157 rv = xblService->LoadBindings(rootElement, bindingUri, sysPrincipal,
158 getter_AddRefs(unused), &ignored);
159 NS_ENSURE_SUCCESS(rv, rv);
160
161 // Fire an event at the bound element to pass it |resultFragment|.
162 RefPtr<CustomEvent> event =
163 NS_NewDOMCustomEvent(rootElement, nullptr, nullptr);
164 MOZ_ASSERT(event);
165 nsCOMPtr<nsIWritableVariant> resultFragmentVariant = new nsVariant();
166 rv = resultFragmentVariant->SetAsISupports(ToSupports(resultFragment.get()));
167 MOZ_ASSERT(NS_SUCCEEDED(rv));
168 rv = event->InitCustomEvent(NS_LITERAL_STRING("prettyprint-dom-created"),
169 /* bubbles = */ false, /* cancelable = */ false,
170 /* detail = */ resultFragmentVariant);
171 NS_ENSURE_SUCCESS(rv, rv);
172 event->SetTrusted(true);
173 bool dummy;
174 rv = rootElement->DispatchEvent(event, &dummy);
175 NS_ENSURE_SUCCESS(rv, rv);
176
177 // Observe the document so we know when to switch to "normal" view
178 aDocument->AddObserver(this);
179 mDocument = aDocument;
180
181 NS_ADDREF_THIS();
182
183 return NS_OK;
184 }
185
MaybeUnhook(nsIContent * aContent)186 void nsXMLPrettyPrinter::MaybeUnhook(nsIContent* aContent) {
187 // If there either aContent is null (the document-node was modified) or
188 // there isn't a binding parent we know it's non-anonymous content.
189 if ((!aContent || !aContent->GetBindingParent()) && !mUnhookPending) {
190 // Can't blindly to mUnhookPending after AddScriptRunner,
191 // since AddScriptRunner _could_ in theory run us
192 // synchronously
193 mUnhookPending = true;
194 nsContentUtils::AddScriptRunner(NewRunnableMethod(
195 "nsXMLPrettyPrinter::Unhook", this, &nsXMLPrettyPrinter::Unhook));
196 }
197 }
198
Unhook()199 void nsXMLPrettyPrinter::Unhook() {
200 mDocument->RemoveObserver(this);
201 nsCOMPtr<Element> element = mDocument->GetDocumentElement();
202
203 if (element) {
204 mDocument->BindingManager()->ClearBinding(element);
205 }
206
207 mDocument = nullptr;
208
209 NS_RELEASE_THIS();
210 }
211
AttributeChanged(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)212 void nsXMLPrettyPrinter::AttributeChanged(Element* aElement,
213 int32_t aNameSpaceID,
214 nsAtom* aAttribute, int32_t aModType,
215 const nsAttrValue* aOldValue) {
216 MaybeUnhook(aElement);
217 }
218
ContentAppended(nsIContent * aFirstNewContent)219 void nsXMLPrettyPrinter::ContentAppended(nsIContent* aFirstNewContent) {
220 MaybeUnhook(aFirstNewContent->GetParent());
221 }
222
ContentInserted(nsIContent * aChild)223 void nsXMLPrettyPrinter::ContentInserted(nsIContent* aChild) {
224 MaybeUnhook(aChild->GetParent());
225 }
226
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)227 void nsXMLPrettyPrinter::ContentRemoved(nsIContent* aChild,
228 nsIContent* aPreviousSibling) {
229 MaybeUnhook(aChild->GetParent());
230 }
231
NodeWillBeDestroyed(const nsINode * aNode)232 void nsXMLPrettyPrinter::NodeWillBeDestroyed(const nsINode* aNode) {
233 mDocument = nullptr;
234 NS_RELEASE_THIS();
235 }
236
NS_NewXMLPrettyPrinter(nsXMLPrettyPrinter ** aPrinter)237 nsresult NS_NewXMLPrettyPrinter(nsXMLPrettyPrinter** aPrinter) {
238 *aPrinter = new nsXMLPrettyPrinter;
239 NS_ADDREF(*aPrinter);
240 return NS_OK;
241 }
242