Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/xpconnect/loader/ScriptPreloader.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
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 "ScriptPreloader-inl.h"
8
#include "mozilla/ScriptPreloader.h"
9
#include "mozilla/loader/ScriptCacheActors.h"
10
11
#include "mozilla/URLPreloader.h"
12
13
#include "mozilla/ArrayUtils.h"
14
#include "mozilla/ClearOnShutdown.h"
15
#include "mozilla/FileUtils.h"
16
#include "mozilla/Logging.h"
17
#include "mozilla/ScopeExit.h"
18
#include "mozilla/Services.h"
19
#include "mozilla/Unused.h"
20
#include "mozilla/dom/ContentChild.h"
21
#include "mozilla/dom/ContentParent.h"
22
23
#include "MainThreadUtils.h"
24
#include "nsDebug.h"
25
#include "nsDirectoryServiceUtils.h"
26
#include "nsIFile.h"
27
#include "nsIObserverService.h"
28
#include "nsJSUtils.h"
29
#include "nsNetUtil.h"
30
#include "nsProxyRelease.h"
31
#include "nsThreadUtils.h"
32
#include "nsXULAppAPI.h"
33
#include "xpcpublic.h"
34
35
6
#define STARTUP_COMPLETE_TOPIC "browser-delayed-startup-finished"
36
0
#define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
37
0
#define CONTENT_DOCUMENT_LOADED_TOPIC "content-document-loaded"
38
6
#define CACHE_WRITE_TOPIC "browser-idle-startup-tasks-finished"
39
6
#define CLEANUP_TOPIC "xpcom-shutdown"
40
6
#define SHUTDOWN_TOPIC "quit-application-granted"
41
6
#define CACHE_INVALIDATE_TOPIC "startupcache-invalidate"
42
43
// The maximum time we'll wait for a child process to finish starting up before
44
// we send its script data back to the parent.
45
constexpr uint32_t CHILD_STARTUP_TIMEOUT_MS = 8000;
46
47
namespace mozilla {
48
namespace {
49
static LazyLogModule gLog("ScriptPreloader");
50
51
0
#define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
52
}
53
54
using mozilla::dom::AutoJSAPI;
55
using mozilla::dom::ContentChild;
56
using mozilla::dom::ContentParent;
57
using namespace mozilla::loader;
58
59
ProcessType ScriptPreloader::sProcessType;
60
61
// This type correspond to js::vm::XDRAlignment type, which is used as a size
62
// reference for alignment of XDR buffers.
63
using XDRAlign = uint16_t;
64
static const uint8_t sAlignPadding[sizeof(XDRAlign)] = { 0, 0 };
65
66
static inline size_t
67
ComputeByteAlignment(size_t bytes, size_t align)
68
0
{
69
0
    return (align - (bytes % align)) % align;
70
0
}
71
72
nsresult
73
ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
74
                                nsISupports* aData, bool aAnonymize)
75
0
{
76
0
    MOZ_COLLECT_REPORT(
77
0
        "explicit/script-preloader/heap/saved-scripts", KIND_HEAP, UNITS_BYTES,
78
0
        SizeOfHashEntries<ScriptStatus::Saved>(mScripts, MallocSizeOf),
79
0
        "Memory used to hold the scripts which have been executed in this "
80
0
        "session, and will be written to the startup script cache file.");
81
0
82
0
    MOZ_COLLECT_REPORT(
83
0
        "explicit/script-preloader/heap/restored-scripts", KIND_HEAP, UNITS_BYTES,
84
0
        SizeOfHashEntries<ScriptStatus::Restored>(mScripts, MallocSizeOf),
85
0
        "Memory used to hold the scripts which have been restored from the "
86
0
        "startup script cache file, but have not been executed in this session.");
87
0
88
0
    MOZ_COLLECT_REPORT(
89
0
        "explicit/script-preloader/heap/other", KIND_HEAP, UNITS_BYTES,
90
0
        ShallowHeapSizeOfIncludingThis(MallocSizeOf),
91
0
        "Memory used by the script cache service itself.");
92
0
93
0
    // Since the mem-mapped cache file is mapped into memory, we want to report
94
0
    // it as explicit memory somewhere. But since the child cache is shared
95
0
    // between all processes, we don't want to report it as explicit memory for
96
0
    // all of them. So we report it as explicit only in the parent process, and
97
0
    // non-explicit everywhere else.
98
0
    if (XRE_IsParentProcess()) {
99
0
        MOZ_COLLECT_REPORT(
100
0
            "explicit/script-preloader/non-heap/memmapped-cache", KIND_NONHEAP, UNITS_BYTES,
101
0
            mCacheData.nonHeapSizeOfExcludingThis(),
102
0
            "The memory-mapped startup script cache file.");
103
0
    } else {
104
0
        MOZ_COLLECT_REPORT(
105
0
            "script-preloader-memmapped-cache", KIND_NONHEAP, UNITS_BYTES,
106
0
            mCacheData.nonHeapSizeOfExcludingThis(),
107
0
            "The memory-mapped startup script cache file.");
108
0
    }
109
0
110
0
    return NS_OK;
111
0
}
112
113
114
ScriptPreloader&
115
ScriptPreloader::GetSingleton()
116
13
{
117
13
    static RefPtr<ScriptPreloader> singleton;
118
13
119
13
    if (!singleton) {
120
3
        if (XRE_IsParentProcess()) {
121
3
            singleton = new ScriptPreloader();
122
3
            singleton->mChildCache = &GetChildSingleton();
123
3
            Unused << singleton->InitCache();
124
3
        } else {
125
0
            singleton = &GetChildSingleton();
126
0
        }
127
3
128
3
        ClearOnShutdown(&singleton);
129
3
    }
130
13
131
13
    return *singleton;
132
13
}
133
134
// The child singleton is available in all processes, including the parent, and
135
// is used for scripts which are expected to be loaded into child processes
136
// (such as process and frame scripts), or scripts that have already been loaded
137
// into a child. The child caches are managed as follows:
138
//
139
// - Every startup, we open the cache file from the last session, move it to a
140
//  new location, and begin pre-loading the scripts that are stored in it. There
141
//  is a separate cache file for parent and content processes, but the parent
142
//  process opens both the parent and content cache files.
143
//
144
// - Once startup is complete, we write a new cache file for the next session,
145
//   containing only the scripts that were used during early startup, so we don't
146
//   waste pre-loading scripts that may not be needed.
147
//
148
// - For content processes, opening and writing the cache file is handled in the
149
//  parent process. The first content process of each type sends back the data
150
//  for scripts that were loaded in early startup, and the parent merges them and
151
//  writes them to a cache file.
152
//
153
// - Currently, content processes only benefit from the cache data written
154
//  during the *previous* session. Ideally, new content processes should probably
155
//  use the cache data written during this session if there was no previous cache
156
//  file, but I'd rather do that as a follow-up.
157
ScriptPreloader&
158
ScriptPreloader::GetChildSingleton()
159
3
{
160
3
    static RefPtr<ScriptPreloader> singleton;
161
3
162
3
    if (!singleton) {
163
3
        singleton = new ScriptPreloader();
164
3
        if (XRE_IsParentProcess()) {
165
3
            Unused << singleton->InitCache(NS_LITERAL_STRING("scriptCache-child"));
166
3
        }
167
3
        ClearOnShutdown(&singleton);
168
3
    }
169
3
170
3
    return *singleton;
171
3
}
172
173
void
174
ScriptPreloader::InitContentChild(ContentParent& parent)
175
0
{
176
0
    auto& cache = GetChildSingleton();
177
0
178
0
    // We want startup script data from the first process of a given type.
179
0
    // That process sends back its script data before it executes any
180
0
    // untrusted code, and then we never accept further script data for that
181
0
    // type of process for the rest of the session.
182
0
    //
183
0
    // The script data from each process type is merged with the data from the
184
0
    // parent process's frame and process scripts, and shared between all
185
0
    // content process types in the next session.
186
0
    //
187
0
    // Note that if the first process of a given type crashes or shuts down
188
0
    // before sending us its script data, we silently ignore it, and data for
189
0
    // that process type is not included in the next session's cache. This
190
0
    // should be a sufficiently rare occurrence that it's not worth trying to
191
0
    // handle specially.
192
0
    auto processType = GetChildProcessType(parent.GetRemoteType());
193
0
    bool wantScriptData = !cache.mInitializedProcesses.contains(processType);
194
0
    cache.mInitializedProcesses += processType;
195
0
196
0
    auto fd = cache.mCacheData.cloneFileDescriptor();
197
0
    // Don't send original cache data to new processes if the cache has been
198
0
    // invalidated.
199
0
    if (fd.IsValid() && !cache.mCacheInvalidated) {
200
0
        Unused << parent.SendPScriptCacheConstructor(fd, wantScriptData);
201
0
    } else {
202
0
        Unused << parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND, wantScriptData);
203
0
    }
