1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsWyciwygChannel.h"
9 #include "nsILoadGroup.h"
10 #include "nsNetUtil.h"
11 #include "LoadContextInfo.h"
12 #include "nsICacheService.h" // only to initialize
13 #include "nsICacheStorageService.h"
14 #include "nsICacheStorage.h"
15 #include "nsICacheEntry.h"
16 #include "CacheObserver.h"
17 #include "nsCharsetSource.h"
18 #include "nsProxyRelease.h"
19 #include "nsThreadUtils.h"
20 #include "nsIEventTarget.h"
21 #include "nsIInputStream.h"
22 #include "nsIInputStreamPump.h"
23 #include "nsIOutputStream.h"
24 #include "nsIProgressEventSink.h"
26 #include "mozilla/DebugOnly.h"
27 #include "mozilla/unused.h"
29 typedef mozilla::net::LoadContextInfo LoadContextInfo
;
31 // Must release mChannel on the main thread
32 class nsWyciwygAsyncEvent
: public nsRunnable
{
34 explicit nsWyciwygAsyncEvent(nsWyciwygChannel
*aChannel
) : mChannel(aChannel
) {}
36 ~nsWyciwygAsyncEvent()
38 nsCOMPtr
<nsIThread
> thread
= do_GetMainThread();
39 NS_WARN_IF_FALSE(thread
, "Couldn't get the main thread!");
41 nsIWyciwygChannel
*chan
= static_cast<nsIWyciwygChannel
*>(mChannel
);
42 mozilla::unused
<< mChannel
.forget();
43 NS_ProxyRelease(thread
, chan
);
47 nsRefPtr
<nsWyciwygChannel
> mChannel
;
50 class nsWyciwygSetCharsetandSourceEvent
: public nsWyciwygAsyncEvent
{
52 explicit nsWyciwygSetCharsetandSourceEvent(nsWyciwygChannel
*aChannel
)
53 : nsWyciwygAsyncEvent(aChannel
) {}
57 mChannel
->SetCharsetAndSourceInternal();
62 class nsWyciwygWriteEvent
: public nsWyciwygAsyncEvent
{
64 nsWyciwygWriteEvent(nsWyciwygChannel
*aChannel
, const nsAString
&aData
)
65 : nsWyciwygAsyncEvent(aChannel
), mData(aData
) {}
69 mChannel
->WriteToCacheEntryInternal(mData
);
76 class nsWyciwygCloseEvent
: public nsWyciwygAsyncEvent
{
78 nsWyciwygCloseEvent(nsWyciwygChannel
*aChannel
, nsresult aReason
)
79 : nsWyciwygAsyncEvent(aChannel
), mReason(aReason
) {}
83 mChannel
->CloseCacheEntryInternal(mReason
);
91 // nsWyciwygChannel methods
92 nsWyciwygChannel::nsWyciwygChannel()
96 mCharsetAndSourceSet(false),
97 mNeedToWriteCharset(false),
98 mCharsetSource(kCharsetUninitialized
),
100 mLoadFlags(LOAD_NORMAL
),
101 mAppId(NECKO_NO_APP_ID
),
106 nsWyciwygChannel::~nsWyciwygChannel()
110 NS_IMPL_ISUPPORTS(nsWyciwygChannel
,
115 nsICacheEntryOpenCallback
,
117 nsIPrivateBrowsingChannel
)
120 nsWyciwygChannel::Init(nsIURI
* uri
)
122 NS_ENSURE_ARG_POINTER(uri
);
126 if (!mozilla::net::CacheObserver::UseNewCache()) {
127 // Since nsWyciwygChannel can use the new cache API off the main thread
128 // and that API normally does this initiation, we need to take care
129 // of initiating the old cache service here manually. Will be removed
131 MOZ_ASSERT(NS_IsMainThread());
132 nsCOMPtr
<nsICacheService
> service
=
133 do_GetService(NS_CACHESERVICE_CONTRACTID
, &rv
);
139 nsCOMPtr
<nsICacheStorageService
> serv
=
140 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv
);
141 NS_ENSURE_SUCCESS(rv
, rv
);
143 rv
= serv
->GetIoTarget(getter_AddRefs(mCacheIOTarget
));
144 NS_ENSURE_SUCCESS(rv
, rv
);
149 ///////////////////////////////////////////////////////////////////////////////
150 // nsIRequest methods:
151 ///////////////////////////////////////////////////////////////////////////////
154 nsWyciwygChannel::GetName(nsACString
&aName
)
156 return mURI
->GetSpec(aName
);
160 nsWyciwygChannel::IsPending(bool *aIsPending
)
162 *aIsPending
= mIsPending
;
167 nsWyciwygChannel::GetStatus(nsresult
*aStatus
)
169 if (NS_SUCCEEDED(mStatus
) && mPump
)
170 mPump
->GetStatus(aStatus
);
177 nsWyciwygChannel::Cancel(nsresult status
)
181 mPump
->Cancel(status
);
182 // else we're waiting for OnCacheEntryAvailable
187 nsWyciwygChannel::Suspend()
191 // XXX else, we'll ignore this ... and that's probably bad!
196 nsWyciwygChannel::Resume()
200 // XXX else, we'll ignore this ... and that's probably bad!
205 nsWyciwygChannel::GetLoadGroup(nsILoadGroup
* *aLoadGroup
)
207 *aLoadGroup
= mLoadGroup
;
208 NS_IF_ADDREF(*aLoadGroup
);
213 nsWyciwygChannel::SetLoadGroup(nsILoadGroup
* aLoadGroup
)
215 if (!CanSetLoadGroup(aLoadGroup
)) {
216 return NS_ERROR_FAILURE
;
219 mLoadGroup
= aLoadGroup
;
220 NS_QueryNotificationCallbacks(mCallbacks
,
222 NS_GET_IID(nsIProgressEventSink
),
223 getter_AddRefs(mProgressSink
));
224 mPrivateBrowsing
= NS_UsePrivateBrowsing(this);
225 NS_GetAppInfo(this, &mAppId
, &mInBrowser
);
230 nsWyciwygChannel::SetLoadFlags(uint32_t aLoadFlags
)
232 mLoadFlags
= aLoadFlags
;
237 nsWyciwygChannel::GetLoadFlags(uint32_t * aLoadFlags
)
239 *aLoadFlags
= mLoadFlags
;
243 ////////////////////////////////////////////////////////////////////////////////
244 // nsIChannel methods:
245 ///////////////////////////////////////////////////////////////////////////////
248 nsWyciwygChannel::GetOriginalURI(nsIURI
* *aURI
)
250 *aURI
= mOriginalURI
;
256 nsWyciwygChannel::SetOriginalURI(nsIURI
* aURI
)
258 NS_ENSURE_ARG_POINTER(aURI
);
264 nsWyciwygChannel::GetURI(nsIURI
* *aURI
)
272 nsWyciwygChannel::GetOwner(nsISupports
**aOwner
)
274 NS_IF_ADDREF(*aOwner
= mOwner
);
279 nsWyciwygChannel::SetOwner(nsISupports
* aOwner
)
286 nsWyciwygChannel::GetLoadInfo(nsILoadInfo
**aLoadInfo
)
288 NS_IF_ADDREF(*aLoadInfo
= mLoadInfo
);
293 nsWyciwygChannel::SetLoadInfo(nsILoadInfo
* aLoadInfo
)
295 mLoadInfo
= aLoadInfo
;
300 nsWyciwygChannel::GetNotificationCallbacks(nsIInterfaceRequestor
* *aCallbacks
)
302 *aCallbacks
= mCallbacks
.get();
303 NS_IF_ADDREF(*aCallbacks
);
308 nsWyciwygChannel::SetNotificationCallbacks(nsIInterfaceRequestor
* aNotificationCallbacks
)
310 if (!CanSetCallbacks(aNotificationCallbacks
)) {
311 return NS_ERROR_FAILURE
;
314 mCallbacks
= aNotificationCallbacks
;
315 NS_QueryNotificationCallbacks(mCallbacks
,
317 NS_GET_IID(nsIProgressEventSink
),
318 getter_AddRefs(mProgressSink
));
320 mPrivateBrowsing
= NS_UsePrivateBrowsing(this);
321 NS_GetAppInfo(this, &mAppId
, &mInBrowser
);
327 nsWyciwygChannel::GetSecurityInfo(nsISupports
* *aSecurityInfo
)
329 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
335 nsWyciwygChannel::GetContentType(nsACString
&aContentType
)
337 aContentType
.AssignLiteral(WYCIWYG_TYPE
);
342 nsWyciwygChannel::SetContentType(const nsACString
&aContentType
)
344 return NS_ERROR_NOT_IMPLEMENTED
;
348 nsWyciwygChannel::GetContentCharset(nsACString
&aContentCharset
)
350 aContentCharset
.AssignLiteral("UTF-16");
355 nsWyciwygChannel::SetContentCharset(const nsACString
&aContentCharset
)
357 return NS_ERROR_NOT_IMPLEMENTED
;
361 nsWyciwygChannel::GetContentDisposition(uint32_t *aContentDisposition
)
363 return NS_ERROR_NOT_AVAILABLE
;
367 nsWyciwygChannel::SetContentDisposition(uint32_t aContentDisposition
)
369 return NS_ERROR_NOT_AVAILABLE
;
373 nsWyciwygChannel::GetContentDispositionFilename(nsAString
&aContentDispositionFilename
)
375 return NS_ERROR_NOT_AVAILABLE
;
379 nsWyciwygChannel::SetContentDispositionFilename(const nsAString
&aContentDispositionFilename
)
381 return NS_ERROR_NOT_AVAILABLE
;
385 nsWyciwygChannel::GetContentDispositionHeader(nsACString
&aContentDispositionHeader
)
387 return NS_ERROR_NOT_AVAILABLE
;
391 nsWyciwygChannel::GetContentLength(int64_t *aContentLength
)
393 *aContentLength
= mContentLength
;
398 nsWyciwygChannel::SetContentLength(int64_t aContentLength
)
400 mContentLength
= aContentLength
;
406 nsWyciwygChannel::Open(nsIInputStream
** aReturn
)
408 return NS_ERROR_NOT_IMPLEMENTED
;
412 nsWyciwygChannel::AsyncOpen(nsIStreamListener
*listener
, nsISupports
*ctx
)
414 LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this));
415 MOZ_ASSERT(mMode
== NONE
, "nsWyciwygChannel already open");
417 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
418 NS_ENSURE_TRUE(mMode
== NONE
, NS_ERROR_IN_PROGRESS
);
419 NS_ENSURE_ARG_POINTER(listener
);
423 // open a cache entry for this channel...
424 // mIsPending set to true since OnCacheEntryAvailable may be called
425 // synchronously and fails when mIsPending found false.
427 nsresult rv
= OpenCacheEntry(mURI
, nsICacheStorage::OPEN_READONLY
|
428 nsICacheStorage::CHECK_MULTITHREADED
);
430 LOG(("nsWyciwygChannel::OpenCacheEntry failed [rv=%x]\n", rv
));
435 // There is no code path that would invoke the listener sooner than
436 // we get to this line in case OnCacheEntryAvailable is invoked
438 mListener
= listener
;
439 mListenerContext
= ctx
;
442 mLoadGroup
->AddRequest(this, nullptr);
447 //////////////////////////////////////////////////////////////////////////////
449 //////////////////////////////////////////////////////////////////////////////
452 nsWyciwygChannel::EnsureWriteCacheEntry()
454 MOZ_ASSERT(mMode
== WRITING
, "nsWyciwygChannel not open for writing");
457 // OPEN_TRUNCATE will give us the entry instantly
458 nsresult rv
= OpenCacheEntry(mURI
, nsICacheStorage::OPEN_TRUNCATE
);
459 if (NS_FAILED(rv
) || !mCacheEntry
) {
460 LOG((" could not synchronously open cache entry for write!"));
461 return NS_ERROR_FAILURE
;
469 nsWyciwygChannel::WriteToCacheEntry(const nsAString
&aData
)
471 if (mMode
== READING
) {
472 LOG(("nsWyciwygChannel::WriteToCacheEntry already open for reading"));
474 return NS_ERROR_UNEXPECTED
;
479 if (mozilla::net::CacheObserver::UseNewCache()) {
480 nsresult rv
= EnsureWriteCacheEntry();
481 if (NS_FAILED(rv
)) return rv
;
484 return mCacheIOTarget
->Dispatch(new nsWyciwygWriteEvent(this, aData
),
489 nsWyciwygChannel::WriteToCacheEntryInternal(const nsAString
&aData
)
491 LOG(("nsWyciwygChannel::WriteToCacheEntryInternal [this=%p]", this));
492 NS_ASSERTION(IsOnCacheIOThread(), "wrong thread");
496 // With the new cache entry this will just pass as a no-op since we
497 // are opening the entry in WriteToCacheEntry.
498 rv
= EnsureWriteCacheEntry();
499 if (NS_WARN_IF(NS_FAILED(rv
))) {
503 if (mLoadFlags
& INHIBIT_PERSISTENT_CACHING
) {
504 rv
= mCacheEntry
->SetMetaDataElement("inhibit-persistent-caching", "1");
505 if (NS_FAILED(rv
)) return rv
;
509 mCacheEntry
->SetSecurityInfo(mSecurityInfo
);
512 if (mNeedToWriteCharset
) {
513 WriteCharsetAndSourceToCache(mCharsetSource
, mCharset
);
514 mNeedToWriteCharset
= false;
518 if (!mCacheOutputStream
) {
519 // Get the outputstream from the cache entry.
520 rv
= mCacheEntry
->OpenOutputStream(0, getter_AddRefs(mCacheOutputStream
));
521 if (NS_FAILED(rv
)) return rv
;
523 // Write out a Byte Order Mark, so that we'll know if the data is
524 // BE or LE when we go to read it.
525 char16_t bom
= 0xFEFF;
526 rv
= mCacheOutputStream
->Write((char *)&bom
, sizeof(bom
), &out
);
527 if (NS_FAILED(rv
)) return rv
;
530 return mCacheOutputStream
->Write((const char *)PromiseFlatString(aData
).get(),
531 aData
.Length() * sizeof(char16_t
), &out
);
536 nsWyciwygChannel::CloseCacheEntry(nsresult reason
)
538 return mCacheIOTarget
->Dispatch(new nsWyciwygCloseEvent(this, reason
),
543 nsWyciwygChannel::CloseCacheEntryInternal(nsresult reason
)
545 NS_ASSERTION(IsOnCacheIOThread(), "wrong thread");
548 LOG(("nsWyciwygChannel::CloseCacheEntryInternal [this=%p ]", this));
549 mCacheOutputStream
= 0;
550 mCacheInputStream
= 0;
552 if (NS_FAILED(reason
))
553 mCacheEntry
->AsyncDoom(nullptr); // here we were calling Doom() ...
561 nsWyciwygChannel::SetSecurityInfo(nsISupports
*aSecurityInfo
)
563 mSecurityInfo
= aSecurityInfo
;
569 nsWyciwygChannel::SetCharsetAndSource(int32_t aSource
,
570 const nsACString
& aCharset
)
572 NS_ENSURE_ARG(!aCharset
.IsEmpty());
574 mCharsetAndSourceSet
= true;
576 mCharsetSource
= aSource
;
578 return mCacheIOTarget
->Dispatch(new nsWyciwygSetCharsetandSourceEvent(this),
583 nsWyciwygChannel::SetCharsetAndSourceInternal()
585 NS_ASSERTION(IsOnCacheIOThread(), "wrong thread");
588 WriteCharsetAndSourceToCache(mCharsetSource
, mCharset
);
590 mNeedToWriteCharset
= true;
595 nsWyciwygChannel::GetCharsetAndSource(int32_t* aSource
, nsACString
& aCharset
)
597 if (mCharsetAndSourceSet
) {
598 *aSource
= mCharsetSource
;
604 return NS_ERROR_NOT_AVAILABLE
;
608 mCacheEntry
->GetMetaDataElement("charset", getter_Copies(data
));
610 if (data
.IsEmpty()) {
611 return NS_ERROR_NOT_AVAILABLE
;
614 nsXPIDLCString sourceStr
;
615 mCacheEntry
->GetMetaDataElement("charset-source", getter_Copies(sourceStr
));
619 source
= sourceStr
.ToInteger(&err
);
620 if (NS_FAILED(err
) || source
== 0) {
621 return NS_ERROR_NOT_AVAILABLE
;
629 //////////////////////////////////////////////////////////////////////////////
630 // nsICacheEntryOpenCallback
631 //////////////////////////////////////////////////////////////////////////////
634 nsWyciwygChannel::OnCacheEntryCheck(nsICacheEntry
* entry
, nsIApplicationCache
* appCache
,
637 *aResult
= ENTRY_WANTED
;
642 nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntry
*aCacheEntry
,
644 nsIApplicationCache
* aAppCache
,
647 LOG(("nsWyciwygChannel::OnCacheEntryAvailable [this=%p entry=%p "
648 "new=%d status=%x]\n", this, aCacheEntry
, aNew
, aStatus
));
650 // if the channel's already fired onStopRequest,
651 // then we should ignore this event.
652 if (!mIsPending
&& !aNew
)
655 // otherwise, we have to handle this event.
656 if (NS_SUCCEEDED(aStatus
))
657 mCacheEntry
= aCacheEntry
;
658 else if (NS_SUCCEEDED(mStatus
))
662 if (NS_FAILED(mStatus
)) {
663 LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus
));
666 else if (!aNew
) { // advance to the next state...
667 rv
= ReadFromCache();
670 // a failure from Connect means that we have to abort the channel.
675 // Since OnCacheEntryAvailable can be called directly from AsyncOpen
677 NS_DispatchToCurrentThread(NS_NewRunnableMethod(
678 this, &nsWyciwygChannel::NotifyListener
));
685 //-----------------------------------------------------------------------------
686 // nsWyciwygChannel::nsIStreamListener
687 //-----------------------------------------------------------------------------
690 nsWyciwygChannel::OnDataAvailable(nsIRequest
*request
, nsISupports
*ctx
,
691 nsIInputStream
*input
,
692 uint64_t offset
, uint32_t count
)
694 LOG(("nsWyciwygChannel::OnDataAvailable [this=%p request=%x offset=%llu count=%u]\n",
695 this, request
, offset
, count
));
699 rv
= mListener
->OnDataAvailable(this, mListenerContext
, input
, offset
, count
);
701 // XXX handle 64-bit stuff for real
702 if (mProgressSink
&& NS_SUCCEEDED(rv
)) {
703 mProgressSink
->OnProgress(this, nullptr, offset
+ count
,
704 uint64_t(mContentLength
));
707 return rv
; // let the pump cancel on failure
710 //////////////////////////////////////////////////////////////////////////////
711 // nsIRequestObserver
712 //////////////////////////////////////////////////////////////////////////////
715 nsWyciwygChannel::OnStartRequest(nsIRequest
*request
, nsISupports
*ctx
)
717 LOG(("nsWyciwygChannel::OnStartRequest [this=%p request=%x\n",
720 return mListener
->OnStartRequest(this, mListenerContext
);
725 nsWyciwygChannel::OnStopRequest(nsIRequest
*request
, nsISupports
*ctx
, nsresult status
)
727 LOG(("nsWyciwygChannel::OnStopRequest [this=%p request=%x status=%d\n",
728 this, request
, status
));
730 if (NS_SUCCEEDED(mStatus
))
733 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
735 mListenerContext
= 0;
738 mLoadGroup
->RemoveRequest(this, nullptr, mStatus
);
740 CloseCacheEntry(mStatus
);
744 // Drop notification callbacks to prevent cycles.
751 //////////////////////////////////////////////////////////////////////////////
753 //////////////////////////////////////////////////////////////////////////////
756 nsWyciwygChannel::OpenCacheEntry(nsIURI
*aURI
,
761 nsCOMPtr
<nsICacheStorageService
> cacheService
=
762 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv
);
763 NS_ENSURE_SUCCESS(rv
, rv
);
765 bool anonymous
= mLoadFlags
& LOAD_ANONYMOUS
;
766 nsRefPtr
<LoadContextInfo
> loadInfo
= mozilla::net::GetLoadContextInfo(
767 mPrivateBrowsing
, mAppId
, mInBrowser
, anonymous
);
769 nsCOMPtr
<nsICacheStorage
> cacheStorage
;
770 if (mLoadFlags
& INHIBIT_PERSISTENT_CACHING
)
771 rv
= cacheService
->MemoryCacheStorage(loadInfo
, getter_AddRefs(cacheStorage
));
773 rv
= cacheService
->DiskCacheStorage(loadInfo
, false, getter_AddRefs(cacheStorage
));
774 NS_ENSURE_SUCCESS(rv
, rv
);
776 rv
= cacheStorage
->AsyncOpenURI(aURI
, EmptyCString(), aOpenFlags
, this);
777 NS_ENSURE_SUCCESS(rv
, rv
);
783 nsWyciwygChannel::ReadFromCache()
785 LOG(("nsWyciwygChannel::ReadFromCache [this=%p] ", this));
787 NS_ENSURE_TRUE(mCacheEntry
, NS_ERROR_FAILURE
);
790 // Get the stored security info
791 mCacheEntry
->GetSecurityInfo(getter_AddRefs(mSecurityInfo
));
793 nsAutoCString tmpStr
;
794 rv
= mCacheEntry
->GetMetaDataElement("inhibit-persistent-caching",
795 getter_Copies(tmpStr
));
796 if (NS_SUCCEEDED(rv
) && tmpStr
.EqualsLiteral("1"))
797 mLoadFlags
|= INHIBIT_PERSISTENT_CACHING
;
799 // Get a transport to the cached data...
800 rv
= mCacheEntry
->OpenInputStream(0, getter_AddRefs(mCacheInputStream
));
803 NS_ENSURE_TRUE(mCacheInputStream
, NS_ERROR_UNEXPECTED
);
805 rv
= NS_NewInputStreamPump(getter_AddRefs(mPump
), mCacheInputStream
);
806 if (NS_FAILED(rv
)) return rv
;
808 // Pump the cache data downstream
809 return mPump
->AsyncRead(this, nullptr);
813 nsWyciwygChannel::WriteCharsetAndSourceToCache(int32_t aSource
,
814 const nsCString
& aCharset
)
816 NS_ASSERTION(IsOnCacheIOThread(), "wrong thread");
817 NS_PRECONDITION(mCacheEntry
, "Better have cache entry!");
819 mCacheEntry
->SetMetaDataElement("charset", aCharset
.get());
821 nsAutoCString source
;
822 source
.AppendInt(aSource
);
823 mCacheEntry
->SetMetaDataElement("charset-source", source
.get());
827 nsWyciwygChannel::NotifyListener()
830 mListener
->OnStartRequest(this, mListenerContext
);
831 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
833 mListenerContext
= 0;
838 // Remove ourselves from the load group.
840 mLoadGroup
->RemoveRequest(this, nullptr, mStatus
);
845 nsWyciwygChannel::IsOnCacheIOThread()
848 mCacheIOTarget
->IsOnCurrentThread(&correctThread
);
849 return correctThread
;