Bug 460926 A11y hierachy is broken on Ubuntu 8.10 (GNOME 2.24), r=Evan.Yan sr=roc
[wine-gecko.git] / uriloader / prefetch / nsOfflineCacheUpdate.cpp
blob3936b51328ab9d1d33139590efc1e7a857df2748
1 /* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation
19 * Portions created by the Initial Developer are Copyright (C) 2007
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Dave Camp <dcamp@mozilla.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsOfflineCacheUpdate.h"
41 #include "nsCPrefetchService.h"
42 #include "nsCURILoader.h"
43 #include "nsIApplicationCacheContainer.h"
44 #include "nsIApplicationCacheChannel.h"
45 #include "nsIApplicationCacheService.h"
46 #include "nsICache.h"
47 #include "nsICacheService.h"
48 #include "nsICacheSession.h"
49 #include "nsICachingChannel.h"
50 #include "nsIContent.h"
51 #include "nsIDocumentLoader.h"
52 #include "nsIDOMElement.h"
53 #include "nsIDOMWindow.h"
54 #include "nsIDOMOfflineResourceList.h"
55 #include "nsIDocument.h"
56 #include "nsIObserverService.h"
57 #include "nsIURL.h"
58 #include "nsIWebProgress.h"
59 #include "nsICryptoHash.h"
60 #include "nsICacheEntryDescriptor.h"
61 #include "nsIPermissionManager.h"
62 #include "nsIPrincipal.h"
63 #include "nsIPrefBranch.h"
64 #include "nsIPrefService.h"
65 #include "nsNetCID.h"
66 #include "nsNetUtil.h"
67 #include "nsServiceManagerUtils.h"
68 #include "nsStreamUtils.h"
69 #include "nsThreadUtils.h"
70 #include "prlog.h"
72 static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nsnull;
74 static const PRUint32 kRescheduleLimit = 3;
76 #if defined(PR_LOGGING)
78 // To enable logging (see prlog.h for full details):
80 // set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5
81 // set NSPR_LOG_FILE=offlineupdate.log
83 // this enables PR_LOG_ALWAYS level information and places all output in
84 // the file offlineupdate.log
86 static PRLogModuleInfo *gOfflineCacheUpdateLog;
87 #endif
88 #define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args)
89 #define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4)
91 class AutoFreeArray {
92 public:
93 AutoFreeArray(PRUint32 count, char **values)
94 : mCount(count), mValues(values) {};
95 ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
96 private:
97 PRUint32 mCount;
98 char **mValues;
101 static nsresult
102 DropReferenceFromURL(nsIURI * aURI)
104 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
105 if (url) {
106 nsresult rv = url->SetRef(EmptyCString());
107 NS_ENSURE_SUCCESS(rv, rv);
110 return NS_OK;
113 //-----------------------------------------------------------------------------
114 // nsManifestCheck
115 //-----------------------------------------------------------------------------
117 class nsManifestCheck : public nsIStreamListener
118 , public nsIChannelEventSink
119 , public nsIInterfaceRequestor
121 public:
122 nsManifestCheck(nsOfflineCacheUpdate *aUpdate,
123 nsIURI *aURI,
124 nsIURI *aReferrerURI)
125 : mUpdate(aUpdate)
126 , mURI(aURI)
127 , mReferrerURI(aReferrerURI)
130 NS_DECL_ISUPPORTS
131 NS_DECL_NSIREQUESTOBSERVER
132 NS_DECL_NSISTREAMLISTENER
133 NS_DECL_NSICHANNELEVENTSINK
134 NS_DECL_NSIINTERFACEREQUESTOR
136 nsresult Begin();
138 private:
140 static NS_METHOD ReadManifest(nsIInputStream *aInputStream,
141 void *aClosure,
142 const char *aFromSegment,
143 PRUint32 aOffset,
144 PRUint32 aCount,
145 PRUint32 *aBytesConsumed);
147 nsRefPtr<nsOfflineCacheUpdate> mUpdate;
148 nsCOMPtr<nsIURI> mURI;
149 nsCOMPtr<nsIURI> mReferrerURI;
150 nsCOMPtr<nsICryptoHash> mManifestHash;
151 nsCOMPtr<nsIChannel> mChannel;
154 //-----------------------------------------------------------------------------
155 // nsManifestCheck::nsISupports
156 //-----------------------------------------------------------------------------
157 NS_IMPL_ISUPPORTS4(nsManifestCheck,
158 nsIRequestObserver,
159 nsIStreamListener,
160 nsIChannelEventSink,
161 nsIInterfaceRequestor)
163 //-----------------------------------------------------------------------------
164 // nsManifestCheck <public>
165 //-----------------------------------------------------------------------------
167 nsresult
168 nsManifestCheck::Begin()
170 nsresult rv;
171 mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
172 NS_ENSURE_SUCCESS(rv, rv);
174 rv = mManifestHash->Init(nsICryptoHash::MD5);
175 NS_ENSURE_SUCCESS(rv, rv);
177 rv = NS_NewChannel(getter_AddRefs(mChannel),
178 mURI,
179 nsnull, nsnull, nsnull,
180 nsIRequest::LOAD_BYPASS_CACHE);
181 NS_ENSURE_SUCCESS(rv, rv);
183 // configure HTTP specific stuff
184 nsCOMPtr<nsIHttpChannel> httpChannel =
185 do_QueryInterface(mChannel);
186 if (httpChannel) {
187 httpChannel->SetReferrer(mReferrerURI);
188 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
189 NS_LITERAL_CSTRING("offline-resource"),
190 PR_FALSE);
193 rv = mChannel->AsyncOpen(this, nsnull);
194 NS_ENSURE_SUCCESS(rv, rv);
196 return NS_OK;
199 //-----------------------------------------------------------------------------
200 // nsManifestCheck <public>
201 //-----------------------------------------------------------------------------
203 /* static */
204 NS_METHOD
205 nsManifestCheck::ReadManifest(nsIInputStream *aInputStream,
206 void *aClosure,
207 const char *aFromSegment,
208 PRUint32 aOffset,
209 PRUint32 aCount,
210 PRUint32 *aBytesConsumed)
212 nsManifestCheck *manifestCheck =
213 static_cast<nsManifestCheck*>(aClosure);
215 nsresult rv;
216 *aBytesConsumed = aCount;
218 rv = manifestCheck->mManifestHash->Update(
219 reinterpret_cast<const PRUint8 *>(aFromSegment), aCount);
220 NS_ENSURE_SUCCESS(rv, rv);
222 return NS_OK;
225 //-----------------------------------------------------------------------------
226 // nsManifestCheck::nsIStreamListener
227 //-----------------------------------------------------------------------------
229 NS_IMETHODIMP
230 nsManifestCheck::OnStartRequest(nsIRequest *aRequest,
231 nsISupports *aContext)
233 return NS_OK;
236 NS_IMETHODIMP
237 nsManifestCheck::OnDataAvailable(nsIRequest *aRequest,
238 nsISupports *aContext,
239 nsIInputStream *aStream,
240 PRUint32 aOffset,
241 PRUint32 aCount)
243 PRUint32 bytesRead;
244 aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
245 return NS_OK;
248 NS_IMETHODIMP
249 nsManifestCheck::OnStopRequest(nsIRequest *aRequest,
250 nsISupports *aContext,
251 nsresult aStatus)
253 nsCAutoString manifestHash;
254 if (NS_SUCCEEDED(aStatus)) {
255 mManifestHash->Finish(PR_TRUE, manifestHash);
258 mUpdate->ManifestCheckCompleted(aStatus, manifestHash);
260 return NS_OK;
263 //-----------------------------------------------------------------------------
264 // nsManifestCheck::nsIInterfaceRequestor
265 //-----------------------------------------------------------------------------
267 NS_IMETHODIMP
268 nsManifestCheck::GetInterface(const nsIID &aIID, void **aResult)
270 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
271 NS_ADDREF_THIS();
272 *aResult = static_cast<nsIChannelEventSink *>(this);
273 return NS_OK;
276 return NS_ERROR_NO_INTERFACE;
279 //-----------------------------------------------------------------------------
280 // nsManifestCheck::nsIChannelEventSink
281 //-----------------------------------------------------------------------------
283 NS_IMETHODIMP
284 nsManifestCheck::OnChannelRedirect(nsIChannel *aOldChannel,
285 nsIChannel *aNewChannel,
286 PRUint32 aFlags)
288 // Redirects should cause the load (and therefore the update) to fail.
289 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)
290 return NS_OK;
291 aOldChannel->Cancel(NS_ERROR_ABORT);
292 return NS_ERROR_ABORT;
295 //-----------------------------------------------------------------------------
296 // nsOfflineCacheUpdateItem::nsISupports
297 //-----------------------------------------------------------------------------
299 NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateItem,
300 nsIDOMLoadStatus,
301 nsIRequestObserver,
302 nsIStreamListener,
303 nsIRunnable,
304 nsIInterfaceRequestor,
305 nsIChannelEventSink)
307 //-----------------------------------------------------------------------------
308 // nsOfflineCacheUpdateItem <public>
309 //-----------------------------------------------------------------------------
311 nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
312 nsIURI *aURI,
313 nsIURI *aReferrerURI,
314 nsIApplicationCache *aPreviousApplicationCache,
315 const nsACString &aClientID,
316 PRUint32 type)
317 : mURI(aURI)
318 , mReferrerURI(aReferrerURI)
319 , mPreviousApplicationCache(aPreviousApplicationCache)
320 , mClientID(aClientID)
321 , mItemType(type)
322 , mUpdate(aUpdate)
323 , mChannel(nsnull)
324 , mState(nsIDOMLoadStatus::UNINITIALIZED)
325 , mBytesRead(0)
329 nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
333 nsresult
334 nsOfflineCacheUpdateItem::OpenChannel()
336 #if defined(PR_LOGGING)
337 if (LOG_ENABLED()) {
338 nsCAutoString spec;
339 mURI->GetSpec(spec);
340 LOG(("%p: Opening channel for %s", this, spec.get()));
342 #endif
344 nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
345 NS_ENSURE_SUCCESS(rv, rv);
347 rv = NS_NewChannel(getter_AddRefs(mChannel),
348 mURI,
349 nsnull, nsnull, this,
350 nsIRequest::LOAD_BACKGROUND |
351 nsICachingChannel::LOAD_ONLY_IF_MODIFIED |
352 nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE);
353 NS_ENSURE_SUCCESS(rv, rv);
355 nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
356 do_QueryInterface(mChannel, &rv);
358 // Support for nsIApplicationCacheChannel is required.
359 NS_ENSURE_SUCCESS(rv, rv);
361 // Use the existing application cache as the cache to check.
362 rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
363 NS_ENSURE_SUCCESS(rv, rv);
365 // configure HTTP specific stuff
366 nsCOMPtr<nsIHttpChannel> httpChannel =
367 do_QueryInterface(mChannel);
368 if (httpChannel) {
369 httpChannel->SetReferrer(mReferrerURI);
370 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
371 NS_LITERAL_CSTRING("offline-resource"),
372 PR_FALSE);
375 nsCOMPtr<nsICachingChannel> cachingChannel =
376 do_QueryInterface(mChannel);
377 if (cachingChannel) {
378 rv = cachingChannel->SetCacheForOfflineUse(PR_TRUE);
379 NS_ENSURE_SUCCESS(rv, rv);
381 if (!mClientID.IsEmpty()) {
382 rv = cachingChannel->SetOfflineCacheClientID(mClientID);
383 NS_ENSURE_SUCCESS(rv, rv);
387 rv = mChannel->AsyncOpen(this, nsnull);
388 NS_ENSURE_SUCCESS(rv, rv);
390 mState = nsIDOMLoadStatus::REQUESTED;
392 return NS_OK;
395 nsresult
396 nsOfflineCacheUpdateItem::Cancel()
398 if (mChannel) {
399 mChannel->Cancel(NS_ERROR_ABORT);
400 mChannel = nsnull;
403 mState = nsIDOMLoadStatus::UNINITIALIZED;
405 return NS_OK;
408 //-----------------------------------------------------------------------------
409 // nsOfflineCacheUpdateItem::nsIStreamListener
410 //-----------------------------------------------------------------------------
412 NS_IMETHODIMP
413 nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest,
414 nsISupports *aContext)
416 mState = nsIDOMLoadStatus::RECEIVING;
418 return NS_OK;
421 NS_IMETHODIMP
422 nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
423 nsISupports *aContext,
424 nsIInputStream *aStream,
425 PRUint32 aOffset,
426 PRUint32 aCount)
428 PRUint32 bytesRead = 0;
429 aStream->ReadSegments(NS_DiscardSegment, nsnull, aCount, &bytesRead);
430 mBytesRead += bytesRead;
431 LOG(("loaded %u bytes into offline cache [offset=%u]\n",
432 bytesRead, aOffset));
433 return NS_OK;
436 NS_IMETHODIMP
437 nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
438 nsISupports *aContext,
439 nsresult aStatus)
441 LOG(("done fetching offline item [status=%x]\n", aStatus));
443 mState = nsIDOMLoadStatus::LOADED;
445 if (mBytesRead == 0 && aStatus == NS_OK) {
446 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
447 // specified), but the object should report loadedSize as if it
448 // did.
449 mChannel->GetContentLength(&mBytesRead);
452 // We need to notify the update that the load is complete, but we
453 // want to give the channel a chance to close the cache entries.
454 NS_DispatchToCurrentThread(this);
456 return NS_OK;
460 //-----------------------------------------------------------------------------
461 // nsOfflineCacheUpdateItem::nsIRunnable
462 //-----------------------------------------------------------------------------
463 NS_IMETHODIMP
464 nsOfflineCacheUpdateItem::Run()
466 mUpdate->LoadCompleted();
468 return NS_OK;
471 //-----------------------------------------------------------------------------
472 // nsOfflineCacheUpdateItem::nsIInterfaceRequestor
473 //-----------------------------------------------------------------------------
475 NS_IMETHODIMP
476 nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult)
478 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
479 NS_ADDREF_THIS();
480 *aResult = static_cast<nsIChannelEventSink *>(this);
481 return NS_OK;
484 return NS_ERROR_NO_INTERFACE;
487 //-----------------------------------------------------------------------------
488 // nsOfflineCacheUpdateItem::nsIChannelEventSink
489 //-----------------------------------------------------------------------------
491 NS_IMETHODIMP
492 nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
493 nsIChannel *aNewChannel,
494 PRUint32 aFlags)
496 nsCOMPtr<nsIURI> newURI;
497 nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
498 if (NS_FAILED(rv))
499 return rv;
501 nsCOMPtr<nsICachingChannel> oldCachingChannel =
502 do_QueryInterface(aOldChannel);
503 nsCOMPtr<nsICachingChannel> newCachingChannel =
504 do_QueryInterface(aOldChannel);
505 if (newCachingChannel) {
506 rv = newCachingChannel->SetCacheForOfflineUse(PR_TRUE);
507 NS_ENSURE_SUCCESS(rv, rv);
508 if (!mClientID.IsEmpty()) {
509 rv = newCachingChannel->SetOfflineCacheClientID(mClientID);
510 NS_ENSURE_SUCCESS(rv, rv);
514 nsCAutoString oldScheme;
515 mURI->GetScheme(oldScheme);
517 PRBool match;
518 if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
519 LOG(("rejected: redirected to a different scheme\n"));
520 return NS_ERROR_ABORT;
523 // HTTP request headers are not automatically forwarded to the new channel.
524 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
525 NS_ENSURE_STATE(httpChannel);
527 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
528 NS_LITERAL_CSTRING("offline-resource"),
529 PR_FALSE);
531 mChannel = aNewChannel;
533 return NS_OK;
536 //-----------------------------------------------------------------------------
537 // nsOfflineCacheUpdateItem::nsIDOMLoadStatus
538 //-----------------------------------------------------------------------------
540 NS_IMETHODIMP
541 nsOfflineCacheUpdateItem::GetSource(nsIDOMNode **aSource)
543 *aSource = nsnull;
544 return NS_OK;
547 NS_IMETHODIMP
548 nsOfflineCacheUpdateItem::GetUri(nsAString &aURI)
550 nsCAutoString spec;
551 nsresult rv = mURI->GetSpec(spec);
552 NS_ENSURE_SUCCESS(rv, rv);
554 CopyUTF8toUTF16(spec, aURI);
555 return NS_OK;
558 NS_IMETHODIMP
559 nsOfflineCacheUpdateItem::GetTotalSize(PRInt32 *aTotalSize)
561 if (mChannel) {
562 return mChannel->GetContentLength(aTotalSize);
565 *aTotalSize = -1;
566 return NS_OK;
569 NS_IMETHODIMP
570 nsOfflineCacheUpdateItem::GetLoadedSize(PRInt32 *aLoadedSize)
572 *aLoadedSize = mBytesRead;
573 return NS_OK;
576 NS_IMETHODIMP
577 nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState)
579 *aReadyState = mState;
580 return NS_OK;
583 NS_IMETHODIMP
584 nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
586 if (!mChannel) {
587 *aStatus = 0;
588 return NS_OK;
591 nsresult rv;
592 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
593 NS_ENSURE_SUCCESS(rv, rv);
595 PRUint32 httpStatus;
596 rv = httpChannel->GetResponseStatus(&httpStatus);
597 if (rv == NS_ERROR_NOT_AVAILABLE) {
598 // Someone's calling this before we got a response... Check our
599 // ReadyState. If we're at RECEIVING or LOADED, then this means the
600 // connection errored before we got any data; return a somewhat
601 // sensible error code in that case.
602 if (mState >= nsIDOMLoadStatus::RECEIVING) {
603 *aStatus = NS_ERROR_NOT_AVAILABLE;
604 return NS_OK;
607 *aStatus = 0;
608 return NS_OK;
611 NS_ENSURE_SUCCESS(rv, rv);
612 *aStatus = PRUint16(httpStatus);
613 return NS_OK;
616 //-----------------------------------------------------------------------------
617 // nsOfflineManifestItem
618 //-----------------------------------------------------------------------------
620 //-----------------------------------------------------------------------------
621 // nsOfflineManifestItem <public>
622 //-----------------------------------------------------------------------------
624 nsOfflineManifestItem::nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
625 nsIURI *aURI,
626 nsIURI *aReferrerURI,
627 nsIApplicationCache *aPreviousApplicationCache,
628 const nsACString &aClientID)
629 : nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI,
630 aPreviousApplicationCache, aClientID,
631 nsIApplicationCache::ITEM_MANIFEST)
632 , mParserState(PARSE_INIT)
633 , mNeedsUpdate(PR_TRUE)
634 , mManifestHashInitialized(PR_FALSE)
636 ReadStrictFileOriginPolicyPref();
639 nsOfflineManifestItem::~nsOfflineManifestItem()
643 //-----------------------------------------------------------------------------
644 // nsOfflineManifestItem <private>
645 //-----------------------------------------------------------------------------
647 /* static */
648 NS_METHOD
649 nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
650 void *aClosure,
651 const char *aFromSegment,
652 PRUint32 aOffset,
653 PRUint32 aCount,
654 PRUint32 *aBytesConsumed)
656 nsOfflineManifestItem *manifest =
657 static_cast<nsOfflineManifestItem*>(aClosure);
659 nsresult rv;
661 *aBytesConsumed = aCount;
663 if (manifest->mParserState == PARSE_ERROR) {
664 // parse already failed, ignore this
665 return NS_OK;
668 if (!manifest->mManifestHashInitialized) {
669 // Avoid re-creation of crypto hash when it fails from some reason the first time
670 manifest->mManifestHashInitialized = PR_TRUE;
672 manifest->mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
673 if (NS_SUCCEEDED(rv)) {
674 rv = manifest->mManifestHash->Init(nsICryptoHash::MD5);
675 if (NS_FAILED(rv)) {
676 manifest->mManifestHash = nsnull;
677 LOG(("Could not initialize manifest hash for byte-to-byte check, rv=%08x", rv));
682 if (manifest->mManifestHash) {
683 rv = manifest->mManifestHash->Update(reinterpret_cast<const PRUint8 *>(aFromSegment), aCount);
684 if (NS_FAILED(rv)) {
685 manifest->mManifestHash = nsnull;
686 LOG(("Could not update manifest hash, rv=%08x", rv));
690 manifest->mReadBuf.Append(aFromSegment, aCount);
692 nsCString::const_iterator begin, iter, end;
693 manifest->mReadBuf.BeginReading(begin);
694 manifest->mReadBuf.EndReading(end);
696 for (iter = begin; iter != end; iter++) {
697 if (*iter == '\r' || *iter == '\n') {
698 nsresult rv = manifest->HandleManifestLine(begin, iter);
700 if (NS_FAILED(rv)) {
701 LOG(("HandleManifestLine failed with 0x%08x", rv));
702 return NS_ERROR_ABORT;
705 begin = iter;
706 begin++;
710 // any leftovers are saved for next time
711 manifest->mReadBuf = Substring(begin, end);
713 return NS_OK;
716 nsresult
717 nsOfflineManifestItem::AddNamespace(PRUint32 namespaceType,
718 const nsCString &namespaceSpec,
719 const nsCString &data)
722 nsresult rv;
723 if (!mNamespaces) {
724 mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
725 NS_ENSURE_SUCCESS(rv, rv);
728 nsCOMPtr<nsIApplicationCacheNamespace> ns =
729 do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
730 NS_ENSURE_SUCCESS(rv, rv);
732 rv = ns->Init(namespaceType, namespaceSpec, data);
733 NS_ENSURE_SUCCESS(rv, rv);
735 rv = mNamespaces->AppendElement(ns, PR_FALSE);
736 NS_ENSURE_SUCCESS(rv, rv);
738 return NS_OK;
741 nsresult
742 nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
743 const nsCString::const_iterator &aEnd)
745 nsCString::const_iterator begin = aBegin;
746 nsCString::const_iterator end = aEnd;
748 // all lines ignore trailing spaces and tabs
749 nsCString::const_iterator last = end;
750 --last;
751 while (end != begin && (*last == ' ' || *last == '\t')) {
752 --end;
753 --last;
756 if (mParserState == PARSE_INIT) {
757 // Allow a UTF-8 BOM
758 if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
759 if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
760 ++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
761 mParserState = PARSE_ERROR;
762 return NS_OK;
764 ++begin;
767 const nsCSubstring &magic = Substring(begin, end);
769 if (!magic.EqualsLiteral("CACHE MANIFEST")) {
770 mParserState = PARSE_ERROR;
771 return NS_OK;
774 mParserState = PARSE_CACHE_ENTRIES;
775 return NS_OK;
778 // lines other than the first ignore leading spaces and tabs
779 while (begin != end && (*begin == ' ' || *begin == '\t'))
780 begin++;
782 // ignore blank lines and comments
783 if (begin == end || *begin == '#')
784 return NS_OK;
786 const nsCSubstring &line = Substring(begin, end);
788 if (line.EqualsLiteral("CACHE:")) {
789 mParserState = PARSE_CACHE_ENTRIES;
790 return NS_OK;
793 if (line.EqualsLiteral("FALLBACK:")) {
794 mParserState = PARSE_FALLBACK_ENTRIES;
795 return NS_OK;
798 if (line.EqualsLiteral("NETWORK:")) {
799 mParserState = PARSE_BYPASS_ENTRIES;
800 return NS_OK;
803 nsresult rv;
805 switch(mParserState) {
806 case PARSE_INIT:
807 case PARSE_ERROR: {
808 // this should have been dealt with earlier
809 return NS_ERROR_FAILURE;
812 case PARSE_CACHE_ENTRIES: {
813 nsCOMPtr<nsIURI> uri;
814 rv = NS_NewURI(getter_AddRefs(uri), line, nsnull, mURI);
815 if (NS_FAILED(rv))
816 break;
817 if (NS_FAILED(DropReferenceFromURL(uri)))
818 break;
820 nsCAutoString scheme;
821 uri->GetScheme(scheme);
823 // Manifest URIs must have the same scheme as the manifest.
824 PRBool match;
825 if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
826 break;
828 mExplicitURIs.AppendObject(uri);
829 break;
832 case PARSE_FALLBACK_ENTRIES: {
833 PRInt32 separator = line.FindChar(' ');
834 if (separator == kNotFound) {
835 separator = line.FindChar('\t');
836 if (separator == kNotFound)
837 break;
840 nsCString namespaceSpec(Substring(line, 0, separator));
841 nsCString fallbackSpec(Substring(line, separator + 1));
842 namespaceSpec.CompressWhitespace();
843 fallbackSpec.CompressWhitespace();
845 nsCOMPtr<nsIURI> namespaceURI;
846 rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nsnull, mURI);
847 if (NS_FAILED(rv))
848 break;
849 if (NS_FAILED(DropReferenceFromURL(namespaceURI)))
850 break;
851 rv = namespaceURI->GetAsciiSpec(namespaceSpec);
852 if (NS_FAILED(rv))
853 break;
856 nsCOMPtr<nsIURI> fallbackURI;
857 rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nsnull, mURI);
858 if (NS_FAILED(rv))
859 break;
860 if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
861 break;
862 rv = fallbackURI->GetAsciiSpec(fallbackSpec);
863 if (NS_FAILED(rv))
864 break;
866 // Manifest and namespace must be same origin
867 if (!NS_SecurityCompareURIs(mURI, namespaceURI,
868 mStrictFileOriginPolicy))
869 break;
871 // Fallback and namespace must be same origin
872 if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
873 mStrictFileOriginPolicy))
874 break;
876 mFallbackURIs.AppendObject(fallbackURI);
878 AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
879 namespaceSpec, fallbackSpec);
880 break;
883 case PARSE_BYPASS_ENTRIES: {
884 nsCOMPtr<nsIURI> bypassURI;
885 rv = NS_NewURI(getter_AddRefs(bypassURI), line, nsnull, mURI);
886 if (NS_FAILED(rv))
887 break;
889 nsCAutoString scheme;
890 bypassURI->GetScheme(scheme);
891 PRBool equals;
892 if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
893 break;
894 if (NS_FAILED(DropReferenceFromURL(bypassURI)))
895 break;
896 nsCString spec;
897 if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
898 break;
900 AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
901 spec, EmptyCString());
902 break;
906 return NS_OK;
909 nsresult
910 nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest)
912 nsresult rv;
914 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
915 NS_ENSURE_SUCCESS(rv, rv);
917 // load the main cache token that is actually the old offline cache token and
918 // read previous manifest content hash value
919 nsCOMPtr<nsISupports> cacheToken;
920 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
921 if (cacheToken) {
922 nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
923 NS_ENSURE_SUCCESS(rv, rv);
925 rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue));
926 if (NS_FAILED(rv))
927 mOldManifestHashValue.Truncate();
930 return NS_OK;
933 nsresult
934 nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest)
936 nsresult rv;
938 if (!mManifestHash) {
939 // Nothing to compare against...
940 return NS_OK;
943 nsCString newManifestHashValue;
944 rv = mManifestHash->Finish(PR_TRUE, mManifestHashValue);
945 mManifestHash = nsnull;
947 if (NS_FAILED(rv)) {
948 LOG(("Could not finish manifest hash, rv=%08x", rv));
949 // This is not critical error
950 return NS_OK;
953 if (!ParseSucceeded()) {
954 // Parsing failed, the hash is not valid
955 return NS_OK;
958 if (mOldManifestHashValue == mManifestHashValue) {
959 LOG(("Update not needed, downloaded manifest content is byte-for-byte identical"));
960 mNeedsUpdate = PR_FALSE;
963 // Store the manifest content hash value to the new
964 // offline cache token
965 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
966 NS_ENSURE_SUCCESS(rv, rv);
968 nsCOMPtr<nsISupports> cacheToken;
969 cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
970 if (cacheToken) {
971 nsCOMPtr<nsICacheEntryDescriptor> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
972 NS_ENSURE_SUCCESS(rv, rv);
974 rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get());
975 NS_ENSURE_SUCCESS(rv, rv);
978 return NS_OK;
981 void
982 nsOfflineManifestItem::ReadStrictFileOriginPolicyPref()
984 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
985 mStrictFileOriginPolicy =
986 (!prefs ||
987 NS_FAILED(prefs->GetBoolPref("security.fileuri.strict_origin_policy",
988 &mStrictFileOriginPolicy)));
991 NS_IMETHODIMP
992 nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
993 nsISupports *aContext)
995 nsresult rv;
997 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
998 NS_ENSURE_SUCCESS(rv, rv);
1000 PRBool succeeded;
1001 rv = channel->GetRequestSucceeded(&succeeded);
1002 NS_ENSURE_SUCCESS(rv, rv);
1004 if (!succeeded) {
1005 LOG(("HTTP request failed"));
1006 mParserState = PARSE_ERROR;
1007 return NS_ERROR_ABORT;
1010 nsCAutoString contentType;
1011 rv = channel->GetContentType(contentType);
1012 NS_ENSURE_SUCCESS(rv, rv);
1014 if (!contentType.EqualsLiteral("text/cache-manifest")) {
1015 LOG(("Rejected cache manifest with Content-Type %s (expecting text/cache-manifest)",
1016 contentType.get()));
1017 mParserState = PARSE_ERROR;
1018 return NS_ERROR_ABORT;
1021 rv = GetOldManifestContentHash(aRequest);
1022 NS_ENSURE_SUCCESS(rv, rv);
1024 return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext);
1027 NS_IMETHODIMP
1028 nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
1029 nsISupports *aContext,
1030 nsIInputStream *aStream,
1031 PRUint32 aOffset,
1032 PRUint32 aCount)
1034 PRUint32 bytesRead = 0;
1035 aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
1036 mBytesRead += bytesRead;
1038 if (mParserState == PARSE_ERROR) {
1039 LOG(("OnDataAvailable is canceling the request due a parse error\n"));
1040 return NS_ERROR_ABORT;
1043 LOG(("loaded %u bytes into offline cache [offset=%u]\n",
1044 bytesRead, aOffset));
1046 // All the parent method does is read and discard, don't bother
1047 // chaining up.
1049 return NS_OK;
1052 NS_IMETHODIMP
1053 nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
1054 nsISupports *aContext,
1055 nsresult aStatus)
1057 // handle any leftover manifest data
1058 nsCString::const_iterator begin, end;
1059 mReadBuf.BeginReading(begin);
1060 mReadBuf.EndReading(end);
1061 nsresult rv = HandleManifestLine(begin, end);
1062 NS_ENSURE_SUCCESS(rv, rv);
1064 if (mBytesRead == 0) {
1065 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
1066 // specified.)
1067 mNeedsUpdate = PR_FALSE;
1068 } else {
1069 rv = CheckNewManifestContentHash(aRequest);
1070 NS_ENSURE_SUCCESS(rv, rv);
1073 return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
1076 //-----------------------------------------------------------------------------
1077 // nsOfflineCacheUpdate::nsISupports
1078 //-----------------------------------------------------------------------------
1080 NS_IMPL_ISUPPORTS1(nsOfflineCacheUpdate,
1081 nsIOfflineCacheUpdate)
1083 //-----------------------------------------------------------------------------
1084 // nsOfflineCacheUpdate <public>
1085 //-----------------------------------------------------------------------------
1087 nsOfflineCacheUpdate::nsOfflineCacheUpdate()
1088 : mState(STATE_UNINITIALIZED)
1089 , mAddedItems(PR_FALSE)
1090 , mPartialUpdate(PR_FALSE)
1091 , mSucceeded(PR_TRUE)
1092 , mObsolete(PR_FALSE)
1093 , mCurrentItem(-1)
1094 , mRescheduleCount(0)
1098 nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
1100 LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
1103 /* static */
1104 nsresult
1105 nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey)
1107 aKey.Truncate();
1109 nsCOMPtr<nsIURI> newURI;
1110 nsresult rv = aURI->Clone(getter_AddRefs(newURI));
1111 NS_ENSURE_SUCCESS(rv, rv);
1113 nsCOMPtr<nsIURL> newURL;
1114 newURL = do_QueryInterface(newURI);
1115 if (newURL) {
1116 newURL->SetRef(EmptyCString());
1119 rv = newURI->GetAsciiSpec(aKey);
1120 NS_ENSURE_SUCCESS(rv, rv);
1122 return NS_OK;
1125 nsresult
1126 nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
1127 nsIURI *aDocumentURI)
1129 nsresult rv;
1131 // Make sure the service has been initialized
1132 nsOfflineCacheUpdateService* service =
1133 nsOfflineCacheUpdateService::EnsureService();
1134 if (!service)
1135 return NS_ERROR_FAILURE;
1137 LOG(("nsOfflineCacheUpdate::Init [%p]", this));
1139 mPartialUpdate = PR_FALSE;
1141 // Only http and https applications are supported.
1142 PRBool match;
1143 rv = aManifestURI->SchemeIs("http", &match);
1144 NS_ENSURE_SUCCESS(rv, rv);
1146 if (!match) {
1147 rv = aManifestURI->SchemeIs("https", &match);
1148 NS_ENSURE_SUCCESS(rv, rv);
1149 if (!match)
1150 return NS_ERROR_ABORT;
1153 mManifestURI = aManifestURI;
1155 rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1156 NS_ENSURE_SUCCESS(rv, rv);
1158 nsCAutoString manifestSpec;
1160 rv = GetCacheKey(mManifestURI, manifestSpec);
1161 NS_ENSURE_SUCCESS(rv, rv);
1163 mDocumentURI = aDocumentURI;
1165 nsCOMPtr<nsIApplicationCacheService> cacheService =
1166 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1167 NS_ENSURE_SUCCESS(rv, rv);
1169 rv = cacheService->GetActiveCache(manifestSpec,
1170 getter_AddRefs(mPreviousApplicationCache));
1171 NS_ENSURE_SUCCESS(rv, rv);
1173 rv = cacheService->CreateApplicationCache(manifestSpec,
1174 getter_AddRefs(mApplicationCache));
1175 NS_ENSURE_SUCCESS(rv, rv);
1177 rv = mApplicationCache->GetClientID(mClientID);
1178 NS_ENSURE_SUCCESS(rv, rv);
1180 mState = STATE_INITIALIZED;
1181 return NS_OK;
1184 nsresult
1185 nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
1186 const nsACString& clientID,
1187 nsIURI *aDocumentURI)
1189 nsresult rv;
1191 // Make sure the service has been initialized
1192 nsOfflineCacheUpdateService* service =
1193 nsOfflineCacheUpdateService::EnsureService();
1194 if (!service)
1195 return NS_ERROR_FAILURE;
1197 LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
1199 mPartialUpdate = PR_TRUE;
1200 mClientID = clientID;
1201 mDocumentURI = aDocumentURI;
1203 mManifestURI = aManifestURI;
1204 rv = mManifestURI->GetAsciiHost(mUpdateDomain);
1205 NS_ENSURE_SUCCESS(rv, rv);
1207 nsCOMPtr<nsIApplicationCacheService> cacheService =
1208 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
1209 NS_ENSURE_SUCCESS(rv, rv);
1211 rv = cacheService->GetApplicationCache(mClientID,
1212 getter_AddRefs(mApplicationCache));
1213 NS_ENSURE_SUCCESS(rv, rv);
1215 if (!mApplicationCache) {
1216 nsCAutoString manifestSpec;
1217 rv = GetCacheKey(mManifestURI, manifestSpec);
1218 NS_ENSURE_SUCCESS(rv, rv);
1220 rv = cacheService->CreateApplicationCache
1221 (manifestSpec, getter_AddRefs(mApplicationCache));
1222 NS_ENSURE_SUCCESS(rv, rv);
1225 nsCAutoString groupID;
1226 rv = mApplicationCache->GetGroupID(groupID);
1227 NS_ENSURE_SUCCESS(rv, rv);
1229 rv = NS_NewURI(getter_AddRefs(mManifestURI), groupID);
1230 NS_ENSURE_SUCCESS(rv, rv);
1232 mState = STATE_INITIALIZED;
1233 return NS_OK;
1236 nsresult
1237 nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
1239 // Be pessimistic
1240 *aDoUpdate = PR_FALSE;
1242 PRUint16 status;
1243 nsresult rv = mManifestItem->GetStatus(&status);
1244 NS_ENSURE_SUCCESS(rv, rv);
1246 if (status == 0 || status >= 400 || !mManifestItem->ParseSucceeded()) {
1247 return NS_ERROR_FAILURE;
1250 if (!mManifestItem->NeedsUpdate()) {
1251 return NS_OK;
1254 // Add items requested by the manifest.
1255 const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
1256 for (PRInt32 i = 0; i < manifestURIs.Count(); i++) {
1257 rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
1258 NS_ENSURE_SUCCESS(rv, rv);
1261 const nsCOMArray<nsIURI> &fallbackURIs = mManifestItem->GetFallbackURIs();
1262 for (PRInt32 i = 0; i < fallbackURIs.Count(); i++) {
1263 rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
1264 NS_ENSURE_SUCCESS(rv, rv);
1267 // The document that requested the manifest is implicitly included
1268 // as part of that manifest update.
1269 rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
1270 NS_ENSURE_SUCCESS(rv, rv);
1272 // Add items previously cached implicitly
1273 rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
1274 NS_ENSURE_SUCCESS(rv, rv);
1276 // Add items requested by the script API
1277 rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
1278 NS_ENSURE_SUCCESS(rv, rv);
1280 // Add opportunistically cached items conforming current opportunistic
1281 // namespace list
1282 rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
1283 &mManifestItem->GetOpportunisticNamespaces());
1284 NS_ENSURE_SUCCESS(rv, rv);
1286 *aDoUpdate = PR_TRUE;
1288 return NS_OK;
1291 void
1292 nsOfflineCacheUpdate::LoadCompleted()
1294 nsresult rv;
1296 LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
1298 if (mState == STATE_CANCELLED) {
1299 Finish();
1300 return;
1303 if (mState == STATE_CHECKING) {
1304 // Manifest load finished.
1306 NS_ASSERTION(mManifestItem,
1307 "Must have a manifest item in STATE_CHECKING.");
1309 // A 404 or 410 is interpreted as an intentional removal of
1310 // the manifest file, rather than a transient server error.
1311 // Obsolete this cache group if one of these is returned.
1312 PRUint16 status;
1313 rv = mManifestItem->GetStatus(&status);
1314 if (status == 404 || status == 410) {
1315 mSucceeded = PR_FALSE;
1316 mObsolete = PR_TRUE;
1317 NotifyObsolete();
1318 Finish();
1319 return;
1322 PRBool doUpdate;
1323 if (NS_FAILED(HandleManifest(&doUpdate))) {
1324 mSucceeded = PR_FALSE;
1325 NotifyError();
1326 Finish();
1327 return;
1330 if (!doUpdate) {
1331 mSucceeded = PR_FALSE;
1332 NotifyNoUpdate();
1333 Finish();
1334 ScheduleImplicit();
1335 return;
1338 rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
1339 mManifestItem->mItemType);
1340 if (NS_FAILED(rv)) {
1341 mSucceeded = PR_FALSE;
1342 NotifyError();
1343 Finish();
1344 return;
1347 mState = STATE_DOWNLOADING;
1348 NotifyDownloading();
1350 // Start fetching resources.
1351 ProcessNextURI();
1353 return;
1356 // Normal load finished.
1358 nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
1359 mCurrentItem++;
1361 PRUint16 status;
1362 rv = item->GetStatus(&status);
1364 // Check for failures. 4XX and 5XX errors on items explicitly
1365 // listed in the manifest will cause the update to fail.
1366 if (NS_FAILED(rv) || status == 0 || status >= 400) {
1367 if (item->mItemType &
1368 (nsIApplicationCache::ITEM_EXPLICIT |
1369 nsIApplicationCache::ITEM_FALLBACK)) {
1370 mSucceeded = PR_FALSE;
1372 } else {
1373 rv = mApplicationCache->MarkEntry(item->mCacheKey, item->mItemType);
1374 if (NS_FAILED(rv)) {
1375 mSucceeded = PR_FALSE;
1379 if (!mSucceeded) {
1380 NotifyError();
1381 Finish();
1382 return;
1385 rv = NotifyCompleted(item);
1386 if (NS_FAILED(rv)) return;
1388 ProcessNextURI();
1391 void
1392 nsOfflineCacheUpdate::ManifestCheckCompleted(nsresult aStatus,
1393 const nsCString &aManifestHash)
1395 if (NS_SUCCEEDED(aStatus)) {
1396 nsCAutoString firstManifestHash;
1397 mManifestItem->GetManifestHash(firstManifestHash);
1398 if (aManifestHash != firstManifestHash) {
1399 aStatus = NS_ERROR_FAILURE;
1403 if (NS_FAILED(aStatus)) {
1404 mSucceeded = PR_FALSE;
1405 NotifyError();
1408 Finish();
1410 if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
1411 // Reschedule this update.
1412 nsRefPtr<nsOfflineCacheUpdate> newUpdate =
1413 new nsOfflineCacheUpdate();
1414 newUpdate->Init(mManifestURI, mDocumentURI);
1416 for (PRInt32 i = 0; i < mDocuments.Count(); i++) {
1417 newUpdate->AddDocument(mDocuments[i]);
1420 newUpdate->mRescheduleCount = mRescheduleCount + 1;
1421 newUpdate->Schedule();
1425 nsresult
1426 nsOfflineCacheUpdate::Begin()
1428 LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
1430 mCurrentItem = 0;
1432 if (mPartialUpdate) {
1433 mState = STATE_DOWNLOADING;
1434 NotifyDownloading();
1435 ProcessNextURI();
1436 return NS_OK;
1439 // Start checking the manifest.
1440 nsCOMPtr<nsIURI> uri;
1442 mManifestItem = new nsOfflineManifestItem(this, mManifestURI,
1443 mDocumentURI,
1444 mPreviousApplicationCache,
1445 mClientID);
1446 if (!mManifestItem) {
1447 return NS_ERROR_OUT_OF_MEMORY;
1450 mState = STATE_CHECKING;
1451 NotifyChecking();
1453 nsresult rv = mManifestItem->OpenChannel();
1454 if (NS_FAILED(rv)) {
1455 LoadCompleted();
1458 return NS_OK;
1461 nsresult
1462 nsOfflineCacheUpdate::Cancel()
1464 LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
1466 mState = STATE_CANCELLED;
1467 mSucceeded = PR_FALSE;
1469 if (mCurrentItem >= 0 &&
1470 mCurrentItem < static_cast<PRInt32>(mItems.Length())) {
1471 // Load might be running
1472 mItems[mCurrentItem]->Cancel();
1475 return NS_OK;
1478 //-----------------------------------------------------------------------------
1479 // nsOfflineCacheUpdate <private>
1480 //-----------------------------------------------------------------------------
1482 nsresult
1483 nsOfflineCacheUpdate::AddExistingItems(PRUint32 aType,
1484 nsTArray<nsCString>* namespaceFilter)
1486 if (!mPreviousApplicationCache) {
1487 return NS_OK;
1490 if (namespaceFilter && namespaceFilter->Length() == 0) {
1491 // Don't bother to walk entries when there are no namespaces
1492 // defined.
1493 return NS_OK;
1496 PRUint32 count = 0;
1497 char **keys = nsnull;
1498 nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
1499 &count, &keys);
1500 NS_ENSURE_SUCCESS(rv, rv);
1502 AutoFreeArray autoFree(count, keys);
1504 for (PRUint32 i = 0; i < count; i++) {
1505 if (namespaceFilter) {
1506 PRBool found = PR_FALSE;
1507 for (PRUint32 j = 0; j < namespaceFilter->Length() && !found; j++) {
1508 found = StringBeginsWith(nsDependentCString(keys[i]),
1509 namespaceFilter->ElementAt(j));
1512 if (!found)
1513 continue;
1516 nsCOMPtr<nsIURI> uri;
1517 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
1518 rv = AddURI(uri, aType);
1519 NS_ENSURE_SUCCESS(rv, rv);
1523 return NS_OK;
1526 nsresult
1527 nsOfflineCacheUpdate::ProcessNextURI()
1529 LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, current=%d, numItems=%d]",
1530 this, mCurrentItem, mItems.Length()));
1532 NS_ASSERTION(mState == STATE_DOWNLOADING,
1533 "ProcessNextURI should only be called from the DOWNLOADING state");
1535 if (mCurrentItem >= static_cast<PRInt32>(mItems.Length())) {
1536 if (mPartialUpdate) {
1537 return Finish();
1538 } else {
1539 // Verify that the manifest wasn't changed during the
1540 // update, to prevent capturing a cache while the server
1541 // is being updated. The check will call
1542 // ManifestCheckCompleted() when it's done.
1543 nsRefPtr<nsManifestCheck> manifestCheck =
1544 new nsManifestCheck(this, mManifestURI, mDocumentURI);
1545 if (NS_FAILED(manifestCheck->Begin())) {
1546 mSucceeded = PR_FALSE;
1547 NotifyError();
1548 return Finish();
1551 return NS_OK;
1555 #if defined(PR_LOGGING)
1556 if (LOG_ENABLED()) {
1557 nsCAutoString spec;
1558 mItems[mCurrentItem]->mURI->GetSpec(spec);
1559 LOG(("%p: Opening channel for %s", this, spec.get()));
1561 #endif
1563 NotifyStarted(mItems[mCurrentItem]);
1565 nsresult rv = mItems[mCurrentItem]->OpenChannel();
1566 if (NS_FAILED(rv)) {
1567 LoadCompleted();
1568 return rv;
1571 return NS_OK;
1574 nsresult
1575 nsOfflineCacheUpdate::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
1577 for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
1578 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
1579 do_QueryReferent(mWeakObservers[i]);
1580 if (observer)
1581 aObservers.AppendObject(observer);
1582 else
1583 mWeakObservers.RemoveObjectAt(i--);
1586 for (PRInt32 i = 0; i < mObservers.Count(); i++) {
1587 aObservers.AppendObject(mObservers[i]);
1590 return NS_OK;
1593 nsresult
1594 nsOfflineCacheUpdate::NotifyError()
1596 LOG(("nsOfflineCacheUpdate::NotifyError [%p]", this));
1598 mState = STATE_FINISHED;
1600 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1601 nsresult rv = GatherObservers(observers);
1602 NS_ENSURE_SUCCESS(rv, rv);
1604 for (PRInt32 i = 0; i < observers.Count(); i++) {
1605 observers[i]->Error(this);
1608 return NS_OK;
1611 nsresult
1612 nsOfflineCacheUpdate::NotifyChecking()
1614 LOG(("nsOfflineCacheUpdate::NotifyChecking [%p]", this));
1616 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1617 nsresult rv = GatherObservers(observers);
1618 NS_ENSURE_SUCCESS(rv, rv);
1620 for (PRInt32 i = 0; i < observers.Count(); i++) {
1621 observers[i]->Checking(this);
1624 return NS_OK;
1627 nsresult
1628 nsOfflineCacheUpdate::NotifyNoUpdate()
1630 LOG(("nsOfflineCacheUpdate::NotifyNoUpdate [%p]", this));
1632 mState = STATE_FINISHED;
1634 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1635 nsresult rv = GatherObservers(observers);
1636 NS_ENSURE_SUCCESS(rv, rv);
1638 for (PRInt32 i = 0; i < observers.Count(); i++) {
1639 observers[i]->NoUpdate(this);
1642 return NS_OK;
1645 nsresult
1646 nsOfflineCacheUpdate::NotifyObsolete()
1648 LOG(("nsOfflineCacheUpdate::NotifyObsolete [%p]", this));
1650 mState = STATE_FINISHED;
1652 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1653 nsresult rv = GatherObservers(observers);
1654 NS_ENSURE_SUCCESS(rv, rv);
1656 for (PRInt32 i = 0; i < observers.Count(); i++) {
1657 observers[i]->Obsolete(this);
1660 return NS_OK;
1663 nsresult
1664 nsOfflineCacheUpdate::NotifyDownloading()
1666 LOG(("nsOfflineCacheUpdate::NotifyDownloading [%p]", this));
1668 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1669 nsresult rv = GatherObservers(observers);
1670 NS_ENSURE_SUCCESS(rv, rv);
1672 for (PRInt32 i = 0; i < observers.Count(); i++) {
1673 observers[i]->Downloading(this);
1676 return NS_OK;
1679 nsresult
1680 nsOfflineCacheUpdate::NotifyStarted(nsOfflineCacheUpdateItem *aItem)
1682 LOG(("nsOfflineCacheUpdate::NotifyStarted [%p, %p]", this, aItem));
1684 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1685 nsresult rv = GatherObservers(observers);
1686 NS_ENSURE_SUCCESS(rv, rv);
1688 for (PRInt32 i = 0; i < observers.Count(); i++) {
1689 observers[i]->ItemStarted(this, aItem);
1692 return NS_OK;
1695 nsresult
1696 nsOfflineCacheUpdate::NotifyCompleted(nsOfflineCacheUpdateItem *aItem)
1698 LOG(("nsOfflineCacheUpdate::NotifyCompleted [%p, %p]", this, aItem));
1700 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
1701 nsresult rv = GatherObservers(observers);
1702 NS_ENSURE_SUCCESS(rv, rv);
1704 for (PRInt32 i = 0; i < observers.Count(); i++) {
1705 observers[i]->ItemCompleted(this, aItem);
1708 return NS_OK;
1711 void
1712 nsOfflineCacheUpdate::AddDocument(nsIDOMDocument *aDocument)
1714 // Add document only if it was not loaded from an offline cache.
1715 // If it were loaded from an offline cache then it has already
1716 // been associated with it and must not be again cached as
1717 // implicit (which are the reasons we collect documents here).
1718 nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
1719 if (!document)
1720 return;
1722 nsIChannel* channel = document->GetChannel();
1723 nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
1724 do_QueryInterface(channel);
1725 if (!appCacheChannel)
1726 return;
1728 PRBool loadedFromAppCache;
1729 appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
1730 if (loadedFromAppCache)
1731 return;
1733 mDocuments.AppendObject(aDocument);
1736 nsresult
1737 nsOfflineCacheUpdate::ScheduleImplicit()
1739 if (mDocuments.Count() == 0)
1740 return NS_OK;
1742 nsresult rv;
1744 nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
1745 NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
1747 nsCAutoString clientID;
1748 if (mPreviousApplicationCache) {
1749 rv = mPreviousApplicationCache->GetClientID(clientID);
1750 NS_ENSURE_SUCCESS(rv, rv);
1752 else {
1753 clientID = mClientID;
1756 rv = update->InitPartial(mManifestURI, clientID, mDocumentURI);
1757 NS_ENSURE_SUCCESS(rv, rv);
1759 PRBool added = PR_FALSE;
1760 for (PRInt32 i = 0; i < mDocuments.Count(); i++) {
1761 nsIDOMDocument* domDoc = mDocuments[i];
1762 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
1763 if (!doc)
1764 continue;
1766 nsIURI* uri = doc->GetDocumentURI();
1767 if (!uri)
1768 continue;
1770 nsIContent* content = doc->GetRootContent();
1771 nsCOMPtr<nsIDOMElement> root = do_QueryInterface(content);
1772 if (!root)
1773 continue;
1775 nsAutoString manifestSpec;
1776 rv = root->GetAttribute(NS_LITERAL_STRING("manifest"), manifestSpec);
1777 NS_ENSURE_SUCCESS(rv, rv);
1779 nsCOMPtr<nsIURI> manifestURI;
1780 NS_NewURI(getter_AddRefs(manifestURI), manifestSpec,
1781 doc->GetDocumentCharacterSet().get(),
1782 doc->GetDocumentURI());
1783 if (!manifestURI)
1784 continue;
1786 rv = update->AddURI(uri, nsIApplicationCache::ITEM_IMPLICIT);
1787 NS_ENSURE_SUCCESS(rv, rv);
1789 added = PR_TRUE;
1792 if (!added)
1793 return NS_OK;
1795 rv = update->Schedule();
1796 NS_ENSURE_SUCCESS(rv, rv);
1798 return NS_OK;
1801 nsresult
1802 nsOfflineCacheUpdate::AssociateDocument(nsIDOMDocument *aDocument)
1804 // Check that the document that requested this update was
1805 // previously associated with an application cache. If not, it
1806 // should be associated with the new one.
1807 nsCOMPtr<nsIApplicationCacheContainer> container =
1808 do_QueryInterface(aDocument);
1809 if (!container)
1810 return NS_OK;
1812 nsCOMPtr<nsIApplicationCache> existingCache;
1813 nsresult rv = container->GetApplicationCache(getter_AddRefs(existingCache));
1814 NS_ENSURE_SUCCESS(rv, rv);
1816 if (!existingCache) {
1817 LOG(("Update %p: associating app cache %s to document %p", this, mClientID.get(), aDocument));
1819 rv = container->SetApplicationCache(mApplicationCache);
1820 NS_ENSURE_SUCCESS(rv, rv);
1823 return NS_OK;
1826 nsresult
1827 nsOfflineCacheUpdate::Finish()
1829 LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
1831 mState = STATE_FINISHED;
1833 nsOfflineCacheUpdateService* service =
1834 nsOfflineCacheUpdateService::EnsureService();
1836 if (!service)
1837 return NS_ERROR_FAILURE;
1839 if (!mPartialUpdate) {
1840 if (mSucceeded) {
1841 nsIArray *namespaces = mManifestItem->GetNamespaces();
1842 nsresult rv = mApplicationCache->AddNamespaces(namespaces);
1843 if (NS_FAILED(rv)) {
1844 NotifyError();
1845 mSucceeded = PR_FALSE;
1848 rv = mApplicationCache->Activate();
1849 if (NS_FAILED(rv)) {
1850 NotifyError();
1851 mSucceeded = PR_FALSE;
1854 for (PRInt32 i = 0; i < mDocuments.Count(); i++) {
1855 AssociateDocument(mDocuments[i]);
1859 if (mObsolete) {
1860 nsCOMPtr<nsIApplicationCacheService> appCacheService =
1861 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
1862 if (appCacheService) {
1863 nsCAutoString groupID;
1864 mApplicationCache->GetGroupID(groupID);
1865 appCacheService->DeactivateGroup(groupID);
1869 if (!mSucceeded) {
1870 // Update was not merged, mark all the loads as failures
1871 for (PRUint32 i = 0; i < mItems.Length(); i++) {
1872 mItems[i]->Cancel();
1875 mApplicationCache->Discard();
1879 return service->UpdateFinished(this);
1882 //-----------------------------------------------------------------------------
1883 // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
1884 //-----------------------------------------------------------------------------
1886 NS_IMETHODIMP
1887 nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
1889 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1891 aUpdateDomain = mUpdateDomain;
1892 return NS_OK;
1895 NS_IMETHODIMP
1896 nsOfflineCacheUpdate::GetStatus(PRUint16 *aStatus)
1898 switch (mState) {
1899 case STATE_CHECKING :
1900 *aStatus = nsIDOMOfflineResourceList::CHECKING;
1901 return NS_OK;
1902 case STATE_DOWNLOADING :
1903 *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
1904 return NS_OK;
1905 default :
1906 *aStatus = nsIDOMOfflineResourceList::IDLE;
1907 return NS_OK;
1910 return NS_ERROR_FAILURE;
1913 NS_IMETHODIMP
1914 nsOfflineCacheUpdate::GetPartial(PRBool *aPartial)
1916 *aPartial = mPartialUpdate;
1917 return NS_OK;
1920 NS_IMETHODIMP
1921 nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
1923 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1925 NS_IF_ADDREF(*aManifestURI = mManifestURI);
1926 return NS_OK;
1929 NS_IMETHODIMP
1930 nsOfflineCacheUpdate::GetSucceeded(PRBool *aSucceeded)
1932 NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
1934 *aSucceeded = mSucceeded;
1936 return NS_OK;
1939 NS_IMETHODIMP
1940 nsOfflineCacheUpdate::GetIsUpgrade(PRBool *aIsUpgrade)
1942 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1944 *aIsUpgrade = (mPreviousApplicationCache != nsnull);
1946 return NS_OK;
1949 nsresult
1950 nsOfflineCacheUpdate::AddURI(nsIURI *aURI, PRUint32 aType)
1952 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
1954 if (mState >= STATE_DOWNLOADING)
1955 return NS_ERROR_NOT_AVAILABLE;
1957 // Resource URIs must have the same scheme as the manifest.
1958 nsCAutoString scheme;
1959 aURI->GetScheme(scheme);
1961 PRBool match;
1962 if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
1963 return NS_ERROR_FAILURE;
1965 // Don't fetch the same URI twice.
1966 for (PRUint32 i = 0; i < mItems.Length(); i++) {
1967 PRBool equals;
1968 if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
1969 // retain both types.
1970 mItems[i]->mItemType |= aType;
1971 return NS_OK;
1975 nsRefPtr<nsOfflineCacheUpdateItem> item =
1976 new nsOfflineCacheUpdateItem(this, aURI, mDocumentURI,
1977 mPreviousApplicationCache, mClientID,
1978 aType);
1979 if (!item) return NS_ERROR_OUT_OF_MEMORY;
1981 mItems.AppendElement(item);
1982 mAddedItems = PR_TRUE;
1984 return NS_OK;
1987 NS_IMETHODIMP
1988 nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
1990 // If this is a partial update and the resource is already in the
1991 // cache, we should only mark the entry, not fetch it again.
1992 if (mPartialUpdate) {
1993 nsCAutoString key;
1994 GetCacheKey(aURI, key);
1996 PRUint32 types;
1997 nsresult rv = mApplicationCache->GetTypes(key, &types);
1998 if (NS_SUCCEEDED(rv)) {
1999 if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
2000 mApplicationCache->MarkEntry
2001 (key, nsIApplicationCache::ITEM_DYNAMIC);
2003 return NS_OK;
2007 return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
2010 NS_IMETHODIMP
2011 nsOfflineCacheUpdate::GetCount(PRUint32 *aNumItems)
2013 LOG(("nsOfflineCacheUpdate::GetNumItems [%p, num=%d]",
2014 this, mItems.Length()));
2016 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2018 *aNumItems = mItems.Length();
2019 return NS_OK;
2022 NS_IMETHODIMP
2023 nsOfflineCacheUpdate::Item(PRUint32 aIndex, nsIDOMLoadStatus **aItem)
2025 LOG(("nsOfflineCacheUpdate::GetItems [%p, index=%d]", this, aIndex));
2027 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2029 if (aIndex < mItems.Length())
2030 NS_IF_ADDREF(*aItem = mItems.ElementAt(aIndex));
2031 else
2032 *aItem = nsnull;
2034 return NS_OK;
2037 NS_IMETHODIMP
2038 nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
2039 PRBool aHoldWeak)
2041 LOG(("nsOfflineCacheUpdate::AddObserver [%p]", this));
2043 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2045 if (aHoldWeak) {
2046 nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
2047 mWeakObservers.AppendObject(weakRef);
2048 } else {
2049 mObservers.AppendObject(aObserver);
2052 return NS_OK;
2055 NS_IMETHODIMP
2056 nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
2058 LOG(("nsOfflineCacheUpdate::RemoveObserver [%p]", this));
2060 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
2062 for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
2063 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
2064 do_QueryReferent(mWeakObservers[i]);
2065 if (observer == aObserver) {
2066 mWeakObservers.RemoveObjectAt(i);
2067 return NS_OK;
2071 for (PRInt32 i = 0; i < mObservers.Count(); i++) {
2072 if (mObservers[i] == aObserver) {
2073 mObservers.RemoveObjectAt(i);
2074 return NS_OK;
2078 return NS_OK;
2082 NS_IMETHODIMP
2083 nsOfflineCacheUpdate::Schedule()
2085 LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
2087 nsOfflineCacheUpdateService* service =
2088 nsOfflineCacheUpdateService::EnsureService();
2090 if (!service) {
2091 return NS_ERROR_FAILURE;
2094 return service->Schedule(this);
2097 //-----------------------------------------------------------------------------
2098 // nsOfflineCacheUpdateService::nsISupports
2099 //-----------------------------------------------------------------------------
2101 NS_IMPL_ISUPPORTS4(nsOfflineCacheUpdateService,
2102 nsIOfflineCacheUpdateService,
2103 nsIWebProgressListener,
2104 nsIObserver,
2105 nsISupportsWeakReference)
2107 //-----------------------------------------------------------------------------
2108 // nsOfflineCacheUpdateService <public>
2109 //-----------------------------------------------------------------------------
2111 nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
2112 : mDisabled(PR_FALSE)
2113 , mUpdateRunning(PR_FALSE)
2117 nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService()
2119 gOfflineCacheUpdateService = nsnull;
2122 nsresult
2123 nsOfflineCacheUpdateService::Init()
2125 nsresult rv;
2127 #if defined(PR_LOGGING)
2128 if (!gOfflineCacheUpdateLog)
2129 gOfflineCacheUpdateLog = PR_NewLogModule("nsOfflineCacheUpdate");
2130 #endif
2132 if (!mDocUpdates.Init())
2133 return NS_ERROR_FAILURE;
2135 // Observe xpcom-shutdown event
2136 nsCOMPtr<nsIObserverService> observerService =
2137 do_GetService("@mozilla.org/observer-service;1", &rv);
2138 NS_ENSURE_SUCCESS(rv, rv);
2140 rv = observerService->AddObserver(this,
2141 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
2142 PR_TRUE);
2143 NS_ENSURE_SUCCESS(rv, rv);
2145 // Register as an observer for the document loader
2146 nsCOMPtr<nsIWebProgress> progress =
2147 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
2148 if (progress) {
2149 nsresult rv = progress->AddProgressListener
2150 (this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
2151 NS_ENSURE_SUCCESS(rv, rv);
2154 gOfflineCacheUpdateService = this;
2156 return NS_OK;
2159 /* static */
2160 nsOfflineCacheUpdateService *
2161 nsOfflineCacheUpdateService::GetInstance()
2163 if (!gOfflineCacheUpdateService) {
2164 gOfflineCacheUpdateService = new nsOfflineCacheUpdateService();
2165 if (!gOfflineCacheUpdateService)
2166 return nsnull;
2167 NS_ADDREF(gOfflineCacheUpdateService);
2168 nsresult rv = gOfflineCacheUpdateService->Init();
2169 if (NS_FAILED(rv)) {
2170 NS_RELEASE(gOfflineCacheUpdateService);
2171 return nsnull;
2173 return gOfflineCacheUpdateService;
2176 NS_ADDREF(gOfflineCacheUpdateService);
2178 return gOfflineCacheUpdateService;
2181 /* static */
2182 nsOfflineCacheUpdateService *
2183 nsOfflineCacheUpdateService::EnsureService()
2185 if (!gOfflineCacheUpdateService) {
2186 // Make the service manager hold a long-lived reference to the service
2187 nsCOMPtr<nsIOfflineCacheUpdateService> service =
2188 do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
2191 return gOfflineCacheUpdateService;
2194 nsresult
2195 nsOfflineCacheUpdateService::Schedule(nsOfflineCacheUpdate *aUpdate)
2197 LOG(("nsOfflineCacheUpdateService::Schedule [%p, update=%p]",
2198 this, aUpdate));
2200 nsresult rv;
2201 nsCOMPtr<nsIObserverService> observerService =
2202 do_GetService("@mozilla.org/observer-service;1", &rv);
2203 NS_ENSURE_SUCCESS(rv, rv);
2205 observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(aUpdate),
2206 "offline-cache-update-added",
2207 nsnull);
2209 mUpdates.AppendElement(aUpdate);
2211 ProcessNextUpdate();
2213 return NS_OK;
2216 NS_IMETHODIMP
2217 nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsIURI *aManifestURI,
2218 nsIURI *aDocumentURI,
2219 nsIDOMDocument *aDocument)
2221 LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]",
2222 this, aManifestURI, aDocumentURI, aDocument));
2224 // Proceed with cache update
2225 PendingUpdate *update = new PendingUpdate();
2226 update->mManifestURI = aManifestURI;
2227 update->mDocumentURI = aDocumentURI;
2228 if (!mDocUpdates.Put(aDocument, update))
2229 return NS_ERROR_FAILURE;
2231 return NS_OK;
2234 nsresult
2235 nsOfflineCacheUpdateService::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
2237 LOG(("nsOfflineCacheUpdateService::UpdateFinished [%p, update=%p]",
2238 this, aUpdate));
2240 NS_ASSERTION(mUpdates.Length() > 0 &&
2241 mUpdates[0] == aUpdate, "Unknown update completed");
2243 // keep this item alive until we're done notifying observers
2244 nsRefPtr<nsOfflineCacheUpdate> update = mUpdates[0];
2245 mUpdates.RemoveElementAt(0);
2246 mUpdateRunning = PR_FALSE;
2248 nsresult rv;
2249 nsCOMPtr<nsIObserverService> observerService =
2250 do_GetService("@mozilla.org/observer-service;1", &rv);
2251 NS_ENSURE_SUCCESS(rv, rv);
2253 observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(aUpdate),
2254 "offline-cache-update-completed",
2255 nsnull);
2257 ProcessNextUpdate();
2259 return NS_OK;
2262 //-----------------------------------------------------------------------------
2263 // nsOfflineCacheUpdateService <private>
2264 //-----------------------------------------------------------------------------
2266 nsresult
2267 nsOfflineCacheUpdateService::ProcessNextUpdate()
2269 LOG(("nsOfflineCacheUpdateService::ProcessNextUpdate [%p, num=%d]",
2270 this, mUpdates.Length()));
2272 if (mDisabled)
2273 return NS_ERROR_ABORT;
2275 if (mUpdateRunning)
2276 return NS_OK;
2278 if (mUpdates.Length() > 0) {
2279 mUpdateRunning = PR_TRUE;
2280 return mUpdates[0]->Begin();
2283 return NS_OK;
2286 //-----------------------------------------------------------------------------
2287 // nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
2288 //-----------------------------------------------------------------------------
2290 NS_IMETHODIMP
2291 nsOfflineCacheUpdateService::GetNumUpdates(PRUint32 *aNumUpdates)
2293 LOG(("nsOfflineCacheUpdateService::GetNumUpdates [%p]", this));
2295 *aNumUpdates = mUpdates.Length();
2296 return NS_OK;
2299 NS_IMETHODIMP
2300 nsOfflineCacheUpdateService::GetUpdate(PRUint32 aIndex,
2301 nsIOfflineCacheUpdate **aUpdate)
2303 LOG(("nsOfflineCacheUpdateService::GetUpdate [%p, %d]", this, aIndex));
2305 if (aIndex < mUpdates.Length()) {
2306 NS_ADDREF(*aUpdate = mUpdates[aIndex]);
2307 } else {
2308 *aUpdate = nsnull;
2311 return NS_OK;
2314 nsresult
2315 nsOfflineCacheUpdateService::Schedule(nsIURI *aManifestURI,
2316 nsIURI *aDocumentURI,
2317 nsIDOMDocument *aDocument,
2318 nsIOfflineCacheUpdate **aUpdate)
2320 // Check for existing updates
2321 nsresult rv;
2322 for (PRUint32 i = 0; i < mUpdates.Length(); i++) {
2323 nsRefPtr<nsOfflineCacheUpdate> update = mUpdates[i];
2325 PRBool partial;
2326 rv = update->GetPartial(&partial);
2327 NS_ENSURE_SUCCESS(rv, rv);
2329 if (partial) {
2330 // Partial updates aren't considered
2331 continue;
2334 nsCOMPtr<nsIURI> manifestURI;
2335 update->GetManifestURI(getter_AddRefs(manifestURI));
2336 if (manifestURI) {
2337 PRBool equals;
2338 rv = manifestURI->Equals(aManifestURI, &equals);
2339 if (equals) {
2340 if (aDocument) {
2341 LOG(("Document %p added to update %p", aDocument, update.get()));
2342 update->AddDocument(aDocument);
2344 NS_ADDREF(*aUpdate = update);
2345 return NS_OK;
2350 // There is no existing update, start one.
2352 nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
2353 if (!update)
2354 return NS_ERROR_OUT_OF_MEMORY;
2356 rv = update->Init(aManifestURI, aDocumentURI);
2357 NS_ENSURE_SUCCESS(rv, rv);
2359 if (aDocument) {
2360 LOG(("First document %p added to update %p", aDocument, update.get()));
2361 update->AddDocument(aDocument);
2364 rv = update->Schedule();
2365 NS_ENSURE_SUCCESS(rv, rv);
2367 NS_ADDREF(*aUpdate = update);
2369 return NS_OK;
2372 NS_IMETHODIMP
2373 nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI *aManifestURI,
2374 nsIURI *aDocumentURI,
2375 nsIOfflineCacheUpdate **aUpdate)
2377 return Schedule(aManifestURI, aDocumentURI, nsnull, aUpdate);
2380 //-----------------------------------------------------------------------------
2381 // nsOfflineCacheUpdateService::nsIObserver
2382 //-----------------------------------------------------------------------------
2384 NS_IMETHODIMP
2385 nsOfflineCacheUpdateService::Observe(nsISupports *aSubject,
2386 const char *aTopic,
2387 const PRUnichar *aData)
2389 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2390 if (mUpdates.Length() > 0)
2391 mUpdates[0]->Cancel();
2392 mDisabled = PR_TRUE;
2395 return NS_OK;
2398 //-----------------------------------------------------------------------------
2399 // nsOfflineCacheUpdateService::nsIWebProgressListener
2400 //-----------------------------------------------------------------------------
2402 NS_IMETHODIMP
2403 nsOfflineCacheUpdateService::OnProgressChange(nsIWebProgress *aProgress,
2404 nsIRequest *aRequest,
2405 PRInt32 curSelfProgress,
2406 PRInt32 maxSelfProgress,
2407 PRInt32 curTotalProgress,
2408 PRInt32 maxTotalProgress)
2410 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2411 return NS_OK;
2414 NS_IMETHODIMP
2415 nsOfflineCacheUpdateService::OnStateChange(nsIWebProgress* aWebProgress,
2416 nsIRequest *aRequest,
2417 PRUint32 progressStateFlags,
2418 nsresult aStatus)
2420 if ((progressStateFlags & STATE_IS_DOCUMENT) &&
2421 (progressStateFlags & STATE_STOP)) {
2422 if (mDocUpdates.Count() == 0)
2423 return NS_OK;
2425 nsCOMPtr<nsIDOMWindow> window;
2426 aWebProgress->GetDOMWindow(getter_AddRefs(window));
2427 if (!window) return NS_OK;
2429 nsCOMPtr<nsIDOMDocument> doc;
2430 window->GetDocument(getter_AddRefs(doc));
2431 if (!doc) return NS_OK;
2433 LOG(("nsOfflineCacheUpdateService::OnStateChange [%p, doc=%p]",
2434 this, doc.get()));
2436 PendingUpdate *pendingUpdate;
2437 if (mDocUpdates.Get(doc, &pendingUpdate)) {
2438 // Only schedule the update if the document loaded successfully
2439 if (NS_SUCCEEDED(aStatus)) {
2440 nsCOMPtr<nsIOfflineCacheUpdate> update;
2441 Schedule(pendingUpdate->mManifestURI,
2442 pendingUpdate->mDocumentURI,
2443 doc, getter_AddRefs(update));
2445 mDocUpdates.Remove(doc);
2448 return NS_OK;
2451 return NS_OK;
2454 NS_IMETHODIMP
2455 nsOfflineCacheUpdateService::OnLocationChange(nsIWebProgress* aWebProgress,
2456 nsIRequest* aRequest,
2457 nsIURI *location)
2459 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2460 return NS_OK;
2463 NS_IMETHODIMP
2464 nsOfflineCacheUpdateService::OnStatusChange(nsIWebProgress* aWebProgress,
2465 nsIRequest* aRequest,
2466 nsresult aStatus,
2467 const PRUnichar* aMessage)
2469 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2470 return NS_OK;
2473 NS_IMETHODIMP
2474 nsOfflineCacheUpdateService::OnSecurityChange(nsIWebProgress *aWebProgress,
2475 nsIRequest *aRequest,
2476 PRUint32 state)
2478 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2479 return NS_OK;
2482 NS_IMETHODIMP
2483 nsOfflineCacheUpdateService::OfflineAppAllowed(nsIPrincipal *aPrincipal,
2484 nsIPrefBranch *aPrefBranch,
2485 PRBool *aAllowed)
2487 nsCOMPtr<nsIURI> codebaseURI;
2488 nsresult rv = aPrincipal->GetURI(getter_AddRefs(codebaseURI));
2489 NS_ENSURE_SUCCESS(rv, rv);
2491 return OfflineAppAllowedForURI(codebaseURI, aPrefBranch, aAllowed);
2494 NS_IMETHODIMP
2495 nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI *aURI,
2496 nsIPrefBranch *aPrefBranch,
2497 PRBool *aAllowed)
2499 *aAllowed = PR_FALSE;
2501 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
2502 if (!innerURI)
2503 return NS_OK;
2505 // only http and https applications can use offline APIs.
2506 PRBool match;
2507 nsresult rv = innerURI->SchemeIs("http", &match);
2508 NS_ENSURE_SUCCESS(rv, rv);
2510 if (!match) {
2511 rv = innerURI->SchemeIs("https", &match);
2512 NS_ENSURE_SUCCESS(rv, rv);
2513 if (!match) {
2514 return NS_OK;
2518 nsCOMPtr<nsIPermissionManager> permissionManager =
2519 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
2520 if (!permissionManager) {
2521 return NS_OK;
2524 PRUint32 perm;
2525 permissionManager->TestExactPermission(innerURI, "offline-app", &perm);
2527 if (perm == nsIPermissionManager::UNKNOWN_ACTION) {
2528 nsCOMPtr<nsIPrefBranch> branch = aPrefBranch;
2529 if (!branch) {
2530 branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
2532 if (branch) {
2533 rv = branch->GetBoolPref("offline-apps.allow_by_default", aAllowed);
2534 if (NS_FAILED(rv)) {
2535 *aAllowed = PR_FALSE;
2539 return NS_OK;
2542 if (perm == nsIPermissionManager::DENY_ACTION) {
2543 return NS_OK;
2546 *aAllowed = PR_TRUE;
2548 return NS_OK;