204
0
}
205
206
ProcessType
207
ScriptPreloader::GetChildProcessType(const nsAString& remoteType)
208
0
{
209
0
    if (remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
210
0
        return ProcessType::Extension;
211
0
    }
212
0
    if (remoteType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) {
213
0
        return ProcessType::Privileged;
214
0
    }
215
0
    return ProcessType::Web;
216
0
}
217
218
219
namespace {
220
221
static void
222
TraceOp(JSTracer* trc, void* data)
223
36
{
224
36
    auto preloader = static_cast<ScriptPreloader*>(data);
225
36
226
36
    preloader->Trace(trc);
227
36
}
228
229
} // anonymous namespace
230
231
void
232
ScriptPreloader::Trace(JSTracer* trc)
233
36
{
234
90
    for (auto& script : IterHash(mScripts)) {
235
90
        JS::TraceEdge(trc, &script->mScript, "ScriptPreloader::CachedScript.mScript");
236
90
    }
237
36
}
238
239
240
ScriptPreloader::ScriptPreloader()
241
  : mMonitor("[ScriptPreloader.mMonitor]")
242
  , mSaveMonitor("[ScriptPreloader.mSaveMonitor]")
243
6
{
244
6
    // We do not set the process type for child processes here because the
245
6
    // remoteType in ContentChild is not ready yet.
246
6
    if (XRE_IsParentProcess()) {
247
6
        sProcessType = ProcessType::Parent;
248
6
    }
249
6
250
6
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
251
6
    MOZ_RELEASE_ASSERT(obs);
252
6
253
6
    if (XRE_IsParentProcess()) {
254
6
        // In the parent process, we want to freeze the script cache as soon
255
6
        // as idle tasks for the first browser window have completed.
256
6
        obs->AddObserver(this, STARTUP_COMPLETE_TOPIC, false);
257
6
        obs->AddObserver(this, CACHE_WRITE_TOPIC, false);
258
6
    }
259
6
260
6
    obs->AddObserver(this, SHUTDOWN_TOPIC, false);
261
6
    obs->AddObserver(this, CLEANUP_TOPIC, false);
262
6
    obs->AddObserver(this, CACHE_INVALIDATE_TOPIC, false);
263
6
264
6
    AutoSafeJSAPI jsapi;
265
6
    JS_AddExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
266
6
}
267
268
void
269
ScriptPreloader::ForceWriteCacheFile()
270
0
{
271
0
    if (mSaveThread) {
272
0
        MonitorAutoLock mal(mSaveMonitor);
273
0
274
0
        // Make sure we've prepared scripts, so we don't risk deadlocking while
275
0
        // dispatching the prepare task during shutdown.
276
0
        PrepareCacheWrite();
277
0
278
0
        // Unblock the save thread, so it can start saving before we get to
279
0
        // XPCOM shutdown.
280
0
        mal.Notify();
281
0
    }
282
0
}
283
284
void
285
ScriptPreloader::Cleanup()
286
0
{
287
0
    if (mSaveThread) {
288
0
        MonitorAutoLock mal(mSaveMonitor);
289
0
290
0
        // Make sure the save thread is not blocked dispatching a sync task to
291
0
        // the main thread, or we will deadlock.
292
0
        MOZ_RELEASE_ASSERT(!mBlockedOnSyncDispatch);
293
0
294
0
        while (!mSaveComplete && mSaveThread) {
295
0
            mal.Wait();
296
0
        }
297
0
    }
298
0
299
0
    // Wait for any pending parses to finish before clearing the mScripts
300
0
    // hashtable, since the parse tasks depend on memory allocated by those
301
0
    // scripts.
302
0
    {
303
0
        MonitorAutoLock mal(mMonitor);
304
0
        FinishPendingParses(mal);
305
0
306
0
        mScripts.Clear();
307
0
    }
308
0
309
0
    AutoSafeJSAPI jsapi;
310
0
    JS_RemoveExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
311
0
312
0
    UnregisterWeakMemoryReporter(this);
313
0
}
314
315
void
316
ScriptPreloader::InvalidateCache()
317
0
{
318
0
    mMonitor.AssertNotCurrentThreadOwns();
319
0
    MonitorAutoLock mal(mMonitor);
320
0
321
0
    mCacheInvalidated = true;
322
0
323
0
    // Wait for pending off-thread parses to finish, since they depend on the
324
0
    // memory allocated by our CachedScripts, and can't be canceled
325
0
    // asynchronously.
326
0
    FinishPendingParses(mal);
327
0
328
0
    // Pending scripts should have been cleared by the above, and new parses
329
0
    // should not have been queued.
330
0
    MOZ_ASSERT(mParsingScripts.empty());
331
0
    MOZ_ASSERT(mParsingSources.empty());
332
0
    MOZ_ASSERT(mPendingScripts.isEmpty());
333
0
334
0
    for (auto& script : IterHash(mScripts)) {
335
0
        script.Remove();
336
0
    }
337
0
338
0
    // If we've already finished saving the cache at this point, start a new
339
0
    // delayed save operation. This will write out an empty cache file in place
340
0
    // of any cache file we've already written out this session, which will
341
0
    // prevent us from falling back to the current session's cache file on the
342
0
    // next startup.
343
0
    if (mSaveComplete && mChildCache) {
344
0
        mSaveComplete = false;
345
0
346
0
        // Make sure scripts are prepared to avoid deadlock when invalidating
347
0
        // the cache during shutdown.
348
0
        PrepareCacheWriteInternal();
349
0
350
0
        Unused << NS_NewNamedThread("SaveScripts",
351
0
                                    getter_AddRefs(mSaveThread), this);
352
0
    }
353
0
}
354
355
nsresult
356
ScriptPreloader::Observe(nsISupports* subject, const char* topic, const char16_t* data)
357
0
{
358
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
359
0
    if (!strcmp(topic, STARTUP_COMPLETE_TOPIC)) {
360
0
        obs->RemoveObserver(this, STARTUP_COMPLETE_TOPIC);
361
0
362
0
        MOZ_ASSERT(XRE_IsParentProcess());
363
0
364
0
        mStartupFinished = true;
365
0
    } else if (!strcmp(topic, CACHE_WRITE_TOPIC)) {
366
0
        obs->RemoveObserver(this, CACHE_WRITE_TOPIC);
367
0
368
0
        MOZ_ASSERT(mStartupFinished);
369
0
        MOZ_ASSERT(XRE_IsParentProcess());
370
0
371
0
        if (mChildCache) {
372
0
            Unused << NS_NewNamedThread("SaveScripts",
373
0
                                        getter_AddRefs(mSaveThread), this);
374
0
        }
375
0
    } else if (mContentStartupFinishedTopic.Equals(topic)) {
376
0
        // If this is an uninitialized about:blank viewer or a chrome: document
377
0
        // (which should always be an XBL binding document), ignore it. We don't
378
0
        // have to worry about it loading malicious content.
379
0
        if (nsCOMPtr<nsIDocument> doc = do_QueryInterface(subject)) {
380
0
            nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
381
0
382
0
            bool schemeIs;
383
0
            if ((NS_IsAboutBlank(uri) &&
384
0
                 doc->GetReadyStateEnum() == doc->READYSTATE_UNINITIALIZED) ||
385
0
                (NS_SUCCEEDED(uri->SchemeIs("chrome", &schemeIs)) && schemeIs)) {
386
0
                return NS_OK;
387
0
            }
388
0
        }
389
0
        FinishContentStartup();
390
0
    } else if (!strcmp(topic, "timer-callback")) {
391
0
        FinishContentStartup();
392
0
    } else if (!strcmp(topic, SHUTDOWN_TOPIC)) {
393
0
        ForceWriteCacheFile();
394
0
    } else if (!strcmp(topic, CLEANUP_TOPIC)) {
395
0
        Cleanup();
396
0
    } else if (!strcmp(topic, CACHE_INVALIDATE_TOPIC)) {
397
0
        InvalidateCache();
398
0
    }
399
0
400
0
    return NS_OK;
401
0
}
402
403
void
404
ScriptPreloader::FinishContentStartup()
405
0
{
406
0
    MOZ_ASSERT(XRE_IsContentProcess());
407
0
408
#ifdef DEBUG
409
    if (mContentStartupFinishedTopic.Equals(CONTENT_DOCUMENT_LOADED_TOPIC)) {
410
        MOZ_ASSERT(sProcessType == ProcessType::Privileged);
411
    } else {
412
        MOZ_ASSERT(sProcessType != ProcessType::Privileged);
413
    }
414
#endif /* DEBUG */
415
416
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
417
0
    obs->RemoveObserver(this, mContentStartupFinishedTopic.get());
418
0
419
0
    mSaveTimer = nullptr;
420
0
421
0
    mStartupFinished = true;
422
0
423
0
    if (mChildActor) {
424
0
        mChildActor->SendScriptsAndFinalize(mScripts);
425
0
    }
426
0
}
427
428
bool
429
ScriptPreloader::WillWriteScripts()
430
0
{
431
0
    return Active() && (XRE_IsParentProcess() || mChildActor);
432
0
}
433
434
Result<nsCOMPtr<nsIFile>, nsresult>
435
ScriptPreloader::GetCacheFile(const nsAString& suffix)
436
6
{
437
6
    NS_ENSURE_TRUE(mProfD, Err(NS_ERROR_NOT_INITIALIZED));
438
6
439
6
    nsCOMPtr<nsIFile> cacheFile;
440
6
    MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
441
6
442
6
    MOZ_TRY(cacheFile->AppendNative(NS_LITERAL_CSTRING("startupCache")));
443
6
    Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
444
6
445
6
    MOZ_TRY(cacheFile->Append(mBaseName + suffix));
446
6
447
6
    return std::move(cacheFile);
448
6
}
449
450
static const uint8_t MAGIC[] = "mozXDRcachev002";
451
452
Result<Ok, nsresult>
453
ScriptPreloader::OpenCache()
454
6
{
455
6
    MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
456
6
457
6
    nsCOMPtr<nsIFile> cacheFile;
458
6
    MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING(".bin")));
