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