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