459
6
460
6
    bool exists;
461
6
    MOZ_TRY(cacheFile->Exists(&exists));
462
6
    if (exists) {
463
0
        MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING("-current.bin")));
464
6
    } else {
465
6
        MOZ_TRY(cacheFile->SetLeafName(mBaseName + NS_LITERAL_STRING("-current.bin")));
466
6
        MOZ_TRY(cacheFile->Exists(&exists));
467
6
        if (!exists) {
468
6
            return Err(NS_ERROR_FILE_NOT_FOUND);
469
6
        }
470
0
    }
471
0
472
0
    MOZ_TRY(mCacheData.init(cacheFile));
473
0
474
0
    return Ok();
475
0
}
476
477
// Opens the script cache file for this session, and initializes the script
478
// cache based on its contents. See WriteCache for details of the cache file.
479
Result<Ok, nsresult>
480
ScriptPreloader::InitCache(const nsAString& basePath)
481
6
{
482
6
    mCacheInitialized = true;
483
6
    mBaseName = basePath;
484
6
485
6
    RegisterWeakMemoryReporter(this);
486
6
487
6
    if (!XRE_IsParentProcess()) {
488
0
        return Ok();
489
0
    }
490
6
491
6
    // Grab the compilation scope before initializing the URLPreloader, since
492
6
    // it's not safe to run component loader code during its critical section.
493
6
    AutoSafeJSAPI jsapi;
494
6
    JS::RootedObject scope(jsapi.cx(), xpc::CompilationScope());
495
6
496
6
    // Note: Code on the main thread *must not access Omnijar in any way* until
497
6
    // this AutoBeginReading guard is destroyed.
498
6
    URLPreloader::AutoBeginReading abr;
499
6
500
6
    MOZ_TRY(OpenCache());
501
6
502
6
    return InitCacheInternal(scope);
503
6
}
504
505
Result<Ok, nsresult>
506
ScriptPreloader::InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild)
507
0
{
508
0
    MOZ_ASSERT(XRE_IsContentProcess());
509
0
510
0
    mCacheInitialized = true;
511
0
    mChildActor = cacheChild;
512
0
    sProcessType = GetChildProcessType(dom::ContentChild::GetSingleton()->GetRemoteType());
513
0
514
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
515
0
    MOZ_RELEASE_ASSERT(obs);
516
0
517
0
    if (sProcessType == ProcessType::Privileged) {
518
0
        // Since we control all of the documents loaded in the privileged
519
0
        // content process, we can increase the window of active time for the
520
0
        // ScriptPreloader to include the scripts that are loaded until the
521
0
        // first document finishes loading.
522
0
        mContentStartupFinishedTopic.AssignLiteral(CONTENT_DOCUMENT_LOADED_TOPIC);
523
0
    } else {
524
0
        // In the child process, we need to freeze the script cache before any
525
0
        // untrusted code has been executed. The insertion of the first DOM
526
0
        // document element may sometimes be earlier than is ideal, but at
527
0
        // least it should always be safe.
528
0
        mContentStartupFinishedTopic.AssignLiteral(DOC_ELEM_INSERTED_TOPIC);
529
0
    }
530
0
    obs->AddObserver(this, mContentStartupFinishedTopic.get(), false);
531
0
532
0
    RegisterWeakMemoryReporter(this);
533
0
534
0
    auto cleanup = MakeScopeExit([&] {
535
0
        // If the parent is expecting cache data from us, make sure we send it
536
0
        // before it writes out its cache file. For normal proceses, this isn't
537
0
        // a concern, since they begin loading documents quite early. For the
538
0
        // preloaded process, we may end up waiting a long time (or, indeed,
539
0
        // never loading a document), so we need an additional timeout.
540
0
        if (cacheChild) {
541
0
            NS_NewTimerWithObserver(getter_AddRefs(mSaveTimer),
542
0
                                    this, CHILD_STARTUP_TIMEOUT_MS,
543
0
                                    nsITimer::TYPE_ONE_SHOT);
544
0
        }
545
0
    });
546
0
547
0
    if (cacheFile.isNothing()){
548
0
        return Ok();
549
0
    }
550
0
551
0
    MOZ_TRY(mCacheData.init(cacheFile.ref()));
552
0
553
0
    return InitCacheInternal();
554
0
}
555
556
Result<Ok, nsresult>
557
ScriptPreloader::InitCacheInternal(JS::HandleObject scope)
558
0
{
559
0
    auto size = mCacheData.size();
560
0
561
0
    uint32_t headerSize;
562
0
    if (size < sizeof(MAGIC) + sizeof(headerSize)) {
563
0
        return Err(NS_ERROR_UNEXPECTED);
564
0
    }
565
0
566
0
    auto data = mCacheData.get<uint8_t>();
567
0
    uint8_t* start = data.get();
568
0
    MOZ_ASSERT(reinterpret_cast<uintptr_t>(start) % sizeof(XDRAlign) == 0);
569
0
    auto end = data + size;
570
0
571
0
    if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) {
572
0
        return Err(NS_ERROR_UNEXPECTED);
573
0
    }
