tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ScriptPreloader.h (18671B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef ScriptPreloader_h
      7 #define ScriptPreloader_h
      8 
      9 #include "mozilla/EnumSet.h"
     10 #include "mozilla/EventTargetAndLockCapability.h"
     11 #include "mozilla/LinkedList.h"
     12 #include "mozilla/MemoryReporting.h"
     13 #include "mozilla/Maybe.h"
     14 #include "mozilla/MaybeOneOf.h"
     15 #include "mozilla/Monitor.h"
     16 #include "mozilla/Range.h"
     17 #include "mozilla/Result.h"
     18 #include "mozilla/SPSCQueue.h"
     19 #include "mozilla/StaticPtr.h"
     20 #include "mozilla/Vector.h"
     21 #include "mozilla/loader/AutoMemMap.h"
     22 #include "MainThreadUtils.h"
     23 #include "nsClassHashtable.h"
     24 #include "nsThreadUtils.h"
     25 #include "nsIAsyncShutdown.h"
     26 #include "nsIFile.h"
     27 #include "nsIMemoryReporter.h"
     28 #include "nsIObserver.h"
     29 #include "nsIThread.h"
     30 #include "nsITimer.h"
     31 
     32 #include "js/CompileOptions.h"  // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
     33 #include "js/experimental/CompileScript.h"  // JS::FrontendContext
     34 #include "js/experimental/JSStencil.h"      // JS::Stencil
     35 #include "js/GCAnnotations.h"               // for JS_HAZ_NON_GC_POINTER
     36 #include "js/RootingAPI.h"                  // for Handle, Heap
     37 #include "js/Transcoding.h"  // for TranscodeBuffer, TranscodeRange, TranscodeSource
     38 #include "js/TypeDecls.h"  // for HandleObject, HandleScript
     39 
     40 #include <prio.h>
     41 
     42 namespace mozilla {
     43 namespace dom {
     44 class ContentParent;
     45 }
     46 namespace ipc {
     47 class FileDescriptor;
     48 }
     49 namespace loader {
     50 class InputBuffer;
     51 class ScriptCacheChild;
     52 
     53 enum class ProcessType : uint8_t {
     54  Uninitialized,
     55  Parent,
     56  Web,
     57  Extension,
     58  PrivilegedAbout,
     59 };
     60 
     61 template <typename T>
     62 struct Matcher {
     63  virtual bool Matches(T) = 0;
     64 };
     65 }  // namespace loader
     66 
     67 using namespace mozilla::loader;
     68 
     69 struct CachedStencilRefAndTime;
     70 
     71 class ScriptPreloader : public nsIObserver,
     72                        public nsIMemoryReporter,
     73                        public nsIRunnable,
     74                        public nsINamed,
     75                        public nsIAsyncShutdownBlocker {
     76  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
     77 
     78  friend class mozilla::loader::ScriptCacheChild;
     79 
     80 public:
     81  NS_DECL_THREADSAFE_ISUPPORTS
     82  NS_DECL_NSIOBSERVER
     83  NS_DECL_NSIMEMORYREPORTER
     84  NS_DECL_NSIRUNNABLE
     85  NS_DECL_NSINAMED
     86  NS_DECL_NSIASYNCSHUTDOWNBLOCKER
     87 
     88 private:
     89  static StaticRefPtr<ScriptPreloader> gScriptPreloader;
     90  static StaticRefPtr<ScriptPreloader> gChildScriptPreloader;
     91  static StaticAutoPtr<AutoMemMap> gCacheData;
     92  static StaticAutoPtr<AutoMemMap> gChildCacheData;
     93 
     94 public:
     95  static ScriptPreloader& GetSingleton();
     96  static ScriptPreloader& GetChildSingleton();
     97 
     98  static void DeleteSingleton();
     99  static void DeleteCacheDataSingleton();
    100 
    101  static ProcessType GetChildProcessType(const nsACString& remoteType);
    102 
    103  // Fill some options that should be consistent across all scripts stored
    104  // into preloader cache.
    105  static void FillCompileOptionsForCachedStencil(JS::CompileOptions& options);
    106  static void FillDecodeOptionsForCachedStencil(JS::DecodeOptions& options);
    107 
    108  // Retrieves the stencil with the given cache key from the cache.
    109  // Returns null if the stencil is not cached.
    110  already_AddRefed<JS::Stencil> GetCachedStencil(
    111      JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
    112      const nsCString& path);
    113 
    114  // Notes the execution of a script with the given URL and cache key.
    115  // Depending on the stage of startup, the script may be serialized and
    116  // stored to the startup script cache.
    117  //
    118  // If isRunOnce is true, this script is expected to run only once per
    119  // process per browser session. A cached instance will not be kept alive
    120  // for repeated execution.
    121  void NoteStencil(const nsCString& url, const nsCString& cachePath,
    122                   JS::Stencil* stencil, bool isRunOnce = false);
    123 
    124  // Notes the IPC arrival of the XDR data of a stencil compiled by some
    125  // child process. See ScriptCacheChild::SendScriptsAndFinalize.
    126  void NoteStencil(const nsCString& url, const nsCString& cachePath,
    127                   ProcessType processType, nsTArray<uint8_t>&& xdrData,
    128                   TimeStamp loadTime);
    129 
    130  // Initializes the script cache from the startup script cache file.
    131  Result<Ok, nsresult> InitCache(const nsAString& = u"scriptCache"_ns)
    132      MOZ_REQUIRES(sMainThreadCapability);
    133 
    134  Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile,
    135                                 ScriptCacheChild* cacheChild)
    136      MOZ_REQUIRES(sMainThreadCapability);
    137 
    138  bool Active() const { return mCacheInitialized && !mStartupFinished; }
    139 
    140 private:
    141  Result<Ok, nsresult> InitCacheInternal(JS::Handle<JSObject*> scope = nullptr);
    142  already_AddRefed<JS::Stencil> GetCachedStencilInternal(
    143      JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
    144      const nsCString& path);
    145 
    146 public:
    147  static ProcessType CurrentProcessType() {
    148    MOZ_ASSERT(sProcessType != ProcessType::Uninitialized);
    149    return sProcessType;
    150  }
    151 
    152  static void InitContentChild(dom::ContentParent& parent);
    153 
    154 protected:
    155  virtual ~ScriptPreloader();
    156 
    157 private:
    158  enum class ScriptStatus {
    159    Restored,
    160    Saved,
    161  };
    162 
    163  // Represents a cached script stencil, either initially read from the
    164  // cache file, to be added to the next session's stencil cache file, or
    165  // both.
    166  //
    167  //  - Read from the cache, and being decoded off thread. In this case:
    168  //      - mReadyToExecute is false
    169  //      - mDecodingScripts contains the CachedStencil
    170  //      - mDecodedStencils have never contained the stencil
    171  //      - mStencil is null
    172  //
    173  //  - Off-thread decode for the stencil has finished, but the stencil has not
    174  //    yet been dequeued nor executed. In this case:
    175  //      - mReadyToExecute is true
    176  //      - mDecodingScripts contains the CachedStencil
    177  //      - mDecodedStencils contains the decoded stencil
    178  //      - mStencil is null
    179  //
    180  //  - Off-thread decode for the stencil has finished, and the stencil has
    181  //    been dequeued, but has not yet been executed. In this case:
    182  //      - mReadyToExecute is true
    183  //      - mDecodingScripts no longer contains the CachedStencil
    184  //      - mDecodedStencils no longer contains the decoded stencil
    185  //      - mStencil is non-null
    186  //
    187  //  - Fully decoded, and ready to be added to the next session's cache
    188  //    file. In this case:
    189  //      - mReadyToExecute is true
    190  //      - mStencil is non-null
    191  //
    192  // A stencil to be added to the next session's cache file always has a
    193  // non-null mStencil value. If it was read from the last session's cache
    194  // file, it also has a non-empty mXDRRange range, which will be stored in
    195  // the next session's cache file. If it was compiled in this session, its
    196  // mXDRRange will initially be empty, and its mXDRData buffer will be
    197  // populated just before it is written to the cache file.
    198  class CachedStencil : public LinkedListElement<CachedStencil> {
    199   public:
    200    CachedStencil(CachedStencil&&) = delete;
    201 
    202    CachedStencil(ScriptPreloader& cache, const nsCString& url,
    203                  const nsCString& cachePath, JS::Stencil* stencil)
    204        : mCache(cache),
    205          mURL(url),
    206          mCachePath(cachePath),
    207          mStencil(stencil),
    208          mReadyToExecute(true),
    209          mIsRunOnce(false) {}
    210 
    211    inline CachedStencil(ScriptPreloader& cache, InputBuffer& buf);
    212 
    213    ~CachedStencil() = default;
    214 
    215    ScriptStatus Status() const {
    216      return mProcessTypes.isEmpty() ? ScriptStatus::Restored
    217                                     : ScriptStatus::Saved;
    218    }
    219 
    220    struct StatusMatcher final : public Matcher<CachedStencil*> {
    221      explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
    222 
    223      virtual bool Matches(CachedStencil* script) override {
    224        return script->Status() == mStatus;
    225      }
    226 
    227      const ScriptStatus mStatus;
    228    };
    229 
    230    void FreeData() {
    231      // If the script data isn't mmapped, we need to release both it
    232      // and the Range that points to it at the same time.
    233      if (!IsMemMapped()) {
    234        mXDRRange.reset();
    235        mXDRData.destroy();
    236      }
    237    }
    238 
    239    void UpdateLoadTime(const TimeStamp& loadTime) {
    240      if (mLoadTime.IsNull() || loadTime < mLoadTime) {
    241        mLoadTime = loadTime;
    242      }
    243    }
    244 
    245    // Checks whether the cached JSScript for this entry will be needed
    246    // again and, if not, drops it and returns true. This is the case for
    247    // run-once scripts that do not still need to be encoded into the
    248    // cache.
    249    //
    250    // If this method returns false, callers may set mScript to a cached
    251    // JSScript instance for this entry. If it returns true, they should
    252    // not.
    253    bool MaybeDropStencil() {
    254      if (mIsRunOnce && (HasRange() || !mCache.WillWriteScripts())) {
    255        mStencil = nullptr;
    256        return true;
    257      }
    258      return false;
    259    }
    260 
    261    // Encodes this script into XDR data, and stores the result in mXDRData.
    262    // Returns true on success, false on failure.
    263    bool XDREncode(JS::FrontendContext* cx);
    264 
    265    // Encodes or decodes this script, in the storage format required by the
    266    // script cache file.
    267    template <typename Buffer>
    268    void Code(Buffer& buffer) {
    269      buffer.codeString(mURL);
    270      buffer.codeString(mCachePath);
    271      buffer.codeUint32(mOffset);
    272      buffer.codeUint32(mSize);
    273      buffer.codeUint8(mProcessTypes);
    274    }
    275 
    276    // Returns the XDR data generated for this script during this session. See
    277    // mXDRData.
    278    JS::TranscodeBuffer& Buffer() {
    279      MOZ_ASSERT(HasBuffer());
    280      return mXDRData.ref<JS::TranscodeBuffer>();
    281    }
    282 
    283    bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
    284 
    285    // Returns the read-only XDR data for this script. See mXDRRange.
    286    const JS::TranscodeRange& Range() {
    287      MOZ_ASSERT(HasRange());
    288      return mXDRRange.ref();
    289    }
    290 
    291    bool HasRange() { return mXDRRange.isSome(); }
    292 
    293    bool IsMemMapped() const { return mXDRData.empty(); }
    294 
    295    nsTArray<uint8_t>& Array() {
    296      MOZ_ASSERT(HasArray());
    297      return mXDRData.ref<nsTArray<uint8_t>>();
    298    }
    299 
    300    bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
    301 
    302    already_AddRefed<JS::Stencil> GetStencil(
    303        JSContext* cx, const JS::ReadOnlyDecodeOptions& options);
    304 
    305    size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
    306      auto size = mallocSizeOf(this);
    307 
    308      if (HasArray()) {
    309        size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
    310      } else if (HasBuffer()) {
    311        size += Buffer().sizeOfExcludingThis(mallocSizeOf);
    312      }
    313 
    314      if (mStencil) {
    315        size += JS::SizeOfStencil(mStencil, mallocSizeOf);
    316      }
    317 
    318      // Note: mURL and mCachePath use the same string for scripts loaded
    319      // by the message manager. The following statement avoids
    320      // double-measuring in that case.
    321      size += (mURL.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
    322               mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
    323 
    324      return size;
    325    }
    326 
    327    ScriptPreloader& mCache;
    328 
    329    // The URL from which this script was initially read and compiled.
    330    nsCString mURL;
    331    // A unique identifier for this script's filesystem location, used as a
    332    // primary cache lookup value.
    333    nsCString mCachePath;
    334 
    335    // The offset of this script in the cache file, from the start of the XDR
    336    // data block.
    337    uint32_t mOffset = 0;
    338    // The size of this script's encoded XDR data.
    339    uint32_t mSize = 0;
    340 
    341    TimeStamp mLoadTime{};
    342 
    343    RefPtr<JS::Stencil> mStencil;
    344 
    345    // True if this script is ready to be executed. This means that either the
    346    // off-thread portion of an off-thread decode has finished, or the
    347    // off-thread decode failed, and may be immediately decoded
    348    // whenever it is first executed.
    349    bool mReadyToExecute = false;
    350 
    351    // True if this script is expected to run once per process. If so, its
    352    // JSScript instance will be dropped as soon as the script has
    353    // executed and been encoded into the cache.
    354    bool mIsRunOnce = false;
    355 
    356    // The set of processes in which this script has been used.
    357    EnumSet<ProcessType> mProcessTypes{};
    358 
    359    // The set of processes which the script was loaded into during the
    360    // last session, as read from the cache file.
    361    EnumSet<ProcessType> mOriginalProcessTypes{};
    362 
    363    // The read-only XDR data for this script, which was either read from an
    364    // existing cache file, or generated by encoding a script which was
    365    // compiled during this session.
    366    Maybe<JS::TranscodeRange> mXDRRange;
    367 
    368    // XDR data which was generated from a script compiled during this
    369    // session, and will be written to the cache file.
    370    //
    371    // The format is JS::TranscodeBuffer if the script was XDR'd as part
    372    // of this process, or nsTArray<> if the script was transfered by IPC
    373    // from a child process.
    374    MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
    375  } JS_HAZ_NON_GC_POINTER;
    376 
    377  friend struct CachedStencilRefAndTime;
    378 
    379  template <ScriptStatus status>
    380  static Matcher<CachedStencil*>* Match() {
    381    static CachedStencil::StatusMatcher matcher{status};
    382    return &matcher;
    383  }
    384 
    385  // The maximum size of scripts to re-decode on the main thread if off-thread
    386  // decoding hasn't finished yet. In practice, we don't hit this very often,
    387  // but when we do, re-decoding some smaller scripts on the main thread gives
    388  // the background decoding a chance to catch up without blocking the main
    389  // thread for quite as long.
    390  static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
    391 
    392  explicit ScriptPreloader(AutoMemMap* cacheData);
    393 
    394  void Cleanup();
    395 
    396  void FinishPendingParses(MonitorAutoLock& aMal);
    397  void InvalidateCache() MOZ_REQUIRES(sMainThreadCapability);
    398 
    399  // Opens the cache file for reading.
    400  Result<Ok, nsresult> OpenCache();
    401 
    402  // Writes a new cache file to disk. Must not be called on the main thread.
    403  Result<Ok, nsresult> WriteCache() MOZ_REQUIRES(mSaveMonitor.Lock());
    404 
    405  void StartCacheWrite();
    406 
    407  // Prepares scripts for writing to the cache, serializing new scripts to
    408  // XDR, and calculating their size-based offsets.
    409  void PrepareCacheWrite();
    410 
    411  void PrepareCacheWriteInternal();
    412 
    413  void CacheWriteComplete();
    414 
    415  void FinishContentStartup();
    416 
    417  // Returns true if scripts added to the cache now will be encoded and
    418  // written to the cache. If we've already encoded scripts for the cache
    419  // write, or this is a content process which hasn't been asked to return
    420  // script bytecode, this will return false.
    421  bool WillWriteScripts();
    422 
    423  // Returns a file pointer for the cache file with the given name in the
    424  // current profile.
    425  Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
    426 
    427  // Waits for the given cached script to finish compiling off-thread, or
    428  // decodes it synchronously on the main thread, as appropriate.
    429  already_AddRefed<JS::Stencil> WaitForCachedStencil(
    430      JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
    431      CachedStencil* script);
    432 
    433  void StartDecodeTask(JS::Handle<JSObject*> scope);
    434 
    435 private:
    436  bool StartDecodeTask(const JS::ReadOnlyDecodeOptions& decodeOptions,
    437                       Vector<JS::TranscodeSource>&& decodingSources);
    438 
    439  class DecodeTask : public Runnable {
    440    ScriptPreloader* mPreloader;
    441    JS::OwningDecodeOptions mDecodeOptions;
    442    Vector<JS::TranscodeSource> mDecodingSources;
    443 
    444   public:
    445    DecodeTask(ScriptPreloader* preloader,
    446               const JS::ReadOnlyDecodeOptions& decodeOptions,
    447               Vector<JS::TranscodeSource>&& decodingSources)
    448        : Runnable("ScriptPreloaderDecodeTask"),
    449          mPreloader(preloader),
    450          mDecodingSources(std::move(decodingSources)) {
    451      mDecodeOptions.infallibleCopy(decodeOptions);
    452    }
    453 
    454    NS_IMETHOD Run() override;
    455  };
    456 
    457  friend class DecodeTask;
    458 
    459  void onDecodedStencilQueued();
    460  void OnDecodeTaskFinished();
    461  void OnDecodeTaskFailed();
    462 
    463 public:
    464  void FinishOffThreadDecode();
    465  void DoFinishOffThreadDecode();
    466 
    467  already_AddRefed<nsIAsyncShutdownClient> GetShutdownBarrier();
    468 
    469  size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
    470    return (mallocSizeOf(this) +
    471            mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
    472            mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
    473  }
    474 
    475  using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedStencil>;
    476 
    477  template <ScriptStatus status>
    478  static size_t SizeOfHashEntries(ScriptHash& scripts,
    479                                  mozilla::MallocSizeOf mallocSizeOf) {
    480    size_t size = 0;
    481    for (auto elem : IterHash(scripts, Match<status>())) {
    482      size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
    483    }
    484    return size;
    485  }
    486 
    487  ScriptHash mScripts;
    488 
    489  // True after we've shown the first window, and are no longer adding new
    490  // scripts to the cache.
    491  bool mStartupFinished = false;
    492 
    493  bool mCacheInitialized = false;
    494  bool mSaveComplete = false;
    495  bool mDataPrepared = false;
    496  // May only be changed on the main thread, while `mSaveMonitor.Lock()` is
    497  // held.
    498  bool mCacheInvalidated MOZ_GUARDED_BY(mSaveMonitor) = false;
    499 
    500  // The list of scripts currently being decoded in a background thread.
    501  LinkedList<CachedStencil> mDecodingScripts;
    502 
    503  // The result of the decode task.
    504  //
    505  // This is emplaced when starting the decode task, with the capacity equal
    506  // to the number of sources.
    507  //
    508  // If the decode task failed, nullptr is enqueued.
    509  Maybe<SPSCQueue<RefPtr<JS::Stencil>>> mDecodedStencils;
    510 
    511  // True is main-thread is blocked and we should notify with Monitor. Access
    512  // only while `mMonitor` is held.
    513  bool mWaitingForDecode MOZ_GUARDED_BY(mMonitor) = false;
    514 
    515  // The process type of the current process.
    516  static ProcessType sProcessType;
    517 
    518  // The process types for which remote processes have been initialized, and
    519  // are expected to send back script data.
    520  EnumSet<ProcessType> mInitializedProcesses{};
    521 
    522  RefPtr<ScriptPreloader> mChildCache;
    523  ScriptCacheChild* mChildActor = nullptr;
    524 
    525  nsString mBaseName;
    526  nsCString mContentStartupFinishedTopic;
    527 
    528  nsCOMPtr<nsIFile> mProfD;
    529  nsCOMPtr<nsIThread> mSaveThread;
    530  nsCOMPtr<nsITimer> mSaveTimer;
    531 
    532  // The mmapped cache data from this session's cache file.
    533  // The instance is held by either `gCacheData` or `gChildCacheData` static
    534  // fields, and its lifetime is guaranteed to be longer than ScriptPreloader
    535  // instance.
    536  AutoMemMap* mCacheData;
    537 
    538  Monitor mMonitor MOZ_ACQUIRED_AFTER(mSaveMonitor.Lock());
    539  MainThreadAndLockCapability<Monitor> mSaveMonitor;
    540 };
    541 
    542 }  // namespace mozilla
    543 
    544 #endif  // ScriptPreloader_h