Bug 460926 A11y hierachy is broken on Ubuntu 8.10 (GNOME 2.24), r=Evan.Yan sr=roc
[wine-gecko.git] / content / xul / document / src / nsXULPrototypeCache.cpp
blob4b2c2341c66ecbeeefc10988a28e2bd3a653f8de
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 Communicator client code.
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.
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>
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.
41 * ***** END LICENSE BLOCK ***** */
43 #include "nsXULPrototypeCache.h"
45 #include "nsContentUtils.h"
46 #include "plstr.h"
47 #include "nsXULPrototypeDocument.h"
48 #include "nsICSSStyleSheet.h"
49 #include "nsIScriptRuntime.h"
50 #include "nsIServiceManager.h"
51 #include "nsIURI.h"
52 #include "nsIXBLDocumentInfo.h"
54 #include "nsIChromeRegistry.h"
55 #include "nsIFastLoadService.h"
56 #include "nsIFastLoadFileControl.h"
57 #include "nsIFile.h"
58 #include "nsIObjectInputStream.h"
59 #include "nsIObjectOutputStream.h"
60 #include "nsIObserverService.h"
62 #include "nsNetUtil.h"
63 #include "nsAppDirectoryServiceDefs.h"
65 #include "jsxdrapi.h"
67 static NS_DEFINE_CID(kXULPrototypeCacheCID, NS_XULPROTOTYPECACHE_CID);
69 static PRBool gDisableXULCache = PR_FALSE; // enabled by default
70 static const char kDisableXULCachePref[] = "nglayout.debug.disable_xul_cache";
72 //----------------------------------------------------------------------
74 static int
75 DisableXULCacheChangedCallback(const char* aPref, void* aClosure)
77 gDisableXULCache =
78 nsContentUtils::GetBoolPref(kDisableXULCachePref, gDisableXULCache);
80 // Flush the cache, regardless
81 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
82 if (cache)
83 cache->Flush();
85 return 0;
88 //----------------------------------------------------------------------
91 nsIFastLoadService* nsXULPrototypeCache::gFastLoadService = nsnull;
92 nsIFile* nsXULPrototypeCache::gFastLoadFile = nsnull;
93 nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nsnull;
96 nsXULPrototypeCache::nsXULPrototypeCache()
101 nsXULPrototypeCache::~nsXULPrototypeCache()
103 FlushScripts();
105 NS_IF_RELEASE(gFastLoadService); // don't need ReleaseService nowadays!
106 NS_IF_RELEASE(gFastLoadFile);
110 NS_IMPL_THREADSAFE_ISUPPORTS2(nsXULPrototypeCache,
111 nsIXULPrototypeCache,
112 nsIObserver)
115 NS_IMETHODIMP
116 NS_NewXULPrototypeCache(nsISupports* aOuter, REFNSIID aIID, void** aResult)
118 NS_PRECONDITION(! aOuter, "no aggregation");
119 if (aOuter)
120 return NS_ERROR_NO_AGGREGATION;
122 nsRefPtr<nsXULPrototypeCache> result = new nsXULPrototypeCache();
123 if (! result)
124 return NS_ERROR_OUT_OF_MEMORY;
126 if (!(result->mPrototypeTable.Init() &&
127 result->mStyleSheetTable.Init() &&
128 result->mScriptTable.Init() &&
129 result->mXBLDocTable.Init() &&
130 result->mFastLoadURITable.Init())) {
131 return NS_ERROR_OUT_OF_MEMORY;
134 // XXX Ignore return values.
135 gDisableXULCache =
136 nsContentUtils::GetBoolPref(kDisableXULCachePref, gDisableXULCache);
137 nsContentUtils::RegisterPrefCallback(kDisableXULCachePref,
138 DisableXULCacheChangedCallback,
139 nsnull);
141 nsresult rv = result->QueryInterface(aIID, aResult);
143 nsCOMPtr<nsIObserverService> obsSvc(do_GetService("@mozilla.org/observer-service;1"));
144 if (obsSvc && NS_SUCCEEDED(rv)) {
145 nsXULPrototypeCache *p = result;
146 obsSvc->AddObserver(p, "chrome-flush-skin-caches", PR_FALSE);
147 obsSvc->AddObserver(p, "chrome-flush-caches", PR_FALSE);
150 return rv;
153 /* static */ nsXULPrototypeCache*
154 nsXULPrototypeCache::GetInstance()
156 // Theoretically this can return nsnull and callers should handle that.
157 if (!sInstance) {
158 nsIXULPrototypeCache* cache;
160 CallGetService(kXULPrototypeCacheCID, &cache);
162 sInstance = static_cast<nsXULPrototypeCache*>(cache);
164 return sInstance;
167 /* static */ nsIFastLoadService*
168 nsXULPrototypeCache::GetFastLoadService()
170 return gFastLoadService;
173 //----------------------------------------------------------------------
175 NS_IMETHODIMP
176 nsXULPrototypeCache::Observe(nsISupports* aSubject,
177 const char *aTopic,
178 const PRUnichar *aData)
180 if (!strcmp(aTopic, "chrome-flush-skin-caches")) {
181 FlushSkinFiles();
183 else if (!strcmp(aTopic, "chrome-flush-caches")) {
184 Flush();
186 else {
187 NS_WARNING("Unexpected observer topic.");
189 return NS_OK;
192 nsXULPrototypeDocument*
193 nsXULPrototypeCache::GetPrototype(nsIURI* aURI)
195 nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(aURI);
197 if (!protoDoc) {
198 // No prototype in XUL memory cache. Spin up FastLoad Service and
199 // look in FastLoad file.
200 nsresult rv = StartFastLoad(aURI);
201 if (NS_SUCCEEDED(rv)) {
202 nsCOMPtr<nsIObjectInputStream> objectInput;
203 gFastLoadService->GetInputStream(getter_AddRefs(objectInput));
205 rv = StartFastLoadingURI(aURI, nsIFastLoadService::NS_FASTLOAD_READ);
206 if (NS_SUCCEEDED(rv)) {
207 nsCOMPtr<nsIURI> oldURI;
208 gFastLoadService->SelectMuxedDocument(aURI, getter_AddRefs(oldURI));
210 // Create a new prototype document.
211 nsRefPtr<nsXULPrototypeDocument> newProto;
212 rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
213 if (NS_FAILED(rv)) return nsnull;
215 rv = newProto->Read(objectInput);
216 if (NS_SUCCEEDED(rv)) {
217 rv = PutPrototype(newProto);
218 if (NS_FAILED(rv))
219 newProto = nsnull;
221 gFastLoadService->EndMuxedDocument(aURI);
222 } else {
223 newProto = nsnull;
226 RemoveFromFastLoadSet(aURI);
227 protoDoc = newProto;
231 return protoDoc;
234 nsresult
235 nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument)
237 nsCOMPtr<nsIURI> uri = aDocument->GetURI();
238 // Put() releases any old value and addrefs the new one
239 NS_ENSURE_TRUE(mPrototypeTable.Put(uri, aDocument), NS_ERROR_OUT_OF_MEMORY);
241 return NS_OK;
244 nsresult
245 nsXULPrototypeCache::PutStyleSheet(nsICSSStyleSheet* aStyleSheet)
247 nsCOMPtr<nsIURI> uri;
248 nsresult rv = aStyleSheet->GetSheetURI(getter_AddRefs(uri));
249 if (NS_FAILED(rv))
250 return rv;
252 NS_ENSURE_TRUE(mStyleSheetTable.Put(uri, aStyleSheet),
253 NS_ERROR_OUT_OF_MEMORY);
255 return NS_OK;
259 void*
260 nsXULPrototypeCache::GetScript(nsIURI* aURI, PRUint32 *aLangID)
262 CacheScriptEntry entry;
263 if (!mScriptTable.Get(aURI, &entry)) {
264 *aLangID = nsIProgrammingLanguage::UNKNOWN;
265 return nsnull;
267 *aLangID = entry.mScriptTypeID;
268 return entry.mScriptObject;
272 /* static */
273 static PLDHashOperator
274 ReleaseScriptObjectCallback(nsIURI* aKey, CacheScriptEntry &aData, void* aClosure)
276 nsCOMPtr<nsIScriptRuntime> rt;
277 if (NS_SUCCEEDED(NS_GetScriptRuntimeByID(aData.mScriptTypeID, getter_AddRefs(rt))))
278 rt->DropScriptObject(aData.mScriptObject);
279 return PL_DHASH_REMOVE;
282 nsresult
283 nsXULPrototypeCache::PutScript(nsIURI* aURI, PRUint32 aLangID, void* aScriptObject)
285 CacheScriptEntry existingEntry;
286 if (mScriptTable.Get(aURI, &existingEntry)) {
287 NS_WARNING("loaded the same script twice (bug 392650)");
289 // Reuse the callback used for enumeration in FlushScripts
290 ReleaseScriptObjectCallback(aURI, existingEntry, nsnull);
293 CacheScriptEntry entry = {aLangID, aScriptObject};
295 NS_ENSURE_TRUE(mScriptTable.Put(aURI, entry), NS_ERROR_OUT_OF_MEMORY);
297 // Lock the object from being gc'd until it is removed from the cache
298 nsCOMPtr<nsIScriptRuntime> rt;
299 nsresult rv = NS_GetScriptRuntimeByID(aLangID, getter_AddRefs(rt));
300 if (NS_SUCCEEDED(rv))
301 rv = rt->HoldScriptObject(aScriptObject);
302 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to GC lock the object");
304 // On failure doing the lock, we should remove the map entry?
305 return rv;
308 void
309 nsXULPrototypeCache::FlushScripts()
311 // This callback will unlock each object so it can once again be gc'd.
312 // XXX - this might be slow - we fetch the runtime each and every object.
313 mScriptTable.Enumerate(ReleaseScriptObjectCallback, nsnull);
317 nsresult
318 nsXULPrototypeCache::PutXBLDocumentInfo(nsIXBLDocumentInfo* aDocumentInfo)
320 nsIURI* uri = aDocumentInfo->DocumentURI();
322 nsCOMPtr<nsIXBLDocumentInfo> info;
323 mXBLDocTable.Get(uri, getter_AddRefs(info));
324 if (!info) {
325 NS_ENSURE_TRUE(mXBLDocTable.Put(uri, aDocumentInfo),
326 NS_ERROR_OUT_OF_MEMORY);
328 return NS_OK;
331 static PLDHashOperator
332 FlushSkinXBL(nsIURI* aKey, nsCOMPtr<nsIXBLDocumentInfo>& aDocInfo, void* aClosure)
334 nsCAutoString str;
335 aKey->GetPath(str);
337 PLDHashOperator ret = PL_DHASH_NEXT;
339 if (!strncmp(str.get(), "/skin", 5)) {
340 ret = PL_DHASH_REMOVE;
343 return ret;
346 static PLDHashOperator
347 FlushSkinSheets(nsIURI* aKey, nsCOMPtr<nsICSSStyleSheet>& aSheet, void* aClosure)
349 nsCOMPtr<nsIURI> uri;
350 aSheet->GetSheetURI(getter_AddRefs(uri));
351 nsCAutoString str;
352 uri->GetPath(str);
354 PLDHashOperator ret = PL_DHASH_NEXT;
356 if (!strncmp(str.get(), "/skin", 5)) {
357 // This is a skin binding. Add the key to the list.
358 ret = PL_DHASH_REMOVE;
360 return ret;
363 static PLDHashOperator
364 FlushScopedSkinStylesheets(nsIURI* aKey, nsCOMPtr<nsIXBLDocumentInfo> &aDocInfo, void* aClosure)
366 aDocInfo->FlushSkinStylesheets();
367 return PL_DHASH_NEXT;
370 void
371 nsXULPrototypeCache::FlushSkinFiles()
373 // Flush out skin XBL files from the cache.
374 mXBLDocTable.Enumerate(FlushSkinXBL, nsnull);
376 // Now flush out our skin stylesheets from the cache.
377 mStyleSheetTable.Enumerate(FlushSkinSheets, nsnull);
379 // Iterate over all the remaining XBL and make sure cached
380 // scoped skin stylesheets are flushed and refetched by the
381 // prototype bindings.
382 mXBLDocTable.Enumerate(FlushScopedSkinStylesheets, nsnull);
386 void
387 nsXULPrototypeCache::Flush()
389 mPrototypeTable.Clear();
391 // Clear the script cache, as it refers to prototype-owned mJSObjects.
392 FlushScripts();
394 mStyleSheetTable.Clear();
395 mXBLDocTable.Clear();
399 PRBool
400 nsXULPrototypeCache::IsEnabled()
402 return !gDisableXULCache;
405 static PRBool gDisableXULFastLoad = PR_FALSE; // enabled by default
406 static PRBool gChecksumXULFastLoadFile = PR_TRUE; // XXXbe too paranoid
408 void
409 nsXULPrototypeCache::AbortFastLoads()
411 #ifdef DEBUG_brendan
412 NS_BREAK();
413 #endif
415 // Save a strong ref to the FastLoad file, so we can remove it after we
416 // close open streams to it.
417 nsCOMPtr<nsIFile> file = gFastLoadFile;
419 // Flush the XUL cache for good measure, in case we cached a bogus/downrev
420 // script, somehow.
421 Flush();
423 // Clear the FastLoad set
424 mFastLoadURITable.Clear();
426 if (! gFastLoadService)
427 return;
429 // Fetch the current input (if FastLoad file existed) or output (if we're
430 // creating the FastLoad file during this app startup) stream.
431 nsCOMPtr<nsIObjectInputStream> objectInput;
432 nsCOMPtr<nsIObjectOutputStream> objectOutput;
433 gFastLoadService->GetInputStream(getter_AddRefs(objectInput));
434 gFastLoadService->GetOutputStream(getter_AddRefs(objectOutput));
436 if (objectOutput) {
437 gFastLoadService->SetOutputStream(nsnull);
439 if (NS_SUCCEEDED(objectOutput->Close()) && gChecksumXULFastLoadFile)
440 gFastLoadService->CacheChecksum(gFastLoadFile,
441 objectOutput);
444 if (objectInput) {
445 // If this is the last of one or more XUL master documents loaded
446 // together at app startup, close the FastLoad service's singleton
447 // input stream now.
448 gFastLoadService->SetInputStream(nsnull);
449 objectInput->Close();
452 // Now rename or remove the file.
453 if (file) {
454 #ifdef DEBUG
455 // Remove any existing Aborted.mfasl files generated in previous runs.
456 nsCOMPtr<nsIFile> existingAbortedFile;
457 file->Clone(getter_AddRefs(existingAbortedFile));
458 if (existingAbortedFile) {
459 existingAbortedFile->SetLeafName(NS_LITERAL_STRING("Aborted.mfasl"));
460 PRBool fileExists = PR_FALSE;
461 existingAbortedFile->Exists(&fileExists);
462 if (fileExists)
463 existingAbortedFile->Remove(PR_FALSE);
465 file->MoveToNative(nsnull, NS_LITERAL_CSTRING("Aborted.mfasl"));
466 #else
467 file->Remove(PR_FALSE);
468 #endif
471 // If the list is empty now, the FastLoad process is done.
472 NS_RELEASE(gFastLoadService);
473 NS_RELEASE(gFastLoadFile);
477 void
478 nsXULPrototypeCache::RemoveFromFastLoadSet(nsIURI* aURI)
480 mFastLoadURITable.Remove(aURI);
483 static const char kDisableXULFastLoadPref[] = "nglayout.debug.disable_xul_fastload";
484 static const char kChecksumXULFastLoadFilePref[] = "nglayout.debug.checksum_xul_fastload_file";
486 nsresult
487 nsXULPrototypeCache::WritePrototype(nsXULPrototypeDocument* aPrototypeDocument)
489 nsresult rv = NS_OK, rv2 = NS_OK;
491 // We're here before the FastLoad service has been initialized, probably because
492 // of the profile manager. Bail quietly, don't worry, we'll be back later.
493 if (! gFastLoadService)
494 return NS_OK;
496 // Fetch the current input (if FastLoad file existed) or output (if we're
497 // creating the FastLoad file during this app startup) stream.
498 nsCOMPtr<nsIObjectInputStream> objectInput;
499 nsCOMPtr<nsIObjectOutputStream> objectOutput;
500 gFastLoadService->GetInputStream(getter_AddRefs(objectInput));
501 gFastLoadService->GetOutputStream(getter_AddRefs(objectOutput));
503 nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
505 // Remove this document from the FastLoad table. We use the table's
506 // emptiness instead of a counter to decide when the FastLoad process
507 // has completed. When complete, we can write footer details to the
508 // FastLoad file.
509 RemoveFromFastLoadSet(protoURI);
511 PRInt32 count = mFastLoadURITable.Count();
513 if (objectOutput) {
514 rv = StartFastLoadingURI(protoURI, nsIFastLoadService::NS_FASTLOAD_WRITE);
515 if (NS_SUCCEEDED(rv)) {
516 // Re-select the URL of the current prototype, as out-of-line script loads
517 // may have changed
518 nsCOMPtr<nsIURI> oldURI;
519 gFastLoadService->SelectMuxedDocument(protoURI, getter_AddRefs(oldURI));
521 aPrototypeDocument->Write(objectOutput);
523 gFastLoadService->EndMuxedDocument(protoURI);
526 // If this is the last of one or more XUL master documents loaded
527 // together at app startup, close the FastLoad service's singleton
528 // output stream now.
530 // NB: we must close input after output, in case the output stream
531 // implementation needs to read from the input stream, to compute a
532 // FastLoad file checksum. In that case, the implementation used
533 // nsIFastLoadFileIO to get the corresponding input stream for this
534 // output stream.
535 if (count == 0) {
536 gFastLoadService->SetOutputStream(nsnull);
537 rv = objectOutput->Close();
539 if (NS_SUCCEEDED(rv) && gChecksumXULFastLoadFile) {
540 rv = gFastLoadService->CacheChecksum(gFastLoadFile,
541 objectOutput);
546 if (objectInput) {
547 // If this is the last of one or more XUL master documents loaded
548 // together at app startup, close the FastLoad service's singleton
549 // input stream now.
550 if (count == 0) {
551 gFastLoadService->SetInputStream(nsnull);
552 rv2 = objectInput->Close();
556 // If the list is empty now, the FastLoad process is done.
557 if (count == 0) {
558 NS_RELEASE(gFastLoadService);
559 NS_RELEASE(gFastLoadFile);
562 return NS_FAILED(rv) ? rv : rv2;
566 nsresult
567 nsXULPrototypeCache::StartFastLoadingURI(nsIURI* aURI, PRInt32 aDirectionFlags)
569 nsresult rv;
571 nsCAutoString urlspec;
572 rv = aURI->GetAsciiSpec(urlspec);
573 if (NS_FAILED(rv)) return rv;
575 // If StartMuxedDocument returns NS_ERROR_NOT_AVAILABLE, then
576 // we must be reading the file, and urlspec was not associated
577 // with any multiplexed stream in it. The FastLoad service
578 // will therefore arrange to update the file, writing new data
579 // at the end while old (available) data continues to be read
580 // from the pre-existing part of the file.
581 return gFastLoadService->StartMuxedDocument(aURI, urlspec.get(), aDirectionFlags);
584 static int
585 FastLoadPrefChangedCallback(const char* aPref, void* aClosure)
587 PRBool wasEnabled = !gDisableXULFastLoad;
588 gDisableXULFastLoad =
589 nsContentUtils::GetBoolPref(kDisableXULFastLoadPref,
590 gDisableXULFastLoad);
592 if (wasEnabled && gDisableXULFastLoad) {
593 static NS_DEFINE_CID(kXULPrototypeCacheCID, NS_XULPROTOTYPECACHE_CID);
594 nsCOMPtr<nsIXULPrototypeCache> cache =
595 do_GetService(kXULPrototypeCacheCID);
597 if (cache)
598 cache->AbortFastLoads();
601 gChecksumXULFastLoadFile =
602 nsContentUtils::GetBoolPref(kChecksumXULFastLoadFilePref,
603 gChecksumXULFastLoadFile);
605 return 0;
609 class nsXULFastLoadFileIO : public nsIFastLoadFileIO
611 public:
612 nsXULFastLoadFileIO(nsIFile* aFile)
613 : mFile(aFile) {
616 virtual ~nsXULFastLoadFileIO() {
619 NS_DECL_ISUPPORTS
620 NS_DECL_NSIFASTLOADFILEIO
622 nsCOMPtr<nsIFile> mFile;
623 nsCOMPtr<nsIInputStream> mInputStream;
624 nsCOMPtr<nsIOutputStream> mOutputStream;
628 NS_IMPL_THREADSAFE_ISUPPORTS1(nsXULFastLoadFileIO, nsIFastLoadFileIO)
631 NS_IMETHODIMP
632 nsXULFastLoadFileIO::GetInputStream(nsIInputStream** aResult)
634 if (! mInputStream) {
635 nsresult rv;
636 nsCOMPtr<nsIInputStream> fileInput;
637 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInput), mFile);
638 if (NS_FAILED(rv)) return rv;
640 rv = NS_NewBufferedInputStream(getter_AddRefs(mInputStream),
641 fileInput,
642 XUL_DESERIALIZATION_BUFFER_SIZE);
643 if (NS_FAILED(rv)) return rv;
646 NS_ADDREF(*aResult = mInputStream);
647 return NS_OK;
651 NS_IMETHODIMP
652 nsXULFastLoadFileIO::GetOutputStream(nsIOutputStream** aResult)
654 if (! mOutputStream) {
655 PRInt32 ioFlags = PR_WRONLY;
656 if (! mInputStream)
657 ioFlags |= PR_CREATE_FILE | PR_TRUNCATE;
659 nsresult rv;
660 nsCOMPtr<nsIOutputStream> fileOutput;
661 rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOutput), mFile,
662 ioFlags, 0644);
663 if (NS_FAILED(rv)) return rv;
665 rv = NS_NewBufferedOutputStream(getter_AddRefs(mOutputStream),
666 fileOutput,
667 XUL_SERIALIZATION_BUFFER_SIZE);
668 if (NS_FAILED(rv)) return rv;
671 NS_ADDREF(*aResult = mOutputStream);
672 return NS_OK;
675 nsresult
676 nsXULPrototypeCache::StartFastLoad(nsIURI* aURI)
678 nsresult rv;
680 nsCAutoString path;
681 aURI->GetPath(path);
682 if (!StringEndsWith(path, NS_LITERAL_CSTRING(".xul")))
683 return NS_ERROR_NOT_AVAILABLE;
685 // Test gFastLoadFile to decide whether this is the first nsXULDocument
686 // participating in FastLoad. If gFastLoadFile is non-null, this document
687 // must not be first, but it can join the FastLoad process. Examples of
688 // multiple master documents participating include hiddenWindow.xul and
689 // navigator.xul on the Mac, and multiple-app-component (e.g., mailnews
690 // and browser) startup due to command-line arguments.
692 // XXXbe we should attempt to update the FastLoad file after startup!
694 // XXXbe we do not yet use nsFastLoadPtrs, but once we do, we must keep
695 // the FastLoad input stream open for the life of the app.
696 if (gFastLoadService && gFastLoadFile) {
697 mFastLoadURITable.Put(aURI, 1);
699 return NS_OK;
702 // Use a local to refer to the service till we're sure we succeeded, then
703 // commit to gFastLoadService. Same for gFastLoadFile, which is used to
704 // delete the FastLoad file on abort.
705 nsCOMPtr<nsIFastLoadService> fastLoadService(do_GetFastLoadService());
706 if (! fastLoadService)
707 return NS_ERROR_FAILURE;
709 gDisableXULFastLoad =
710 nsContentUtils::GetBoolPref(kDisableXULFastLoadPref,
711 gDisableXULFastLoad);
712 gChecksumXULFastLoadFile =
713 nsContentUtils::GetBoolPref(kChecksumXULFastLoadFilePref,
714 gChecksumXULFastLoadFile);
715 nsContentUtils::RegisterPrefCallback(kDisableXULFastLoadPref,
716 FastLoadPrefChangedCallback,
717 nsnull);
718 nsContentUtils::RegisterPrefCallback(kChecksumXULFastLoadFilePref,
719 FastLoadPrefChangedCallback,
720 nsnull);
722 if (gDisableXULFastLoad)
723 return NS_ERROR_NOT_AVAILABLE;
725 // Get the chrome directory to validate against the one stored in the
726 // FastLoad file, or to store there if we're generating a new file.
727 nsCOMPtr<nsIFile> chromeDir;
728 rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
729 if (NS_FAILED(rv))
730 return rv;
731 nsCAutoString chromePath;
732 rv = chromeDir->GetNativePath(chromePath);
733 if (NS_FAILED(rv))
734 return rv;
736 nsCOMPtr<nsIFile> file;
737 rv = fastLoadService->NewFastLoadFile(XUL_FASTLOAD_FILE_BASENAME,
738 getter_AddRefs(file));
739 if (NS_FAILED(rv))
740 return rv;
742 // Give the FastLoad service an object by which it can get or create a
743 // file output stream given an input stream on the same file.
744 nsXULFastLoadFileIO* xio = new nsXULFastLoadFileIO(file);
745 nsCOMPtr<nsIFastLoadFileIO> io = static_cast<nsIFastLoadFileIO*>(xio);
746 if (! io)
747 return NS_ERROR_OUT_OF_MEMORY;
748 fastLoadService->SetFileIO(io);
750 nsCOMPtr<nsIXULChromeRegistry> chromeReg(do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv));
751 if (NS_FAILED(rv))
752 return rv;
754 // XXXbe we assume the first package's locale is the same as the locale of
755 // all subsequent packages of FastLoaded chrome URIs....
756 nsCAutoString package;
757 rv = aURI->GetHost(package);
758 if (NS_FAILED(rv))
759 return rv;
761 nsCAutoString locale;
762 rv = chromeReg->GetSelectedLocale(package, locale);
763 if (NS_FAILED(rv))
764 return rv;
766 // Try to read an existent FastLoad file.
767 PRBool exists = PR_FALSE;
768 if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
769 nsCOMPtr<nsIInputStream> input;
770 rv = io->GetInputStream(getter_AddRefs(input));
771 if (NS_FAILED(rv))
772 return rv;
774 nsCOMPtr<nsIObjectInputStream> objectInput;
775 rv = fastLoadService->NewInputStream(input, getter_AddRefs(objectInput));
777 if (NS_SUCCEEDED(rv)) {
778 if (gChecksumXULFastLoadFile) {
779 nsCOMPtr<nsIFastLoadReadControl>
780 readControl(do_QueryInterface(objectInput));
781 if (readControl) {
782 // Verify checksum, using the fastLoadService's checksum
783 // cache to avoid computing more than once per session.
784 PRUint32 checksum;
785 rv = readControl->GetChecksum(&checksum);
786 if (NS_SUCCEEDED(rv)) {
787 PRUint32 verified;
788 rv = fastLoadService->ComputeChecksum(file,
789 readControl,
790 &verified);
791 if (NS_SUCCEEDED(rv) && verified != checksum) {
792 #ifdef DEBUG
793 printf("bad FastLoad file checksum\n");
794 #endif
795 rv = NS_ERROR_FAILURE;
801 if (NS_SUCCEEDED(rv)) {
802 // Get the XUL fastload file version number, which should be
803 // decremented whenever the XUL-specific file format changes
804 // (see public/nsIXULPrototypeCache.h for the #define).
805 PRUint32 xulFastLoadVersion, jsByteCodeVersion;
806 rv = objectInput->Read32(&xulFastLoadVersion);
807 rv |= objectInput->Read32(&jsByteCodeVersion);
808 if (NS_SUCCEEDED(rv)) {
809 if (xulFastLoadVersion != XUL_FASTLOAD_FILE_VERSION ||
810 jsByteCodeVersion != JSXDR_BYTECODE_VERSION) {
811 #ifdef DEBUG
812 printf((xulFastLoadVersion != XUL_FASTLOAD_FILE_VERSION)
813 ? "bad FastLoad file version\n"
814 : "bad JS bytecode version\n");
815 #endif
816 rv = NS_ERROR_UNEXPECTED;
817 } else {
818 nsCAutoString fileChromePath, fileLocale;
820 rv = objectInput->ReadCString(fileChromePath);
821 rv |= objectInput->ReadCString(fileLocale);
822 if (NS_SUCCEEDED(rv) &&
823 (!fileChromePath.Equals(chromePath) ||
824 !fileLocale.Equals(locale))) {
825 rv = NS_ERROR_UNEXPECTED;
832 if (NS_SUCCEEDED(rv)) {
833 fastLoadService->SetInputStream(objectInput);
834 } else {
835 // NB: we must close before attempting to remove, for non-Unix OSes
836 // that can't do open-unlink.
837 if (objectInput)
838 objectInput->Close();
839 else
840 input->Close();
841 xio->mInputStream = nsnull;
843 #ifdef DEBUG
844 file->MoveToNative(nsnull, NS_LITERAL_CSTRING("Invalid.mfasl"));
845 #else
846 file->Remove(PR_FALSE);
847 #endif
848 exists = PR_FALSE;
852 // FastLoad file not found, or invalid: write a new one.
853 if (! exists) {
854 nsCOMPtr<nsIOutputStream> output;
855 rv = io->GetOutputStream(getter_AddRefs(output));
856 if (NS_FAILED(rv))
857 return rv;
859 nsCOMPtr<nsIObjectOutputStream> objectOutput;
860 rv = fastLoadService->NewOutputStream(output,
861 getter_AddRefs(objectOutput));
862 if (NS_SUCCEEDED(rv)) {
863 rv = objectOutput->Write32(XUL_FASTLOAD_FILE_VERSION);
864 rv |= objectOutput->Write32(JSXDR_BYTECODE_VERSION);
865 rv |= objectOutput->WriteStringZ(chromePath.get());
866 rv |= objectOutput->WriteStringZ(locale.get());
869 // Remove here even though some errors above will lead to a FastLoad
870 // file invalidation. Other errors (failure to note the dependency on
871 // installed-chrome.txt, e.g.) will not cause invalidation, and we may
872 // as well tidy up now.
873 if (NS_FAILED(rv)) {
874 if (objectOutput)
875 objectOutput->Close();
876 else
877 output->Close();
878 xio->mOutputStream = nsnull;
880 file->Remove(PR_FALSE);
881 return NS_ERROR_FAILURE;
884 fastLoadService->SetOutputStream(objectOutput);
887 // Success! Insert this URI into the mFastLoadURITable
888 // and commit locals to globals.
889 mFastLoadURITable.Put(aURI, 1);
891 NS_ADDREF(gFastLoadService = fastLoadService);
892 NS_ADDREF(gFastLoadFile = file);
893 return NS_OK;