574
0
    data += sizeof(MAGIC);
575
0
576
0
    headerSize = LittleEndian::readUint32(data.get());
577
0
    data += sizeof(headerSize);
578
0
579
0
    if (data + headerSize > end) {
580
0
        return Err(NS_ERROR_UNEXPECTED);
581
0
    }
582
0
583
0
    {
584
0
        auto cleanup = MakeScopeExit([&] () {
585
0
            mScripts.Clear();
586
0
        });
587
0
588
0
        LinkedList<CachedScript> scripts;
589
0
590
0
        Range<uint8_t> header(data, data + headerSize);
591
0
        data += headerSize;
592
0
593
0
        InputBuffer buf(header);
594
0
595
0
        size_t len = data.get() - start;
596
0
        size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
597
0
        data += alignLen;
598
0
599
0
        size_t offset = 0;
600
0
        while (!buf.finished()) {
601
0
            auto script = MakeUnique<CachedScript>(*this, buf);
602
0
            MOZ_RELEASE_ASSERT(script);
603
0
604
0
            auto scriptData = data + script->mOffset;
605
0
            if (scriptData + script->mSize > end) {
606
0
                return Err(NS_ERROR_UNEXPECTED);
607
0
            }
608
0
609
0
            // Make sure offsets match what we'd expect based on script ordering and
610
0
            // size, as a basic sanity check.
611
0
            if (script->mOffset != offset) {
612
0
                return Err(NS_ERROR_UNEXPECTED);
613
0
            }
614
0
            offset += script->mSize;
615
0
616
0
            MOZ_ASSERT(reinterpret_cast<uintptr_t>(scriptData.get()) % sizeof(XDRAlign) == 0);
617
0
            script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
618
0
619
0
            // Don't pre-decode the script unless it was used in this process type during the
620
0
            // previous session.
621
0
            if (script->mOriginalProcessTypes.contains(CurrentProcessType())) {
622
0
                scripts.insertBack(script.get());
623
0
            } else {
624
0
                script->mReadyToExecute = true;
625
0
            }
626
0
627
0
            mScripts.Put(script->mCachePath, script.get());
628
0
            Unused << script.release();
629
0
        }
630
0
631
0
        if (buf.error()) {
632
0
            return Err(NS_ERROR_UNEXPECTED);
633
0
        }
634
0
635
0
        mPendingScripts = std::move(scripts);
636
0
        cleanup.release();
637
0
    }
