1 /* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "BackgroundUtils.h"
7 #include "OfflineCacheUpdateChild.h"
8 #include "nsOfflineCacheUpdate.h"
9 #include "mozilla/dom/ContentChild.h"
10 #include "mozilla/dom/TabChild.h"
11 #include "mozilla/ipc/URIUtils.h"
12 #include "mozilla/net/NeckoCommon.h"
13
14 #include "nsIApplicationCacheContainer.h"
15 #include "nsIApplicationCacheChannel.h"
16 #include "nsIApplicationCacheService.h"
17 #include "nsIDocShell.h"
18 #include "nsIDocShellTreeItem.h"
19 #include "nsIDocShellTreeOwner.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsIDOMOfflineResourceList.h"
22 #include "nsIDocument.h"
23 #include "nsIObserverService.h"
24 #include "nsIURL.h"
25 #include "nsITabChild.h"
26 #include "nsNetCID.h"
27 #include "nsNetUtil.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsStreamUtils.h"
30 #include "nsThreadUtils.h"
31 #include "nsProxyRelease.h"
32 #include "mozilla/Logging.h"
33 #include "nsIAsyncVerifyRedirectCallback.h"
34
35 using namespace mozilla::ipc;
36 using namespace mozilla::net;
37 using mozilla::dom::ContentChild;
38 using mozilla::dom::TabChild;
39
40 //
41 // To enable logging (see mozilla/Logging.h for full details):
42 //
43 // set MOZ_LOG=nsOfflineCacheUpdate:5
44 // set MOZ_LOG_FILE=offlineupdate.log
45 //
46 // this enables LogLevel::Debug level information and places all output in
47 // the file offlineupdate.log
48 //
49 extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
50
51 #undef LOG
52 #define LOG(args) \
53 MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
54
55 #undef LOG_ENABLED
56 #define LOG_ENABLED() \
57 MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
58
59 namespace mozilla {
60 namespace docshell {
61
62 //-----------------------------------------------------------------------------
63 // OfflineCacheUpdateChild::nsISupports
64 //-----------------------------------------------------------------------------
65
66 NS_INTERFACE_MAP_BEGIN(OfflineCacheUpdateChild)
NS_INTERFACE_MAP_ENTRY(nsISupports)67 NS_INTERFACE_MAP_ENTRY(nsISupports)
68 NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdate)
69 NS_INTERFACE_MAP_END
70
71 NS_IMPL_ADDREF(OfflineCacheUpdateChild)
72 NS_IMPL_RELEASE(OfflineCacheUpdateChild)
73
74 //-----------------------------------------------------------------------------
75 // OfflineCacheUpdateChild <public>
76 //-----------------------------------------------------------------------------
77
78 OfflineCacheUpdateChild::OfflineCacheUpdateChild(nsPIDOMWindowInner *aWindow)
79 : mState(STATE_UNINITIALIZED),
80 mIsUpgrade(false),
81 mSucceeded(false),
82 mWindow(aWindow),
83 mByteProgress(0) {}
84
~OfflineCacheUpdateChild()85 OfflineCacheUpdateChild::~OfflineCacheUpdateChild() {
86 LOG(("OfflineCacheUpdateChild::~OfflineCacheUpdateChild [%p]", this));
87 }
88
GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> & aObservers)89 void OfflineCacheUpdateChild::GatherObservers(
90 nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers) {
91 for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
92 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
93 do_QueryReferent(mWeakObservers[i]);
94 if (observer)
95 aObservers.AppendObject(observer);
96 else
97 mWeakObservers.RemoveObjectAt(i--);
98 }
99
100 for (int32_t i = 0; i < mObservers.Count(); i++) {
101 aObservers.AppendObject(mObservers[i]);
102 }
103 }
104
SetDocument(nsIDOMDocument * aDocument)105 void OfflineCacheUpdateChild::SetDocument(nsIDOMDocument *aDocument) {
106 // The design is one document for one cache update on the content process.
107 NS_ASSERTION(
108 !mDocument,
109 "Setting more then a single document on a child offline cache update");
110
111 LOG(("Document %p added to update child %p", aDocument, this));
112
113 // Add document only if it was not loaded from an offline cache.
114 // If it were loaded from an offline cache then it has already
115 // been associated with it and must not be again cached as
116 // implicit (which are the reasons we collect documents here).
117 nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
118 if (!document) return;
119
120 nsIChannel *channel = document->GetChannel();
121 nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
122 do_QueryInterface(channel);
123 if (!appCacheChannel) return;
124
125 bool loadedFromAppCache;
126 appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
127 if (loadedFromAppCache) return;
128
129 mDocument = aDocument;
130 }
131
AssociateDocument(nsIDOMDocument * aDocument,nsIApplicationCache * aApplicationCache)132 nsresult OfflineCacheUpdateChild::AssociateDocument(
133 nsIDOMDocument *aDocument, nsIApplicationCache *aApplicationCache) {
134 // Check that the document that requested this update was
135 // previously associated with an application cache. If not, it
136 // should be associated with the new one.
137 nsCOMPtr<nsIApplicationCacheContainer> container =
138 do_QueryInterface(aDocument);
139 if (!container) return NS_OK;
140
141 nsCOMPtr<nsIApplicationCache> existingCache;
142 nsresult rv = container->GetApplicationCache(getter_AddRefs(existingCache));
143 NS_ENSURE_SUCCESS(rv, rv);
144
145 if (!existingCache) {
146 if (LOG_ENABLED()) {
147 nsAutoCString clientID;
148 if (aApplicationCache) {
149 aApplicationCache->GetClientID(clientID);
150 }
151 LOG(("Update %p: associating app cache %s to document %p", this,
152 clientID.get(), aDocument));
153 }
154
155 rv = container->SetApplicationCache(aApplicationCache);
156 NS_ENSURE_SUCCESS(rv, rv);
157 }
158
159 return NS_OK;
160 }
161
162 //-----------------------------------------------------------------------------
163 // OfflineCacheUpdateChild::nsIOfflineCacheUpdate
164 //-----------------------------------------------------------------------------
165
166 NS_IMETHODIMP
Init(nsIURI * aManifestURI,nsIURI * aDocumentURI,nsIPrincipal * aLoadingPrincipal,nsIDOMDocument * aDocument,nsIFile * aCustomProfileDir)167 OfflineCacheUpdateChild::Init(nsIURI *aManifestURI, nsIURI *aDocumentURI,
168 nsIPrincipal *aLoadingPrincipal,
169 nsIDOMDocument *aDocument,
170 nsIFile *aCustomProfileDir) {
171 nsresult rv;
172
173 // Make sure the service has been initialized
174 nsOfflineCacheUpdateService *service =
175 nsOfflineCacheUpdateService::EnsureService();
176 if (!service) return NS_ERROR_FAILURE;
177
178 if (aCustomProfileDir) {
179 NS_ERROR("Custom Offline Cache Update not supported on child process");
180 return NS_ERROR_NOT_IMPLEMENTED;
181 }
182
183 LOG(("OfflineCacheUpdateChild::Init [%p]", this));
184
185 // Only http and https applications are supported.
186 bool match;
187 rv = aManifestURI->SchemeIs("http", &match);
188 NS_ENSURE_SUCCESS(rv, rv);
189
190 if (!match) {
191 rv = aManifestURI->SchemeIs("https", &match);
192 NS_ENSURE_SUCCESS(rv, rv);
193 if (!match) return NS_ERROR_ABORT;
194 }
195
196 mManifestURI = aManifestURI;
197
198 rv = mManifestURI->GetAsciiHost(mUpdateDomain);
199 NS_ENSURE_SUCCESS(rv, rv);
200
201 mDocumentURI = aDocumentURI;
202 mLoadingPrincipal = aLoadingPrincipal;
203
204 mState = STATE_INITIALIZED;
205
206 if (aDocument) SetDocument(aDocument);
207
208 return NS_OK;
209 }
210
211 NS_IMETHODIMP
InitPartial(nsIURI * aManifestURI,const nsACString & clientID,nsIURI * aDocumentURI,nsIPrincipal * aLoadingPrincipal)212 OfflineCacheUpdateChild::InitPartial(nsIURI *aManifestURI,
213 const nsACString &clientID,
214 nsIURI *aDocumentURI,
215 nsIPrincipal *aLoadingPrincipal) {
216 NS_NOTREACHED(
217 "Not expected to do partial offline cache updates"
218 " on the child process");
219 // For now leaving this method, we may discover we need it.
220 return NS_ERROR_NOT_IMPLEMENTED;
221 }
222
223 NS_IMETHODIMP
InitForUpdateCheck(nsIURI * aManifestURI,nsIPrincipal * aLoadingPrincipal,nsIObserver * aObserver)224 OfflineCacheUpdateChild::InitForUpdateCheck(nsIURI *aManifestURI,
225 nsIPrincipal *aLoadingPrincipal,
226 nsIObserver *aObserver) {
227 NS_NOTREACHED(
228 "Not expected to do only update checks"
229 " from the child process");
230 return NS_ERROR_NOT_IMPLEMENTED;
231 }
232
233 NS_IMETHODIMP
GetUpdateDomain(nsACString & aUpdateDomain)234 OfflineCacheUpdateChild::GetUpdateDomain(nsACString &aUpdateDomain) {
235 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
236
237 aUpdateDomain = mUpdateDomain;
238 return NS_OK;
239 }
240
241 NS_IMETHODIMP
GetStatus(uint16_t * aStatus)242 OfflineCacheUpdateChild::GetStatus(uint16_t *aStatus) {
243 switch (mState) {
244 case STATE_CHECKING:
245 *aStatus = nsIDOMOfflineResourceList::CHECKING;
246 return NS_OK;
247 case STATE_DOWNLOADING:
248 *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
249 return NS_OK;
250 default:
251 *aStatus = nsIDOMOfflineResourceList::IDLE;
252 return NS_OK;
253 }
254
255 return NS_ERROR_FAILURE;
256 }
257
258 NS_IMETHODIMP
GetPartial(bool * aPartial)259 OfflineCacheUpdateChild::GetPartial(bool *aPartial) {
260 *aPartial = false;
261 return NS_OK;
262 }
263
264 NS_IMETHODIMP
GetManifestURI(nsIURI ** aManifestURI)265 OfflineCacheUpdateChild::GetManifestURI(nsIURI **aManifestURI) {
266 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
267
268 NS_IF_ADDREF(*aManifestURI = mManifestURI);
269 return NS_OK;
270 }
271
272 NS_IMETHODIMP
GetSucceeded(bool * aSucceeded)273 OfflineCacheUpdateChild::GetSucceeded(bool *aSucceeded) {
274 NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
275
276 *aSucceeded = mSucceeded;
277
278 return NS_OK;
279 }
280
281 NS_IMETHODIMP
GetIsUpgrade(bool * aIsUpgrade)282 OfflineCacheUpdateChild::GetIsUpgrade(bool *aIsUpgrade) {
283 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
284
285 *aIsUpgrade = mIsUpgrade;
286
287 return NS_OK;
288 }
289
290 NS_IMETHODIMP
AddDynamicURI(nsIURI * aURI)291 OfflineCacheUpdateChild::AddDynamicURI(nsIURI *aURI) {
292 return NS_ERROR_NOT_IMPLEMENTED;
293 }
294
295 NS_IMETHODIMP
Cancel()296 OfflineCacheUpdateChild::Cancel() { return NS_ERROR_NOT_IMPLEMENTED; }
297
298 NS_IMETHODIMP
AddObserver(nsIOfflineCacheUpdateObserver * aObserver,bool aHoldWeak)299 OfflineCacheUpdateChild::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
300 bool aHoldWeak) {
301 LOG(("OfflineCacheUpdateChild::AddObserver [%p]", this));
302
303 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
304
305 if (aHoldWeak) {
306 nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
307 mWeakObservers.AppendObject(weakRef);
308 } else {
309 mObservers.AppendObject(aObserver);
310 }
311
312 return NS_OK;
313 }
314
315 NS_IMETHODIMP
RemoveObserver(nsIOfflineCacheUpdateObserver * aObserver)316 OfflineCacheUpdateChild::RemoveObserver(
317 nsIOfflineCacheUpdateObserver *aObserver) {
318 LOG(("OfflineCacheUpdateChild::RemoveObserver [%p]", this));
319
320 NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
321
322 for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
323 nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
324 do_QueryReferent(mWeakObservers[i]);
325 if (observer == aObserver) {
326 mWeakObservers.RemoveObjectAt(i);
327 return NS_OK;
328 }
329 }
330
331 for (int32_t i = 0; i < mObservers.Count(); i++) {
332 if (mObservers[i] == aObserver) {
333 mObservers.RemoveObjectAt(i);
334 return NS_OK;
335 }
336 }
337
338 return NS_OK;
339 }
340
341 NS_IMETHODIMP
GetByteProgress(uint64_t * _result)342 OfflineCacheUpdateChild::GetByteProgress(uint64_t *_result) {
343 NS_ENSURE_ARG(_result);
344
345 *_result = mByteProgress;
346 return NS_OK;
347 }
348
349 NS_IMETHODIMP
Schedule()350 OfflineCacheUpdateChild::Schedule() {
351 LOG(("OfflineCacheUpdateChild::Schedule [%p]", this));
352
353 NS_ASSERTION(mWindow,
354 "Window must be provided to the offline cache update child");
355
356 nsCOMPtr<nsPIDOMWindowInner> window = mWindow.forget();
357 nsCOMPtr<nsIDocShell> docshell = window->GetDocShell();
358 if (!docshell) {
359 NS_WARNING("doc shell tree item is null");
360 return NS_ERROR_FAILURE;
361 }
362
363 nsCOMPtr<nsITabChild> tabchild = docshell->GetTabChild();
364 // because owner implements nsITabChild, we can assume that it is
365 // the one and only TabChild.
366 TabChild *child =
367 tabchild ? static_cast<TabChild *>(tabchild.get()) : nullptr;
368
369 if (MissingRequiredTabChild(child, "offlinecacheupdate")) {
370 return NS_ERROR_FAILURE;
371 }
372
373 URIParams manifestURI, documentURI;
374 SerializeURI(mManifestURI, manifestURI);
375 SerializeURI(mDocumentURI, documentURI);
376
377 nsresult rv = NS_OK;
378 PrincipalInfo loadingPrincipalInfo;
379 rv = PrincipalToPrincipalInfo(mLoadingPrincipal, &loadingPrincipalInfo);
380 NS_ENSURE_SUCCESS(rv, rv);
381
382 nsCOMPtr<nsIObserverService> observerService =
383 mozilla::services::GetObserverService();
384 if (observerService) {
385 LOG(("Calling offline-cache-update-added"));
386 observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate *>(this),
387 "offline-cache-update-added", nullptr);
388 LOG(("Done offline-cache-update-added"));
389 }
390
391 // mDocument is non-null if both:
392 // 1. this update was initiated by a document that referred a manifest
393 // 2. the document has not already been loaded from the application cache
394 // This tells the update to cache this document even in case the manifest
395 // has not been changed since the last fetch.
396 // See also nsOfflineCacheUpdate::ScheduleImplicit.
397 bool stickDocument = mDocument != nullptr;
398
399 // Need to addref ourself here, because the IPC stack doesn't hold
400 // a reference to us. Will be released in RecvFinish() that identifies
401 // the work has been done.
402 ContentChild::GetSingleton()->SendPOfflineCacheUpdateConstructor(
403 this, manifestURI, documentURI, loadingPrincipalInfo, stickDocument);
404
405 // ContentChild::DeallocPOfflineCacheUpdate will release this.
406 NS_ADDREF_THIS();
407
408 return NS_OK;
409 }
410
RecvAssociateDocuments(const nsCString & cacheGroupId,const nsCString & cacheClientId)411 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvAssociateDocuments(
412 const nsCString &cacheGroupId, const nsCString &cacheClientId) {
413 LOG(("OfflineCacheUpdateChild::RecvAssociateDocuments [%p, cache=%s]", this,
414 cacheClientId.get()));
415
416 nsresult rv;
417
418 nsCOMPtr<nsIApplicationCache> cache =
419 do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
420 if (NS_FAILED(rv)) return IPC_OK();
421
422 cache->InitAsHandle(cacheGroupId, cacheClientId);
423
424 if (mDocument) {
425 AssociateDocument(mDocument, cache);
426 }
427
428 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
429 GatherObservers(observers);
430
431 for (int32_t i = 0; i < observers.Count(); i++)
432 observers[i]->ApplicationCacheAvailable(cache);
433
434 return IPC_OK();
435 }
436
RecvNotifyStateEvent(const uint32_t & event,const uint64_t & byteProgress)437 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvNotifyStateEvent(
438 const uint32_t &event, const uint64_t &byteProgress) {
439 LOG(("OfflineCacheUpdateChild::RecvNotifyStateEvent [%p]", this));
440
441 mByteProgress = byteProgress;
442
443 // Convert the public observer state to our internal state
444 switch (event) {
445 case nsIOfflineCacheUpdateObserver::STATE_CHECKING:
446 mState = STATE_CHECKING;
447 break;
448
449 case nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING:
450 mState = STATE_DOWNLOADING;
451 break;
452
453 default:
454 break;
455 }
456
457 nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
458 GatherObservers(observers);
459
460 for (int32_t i = 0; i < observers.Count(); i++)
461 observers[i]->UpdateStateChanged(this, event);
462
463 return IPC_OK();
464 }
465
RecvFinish(const bool & succeeded,const bool & isUpgrade)466 mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvFinish(
467 const bool &succeeded, const bool &isUpgrade) {
468 LOG(("OfflineCacheUpdateChild::RecvFinish [%p]", this));
469
470 RefPtr<OfflineCacheUpdateChild> kungFuDeathGrip(this);
471
472 mState = STATE_FINISHED;
473 mSucceeded = succeeded;
474 mIsUpgrade = isUpgrade;
475
476 nsCOMPtr<nsIObserverService> observerService =
477 mozilla::services::GetObserverService();
478 if (observerService) {
479 LOG(("Calling offline-cache-update-completed"));
480 observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate *>(this),
481 "offline-cache-update-completed", nullptr);
482 LOG(("Done offline-cache-update-completed"));
483 }
484
485 // This is by contract the last notification from the parent, release
486 // us now. This is corresponding to AddRef in Schedule().
487 // TabChild::DeallocPOfflineCacheUpdate will call Release.
488 OfflineCacheUpdateChild::Send__delete__(this);
489
490 return IPC_OK();
491 }
492
493 } // namespace docshell
494 } // namespace mozilla
495