tor-browser

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

ScriptSettings.h (15896B)


      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 /* Utilities for managing the script settings object stack defined in webapps */
      8 
      9 #ifndef mozilla_dom_ScriptSettings_h
     10 #define mozilla_dom_ScriptSettings_h
     11 
     12 #include "js/Exception.h"
     13 #include "js/Warnings.h"  // JS::WarningReporter
     14 #include "jsapi.h"
     15 #include "mozilla/Maybe.h"
     16 #include "mozilla/dom/JSExecutionManager.h"
     17 #include "xpcpublic.h"
     18 
     19 class JSObject;
     20 class nsIGlobalObject;
     21 class nsIPrincipal;
     22 class nsPIDOMWindowInner;
     23 class nsGlobalWindowInner;
     24 class nsIScriptContext;
     25 struct JSContext;
     26 
     27 namespace JS {
     28 class ExceptionStack;
     29 class Value;
     30 }  // namespace JS
     31 
     32 namespace mozilla {
     33 namespace dom {
     34 
     35 class Document;
     36 class WebTaskSchedulingState;
     37 
     38 /*
     39 * Per thread setup/teardown routines. Init and Destroy should be invoked
     40 * once each, at startup and shutdown of the script runtime (respectively).
     41 */
     42 void InitScriptSettings();
     43 void DestroyScriptSettings();
     44 
     45 // To implement a web-compatible browser, it is often necessary to obtain the
     46 // global object that is "associated" with the currently-running code. This
     47 // process is made more complicated by the fact that, historically, different
     48 // algorithms have operated with different definitions of the "associated"
     49 // global.
     50 //
     51 // HTML5 formalizes this into two concepts: the "incumbent global" and the
     52 // "entry global". The incumbent global corresponds to the global of the
     53 // current script being executed, whereas the entry global corresponds to the
     54 // global of the script where the current JS execution began.
     55 //
     56 // There is also a potentially-distinct third global that is determined by the
     57 // current compartment. This roughly corresponds with the notion of Realms in
     58 // ECMAScript.
     59 //
     60 // Suppose some event triggers an event listener in window |A|, which invokes a
     61 // scripted function in window |B|, which invokes the |window.location.href|
     62 // setter in window |C|. The entry global would be |A|, the incumbent global
     63 // would be |B|, and the current compartment would be that of |C|.
     64 //
     65 // In general, it's best to use to use the most-closely-associated global
     66 // unless the spec says to do otherwise. In 95% of the cases, the global of
     67 // the current compartment (GetCurrentGlobal()) is the right thing. For
     68 // example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
     69 // the global of the current compartment (i.e. |C|).
     70 //
     71 // The incumbent global is very similar, but differs in a few edge cases. For
     72 // example, if window |B| does |C.location.href = "..."|, the incumbent global
     73 // used for the navigation algorithm is B, because no script from |C| was ever
     74 // run.
     75 //
     76 // The entry global is used for various things like computing base URIs, mostly
     77 // for historical reasons.
     78 //
     79 // Note that all of these functions return bonafide global objects. This means
     80 // that, for Windows, they always return the inner.
     81 
     82 // Returns the global associated with the top-most Candidate Entry Point on
     83 // the Script Settings Stack. See the HTML spec. This may be null.
     84 nsIGlobalObject* GetEntryGlobal();
     85 
     86 // If the entry global is a window, returns its extant document. Otherwise,
     87 // returns null.
     88 Document* GetEntryDocument();
     89 
     90 // Returns the global associated with the top-most entry of the the Script
     91 // Settings Stack. See the HTML spec. This may be null.
     92 nsIGlobalObject* GetIncumbentGlobal();
     93 
     94 // Returns the global associated with the current compartment. This may be null.
     95 nsIGlobalObject* GetCurrentGlobal();
     96 
     97 WebTaskSchedulingState* GetWebTaskSchedulingState();
     98 
     99 // JS-implemented WebIDL presents an interesting situation with respect to the
    100 // subject principal. A regular C++-implemented API can simply examine the
    101 // compartment of the most-recently-executed script, and use that to infer the
    102 // responsible party. However, JS-implemented APIs are run with system
    103 // principal, and thus clobber the subject principal of the script that
    104 // invoked the API. So we have to do some extra work to keep track of this
    105 // information.
    106 //
    107 // We therefore implement the following behavior:
    108 // * Each Script Settings Object has an optional WebIDL Caller Principal field.
    109 //   This defaults to null.
    110 // * When we push an Entry Point in preparation to run a JS-implemented WebIDL
    111 //   callback, we grab the subject principal at the time of invocation, and
    112 //   store that as the WebIDL Caller Principal.
    113 // * When non-null, callers can query this principal from script via an API on
    114 //   Components.utils.
    115 nsIPrincipal* GetWebIDLCallerPrincipal();
    116 
    117 // Returns whether JSAPI is active right now.  If it is not, working with a
    118 // JSContext you grab from somewhere random is not OK and you should be doing
    119 // AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
    120 bool IsJSAPIActive();
    121 
    122 namespace danger {
    123 
    124 // Get the JSContext for this thread.  This is in the "danger" namespace because
    125 // we generally want people using AutoJSAPI instead, unless they really know
    126 // what they're doing.
    127 JSContext* GetJSContext();
    128 
    129 }  // namespace danger
    130 
    131 JS::RootingContext* RootingCx();
    132 
    133 class ScriptSettingsStack;
    134 class ScriptSettingsStackEntry {
    135  friend class ScriptSettingsStack;
    136 
    137 public:
    138  ~ScriptSettingsStackEntry();
    139 
    140  bool NoJSAPI() const { return mType == eNoJSAPI; }
    141  bool IsEntryCandidate() const {
    142    return mType == eEntryScript || mType == eNoJSAPI;
    143  }
    144  bool IsIncumbentCandidate() { return mType != eJSAPI; }
    145  bool IsIncumbentScript() { return mType == eIncumbentScript; }
    146 
    147 protected:
    148  enum Type { eEntryScript, eIncumbentScript, eJSAPI, eNoJSAPI };
    149 
    150  ScriptSettingsStackEntry(nsIGlobalObject* aGlobal, Type aEntryType);
    151 
    152  nsCOMPtr<nsIGlobalObject> mGlobalObject;
    153  Type mType;
    154 
    155 private:
    156  ScriptSettingsStackEntry* mOlder;
    157 };
    158 
    159 /*
    160 * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
    161 * must be on the stack.
    162 *
    163 * This base class should be instantiated as-is when the caller wants to use
    164 * JSAPI but doesn't expect to run script. The caller must then call one of its
    165 * Init functions before being able to access the JSContext through cx().
    166 * Its current duties are as-follows (see individual Init comments for details):
    167 *
    168 * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
    169 *   the JSContext stack.
    170 * * Entering an initial (possibly null) compartment, to ensure that the
    171 *   previously entered compartment for that JSContext is not used by mistake.
    172 * * Reporting any exceptions left on the JSRuntime, unless the caller steals
    173 *   or silences them.
    174 *
    175 * Additionally, the following duties are planned, but not yet implemented:
    176 *
    177 * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
    178 *   implementing the poisoning first.  For now, this de-poisoning
    179 *   effectively corresponds to having a non-null cx on the stack.
    180 *
    181 * In situations where the consumer expects to run script, AutoEntryScript
    182 * should be used, which does additional manipulation of the script settings
    183 * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
    184 * any attempt to run script without an AutoEntryScript on the stack will
    185 * fail. This prevents system code from accidentally triggering script
    186 * execution at inopportune moments via surreptitious getters and proxies.
    187 */
    188 class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
    189 public:
    190  // Trivial constructor. One of the Init functions must be called before
    191  // accessing the JSContext through cx().
    192  AutoJSAPI();
    193 
    194  ~AutoJSAPI();
    195 
    196  // This uses the SafeJSContext (or worker equivalent), and enters a null
    197  // compartment, so that the consumer is forced to select a compartment to
    198  // enter before manipulating objects.
    199  //
    200  // This variant will ensure that any errors reported by this AutoJSAPI as it
    201  // comes off the stack will not fire error events or be associated with any
    202  // particular web-visible global.
    203  void Init();
    204 
    205  // This uses the SafeJSContext (or worker equivalent), and enters the
    206  // compartment of aGlobalObject.
    207  // If aGlobalObject or its associated JS global are null then it returns
    208  // false and use of cx() will cause an assertion.
    209  //
    210  // If aGlobalObject represents a web-visible global, errors reported by this
    211  // AutoJSAPI as it comes off the stack will fire the relevant error events and
    212  // show up in the corresponding web console.
    213  //
    214  // Successfully initializing the AutoJSAPI will ensure that it enters the
    215  // Realm of aGlobalObject's JSObject and exposes that JSObject to active JS.
    216  [[nodiscard]] bool Init(nsIGlobalObject* aGlobalObject);
    217 
    218  // This is a helper that grabs the native global associated with aObject and
    219  // invokes the above Init() with that. aObject must not be a cross-compartment
    220  // wrapper: CCWs are not associated with a single global.
    221  [[nodiscard]] bool Init(JSObject* aObject);
    222 
    223  // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
    224  // If aGlobalObject or its associated JS global are null then it returns
    225  // false and use of cx() will cause an assertion.
    226  // If aCx is null it will cause an assertion.
    227  //
    228  // If aGlobalObject represents a web-visible global, errors reported by this
    229  // AutoJSAPI as it comes off the stack will fire the relevant error events and
    230  // show up in the corresponding web console.
    231  [[nodiscard]] bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
    232 
    233  // Convenience functions to take an nsPIDOMWindowInner or nsGlobalWindowInner,
    234  // when it is more easily available than an nsIGlobalObject.
    235  [[nodiscard]] bool Init(nsPIDOMWindowInner* aWindow);
    236  [[nodiscard]] bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
    237 
    238  [[nodiscard]] bool Init(nsGlobalWindowInner* aWindow);
    239  [[nodiscard]] bool Init(nsGlobalWindowInner* aWindow, JSContext* aCx);
    240 
    241  JSContext* cx() const {
    242    MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
    243    MOZ_ASSERT(IsStackTop());
    244    return mCx;
    245  }
    246 
    247 #ifdef DEBUG
    248  bool IsStackTop() const;
    249 #endif
    250 
    251  // If HasException, report it.  Otherwise, a no-op.
    252  void ReportException();
    253 
    254  bool HasException() const {
    255    MOZ_ASSERT(IsStackTop());
    256    return JS_IsExceptionPending(cx());
    257  };
    258 
    259  // Transfers ownership of the current exception from the JS engine to the
    260  // caller. Callers must ensure that HasException() is true, and that cx()
    261  // is in a non-null compartment.
    262  //
    263  // Note that this fails if and only if we OOM while wrapping the exception
    264  // into the current compartment.
    265  [[nodiscard]] bool StealException(JS::MutableHandle<JS::Value> aVal);
    266 
    267  // As for StealException(), but uses the JS::ExceptionStack class to also
    268  // include the exception's stack, represented by SavedFrames.
    269  [[nodiscard]] bool StealExceptionAndStack(JS::ExceptionStack* aExnStack);
    270 
    271  // Peek the current exception from the JS engine, without stealing it.
    272  // Callers must ensure that HasException() is true, and that cx() is in a
    273  // non-null compartment.
    274  //
    275  // Note that this fails if and only if we OOM while wrapping the exception
    276  // into the current compartment.
    277  [[nodiscard]] bool PeekException(JS::MutableHandle<JS::Value> aVal);
    278 
    279  void ClearException() {
    280    MOZ_ASSERT(IsStackTop());
    281    JS_ClearPendingException(cx());
    282  }
    283 
    284 protected:
    285  // Protected constructor for subclasses.  This constructor initialises the
    286  // AutoJSAPI, so Init must NOT be called on subclasses that use this.
    287  AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
    288 
    289  mozilla::Maybe<JSAutoNullableRealm> mAutoNullableRealm;
    290  JSContext* mCx;
    291 
    292  // Whether we're mainthread or not; set when we're initialized.
    293  bool mIsMainThread;
    294  Maybe<JS::WarningReporter> mOldWarningReporter;
    295 
    296 private:
    297  void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
    298                    JSContext* aCx, bool aIsMainThread);
    299 
    300  AutoJSAPI(const AutoJSAPI&) = delete;
    301  AutoJSAPI& operator=(const AutoJSAPI&) = delete;
    302 };
    303 
    304 /*
    305 * A class that can be used to force a particular incumbent script on the stack.
    306 */
    307 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
    308 public:
    309  explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
    310  ~AutoIncumbentScript();
    311 
    312 private:
    313  JS::AutoHideScriptedCaller mCallerOverride;
    314 };
    315 
    316 /*
    317 * A class to put the JS engine in an unusable state. The subject principal
    318 * will become System, the information on the script settings stack is
    319 * rendered inaccessible, and JSAPI may not be manipulated until the class is
    320 * either popped or an AutoJSAPI instance is subsequently pushed.
    321 *
    322 * This class may not be instantiated if an exception is pending.
    323 */
    324 class AutoNoJSAPI : protected ScriptSettingsStackEntry,
    325                    protected JSAutoNullableRealm {
    326 public:
    327  AutoNoJSAPI() : AutoNoJSAPI(danger::GetJSContext()) {}
    328  ~AutoNoJSAPI();
    329 
    330 private:
    331  // Helper constructor to avoid doing GetJSContext() multiple times
    332  // during construction.
    333  explicit AutoNoJSAPI(JSContext* aCx);
    334 
    335  // Stashed JSContext* so we don't need to GetJSContext in our destructor.
    336  // It's probably safe to hold on to this, in the sense that the world should
    337  // not get torn down while we're on the stack, and if it's not, we'd need to
    338  // fix JSAutoNullableRealm to not hold on to a JSContext either, or
    339  // something.
    340  JSContext* mCx;
    341 
    342  AutoYieldJSThreadExecution mExecutionYield;
    343 };
    344 
    345 }  // namespace dom
    346 
    347 /**
    348 * Use AutoJSContext when you need a JS context on the stack but don't have one
    349 * passed as a parameter. AutoJSContext will take care of finding the most
    350 * appropriate JS context and release it when leaving the stack.
    351 */
    352 class MOZ_RAII AutoJSContext {
    353 public:
    354  explicit AutoJSContext();
    355  operator JSContext*() const;
    356 
    357 protected:
    358  JSContext* mCx;
    359  dom::AutoJSAPI mJSAPI;
    360 };
    361 
    362 /**
    363 * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
    364 * JS context. That means it will never call
    365 * nsContentUtils::GetCurrentJSContext().
    366 *
    367 * Note - This is deprecated. Please use AutoJSAPI instead.
    368 */
    369 class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
    370 public:
    371  explicit AutoSafeJSContext();
    372  operator JSContext*() const { return cx(); }
    373 
    374 private:
    375 };
    376 
    377 /**
    378 * Use AutoSlowOperation when native side calls many JS callbacks in a row
    379 * and slow script dialog should be activated if too much time is spent going
    380 * through those callbacks.
    381 * AutoSlowOperation puts an AutoScriptActivity on the stack so that we don't
    382 * continue to reset the watchdog. CheckForInterrupt can then be used to check
    383 * whether JS execution should be interrupted.
    384 * This class (including CheckForInterrupt) is a no-op when used off the main
    385 * thread.
    386 */
    387 class MOZ_RAII AutoSlowOperation {
    388 public:
    389  explicit AutoSlowOperation();
    390  void CheckForInterrupt();
    391 
    392 private:
    393  bool mIsMainThread;
    394  Maybe<xpc::AutoScriptActivity> mScriptActivity;
    395 };
    396 
    397 /**
    398 * A class to disable interrupt callback temporary.
    399 */
    400 class MOZ_RAII AutoDisableJSInterruptCallback {
    401 public:
    402  explicit AutoDisableJSInterruptCallback(JSContext* aCx)
    403      : mCx(aCx), mOld(JS_DisableInterruptCallback(aCx)) {}
    404 
    405  ~AutoDisableJSInterruptCallback() { JS_ResetInterruptCallback(mCx, mOld); }
    406 
    407 private:
    408  JSContext* mCx;
    409  bool mOld;
    410 };
    411 
    412 /**
    413 * A helper class which allows to allow-list legacy callers executing script
    414 * in the AutoEntryScript constructor. The goal is to remove these exceptions
    415 * one by one. Do not add a new one without review from a DOM peer.
    416 */
    417 class MOZ_RAII AutoAllowLegacyScriptExecution {
    418 public:
    419  AutoAllowLegacyScriptExecution();
    420  ~AutoAllowLegacyScriptExecution();
    421 
    422  static bool IsAllowed();
    423 
    424 private:
    425  static int sAutoAllowLegacyScriptExecution;
    426 };
    427 
    428 }  // namespace mozilla
    429 
    430 #endif  // mozilla_dom_ScriptSettings_h