638
0
639
0
    DecodeNextBatch(OFF_THREAD_FIRST_CHUNK_SIZE, scope);
640
0
    return Ok();
641
0
}
642
643
void
644
ScriptPreloader::PrepareCacheWriteInternal()
645
0
{
646
0
    MOZ_ASSERT(NS_IsMainThread());
647
0
648
0
    mMonitor.AssertCurrentThreadOwns();
649
0
650
0
    auto cleanup = MakeScopeExit([&] () {
651
0
        if (mChildCache) {
652
0
            mChildCache->PrepareCacheWrite();
653
0
        }
654
0
    });
655
0
656
0
    if (mDataPrepared) {
657
0
        return;
658
0
    }
659
0
660
0
    AutoSafeJSAPI jsapi;
661
0
    bool found = false;
662
0
    for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
663
0
        // Don't write any scripts that are also in the child cache. They'll be
664
0
        // loaded from the child cache in that case, so there's no need to write
665
0
        // them twice.
666
0
        CachedScript* childScript = mChildCache ? mChildCache->mScripts.Get(script->mCachePath) : nullptr;
667
0
        if (childScript && !childScript->mProcessTypes.isEmpty()) {
668
0
            childScript->UpdateLoadTime(script->mLoadTime);
669
0
            childScript->mProcessTypes += script->mProcessTypes;
670
0
            script.Remove();
671
0
            continue;
672
0
        }
673
0
674
0
        if (!(script->mProcessTypes == script->mOriginalProcessTypes)) {
675
0
            // Note: EnumSet doesn't support operator!=, hence the weird form above.
676
0
            found = true;
677
0
        }
678
0
679
0
        if (!script->mSize && !script->XDREncode(jsapi.cx())) {
680
0
            script.Remove();
681
0
        }
682
0
    }
683
0
684
0
    if (!found) {
685
0
        mSaveComplete = true;
686
0
        return;
687
0
    }
688
0
689
0
    mDataPrepared = true;
690
0
}
691
692
void
693
ScriptPreloader::PrepareCacheWrite()
694
0
{
695
0
    MonitorAutoLock mal(mMonitor);
696
0
697
0
    PrepareCacheWriteInternal();
698
0
}
699
700
// Writes out a script cache file for the scripts accessed during early
701
// startup in this session. The cache file is a little-endian binary file with
702
// the following format:
703
//
704
// - A uint32 containing the size of the header block.
705
//
706
// - A header entry for each file stored in the cache containing:
707
//   - The URL that the script was originally read from.
708
//   - Its cache key.
709
//   - The offset of its XDR data within the XDR data block.
710
//   - The size of its XDR data in the XDR data block.
711
//   - A bit field describing which process types the script is used in.
712
//
713
// - A block of XDR data for the encoded scripts, with each script's data at
714
//   an offset from the start of the block, as specified above.
715
Result<Ok, nsresult>
716
ScriptPreloader::WriteCache()
717
0
{
718
0
    MOZ_ASSERT(!NS_IsMainThread());
719
0
720
0
    if (!mDataPrepared && !mSaveComplete) {
721
0
        MOZ_ASSERT(!mBlockedOnSyncDispatch);
722
0
        mBlockedOnSyncDispatch = true;
723
0
724
0
        MonitorAutoUnlock mau(mSaveMonitor);
725
0
726
0
        NS_DispatchToMainThread(
727
0
          NewRunnableMethod("ScriptPreloader::PrepareCacheWrite",
728
0
                            this,
729
0
                            &ScriptPreloader::PrepareCacheWrite),
730
0
          NS_DISPATCH_SYNC);
731
0
    }
732
0
733
0
    mBlockedOnSyncDispatch = false;
734
0
735
0
    if (mSaveComplete) {
736
0
        // If we don't have anything we need to save, we're done.
737
0
        return Ok();
738
0
    }
739
0
740
0
    nsCOMPtr<nsIFile> cacheFile;
741
0
    MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING("-new.bin")));
742
0
743
0
    bool exists;
744
0
    MOZ_TRY(cacheFile->Exists(&exists));
745
0
    if (exists) {
746
0
        MOZ_TRY(cacheFile->Remove(false));
747
0
    }
748
0
749
0
    {
750
0
        AutoFDClose fd;
751
0
        MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644, &fd.rwget()));
752
0
753
0
        // We also need to hold mMonitor while we're touching scripts in
754
0
        // mScripts, or they may be freed before we're done with them.
755
0
        mMonitor.AssertNotCurrentThreadOwns();
756
0
        MonitorAutoLock mal(mMonitor);
757
0
758
0
        nsTArray<CachedScript*> scripts;
759
0
        for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
760
0
            scripts.AppendElement(script);
761
0
        }
762
0
763
0
        // Sort scripts by load time, with async loaded scripts before sync scripts.
764
0
        // Since async scripts are always loaded immediately at startup, it helps to
765
0
        // have them stored contiguously.
766
0
        scripts.Sort(CachedScript::Comparator());
767
0
768
0
        OutputBuffer buf;
769
0
        size_t offset = 0;
770
0
        for (auto script : scripts) {
771
0
            MOZ_ASSERT(offset % sizeof(XDRAlign) == 0);
772
0
            script->mOffset = offset;
773
0
            script->Code(buf);
774
0
775
0
            offset += script->mSize;
776
0
        }
777
0
778
0
        uint8_t headerSize[4];
