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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Chris Waterson <waterson@netscape.com>
24 : * Brendan Eich <brendan@mozilla.org>
25 : * Ben Goodger <ben@netscape.com>
26 : * Benjamin Smedberg <bsmedberg@covad.net>
27 : * Mark Hammond <mhammond@skippinet.com.au>
28 : *
29 : * Alternatively, the contents of this file may be used under the terms of
30 : * either of the GNU General Public License Version 2 or later (the "GPL"),
31 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 : * in which case the provisions of the GPL or the LGPL are applicable instead
33 : * of those above. If you wish to allow use of your version of this file only
34 : * under the terms of either the GPL or the LGPL, and not to allow others to
35 : * use your version of this file under the terms of the MPL, indicate your
36 : * decision by deleting the provisions above and replace them with the notice
37 : * and other provisions required by the GPL or the LGPL. If you do not delete
38 : * the provisions above, a recipient may use your version of this file under
39 : * the terms of any one of the MPL, the GPL or the LGPL.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include "nsXULPrototypeCache.h"
44 :
45 : #include "plstr.h"
46 : #include "nsXULPrototypeDocument.h"
47 : #include "nsCSSStyleSheet.h"
48 : #include "nsIScriptRuntime.h"
49 : #include "nsIServiceManager.h"
50 : #include "nsIURI.h"
51 :
52 : #include "nsIChromeRegistry.h"
53 : #include "nsIFile.h"
54 : #include "nsIObjectInputStream.h"
55 : #include "nsIObjectOutputStream.h"
56 : #include "nsIObserverService.h"
57 : #include "nsIStringStream.h"
58 : #include "nsIStorageStream.h"
59 :
60 : #include "nsNetUtil.h"
61 : #include "nsAppDirectoryServiceDefs.h"
62 :
63 : #include "jsxdrapi.h"
64 :
65 : #include "mozilla/Preferences.h"
66 : #include "mozilla/scache/StartupCache.h"
67 : #include "mozilla/scache/StartupCacheUtils.h"
68 :
69 : using namespace mozilla;
70 : using namespace mozilla::scache;
71 :
72 : static bool gDisableXULCache = false; // enabled by default
73 : static const char kDisableXULCachePref[] = "nglayout.debug.disable_xul_cache";
74 : static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
75 : static const char kXULCachePrefix[] = "xulcache";
76 :
77 : //----------------------------------------------------------------------
78 :
79 : static int
80 0 : DisableXULCacheChangedCallback(const char* aPref, void* aClosure)
81 : {
82 : gDisableXULCache =
83 0 : Preferences::GetBool(kDisableXULCachePref, gDisableXULCache);
84 :
85 : // Flush the cache, regardless
86 0 : nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
87 0 : if (cache)
88 0 : cache->Flush();
89 :
90 0 : return 0;
91 : }
92 :
93 : //----------------------------------------------------------------------
94 :
95 : StartupCache* nsXULPrototypeCache::gStartupCache = nsnull;
96 : nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nsnull;
97 :
98 :
99 140 : nsXULPrototypeCache::nsXULPrototypeCache()
100 : {
101 140 : }
102 :
103 :
104 420 : nsXULPrototypeCache::~nsXULPrototypeCache()
105 : {
106 140 : FlushScripts();
107 560 : }
108 :
109 :
110 2588 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsXULPrototypeCache, nsIObserver)
111 :
112 : /* static */ nsXULPrototypeCache*
113 330 : nsXULPrototypeCache::GetInstance()
114 : {
115 330 : if (!sInstance) {
116 140 : NS_ADDREF(sInstance = new nsXULPrototypeCache());
117 :
118 140 : sInstance->mPrototypeTable.Init();
119 140 : sInstance->mStyleSheetTable.Init();
120 140 : sInstance->mScriptTable.Init();
121 140 : sInstance->mXBLDocTable.Init();
122 :
123 140 : sInstance->mCacheURITable.Init();
124 140 : sInstance->mInputStreamTable.Init();
125 140 : sInstance->mOutputStreamTable.Init();
126 :
127 : gDisableXULCache =
128 140 : Preferences::GetBool(kDisableXULCachePref, gDisableXULCache);
129 : Preferences::RegisterCallback(DisableXULCacheChangedCallback,
130 140 : kDisableXULCachePref);
131 :
132 : nsCOMPtr<nsIObserverService> obsSvc =
133 280 : mozilla::services::GetObserverService();
134 140 : if (obsSvc) {
135 140 : nsXULPrototypeCache *p = sInstance;
136 140 : obsSvc->AddObserver(p, "chrome-flush-skin-caches", false);
137 140 : obsSvc->AddObserver(p, "chrome-flush-caches", false);
138 140 : obsSvc->AddObserver(p, "startupcache-invalidate", false);
139 : }
140 :
141 : }
142 330 : return sInstance;
143 : }
144 :
145 : /* static */ StartupCache*
146 0 : nsXULPrototypeCache::GetStartupCache()
147 : {
148 0 : return gStartupCache;
149 : }
150 :
151 : //----------------------------------------------------------------------
152 :
153 : NS_IMETHODIMP
154 367 : nsXULPrototypeCache::Observe(nsISupports* aSubject,
155 : const char *aTopic,
156 : const PRUnichar *aData)
157 : {
158 367 : if (!strcmp(aTopic, "chrome-flush-skin-caches")) {
159 115 : FlushSkinFiles();
160 : }
161 252 : else if (!strcmp(aTopic, "chrome-flush-caches")) {
162 112 : Flush();
163 : }
164 140 : else if (!strcmp(aTopic, "startupcache-invalidate")) {
165 140 : AbortCaching();
166 : }
167 : else {
168 0 : NS_WARNING("Unexpected observer topic.");
169 : }
170 367 : return NS_OK;
171 : }
172 :
173 : nsXULPrototypeDocument*
174 0 : nsXULPrototypeCache::GetPrototype(nsIURI* aURI)
175 : {
176 0 : nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(aURI);
177 0 : if (protoDoc)
178 0 : return protoDoc;
179 :
180 0 : nsresult rv = BeginCaching(aURI);
181 0 : if (NS_FAILED(rv))
182 0 : return nsnull;
183 :
184 : // No prototype in XUL memory cache. Spin up the cache Service.
185 0 : nsCOMPtr<nsIObjectInputStream> ois;
186 0 : rv = GetInputStream(aURI, getter_AddRefs(ois));
187 0 : if (NS_FAILED(rv))
188 0 : return nsnull;
189 :
190 0 : nsRefPtr<nsXULPrototypeDocument> newProto;
191 0 : rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
192 0 : if (NS_FAILED(rv))
193 0 : return nsnull;
194 :
195 0 : rv = newProto->Read(ois);
196 0 : if (NS_SUCCEEDED(rv)) {
197 0 : rv = PutPrototype(newProto);
198 : } else {
199 0 : newProto = nsnull;
200 : }
201 :
202 0 : mInputStreamTable.Remove(aURI);
203 0 : RemoveFromCacheSet(aURI);
204 0 : return newProto;
205 : }
206 :
207 : nsresult
208 0 : nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument)
209 : {
210 0 : nsCOMPtr<nsIURI> uri = aDocument->GetURI();
211 : // Put() releases any old value and addrefs the new one
212 0 : NS_ENSURE_TRUE(mPrototypeTable.Put(uri, aDocument), NS_ERROR_OUT_OF_MEMORY);
213 :
214 0 : return NS_OK;
215 : }
216 :
217 : nsresult
218 0 : nsXULPrototypeCache::PutStyleSheet(nsCSSStyleSheet* aStyleSheet)
219 : {
220 0 : nsIURI* uri = aStyleSheet->GetSheetURI();
221 :
222 0 : NS_ENSURE_TRUE(mStyleSheetTable.Put(uri, aStyleSheet),
223 : NS_ERROR_OUT_OF_MEMORY);
224 :
225 0 : return NS_OK;
226 : }
227 :
228 :
229 : JSScript*
230 0 : nsXULPrototypeCache::GetScript(nsIURI* aURI, PRUint32 *aLangID)
231 : {
232 : CacheScriptEntry entry;
233 0 : if (!mScriptTable.Get(aURI, &entry)) {
234 0 : *aLangID = nsIProgrammingLanguage::UNKNOWN;
235 0 : return nsnull;
236 : }
237 0 : *aLangID = entry.mScriptTypeID;
238 0 : return entry.mScriptObject;
239 : }
240 :
241 :
242 : /* static */
243 : static PLDHashOperator
244 0 : ReleaseScriptObjectCallback(nsIURI* aKey, CacheScriptEntry &aData, void* aClosure)
245 : {
246 0 : nsCOMPtr<nsIScriptRuntime> rt;
247 0 : if (NS_SUCCEEDED(NS_GetScriptRuntimeByID(aData.mScriptTypeID, getter_AddRefs(rt))))
248 0 : rt->DropScriptObject(aData.mScriptObject);
249 0 : return PL_DHASH_REMOVE;
250 : }
251 :
252 : nsresult
253 0 : nsXULPrototypeCache::PutScript(nsIURI* aURI, PRUint32 aLangID, JSScript* aScriptObject)
254 : {
255 : CacheScriptEntry existingEntry;
256 0 : if (mScriptTable.Get(aURI, &existingEntry)) {
257 0 : NS_WARNING("loaded the same script twice (bug 392650)");
258 :
259 : // Reuse the callback used for enumeration in FlushScripts
260 0 : ReleaseScriptObjectCallback(aURI, existingEntry, nsnull);
261 : }
262 :
263 0 : CacheScriptEntry entry = {aLangID, aScriptObject};
264 :
265 0 : NS_ENSURE_TRUE(mScriptTable.Put(aURI, entry), NS_ERROR_OUT_OF_MEMORY);
266 :
267 : // Lock the object from being gc'd until it is removed from the cache
268 0 : nsCOMPtr<nsIScriptRuntime> rt;
269 0 : nsresult rv = NS_GetScriptRuntimeByID(aLangID, getter_AddRefs(rt));
270 0 : if (NS_SUCCEEDED(rv))
271 0 : rv = rt->HoldScriptObject(aScriptObject);
272 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to GC lock the object");
273 :
274 : // On failure doing the lock, we should remove the map entry?
275 0 : return rv;
276 : }
277 :
278 : void
279 442 : nsXULPrototypeCache::FlushScripts()
280 : {
281 : // This callback will unlock each object so it can once again be gc'd.
282 : // XXX - this might be slow - we fetch the runtime each and every object.
283 442 : mScriptTable.Enumerate(ReleaseScriptObjectCallback, nsnull);
284 442 : }
285 :
286 :
287 : nsresult
288 0 : nsXULPrototypeCache::PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo)
289 : {
290 0 : nsIURI* uri = aDocumentInfo->DocumentURI();
291 :
292 0 : nsRefPtr<nsXBLDocumentInfo> info;
293 0 : mXBLDocTable.Get(uri, getter_AddRefs(info));
294 0 : if (!info) {
295 0 : NS_ENSURE_TRUE(mXBLDocTable.Put(uri, aDocumentInfo),
296 : NS_ERROR_OUT_OF_MEMORY);
297 : }
298 0 : return NS_OK;
299 : }
300 :
301 : static PLDHashOperator
302 0 : FlushSkinXBL(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo>& aDocInfo, void* aClosure)
303 : {
304 0 : nsCAutoString str;
305 0 : aKey->GetPath(str);
306 :
307 0 : PLDHashOperator ret = PL_DHASH_NEXT;
308 :
309 0 : if (!strncmp(str.get(), "/skin", 5)) {
310 0 : ret = PL_DHASH_REMOVE;
311 : }
312 :
313 0 : return ret;
314 : }
315 :
316 : static PLDHashOperator
317 0 : FlushSkinSheets(nsIURI* aKey, nsRefPtr<nsCSSStyleSheet>& aSheet, void* aClosure)
318 : {
319 0 : nsCAutoString str;
320 0 : aSheet->GetSheetURI()->GetPath(str);
321 :
322 0 : PLDHashOperator ret = PL_DHASH_NEXT;
323 :
324 0 : if (!strncmp(str.get(), "/skin", 5)) {
325 : // This is a skin binding. Add the key to the list.
326 0 : ret = PL_DHASH_REMOVE;
327 : }
328 0 : return ret;
329 : }
330 :
331 : static PLDHashOperator
332 0 : FlushScopedSkinStylesheets(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo> &aDocInfo, void* aClosure)
333 : {
334 0 : aDocInfo->FlushSkinStylesheets();
335 0 : return PL_DHASH_NEXT;
336 : }
337 :
338 : void
339 115 : nsXULPrototypeCache::FlushSkinFiles()
340 : {
341 : // Flush out skin XBL files from the cache.
342 115 : mXBLDocTable.Enumerate(FlushSkinXBL, nsnull);
343 :
344 : // Now flush out our skin stylesheets from the cache.
345 115 : mStyleSheetTable.Enumerate(FlushSkinSheets, nsnull);
346 :
347 : // Iterate over all the remaining XBL and make sure cached
348 : // scoped skin stylesheets are flushed and refetched by the
349 : // prototype bindings.
350 115 : mXBLDocTable.Enumerate(FlushScopedSkinStylesheets, nsnull);
351 115 : }
352 :
353 :
354 : void
355 302 : nsXULPrototypeCache::Flush()
356 : {
357 302 : mPrototypeTable.Clear();
358 :
359 : // Clear the script cache, as it refers to prototype-owned mJSObjects.
360 302 : FlushScripts();
361 :
362 302 : mStyleSheetTable.Clear();
363 302 : mXBLDocTable.Clear();
364 302 : }
365 :
366 :
367 : bool
368 0 : nsXULPrototypeCache::IsEnabled()
369 : {
370 0 : return !gDisableXULCache;
371 : }
372 :
373 : static bool gDisableXULDiskCache = false; // enabled by default
374 :
375 : void
376 140 : nsXULPrototypeCache::AbortCaching()
377 : {
378 : #ifdef DEBUG_brendan
379 : NS_BREAK();
380 : #endif
381 :
382 : // Flush the XUL cache for good measure, in case we cached a bogus/downrev
383 : // script, somehow.
384 140 : Flush();
385 :
386 : // Clear the cache set
387 140 : mCacheURITable.Clear();
388 140 : }
389 :
390 :
391 : static const char kDisableXULDiskCachePref[] = "nglayout.debug.disable_xul_fastload";
392 :
393 : void
394 0 : nsXULPrototypeCache::RemoveFromCacheSet(nsIURI* aURI)
395 : {
396 0 : mCacheURITable.Remove(aURI);
397 0 : }
398 :
399 : nsresult
400 0 : nsXULPrototypeCache::WritePrototype(nsXULPrototypeDocument* aPrototypeDocument)
401 : {
402 0 : nsresult rv = NS_OK, rv2 = NS_OK;
403 :
404 : // We're here before the startupcache service has been initialized, probably because
405 : // of the profile manager. Bail quietly, don't worry, we'll be back later.
406 0 : if (!gStartupCache)
407 0 : return NS_OK;
408 :
409 0 : nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
410 :
411 : // Remove this document from the cache table. We use the table's
412 : // emptiness instead of a counter to decide when the caching process
413 : // has completed.
414 0 : RemoveFromCacheSet(protoURI);
415 :
416 0 : nsCOMPtr<nsIObjectOutputStream> oos;
417 0 : rv = GetOutputStream(protoURI, getter_AddRefs(oos));
418 0 : NS_ENSURE_SUCCESS(rv, rv);
419 :
420 0 : rv = aPrototypeDocument->Write(oos);
421 0 : NS_ENSURE_SUCCESS(rv, rv);
422 0 : FinishOutputStream(protoURI);
423 0 : return NS_FAILED(rv) ? rv : rv2;
424 : }
425 :
426 : nsresult
427 0 : nsXULPrototypeCache::GetInputStream(nsIURI* uri, nsIObjectInputStream** stream)
428 : {
429 0 : nsCAutoString spec(kXULCachePrefix);
430 0 : nsresult rv = PathifyURI(uri, spec);
431 0 : if (NS_FAILED(rv))
432 0 : return NS_ERROR_NOT_AVAILABLE;
433 :
434 0 : nsAutoArrayPtr<char> buf;
435 : PRUint32 len;
436 0 : nsCOMPtr<nsIObjectInputStream> ois;
437 0 : if (!gStartupCache)
438 0 : return NS_ERROR_NOT_AVAILABLE;
439 :
440 0 : rv = gStartupCache->GetBuffer(spec.get(), getter_Transfers(buf), &len);
441 0 : if (NS_FAILED(rv))
442 0 : return NS_ERROR_NOT_AVAILABLE;
443 :
444 0 : rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois));
445 0 : NS_ENSURE_SUCCESS(rv, rv);
446 0 : buf.forget();
447 :
448 0 : mInputStreamTable.Put(uri, ois);
449 :
450 0 : NS_ADDREF(*stream = ois);
451 0 : return NS_OK;
452 : }
453 :
454 : nsresult
455 0 : nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
456 0 : mInputStreamTable.Remove(uri);
457 0 : return NS_OK;
458 : }
459 :
460 : nsresult
461 0 : nsXULPrototypeCache::GetOutputStream(nsIURI* uri, nsIObjectOutputStream** stream)
462 : {
463 : nsresult rv;
464 0 : nsCOMPtr<nsIObjectOutputStream> objectOutput;
465 0 : nsCOMPtr<nsIStorageStream> storageStream;
466 0 : bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
467 0 : if (found) {
468 0 : objectOutput = do_CreateInstance("mozilla.org/binaryoutputstream;1");
469 0 : if (!objectOutput) return NS_ERROR_OUT_OF_MEMORY;
470 : nsCOMPtr<nsIOutputStream> outputStream
471 0 : = do_QueryInterface(storageStream);
472 0 : objectOutput->SetOutputStream(outputStream);
473 : } else {
474 0 : rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput),
475 0 : getter_AddRefs(storageStream),
476 0 : false);
477 0 : NS_ENSURE_SUCCESS(rv, rv);
478 0 : mOutputStreamTable.Put(uri, storageStream);
479 : }
480 0 : NS_ADDREF(*stream = objectOutput);
481 0 : return NS_OK;
482 : }
483 :
484 : nsresult
485 0 : nsXULPrototypeCache::FinishOutputStream(nsIURI* uri)
486 : {
487 : nsresult rv;
488 0 : if (!gStartupCache)
489 0 : return NS_ERROR_NOT_AVAILABLE;
490 :
491 0 : nsCOMPtr<nsIStorageStream> storageStream;
492 0 : bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
493 0 : if (!found)
494 0 : return NS_ERROR_UNEXPECTED;
495 : nsCOMPtr<nsIOutputStream> outputStream
496 0 : = do_QueryInterface(storageStream);
497 0 : outputStream->Close();
498 :
499 0 : nsAutoArrayPtr<char> buf;
500 : PRUint32 len;
501 : rv = NewBufferFromStorageStream(storageStream, getter_Transfers(buf),
502 0 : &len);
503 0 : NS_ENSURE_SUCCESS(rv, rv);
504 :
505 0 : nsCAutoString spec(kXULCachePrefix);
506 0 : rv = PathifyURI(uri, spec);
507 0 : if (NS_FAILED(rv))
508 0 : return NS_ERROR_NOT_AVAILABLE;
509 0 : rv = gStartupCache->PutBuffer(spec.get(), buf, len);
510 0 : if (NS_SUCCEEDED(rv))
511 0 : mOutputStreamTable.Remove(uri);
512 :
513 0 : return rv;
514 : }
515 :
516 : // We have data if we're in the middle of writing it or we already
517 : // have it in the cache.
518 : nsresult
519 0 : nsXULPrototypeCache::HasData(nsIURI* uri, bool* exists)
520 : {
521 0 : if (mOutputStreamTable.Get(uri, nsnull)) {
522 0 : *exists = true;
523 0 : return NS_OK;
524 : }
525 0 : nsCAutoString spec(kXULCachePrefix);
526 0 : nsresult rv = PathifyURI(uri, spec);
527 0 : if (NS_FAILED(rv)) {
528 0 : *exists = false;
529 0 : return NS_OK;
530 : }
531 0 : nsAutoArrayPtr<char> buf;
532 : PRUint32 len;
533 0 : if (gStartupCache)
534 : rv = gStartupCache->GetBuffer(spec.get(), getter_Transfers(buf),
535 0 : &len);
536 : else {
537 : // We don't have everything we need to call BeginCaching and set up
538 : // gStartupCache right now, but we just need to check the cache for
539 : // this URI.
540 0 : StartupCache* sc = StartupCache::GetSingleton();
541 0 : if (!sc) {
542 0 : *exists = false;
543 0 : return NS_OK;
544 : }
545 0 : rv = sc->GetBuffer(spec.get(), getter_Transfers(buf), &len);
546 : }
547 0 : *exists = NS_SUCCEEDED(rv);
548 0 : return NS_OK;
549 : }
550 :
551 : static int
552 0 : CachePrefChangedCallback(const char* aPref, void* aClosure)
553 : {
554 0 : bool wasEnabled = !gDisableXULDiskCache;
555 : gDisableXULDiskCache =
556 : Preferences::GetBool(kDisableXULCachePref,
557 0 : gDisableXULDiskCache);
558 :
559 0 : if (wasEnabled && gDisableXULDiskCache) {
560 0 : nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
561 :
562 0 : if (cache)
563 0 : cache->AbortCaching();
564 : }
565 0 : return 0;
566 : }
567 :
568 : nsresult
569 0 : nsXULPrototypeCache::BeginCaching(nsIURI* aURI)
570 : {
571 : nsresult rv;
572 :
573 0 : nsCAutoString path;
574 0 : aURI->GetPath(path);
575 0 : if (!StringEndsWith(path, NS_LITERAL_CSTRING(".xul")))
576 0 : return NS_ERROR_NOT_AVAILABLE;
577 :
578 : // Test gStartupCache to decide whether this is the first nsXULDocument
579 : // participating in the serialization. If gStartupCache is non-null, this document
580 : // must not be first, but it can join the process. Examples of
581 : // multiple master documents participating include hiddenWindow.xul and
582 : // navigator.xul on the Mac, and multiple-app-component (e.g., mailnews
583 : // and browser) startup due to command-line arguments.
584 : //
585 0 : if (gStartupCache) {
586 0 : mCacheURITable.Put(aURI, 1);
587 :
588 0 : return NS_OK;
589 : }
590 :
591 : // Use a local to refer to the service till we're sure we succeeded, then
592 : // commit to gStartupCache.
593 0 : StartupCache* startupCache = StartupCache::GetSingleton();
594 0 : if (!startupCache)
595 0 : return NS_ERROR_FAILURE;
596 :
597 : gDisableXULDiskCache =
598 0 : Preferences::GetBool(kDisableXULCachePref, gDisableXULDiskCache);
599 :
600 : Preferences::RegisterCallback(CachePrefChangedCallback,
601 0 : kDisableXULCachePref);
602 :
603 0 : if (gDisableXULDiskCache)
604 0 : return NS_ERROR_NOT_AVAILABLE;
605 :
606 : // Get the chrome directory to validate against the one stored in the
607 : // cache file, or to store there if we're generating a new file.
608 0 : nsCOMPtr<nsIFile> chromeDir;
609 0 : rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
610 0 : if (NS_FAILED(rv))
611 0 : return rv;
612 0 : nsCAutoString chromePath;
613 0 : rv = chromeDir->GetNativePath(chromePath);
614 0 : if (NS_FAILED(rv))
615 0 : return rv;
616 :
617 : // XXXbe we assume the first package's locale is the same as the locale of
618 : // all subsequent packages of cached chrome URIs....
619 0 : nsCAutoString package;
620 0 : rv = aURI->GetHost(package);
621 0 : if (NS_FAILED(rv))
622 0 : return rv;
623 : nsCOMPtr<nsIXULChromeRegistry> chromeReg
624 0 : = do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
625 0 : nsCAutoString locale;
626 0 : rv = chromeReg->GetSelectedLocale(package, locale);
627 0 : if (NS_FAILED(rv))
628 0 : return rv;
629 :
630 0 : nsCAutoString fileChromePath, fileLocale;
631 :
632 0 : nsAutoArrayPtr<char> buf;
633 : PRUint32 len, amtRead;
634 0 : nsCOMPtr<nsIObjectInputStream> objectInput;
635 :
636 : rv = startupCache->GetBuffer(kXULCacheInfoKey, getter_Transfers(buf),
637 0 : &len);
638 0 : if (NS_SUCCEEDED(rv))
639 0 : rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput));
640 :
641 0 : if (NS_SUCCEEDED(rv)) {
642 0 : buf.forget();
643 0 : rv = objectInput->ReadCString(fileLocale);
644 0 : rv |= objectInput->ReadCString(fileChromePath);
645 0 : if (NS_FAILED(rv) ||
646 0 : (!fileChromePath.Equals(chromePath) ||
647 0 : !fileLocale.Equals(locale))) {
648 : // Our cache won't be valid in this case, we'll need to rewrite.
649 : // XXX This blows away work that other consumers (like
650 : // mozJSComponentLoader) have done, need more fine-grained control.
651 0 : startupCache->InvalidateCache();
652 0 : rv = NS_ERROR_UNEXPECTED;
653 : }
654 0 : } else if (rv != NS_ERROR_NOT_AVAILABLE)
655 : // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
656 0 : return rv;
657 :
658 0 : if (NS_FAILED(rv)) {
659 : // Either the cache entry was invalid or it didn't exist, so write it now.
660 0 : nsCOMPtr<nsIObjectOutputStream> objectOutput;
661 0 : nsCOMPtr<nsIInputStream> inputStream;
662 0 : nsCOMPtr<nsIStorageStream> storageStream;
663 0 : rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput),
664 0 : getter_AddRefs(storageStream),
665 0 : false);
666 0 : if (NS_SUCCEEDED(rv)) {
667 0 : rv = objectOutput->WriteStringZ(locale.get());
668 0 : rv |= objectOutput->WriteStringZ(chromePath.get());
669 0 : rv |= objectOutput->Close();
670 0 : rv |= storageStream->NewInputStream(0, getter_AddRefs(inputStream));
671 : }
672 0 : if (NS_SUCCEEDED(rv))
673 0 : rv = inputStream->Available(&len);
674 :
675 0 : if (NS_SUCCEEDED(rv)) {
676 0 : buf = new char[len];
677 0 : rv = inputStream->Read(buf, len, &amtRead);
678 0 : if (NS_SUCCEEDED(rv) && len == amtRead)
679 0 : rv = startupCache->PutBuffer(kXULCacheInfoKey, buf, len);
680 : else {
681 0 : rv = NS_ERROR_UNEXPECTED;
682 : }
683 : }
684 :
685 : // Failed again, just bail.
686 0 : if (NS_FAILED(rv)) {
687 0 : startupCache->InvalidateCache();
688 0 : return NS_ERROR_FAILURE;
689 : }
690 : }
691 :
692 : // Success! Insert this URI into the mCacheURITable
693 : // and commit locals to globals.
694 0 : mCacheURITable.Put(aURI, 1);
695 :
696 0 : gStartupCache = startupCache;
697 0 : return NS_OK;
698 : }
699 :
700 : static PLDHashOperator
701 0 : MarkXBLInCCGeneration(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo> &aDocInfo,
702 : void* aClosure)
703 : {
704 0 : PRUint32* gen = static_cast<PRUint32*>(aClosure);
705 0 : aDocInfo->MarkInCCGeneration(*gen);
706 0 : return PL_DHASH_NEXT;
707 : }
708 :
709 : static PLDHashOperator
710 0 : MarkXULInCCGeneration(nsIURI* aKey, nsRefPtr<nsXULPrototypeDocument> &aDoc,
711 : void* aClosure)
712 : {
713 0 : PRUint32* gen = static_cast<PRUint32*>(aClosure);
714 0 : aDoc->MarkInCCGeneration(*gen);
715 0 : return PL_DHASH_NEXT;
716 : }
717 :
718 : void
719 280 : nsXULPrototypeCache::MarkInCCGeneration(PRUint32 aGeneration)
720 : {
721 280 : mXBLDocTable.Enumerate(MarkXBLInCCGeneration, &aGeneration);
722 280 : mPrototypeTable.Enumerate(MarkXULInCCGeneration, &aGeneration);
723 280 : }
|