Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsXULPrototypeCache.h"
8
9
#include "plstr.h"
10
#include "nsXULPrototypeDocument.h"
11
#include "nsIURI.h"
12
#include "nsNetUtil.h"
13
14
#include "nsIFile.h"
15
#include "nsIMemoryReporter.h"
16
#include "nsIObjectInputStream.h"
17
#include "nsIObjectOutputStream.h"
18
#include "nsIObserverService.h"
19
#include "nsIStorageStream.h"
20
21
#include "nsAppDirectoryServiceDefs.h"
22
23
#include "js/TracingAPI.h"
24
25
#include "mozilla/StyleSheetInlines.h"
26
#include "mozilla/Preferences.h"
27
#include "mozilla/scache/StartupCache.h"
28
#include "mozilla/scache/StartupCacheUtils.h"
29
#include "mozilla/Telemetry.h"
30
#include "mozilla/intl/LocaleService.h"
31
32
using namespace mozilla;
33
using namespace mozilla::scache;
34
using mozilla::intl::LocaleService;
35
36
static bool gDisableXULCache = false; // enabled by default
37
static const char kDisableXULCachePref[] = "nglayout.debug.disable_xul_cache";
38
static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
39
static const char kXULCachePrefix[] = "xulcache";
40
41
//----------------------------------------------------------------------
42
43
static void UpdategDisableXULCache() {
44
// Get the value of "nglayout.debug.disable_xul_cache" preference
45
gDisableXULCache =
46
Preferences::GetBool(kDisableXULCachePref, gDisableXULCache);
47
48
// Sets the flag if the XUL cache is disabled
49
if (gDisableXULCache) {
50
Telemetry::Accumulate(Telemetry::XUL_CACHE_DISABLED, true);
51
}
52
}
53
54
static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) {
55
bool wasEnabled = !gDisableXULCache;
56
UpdategDisableXULCache();
57
58
if (wasEnabled && gDisableXULCache) {
59
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
60
if (cache) {
61
// AbortCaching() calls Flush() for us.
62
cache->AbortCaching();
63
}
64
}
65
}
66
67
//----------------------------------------------------------------------
68
69
nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
70
71
nsXULPrototypeCache::nsXULPrototypeCache() {}
72
73
nsXULPrototypeCache::~nsXULPrototypeCache() { FlushScripts(); }
74
75
NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
76
77
/* static */
78
nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
79
if (!sInstance) {
80
NS_ADDREF(sInstance = new nsXULPrototypeCache());
81
82
UpdategDisableXULCache();
83
84
Preferences::RegisterCallback(DisableXULCacheChangedCallback,
85
kDisableXULCachePref);
86
87
nsCOMPtr<nsIObserverService> obsSvc =
88
mozilla::services::GetObserverService();
89
if (obsSvc) {
90
nsXULPrototypeCache* p = sInstance;
91
obsSvc->AddObserver(p, "chrome-flush-caches", false);
92
obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
93
obsSvc->AddObserver(p, "startupcache-invalidate", false);
94
}
95
}
96
return sInstance;
97
}
98
99
//----------------------------------------------------------------------
100
101
NS_IMETHODIMP
102
nsXULPrototypeCache::Observe(nsISupports* aSubject, const char* aTopic,
103
const char16_t* aData) {
104
if (!strcmp(aTopic, "chrome-flush-caches") ||
105
!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
106
Flush();
107
} else if (!strcmp(aTopic, "startupcache-invalidate")) {
108
AbortCaching();
109
} else {
110
NS_WARNING("Unexpected observer topic.");
111
}
112
return NS_OK;
113
}
114
115
nsXULPrototypeDocument* nsXULPrototypeCache::GetPrototype(nsIURI* aURI) {
116
if (!aURI) return nullptr;
117
118
nsCOMPtr<nsIURI> uriWithoutRef;
119
NS_GetURIWithoutRef(aURI, getter_AddRefs(uriWithoutRef));
120
121
nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
122
if (protoDoc) {
123
return protoDoc;
124
}
125
126
nsresult rv = BeginCaching(aURI);
127
if (NS_FAILED(rv)) return nullptr;
128
129
// No prototype in XUL memory cache. Spin up the cache Service.
130
nsCOMPtr<nsIObjectInputStream> ois;
131
rv = GetInputStream(aURI, getter_AddRefs(ois));
132
if (NS_FAILED(rv)) {
133
return nullptr;
134
}
135
136
RefPtr<nsXULPrototypeDocument> newProto;
137
rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
138
if (NS_FAILED(rv)) return nullptr;
139
140
rv = newProto->Read(ois);
141
if (NS_SUCCEEDED(rv)) {
142
rv = PutPrototype(newProto);
143
} else {
144
newProto = nullptr;
145
}
146
147
mInputStreamTable.Remove(aURI);
148
return newProto;
149
}
150
151
nsresult nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument) {
152
if (!aDocument->GetURI()) {
153
return NS_ERROR_FAILURE;
154
}
155
156
nsCOMPtr<nsIURI> uri;
157
NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri));
158
159
// Put() releases any old value and addrefs the new one
160
mPrototypeTable.Put(uri, aDocument);
161
162
return NS_OK;
163
}
164
165
mozilla::StyleSheet* nsXULPrototypeCache::GetStyleSheet(nsIURI* aURI) {
166
return mStyleSheetTable.GetWeak(aURI);
167
}
168
169
nsresult nsXULPrototypeCache::PutStyleSheet(StyleSheet* aStyleSheet) {
170
nsIURI* uri = aStyleSheet->GetSheetURI();
171
mStyleSheetTable.Put(uri, aStyleSheet);
172
return NS_OK;
173
}
174
175
JSScript* nsXULPrototypeCache::GetScript(nsIURI* aURI) {
176
return mScriptTable.Get(aURI);
177
}
178
179
nsresult nsXULPrototypeCache::PutScript(nsIURI* aURI,
180
JS::Handle<JSScript*> aScriptObject) {
181
MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
182
183
#ifdef DEBUG_BUG_392650
184
if (mScriptTable.Get(aURI)) {
185
nsAutoCString scriptName;
186
aURI->GetSpec(scriptName);
187
nsAutoCString message("Loaded script ");
188
message += scriptName;
189
message += " twice (bug 392650)";
190
NS_WARNING(message.get());
191
}
192
#endif
193
194
mScriptTable.Put(aURI, aScriptObject);
195
196
return NS_OK;
197
}
198
199
void nsXULPrototypeCache::FlushScripts() { mScriptTable.Clear(); }
200
201
void nsXULPrototypeCache::Flush() {
202
mPrototypeTable.Clear();
203
mScriptTable.Clear();
204
mStyleSheetTable.Clear();
205
}
206
207
bool nsXULPrototypeCache::IsEnabled() { return !gDisableXULCache; }
208
209
void nsXULPrototypeCache::AbortCaching() {
210
#ifdef DEBUG_brendan
211
NS_BREAK();
212
#endif
213
214
// Flush the XUL cache for good measure, in case we cached a bogus/downrev
215
// script, somehow.
216
Flush();
217
218
// Clear the cache set
219
mStartupCacheURITable.Clear();
220
}
221
222
nsresult nsXULPrototypeCache::WritePrototype(
223
nsXULPrototypeDocument* aPrototypeDocument) {
224
nsresult rv = NS_OK, rv2 = NS_OK;
225
226
if (!StartupCache::GetSingleton()) return NS_OK;
227
228
nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
229
230
nsCOMPtr<nsIObjectOutputStream> oos;
231
rv = GetOutputStream(protoURI, getter_AddRefs(oos));
232
NS_ENSURE_SUCCESS(rv, rv);
233
234
rv = aPrototypeDocument->Write(oos);
235
NS_ENSURE_SUCCESS(rv, rv);
236
FinishOutputStream(protoURI);
237
return NS_FAILED(rv) ? rv : rv2;
238
}
239
240
nsresult nsXULPrototypeCache::GetInputStream(nsIURI* uri,
241
nsIObjectInputStream** stream) {
242
nsAutoCString spec(kXULCachePrefix);
243
nsresult rv = PathifyURI(uri, spec);
244
if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
245
246
const char* buf;
247
uint32_t len;
248
nsCOMPtr<nsIObjectInputStream> ois;
249
StartupCache* sc = StartupCache::GetSingleton();
250
if (!sc) return NS_ERROR_NOT_AVAILABLE;
251
252
rv = sc->GetBuffer(spec.get(), &buf, &len);
253
if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
254
255
rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois));
256
NS_ENSURE_SUCCESS(rv, rv);
257
258
mInputStreamTable.Put(uri, ois);
259
260
ois.forget(stream);
261
return NS_OK;
262
}
263
264
nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
265
mInputStreamTable.Remove(uri);
266
return NS_OK;
267
}
268
269
nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri,
270
nsIObjectOutputStream** stream) {
271
nsresult rv;
272
nsCOMPtr<nsIObjectOutputStream> objectOutput;
273
nsCOMPtr<nsIStorageStream> storageStream;
274
bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
275
if (found) {
276
// Setting an output stream here causes crashes on Windows. The previous
277
// version of this code always returned NS_ERROR_OUT_OF_MEMORY here,
278
// because it used a mistyped contract ID to create its object stream.
279
return NS_ERROR_NOT_IMPLEMENTED;
280
#if 0
281
nsCOMPtr<nsIOutputStream> outputStream
282
= do_QueryInterface(storageStream);
283
objectOutput = NS_NewObjectOutputStream(outputStream);
284
#endif
285
} else {
286
rv = NewObjectOutputWrappedStorageStream(
287
getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
288
NS_ENSURE_SUCCESS(rv, rv);
289
mOutputStreamTable.Put(uri, storageStream);
290
}
291
objectOutput.forget(stream);
292
return NS_OK;
293
}
294
295
nsresult nsXULPrototypeCache::FinishOutputStream(nsIURI* uri) {
296
nsresult rv;
297
StartupCache* sc = StartupCache::GetSingleton();
298
if (!sc) return NS_ERROR_NOT_AVAILABLE;
299
300
nsCOMPtr<nsIStorageStream> storageStream;
301
bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
302
if (!found) return NS_ERROR_UNEXPECTED;
303
nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream);
304
outputStream->Close();
305
306
UniquePtr<char[]> buf;
307
uint32_t len;
308
rv = NewBufferFromStorageStream(storageStream, &buf, &len);
309
NS_ENSURE_SUCCESS(rv, rv);
310
311
if (!mStartupCacheURITable.GetEntry(uri)) {
312
nsAutoCString spec(kXULCachePrefix);
313
rv = PathifyURI(uri, spec);
314
if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
315
rv = sc->PutBuffer(spec.get(), std::move(buf), len);
316
if (NS_SUCCEEDED(rv)) {
317
mOutputStreamTable.Remove(uri);
318
mStartupCacheURITable.PutEntry(uri);
319
}
320
}
321
322
return rv;
323
}
324
325
// We have data if we're in the middle of writing it or we already
326
// have it in the cache.
327
nsresult nsXULPrototypeCache::HasData(nsIURI* uri, bool* exists) {
328
if (mOutputStreamTable.Get(uri, nullptr)) {
329
*exists = true;
330
return NS_OK;
331
}
332
nsAutoCString spec(kXULCachePrefix);
333
nsresult rv = PathifyURI(uri, spec);
334
if (NS_FAILED(rv)) {
335
*exists = false;
336
return NS_OK;
337
}
338
UniquePtr<char[]> buf;
339
StartupCache* sc = StartupCache::GetSingleton();
340
if (sc) {
341
*exists = sc->HasEntry(spec.get());
342
} else {
343
*exists = false;
344
return NS_OK;
345
}
346
*exists = NS_SUCCEEDED(rv);
347
return NS_OK;
348
}
349
350
nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) {
351
nsresult rv, tmp;
352
353
nsAutoCString path;
354
aURI->GetPathQueryRef(path);
355
if (!(StringEndsWith(path, NS_LITERAL_CSTRING(".xul")) ||
356
StringEndsWith(path, NS_LITERAL_CSTRING(".xhtml")))) {
357
return NS_ERROR_NOT_AVAILABLE;
358
}
359
360
StartupCache* startupCache = StartupCache::GetSingleton();
361
if (!startupCache) return NS_ERROR_FAILURE;
362
363
if (gDisableXULCache) return NS_ERROR_NOT_AVAILABLE;
364
365
// Get the chrome directory to validate against the one stored in the
366
// cache file, or to store there if we're generating a new file.
367
nsCOMPtr<nsIFile> chromeDir;
368
rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
369
if (NS_FAILED(rv)) return rv;
370
nsAutoCString chromePath;
371
rv = chromeDir->GetPersistentDescriptor(chromePath);
372
if (NS_FAILED(rv)) return rv;
373
374
// XXXbe we assume the first package's locale is the same as the locale of
375
// all subsequent packages of cached chrome URIs....
376
nsAutoCString package;
377
rv = aURI->GetHost(package);
378
if (NS_FAILED(rv)) return rv;
379
nsAutoCString locale;
380
LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
381
382
nsAutoCString fileChromePath, fileLocale;
383
384
const char* buf = nullptr;
385
uint32_t len, amtRead;
386
nsCOMPtr<nsIObjectInputStream> objectInput;
387
388
rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len);
389
if (NS_SUCCEEDED(rv))
390
rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput));
391
392
if (NS_SUCCEEDED(rv)) {
393
rv = objectInput->ReadCString(fileLocale);
394
tmp = objectInput->ReadCString(fileChromePath);
395
if (NS_FAILED(tmp)) {
396
rv = tmp;
397
}
398
if (NS_FAILED(rv) ||
399
(!fileChromePath.Equals(chromePath) || !fileLocale.Equals(locale))) {
400
// Our cache won't be valid in this case, we'll need to rewrite.
401
// XXX This blows away work that other consumers (like
402
// mozJSComponentLoader) have done, need more fine-grained control.
403
startupCache->InvalidateCache();
404
mStartupCacheURITable.Clear();
405
rv = NS_ERROR_UNEXPECTED;
406
}
407
} else if (rv != NS_ERROR_NOT_AVAILABLE)
408
// NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
409
return rv;
410
411
if (NS_FAILED(rv)) {
412
// Either the cache entry was invalid or it didn't exist, so write it now.
413
nsCOMPtr<nsIObjectOutputStream> objectOutput;
414
nsCOMPtr<nsIInputStream> inputStream;
415
nsCOMPtr<nsIStorageStream> storageStream;
416
rv = NewObjectOutputWrappedStorageStream(
417
getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
418
if (NS_SUCCEEDED(rv)) {
419
rv = objectOutput->WriteStringZ(locale.get());
420
tmp = objectOutput->WriteStringZ(chromePath.get());
421
if (NS_FAILED(tmp)) {
422
rv = tmp;
423
}
424
tmp = objectOutput->Close();
425
if (NS_FAILED(tmp)) {
426
rv = tmp;
427
}
428
tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
429
if (NS_FAILED(tmp)) {
430
rv = tmp;
431
}
432
}
433
434
if (NS_SUCCEEDED(rv)) {
435
uint64_t len64;
436
rv = inputStream->Available(&len64);
437
if (NS_SUCCEEDED(rv)) {
438
if (len64 <= UINT32_MAX)
439
len = (uint32_t)len64;
440
else
441
rv = NS_ERROR_FILE_TOO_BIG;
442
}
443
}
444
445
if (NS_SUCCEEDED(rv)) {
446
auto putBuf = MakeUnique<char[]>(len);
447
rv = inputStream->Read(putBuf.get(), len, &amtRead);
448
if (NS_SUCCEEDED(rv) && len == amtRead)
449
rv = startupCache->PutBuffer(kXULCacheInfoKey, std::move(putBuf), len);
450
else {
451
rv = NS_ERROR_UNEXPECTED;
452
}
453
}
454
455
// Failed again, just bail.
456
if (NS_FAILED(rv)) {
457
startupCache->InvalidateCache();
458
mStartupCacheURITable.Clear();
459
return NS_ERROR_FAILURE;
460
}
461
}
462
463
return NS_OK;
464
}
465
466
void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) {
467
for (auto iter = mPrototypeTable.Iter(); !iter.Done(); iter.Next()) {
468
iter.Data()->MarkInCCGeneration(aGeneration);
469
}
470
}
471
472
void nsXULPrototypeCache::MarkInGC(JSTracer* aTrc) {
473
for (auto iter = mScriptTable.Iter(); !iter.Done(); iter.Next()) {
474
JS::Heap<JSScript*>& script = iter.Data();
475
JS::TraceEdge(aTrc, &script, "nsXULPrototypeCache script");
476
}
477
}
478
479
MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf)
480
481
static void ReportSize(const nsCString& aPath, size_t aAmount,
482
const nsCString& aDescription,
483
nsIHandleReportCallback* aHandleReport,
484
nsISupports* aData) {
485
nsAutoCString path("explicit/xul-prototype-cache/");
486
path += aPath;
487
aHandleReport->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,
488
nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription,
489
aData);
490
}
491
492
/* static */
493
void nsXULPrototypeCache::CollectMemoryReports(
494
nsIHandleReportCallback* aHandleReport, nsISupports* aData) {
495
if (!sInstance) {
496
return;
497
}
498
499
MallocSizeOf mallocSizeOf = CacheMallocSizeOf;
500
size_t other = mallocSizeOf(sInstance);
501
502
#define REPORT_SIZE(_path, _amount, _desc) \
503
ReportSize(_path, _amount, NS_LITERAL_CSTRING(_desc), aHandleReport, aData)
504
505
other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf);
506
// TODO Report content in mPrototypeTable?
507
508
other += sInstance->mStyleSheetTable.ShallowSizeOfExcludingThis(mallocSizeOf);
509
// TODO Report content inside mStyleSheetTable?
510
511
other += sInstance->mScriptTable.ShallowSizeOfExcludingThis(mallocSizeOf);
512
// TODO Report content inside mScriptTable?
513
514
other +=
515
sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf);
516
517
other +=
518
sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
519
other +=
520
sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
521
522
REPORT_SIZE(NS_LITERAL_CSTRING("other"), other,
523
"Memory used by "
524
"the instance and tables of the XUL prototype cache.");
525
526
#undef REPORT_SIZE
527
}