779
0
        LittleEndian::writeUint32(headerSize, buf.cursor());
780
0
781
0
        size_t len = 0;
782
0
        MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
783
0
        len += sizeof(MAGIC);
784
0
        MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
785
0
        len += sizeof(headerSize);
786
0
        MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
787
0
        len += buf.cursor();
788
0
        size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
789
0
        if (alignLen) {
790
0
            MOZ_TRY(Write(fd, sAlignPadding, alignLen));
791
0
            len += alignLen;
792
0
        }
793
0
        for (auto script : scripts) {
794
0
            MOZ_ASSERT(script->mSize % sizeof(XDRAlign) == 0);
795
0
            MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
796
0
            len += script->mSize;
797
0
798
0
            if (script->mScript) {
799
0
                script->FreeData();
800
0
            }
801
0
        }
802
0
    }
803
0
804
0
    MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING(".bin")));
805
0
806
0
    return Ok();
807
0
}
808
809
// Runs in the mSaveThread thread, and writes out the cache file for the next
810
// session after a reasonable delay.
811
nsresult
812
ScriptPreloader::Run()
813
0
{
814
0
    MonitorAutoLock mal(mSaveMonitor);
815
0
816
0
    // Ideally wait about 10 seconds before saving, to avoid unnecessary IO
817
0
    // during early startup. But only if the cache hasn't been invalidated,
818
0
    // since that can trigger a new write during shutdown, and we don't want to
819
0
    // cause shutdown hangs.
820
0
    if (!mCacheInvalidated) {
821
0
        mal.Wait(TimeDuration::FromSeconds(10));
822
0
    }
823
0
824
0
    auto result = URLPreloader::GetSingleton().WriteCache();
825
0
    Unused << NS_WARN_IF(result.isErr());
826
0
827
0
    result = WriteCache();
828
0
    Unused << NS_WARN_IF(result.isErr());
829
0
830
0
    result = mChildCache->WriteCache();
831
0
    Unused << NS_WARN_IF(result.isErr());
832
0
833
0
    mSaveComplete = true;
834
0
    NS_ReleaseOnMainThreadSystemGroup("ScriptPreloader::mSaveThread",
835
0
                                      mSaveThread.forget());
836
0
837
0
    mal.NotifyAll();
838
0
    return NS_OK;
839
0
}
840
841
void
842
ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
843
                            JS::HandleScript jsscript, bool isRunOnce)
844
5
{
845
5
    if (!Active()) {
846
0
        if (isRunOnce) {
847
0
            if (auto script = mScripts.Get(cachePath)) {
848
0
                script->mIsRunOnce = true;
849
0
                script->MaybeDropScript();
850
0
            }
851
0
        }
852
0
        return;
853
0
    }
854
5
855
5
    // Don't bother trying to cache any URLs with cache-busting query
856
5
    // parameters.
857
5
    if (cachePath.FindChar('?') >= 0) {
858
0
        return;
859
0
    }
860
5
861
5
    // Don't bother caching files that belong to the mochitest harness.
862
5
    NS_NAMED_LITERAL_CSTRING(mochikitPrefix, "chrome://mochikit/");
863
5
    if (StringHead(url, mochikitPrefix.Length()) == mochikitPrefix) {
864
0
        return;
865
0
    }
866
5
867
5
    auto script = mScripts.LookupOrAdd(cachePath, *this, url, cachePath, jsscript);
868
5
    if (isRunOnce) {
869
0
        script->mIsRunOnce = true;
870
0
    }
871
5
872
5
    if (!script->MaybeDropScript() && !script->mScript) {
873
0
        MOZ_ASSERT(jsscript);
874
0
        script->mScript = jsscript;
875
0
        script->mReadyToExecute = true;
876
0
    }
877
5
878
5
    script->UpdateLoadTime(TimeStamp::Now());
879
5
    script->mProcessTypes += CurrentProcessType();
880
5
}
881
882
void
883
ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
884
                            ProcessType processType, nsTArray<uint8_t>&& xdrData,
885
                            TimeStamp loadTime)
