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/. */
7 * A service that provides methods for synchronously loading a DOM in various
11 #include "nsSyncLoadService.h"
13 #include "nsIChannel.h"
14 #include "nsIChannelEventSink.h"
15 #include "nsIAsyncVerifyRedirectCallback.h"
16 #include "nsIInterfaceRequestor.h"
17 #include "nsIStreamListener.h"
20 #include "nsWeakReference.h"
21 #include "mozilla/dom/Document.h"
22 #include "nsIHttpChannel.h"
23 #include "nsIPrincipal.h"
24 #include "nsContentUtils.h" // for kLoadAsData
25 #include "nsThreadUtils.h"
26 #include "nsNetUtil.h"
27 #include "nsStreamUtils.h"
28 #include "ReferrerInfo.h"
31 using namespace mozilla
;
32 using namespace mozilla::dom
;
34 using mozilla::dom::ReferrerPolicy
;
37 * This class manages loading a single XML document
40 class nsSyncLoader
: public nsIStreamListener
,
41 public nsIChannelEventSink
,
42 public nsIInterfaceRequestor
,
43 public nsSupportsWeakReference
{
46 : mLoading(false), mAsyncLoadStatus(NS_ERROR_NOT_INITIALIZED
) {}
50 nsresult
LoadDocument(nsIChannel
* aChannel
, bool aChannelIsSync
,
51 bool aForceToXML
, ReferrerPolicy aReferrerPolicy
,
54 NS_FORWARD_NSISTREAMLISTENER(mListener
->)
55 NS_DECL_NSIREQUESTOBSERVER
57 NS_DECL_NSICHANNELEVENTSINK
59 NS_DECL_NSIINTERFACEREQUESTOR
62 virtual ~nsSyncLoader();
64 nsresult
PushAsyncStream(nsIStreamListener
* aListener
);
65 nsresult
PushSyncStream(nsIStreamListener
* aListener
);
67 nsCOMPtr
<nsIChannel
> mChannel
;
68 nsCOMPtr
<nsIStreamListener
> mListener
;
70 nsresult mAsyncLoadStatus
;
73 class nsForceXMLListener
: public nsIStreamListener
{
74 virtual ~nsForceXMLListener();
77 explicit nsForceXMLListener(nsIStreamListener
* aListener
);
80 NS_FORWARD_NSISTREAMLISTENER(mListener
->)
81 NS_DECL_NSIREQUESTOBSERVER
84 nsCOMPtr
<nsIStreamListener
> mListener
;
87 nsForceXMLListener::nsForceXMLListener(nsIStreamListener
* aListener
)
88 : mListener(aListener
) {}
90 nsForceXMLListener::~nsForceXMLListener() = default;
92 NS_IMPL_ISUPPORTS(nsForceXMLListener
, nsIStreamListener
, nsIRequestObserver
)
95 nsForceXMLListener::OnStartRequest(nsIRequest
* aRequest
) {
97 aRequest
->GetStatus(&status
);
98 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
99 if (channel
&& NS_SUCCEEDED(status
)) {
100 channel
->SetContentType("text/xml"_ns
);
103 return mListener
->OnStartRequest(aRequest
);
107 nsForceXMLListener::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
108 return mListener
->OnStopRequest(aRequest
, aStatusCode
);
111 nsSyncLoader::~nsSyncLoader() {
112 if (mLoading
&& mChannel
) {
113 mChannel
->CancelWithReason(NS_BINDING_ABORTED
,
114 "nsSyncLoader::~nsSyncLoader"_ns
);
118 NS_IMPL_ISUPPORTS(nsSyncLoader
, nsIStreamListener
, nsIRequestObserver
,
119 nsIChannelEventSink
, nsIInterfaceRequestor
,
120 nsISupportsWeakReference
)
122 nsresult
nsSyncLoader::LoadDocument(nsIChannel
* aChannel
, bool aChannelIsSync
,
124 ReferrerPolicy aReferrerPolicy
,
125 Document
** aResult
) {
126 NS_ENSURE_ARG(aChannel
);
127 NS_ENSURE_ARG_POINTER(aResult
);
132 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(mChannel
);
134 rv
= http
->SetRequestHeader(
137 "text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
139 MOZ_ASSERT(NS_SUCCEEDED(rv
));
140 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
141 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
142 loadInfo
->TriggeringPrincipal()->CreateReferrerInfo(
143 aReferrerPolicy
, getter_AddRefs(referrerInfo
));
145 rv
= http
->SetReferrerInfoWithoutClone(referrerInfo
);
146 MOZ_ASSERT(NS_SUCCEEDED(rv
));
150 // Hook us up to listen to redirects and the like.
151 // Do this before setting up the cross-site proxy since
152 // that installs its own proxies.
153 mChannel
->SetNotificationCallbacks(this);
155 // Get the loadgroup of the channel
156 nsCOMPtr
<nsILoadGroup
> loadGroup
;
157 rv
= aChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
158 NS_ENSURE_SUCCESS(rv
, rv
);
161 nsCOMPtr
<Document
> document
;
162 rv
= NS_NewXMLDocument(getter_AddRefs(document
), nullptr, nullptr);
163 NS_ENSURE_SUCCESS(rv
, rv
);
165 // Start the document load. Do this before we attach the load listener
166 // since we reset the document which drops all observers.
167 nsCOMPtr
<nsIStreamListener
> listener
;
168 rv
= document
->StartDocumentLoad(kLoadAsData
, mChannel
, loadGroup
, nullptr,
169 getter_AddRefs(listener
), true);
170 NS_ENSURE_SUCCESS(rv
, rv
);
173 nsCOMPtr
<nsIStreamListener
> forceListener
=
174 new nsForceXMLListener(listener
);
175 listener
.swap(forceListener
);
178 if (aChannelIsSync
) {
179 rv
= PushSyncStream(listener
);
181 rv
= PushAsyncStream(listener
);
184 http
= do_QueryInterface(mChannel
);
185 if (NS_SUCCEEDED(rv
) && http
) {
187 if (NS_FAILED(http
->GetRequestSucceeded(&succeeded
)) || !succeeded
) {
188 rv
= NS_ERROR_FAILURE
;
193 // check that the load succeeded
194 NS_ENSURE_SUCCESS(rv
, rv
);
196 NS_ENSURE_TRUE(document
->GetRootElement(), NS_ERROR_FAILURE
);
198 document
.forget(aResult
);
203 nsresult
nsSyncLoader::PushAsyncStream(nsIStreamListener
* aListener
) {
204 mListener
= aListener
;
206 mAsyncLoadStatus
= NS_OK
;
208 // Start reading from the channel
209 nsresult rv
= mChannel
->AsyncOpen(this);
211 if (NS_SUCCEEDED(rv
)) {
212 // process events until we're finished.
214 nsIThread
* thread
= NS_GetCurrentThread();
215 while (mLoading
&& NS_SUCCEEDED(rv
)) {
217 rv
= thread
->ProcessNextEvent(true, &processedEvent
);
218 if (NS_SUCCEEDED(rv
) && !processedEvent
) rv
= NS_ERROR_UNEXPECTED
;
224 NS_ENSURE_SUCCESS(rv
, rv
);
226 // Note that if AsyncOpen failed that's ok -- the only caller of
227 // this method nulls out mChannel immediately after we return.
229 return mAsyncLoadStatus
;
232 nsresult
nsSyncLoader::PushSyncStream(nsIStreamListener
* aListener
) {
233 nsCOMPtr
<nsIInputStream
> in
;
234 nsresult rv
= mChannel
->Open(getter_AddRefs(in
));
235 NS_ENSURE_SUCCESS(rv
, rv
);
238 rv
= nsSyncLoadService::PushSyncStreamToListener(in
.forget(), aListener
,
246 nsSyncLoader::OnStartRequest(nsIRequest
* aRequest
) {
247 return mListener
->OnStartRequest(aRequest
);
251 nsSyncLoader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
252 if (NS_SUCCEEDED(mAsyncLoadStatus
) && NS_FAILED(aStatusCode
)) {
253 mAsyncLoadStatus
= aStatusCode
;
255 nsresult rv
= mListener
->OnStopRequest(aRequest
, aStatusCode
);
256 if (NS_SUCCEEDED(mAsyncLoadStatus
) && NS_FAILED(rv
)) {
257 mAsyncLoadStatus
= rv
;
265 nsSyncLoader::AsyncOnChannelRedirect(nsIChannel
* aOldChannel
,
266 nsIChannel
* aNewChannel
, uint32_t aFlags
,
267 nsIAsyncVerifyRedirectCallback
* callback
) {
268 MOZ_ASSERT(aNewChannel
, "Redirecting to null channel?");
270 mChannel
= aNewChannel
;
272 callback
->OnRedirectVerifyCallback(NS_OK
);
277 nsSyncLoader::GetInterface(const nsIID
& aIID
, void** aResult
) {
278 return QueryInterface(aIID
, aResult
);
282 nsresult
nsSyncLoadService::LoadDocument(
283 nsIURI
* aURI
, nsContentPolicyType aContentPolicyType
,
284 nsIPrincipal
* aLoaderPrincipal
, nsSecurityFlags aSecurityFlags
,
285 nsILoadGroup
* aLoadGroup
, nsICookieJarSettings
* aCookieJarSettings
,
286 bool aForceToXML
, ReferrerPolicy aReferrerPolicy
, Document
** aResult
) {
287 nsCOMPtr
<nsIChannel
> channel
;
289 NS_NewChannel(getter_AddRefs(channel
), aURI
, aLoaderPrincipal
,
290 aSecurityFlags
, aContentPolicyType
, aCookieJarSettings
,
291 nullptr, // PerformanceStorage
293 NS_ENSURE_SUCCESS(rv
, rv
);
296 channel
->SetContentType("text/xml"_ns
);
299 // if the load needs to enforce CORS, then force the load to be async
301 !(aSecurityFlags
& nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
) &&
302 (aURI
->SchemeIs("chrome") || aURI
->SchemeIs("resource"));
303 RefPtr
<nsSyncLoader
> loader
= new nsSyncLoader();
304 return loader
->LoadDocument(channel
, isSync
, aForceToXML
, aReferrerPolicy
,
309 nsresult
nsSyncLoadService::PushSyncStreamToListener(
310 already_AddRefed
<nsIInputStream
> aIn
, nsIStreamListener
* aListener
,
311 nsIChannel
* aChannel
) {
312 nsCOMPtr
<nsIInputStream
> in
= std::move(aIn
);
314 // Set up buffering stream
316 nsCOMPtr
<nsIInputStream
> bufferedStream
;
317 if (!NS_InputStreamIsBuffered(in
)) {
319 rv
= aChannel
->GetContentLength(&chunkSize
);
320 if (NS_FAILED(rv
) || chunkSize
< 1) {
323 chunkSize
= std::min(int64_t(UINT16_MAX
), chunkSize
);
325 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufferedStream
), in
.forget(),
327 NS_ENSURE_SUCCESS(rv
, rv
);
333 rv
= aListener
->OnStartRequest(aChannel
);
334 if (NS_SUCCEEDED(rv
)) {
335 uint64_t sourceOffset
= 0;
337 uint64_t readCount
= 0;
338 rv
= in
->Available(&readCount
);
339 if (NS_FAILED(rv
) || !readCount
) {
340 if (rv
== NS_BASE_STREAM_CLOSED
) {
341 // End of file, but not an error
347 if (readCount
> UINT32_MAX
) readCount
= UINT32_MAX
;
349 rv
= aListener
->OnDataAvailable(aChannel
, in
, sourceOffset
,
350 (uint32_t)readCount
);
354 sourceOffset
+= readCount
;
358 aChannel
->Cancel(rv
);
360 aListener
->OnStopRequest(aChannel
, rv
);