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