886
0
{
887
0
    // After data has been prepared, there's no point in noting further scripts,
888
0
    // since the cache either has already been written, or is about to be
889
0
    // written. Any time prior to the data being prepared, we can safely mutate
890
0
    // mScripts without locking. After that point, the save thread is free to
891
0
    // access it, and we can't alter it without locking.
892
0
    if (mDataPrepared) {
893
0
        return;
894
0
    }
895
0
896
0
    auto script = mScripts.LookupOrAdd(cachePath, *this, url, cachePath, nullptr);
897
0
898
0
    if (!script->HasRange()) {
899
0
        MOZ_ASSERT(!script->HasArray());
900
0
901
0
        script->mSize = xdrData.Length();
902
0
        script->mXDRData.construct<nsTArray<uint8_t>>(std::forward<nsTArray<uint8_t>>(xdrData));
903
0
904
0
        auto& data = script->Array();
905
0
        script->mXDRRange.emplace(data.Elements(), data.Length());
906
0
    }
907
0
908
0
    if (!script->mSize && !script->mScript) {
909
0
        // If the content process is sending us a script entry for a script
910
0
        // which was in the cache at startup, it expects us to already have this
911
0
        // script data, so it doesn't send it.
912
0
        //
913
0
        // However, the cache may have been invalidated at this point (usually
914
0
        // due to the add-on manager installing or uninstalling a legacy
915
0
        // extension during very early startup), which means we may no longer
916
0
        // have an entry for this script. Since that means we have no data to
917
0
        // write to the new cache, and no JSScript to generate it from, we need
918
0
        // to discard this entry.
919
0
        mScripts.Remove(cachePath);
920
0
        return;
921
0
    }
922
0
923
0
    script->UpdateLoadTime(loadTime);
924
0
    script->mProcessTypes += processType;
925
0
}
926
927
JSScript*
928
ScriptPreloader::GetCachedScript(JSContext* cx, const nsCString& path)
929
10
{
930
10
    // If a script is used by both the parent and the child, it's stored only
931
10
    // in the child cache.
932
10
    if (mChildCache) {
933
5
        auto script = mChildCache->GetCachedScript(cx, path);
934
5
        if (script) {
935
0
            return script;
936
0
        }
937
10
    }
938
10
939
10
    auto script = mScripts.Get(path);
940
10
    if (script) {
941
0
        return WaitForCachedScript(cx, script);
942
0
    }
943
10
944
10
    return nullptr;
945
10
}
946
947
JSScript*
948
ScriptPreloader::WaitForCachedScript(JSContext* cx, CachedScript* script)
949
0
{
950
0
    // Check for finished operations before locking so that we can move onto
951
0
    // decoding the next batch as soon as possible after the pending batch is
952
0
    // ready. If we wait until we hit an unfinished script, we wind up having at
953
0
    // most one batch of buffered scripts, and occasionally under-running that
954
0
    // buffer.
955
0
    MaybeFinishOffThreadDecode();
956
0
957
0
    if (!script->mReadyToExecute) {
958
0
        LOG(Info, "Must wait for async script load: %s\n", script->mURL.get());
959
0
        auto start = TimeStamp::Now();
960
0
961
0
        mMonitor.AssertNotCurrentThreadOwns();
962
0
        MonitorAutoLock mal(mMonitor);
963
0
964
0
        // Check for finished operations again *after* locking, or we may race
965
0
        // against mToken being set between our last check and the time we
966
0
        // entered the mutex.
967
0
        MaybeFinishOffThreadDecode();
968
0
969
0
        if (!script->mReadyToExecute && script->mSize < MAX_MAINTHREAD_DECODE_SIZE) {
970
0
            LOG(Info, "Script is small enough to recompile on main thread\n");
971
0
972
0
            script->mReadyToExecute = true;
973
0
        } else {
974
0
            while (!script->mReadyToExecute) {
975
0
                mal.Wait();
976
0
977
0
                MonitorAutoUnlock mau(mMonitor);
978
0
                MaybeFinishOffThreadDecode();
979
0
            }
980
0
        }
981
0
982
0
        LOG(Debug, "Waited %fms\n", (TimeStamp::Now() - start).ToMilliseconds());
983
0
    }
984
0
985
0
    return script->GetJSScript(cx);
986
0
}
987
988
989
990
/* static */ void
991
ScriptPreloader::OffThreadDecodeCallback(JS::OffThreadToken* token, void* context)
992
0
{
993
0
    auto cache = static_cast<ScriptPreloader*>(context);
994
0
995
0
    cache->mMonitor.AssertNotCurrentThreadOwns();
996
0
    MonitorAutoLock mal(cache->mMonitor);
997
0
998
0
    // First notify any tasks that are already waiting on scripts, since they'll
999
0
    // be blocking the main thread, and prevent any runnables from executing.
1000
0
    cache->mToken = token;
1001
0
    mal.NotifyAll();
1002
0
1003
0
    // If nothing processed the token, and we don't already have a pending
1004
0
    // runnable, then dispatch a new one to finish the processing on the main
1005
0
    // thread as soon as possible.
1006
0
    if (cache->mToken && !cache->mFinishDecodeRunnablePending) {
1007
0
        cache->mFinishDecodeRunnablePending = true;
1008
0
        NS_DispatchToMainThread(
1009
0
          NewRunnableMethod("ScriptPreloader::DoFinishOffThreadDecode",
1010
0
                            cache,
1011
0
                            &ScriptPreloader::DoFinishOffThreadDecode));
1012
0
    }
1013
0
}
1014
1015
void
1016
ScriptPreloader::FinishPendingParses(MonitorAutoLock& aMal)
1017
0
{
1018
0
    mMonitor.AssertCurrentThreadOwns();
1019
0
1020
0
    mPendingScripts.clear();
1021
0
1022
0
    MaybeFinishOffThreadDecode();
1023
0
1024
0
    // Loop until all pending decode operations finish.
1025
0
    while (!mParsingScripts.empty()) {
1026
0
        aMal.Wait();
1027
0
        MaybeFinishOffThreadDecode();
1028
0
    }
1029
0
}
1030
1031
void
1032
ScriptPreloader::DoFinishOffThreadDecode()
1033
0
{
1034
0
    mFinishDecodeRunnablePending = false;
1035
0
    MaybeFinishOffThreadDecode();
1036
0
}
1037
1038
void
1039
ScriptPreloader::MaybeFinishOffThreadDecode()
1040
0
{
1041
0
    if (!mToken) {
1042
0
        return;
1043
0
    }
1044
0
1045
0
    auto cleanup = MakeScopeExit([&] () {
1046
0
        mToken = nullptr;
1047
0
        mParsingSources.clear();
1048
0
        mParsingScripts.clear();
1049
0
1050
0
        DecodeNextBatch(OFF_THREAD_CHUNK_SIZE);
1051
0
    });
1052
0
1053
0
    AutoSafeJSAPI jsapi;
1054
0
    JSContext* cx = jsapi.cx();
1055
0
1056
0
    JSAutoRealm ar(cx, xpc::CompilationScope());
1057
0
    JS::Rooted<JS::ScriptVector> jsScripts(cx, JS::ScriptVector(cx));
1058
0
1059
0
    // If this fails, we still need to mark the scripts as finished. Any that
1060
0
    // weren't successfully compiled in this operation (which should never
1061
0
    // happen under ordinary circumstances) will be re-decoded on the main
1062
0
    // thread, and raise the appropriate errors when they're executed.
1063
0
    //
1064
0
    // The exception from the off-thread decode operation will be reported when
1065
0
    // we pop the AutoJSAPI off the stack.
1066
0
    Unused << JS::FinishMultiOffThreadScriptsDecoder(cx, mToken, &jsScripts);
1067
0
1068
0
    unsigned i = 0;
1069
0
    for (auto script : mParsingScripts) {
1070
0
        LOG(Debug, "Finished off-thread decode of %s\n", script->mURL.get());
1071
0
        if (i < jsScripts.length()) {
1072
0
            script->mScript = jsScripts[i++];
1073
0
        }
1074
0
        script->mReadyToExecute = true;
1075
0
    }
1076
0
}
1077
1078
void
1079
ScriptPreloader::DecodeNextBatch(size_t chunkSize, JS::HandleObject scope)
1080
0
{
1081
0
    MOZ_ASSERT(mParsingSources.length() == 0);
1082
0
    MOZ_ASSERT(mParsingScripts.length() == 0);
1083
0
1084
0
    auto cleanup = MakeScopeExit([&] () {
1085
0
        mParsingScripts.clearAndFree();
1086
0
        mParsingSources.clearAndFree();
1087
0
    });
1088
0
1089
0
    auto start = TimeStamp::Now();
1090
0
    LOG(Debug, "Off-thread decoding scripts...\n");
1091
0
1092
0
    size_t size = 0;
1093
0
    for (CachedScript* next = mPendingScripts.getFirst(); next;) {
1094
0
        auto script = next;
1095
0
        next = script->getNext();
1096
0
1097
0
        // Skip any scripts that we decoded on the main thread rather than
1098
0
        // waiting for an off-thread operation to complete.
1099
0
        if (script->mReadyToExecute) {
1100
0
            script->remove();
1101
0
            continue;
1102
0
        }
1103
0
        // If we have enough data for one chunk and this script would put us
1104
0
        // over our chunk size limit, we're done.
1105
0
        if (size > SMALL_SCRIPT_CHUNK_THRESHOLD &&
1106
0
            size + script->mSize > chunkSize) {
1107
0
            break;
1108
0
        }
1109
0
        if (!mParsingScripts.append(script) ||
1110
0
            !mParsingSources.emplaceBack(script->Range(), script->mURL.get(), 0)) {
1111
0
            break;
1112
0
        }
1113
0
1114
0
        LOG(Debug, "Beginning off-thread decode of script %s (%u bytes)\n",
1115
0
            script->mURL.get(), script->mSize);
1116
0
1117
0
        script->remove();
1118
0
        size += script->mSize;
1119
0
    }
1120
0
1121
0
    if (size == 0 && mPendingScripts.isEmpty()) {
1122
0
        return;
1123
0
    }
1124
0
1125
0
    AutoSafeJSAPI jsapi;
1126
0
    JSContext* cx = jsapi.cx();
1127
0
    JSAutoRealm ar(cx, scope ? scope : xpc::CompilationScope());
1128
0
1129
0
    JS::CompileOptions options(cx);
1130
0
    options.setNoScriptRval(true)
1131
0
           .setSourceIsLazy(true);
1132
0
1133
0
    if (!JS::CanCompileOffThread(cx, options, size) ||
1134
0
        !JS::DecodeMultiOffThreadScripts(cx, options, mParsingSources,
1135
0
                                         OffThreadDecodeCallback,
1136
0
                                         static_cast<void*>(this))) {
1137
0
        // If we fail here, we don't move on to process the next batch, so make
1138
0
        // sure we don't have any other scripts left to process.
1139
0
        MOZ_ASSERT(mPendingScripts.isEmpty());
1140
0
        for (auto script : mPendingScripts) {
1141
0
            script->mReadyToExecute = true;
1142
0
        }
1143
0
1144
0
        LOG(Info, "Can't decode %lu bytes of scripts off-thread", (unsigned long)size);
1145
0
        for (auto script : mParsingScripts) {
1146
0
            script->mReadyToExecute = true;
1147
0
        }
1148
0
        return;
1149
0
    }
1150
0
1151
0
    cleanup.release();
1152
0
1153
0
    LOG(Debug, "Initialized decoding of %u scripts (%u bytes) in %fms\n",
1154
0
        (unsigned)mParsingSources.length(), (unsigned)size, (TimeStamp::Now() - start).ToMilliseconds());
1155
0
}
1156
1157
1158
ScriptPreloader::CachedScript::CachedScript(ScriptPreloader& cache, InputBuffer& buf)
1159
    : mCache(cache)
1160
0
{
1161
0
    Code(buf);
1162
0
1163
0
    // Swap the mProcessTypes and mOriginalProcessTypes values, since we want to
1164
0
    // start with an empty set of processes loaded into for this session, and
1165
0
    // compare against last session's values later.
1166
0
    mOriginalProcessTypes = mProcessTypes;
1167
0
    mProcessTypes = {};
1168
0
}
1169
1170
bool
1171
ScriptPreloader::CachedScript::XDREncode(JSContext* cx)
1172
0
{
1173
0
    auto cleanup = MakeScopeExit([&] () {
1174
0
        MaybeDropScript();
1175
0
    });
1176
0
1177
0
    JSAutoRealm ar(cx, mScript);
1178
0
    JS::RootedScript jsscript(cx, mScript);
1179
0
1180
0
    mXDRData.construct<JS::TranscodeBuffer>();
1181
0
1182
0
    JS::TranscodeResult code = JS::EncodeScript(cx, Buffer(), jsscript);
1183
0
    if (code == JS::TranscodeResult_Ok) {
1184
0
        mXDRRange.emplace(Buffer().begin(), Buffer().length());
1185
0
        mSize = Range().length();
1186
0
        return true;
1187
0
    }
1188
0
    mXDRData.destroy();
1189
0
    JS_ClearPendingException(cx);
1190
0
    return false;
1191
0
}
1192
1193
JSScript*
1194
ScriptPreloader::CachedScript::GetJSScript(JSContext* cx)
1195
0
{
1196
0
    MOZ_ASSERT(mReadyToExecute);
1197
0
    if (mScript) {
1198
0
        return mScript;
1199
0
    }
1200
0
1201
0
    if (!HasRange()) {
1202
0
        // We've already executed the script, and thrown it away. But it wasn't
1203
0
        // in the cache at startup, so we don't have any data to decode. Give
1204
0
        // up.
1205
0
        return nullptr;
1206
0
    }
1207
0
1208
0
    // If we have no script at this point, the script was too small to decode
1209
0
    // off-thread, or it was needed before the off-thread compilation was
1210
0
    // finished, and is small enough to decode on the main thread rather than
1211
0
    // wait for the off-thread decoding to finish. In either case, we decode
1212
0
    // it synchronously the first time it's needed.
1213
0
1214
0
    auto start = TimeStamp::Now();
1215
0
    LOG(Info, "Decoding script %s on main thread...\n", mURL.get());
1216
0
1217
0
    JS::RootedScript script(cx);
1218
0
    if (JS::DecodeScript(cx, Range(), &script)) {
1219
0
        mScript = script;
1220
0
1221
0
        if (mCache.mSaveComplete) {
1222
0
            FreeData();
1223
0
        }
1224
0
    }
1225
0
1226
0
    LOG(Debug, "Finished decoding in %fms", (TimeStamp::Now() - start).ToMilliseconds());
1227
0
1228
0
    return mScript;
1229
0
}
1230
1231
NS_IMPL_ISUPPORTS(ScriptPreloader, nsIObserver, nsIRunnable, nsIMemoryReporter)
1232
1233
#undef LOG
1234
1235
} // namespace mozilla