Line data Source code
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 "MainThreadUtils.h"
13 : #include "nsIGlobalObject.h"
14 : #include "nsIPrincipal.h"
15 :
16 : #include "mozilla/Maybe.h"
17 :
18 : #include "jsapi.h"
19 : #include "js/Debug.h"
20 :
21 : class nsPIDOMWindowInner;
22 : class nsGlobalWindow;
23 : class nsIScriptContext;
24 : class nsIDocument;
25 : class nsIDocShell;
26 :
27 : namespace mozilla {
28 : namespace dom {
29 :
30 : /*
31 : * System-wide setup/teardown routines. Init and Destroy should be invoked
32 : * once each, at startup and shutdown (respectively).
33 : */
34 : void InitScriptSettings();
35 : void DestroyScriptSettings();
36 : bool ScriptSettingsInitialized();
37 :
38 : /*
39 : * Static helpers in ScriptSettings which track the number of listeners
40 : * of Javascript RunToCompletion events. These should be used by the code in
41 : * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
42 : * settings that script run-to-completion needs to be monitored.
43 : * SHOULD BE CALLED ONLY BY MAIN THREAD.
44 : */
45 : void UseEntryScriptProfiling();
46 : void UnuseEntryScriptProfiling();
47 :
48 : // To implement a web-compatible browser, it is often necessary to obtain the
49 : // global object that is "associated" with the currently-running code. This
50 : // process is made more complicated by the fact that, historically, different
51 : // algorithms have operated with different definitions of the "associated"
52 : // global.
53 : //
54 : // HTML5 formalizes this into two concepts: the "incumbent global" and the
55 : // "entry global". The incumbent global corresponds to the global of the
56 : // current script being executed, whereas the entry global corresponds to the
57 : // global of the script where the current JS execution began.
58 : //
59 : // There is also a potentially-distinct third global that is determined by the
60 : // current compartment. This roughly corresponds with the notion of Realms in
61 : // ECMAScript.
62 : //
63 : // Suppose some event triggers an event listener in window |A|, which invokes a
64 : // scripted function in window |B|, which invokes the |window.location.href|
65 : // setter in window |C|. The entry global would be |A|, the incumbent global
66 : // would be |B|, and the current compartment would be that of |C|.
67 : //
68 : // In general, it's best to use to use the most-closely-associated global
69 : // unless the spec says to do otherwise. In 95% of the cases, the global of
70 : // the current compartment (GetCurrentGlobal()) is the right thing. For
71 : // example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
72 : // the global of the current compartment (i.e. |C|).
73 : //
74 : // The incumbent global is very similar, but differs in a few edge cases. For
75 : // example, if window |B| does |C.location.href = "..."|, the incumbent global
76 : // used for the navigation algorithm is B, because no script from |C| was ever run.
77 : //
78 : // The entry global is used for various things like computing base URIs, mostly
79 : // for historical reasons.
80 : //
81 : // Note that all of these functions return bonafide global objects. This means
82 : // that, for Windows, they always return the inner.
83 :
84 : // Returns the global associated with the top-most Candidate Entry Point on
85 : // the Script Settings Stack. See the HTML spec. This may be null.
86 : nsIGlobalObject* GetEntryGlobal();
87 :
88 : // If the entry global is a window, returns its extant document. Otherwise,
89 : // returns null.
90 : nsIDocument* GetEntryDocument();
91 :
92 : // Returns the global associated with the top-most entry of the the Script
93 : // Settings Stack. See the HTML spec. This may be null.
94 : nsIGlobalObject* GetIncumbentGlobal();
95 :
96 : // Returns the global associated with the current compartment. This may be null.
97 : nsIGlobalObject* GetCurrentGlobal();
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 : // This may be used by callers that know that their incumbent global is non-
118 : // null (i.e. they know there have been no System Caller pushes since the
119 : // inner-most script execution).
120 : inline JSObject& IncumbentJSGlobal()
121 : {
122 : return *GetIncumbentGlobal()->GetGlobalJSObject();
123 : }
124 :
125 : // Returns whether JSAPI is active right now. If it is not, working with a
126 : // JSContext you grab from somewhere random is not OK and you should be doing
127 : // AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
128 : bool IsJSAPIActive();
129 :
130 : namespace danger {
131 :
132 : // Get the JSContext for this thread. This is in the "danger" namespace because
133 : // we generally want people using AutoJSAPI instead, unless they really know
134 : // what they're doing.
135 : JSContext* GetJSContext();
136 :
137 : } // namespace danger
138 :
139 : JS::RootingContext* RootingCx();
140 :
141 : class ScriptSettingsStack;
142 : class ScriptSettingsStackEntry {
143 : friend class ScriptSettingsStack;
144 :
145 : public:
146 : ~ScriptSettingsStackEntry();
147 :
148 205713 : bool NoJSAPI() const { return mType == eNoJSAPI; }
149 5 : bool IsEntryCandidate() const {
150 5 : return mType == eEntryScript || mType == eNoJSAPI;
151 : }
152 133788 : bool IsIncumbentCandidate() { return mType != eJSAPI; }
153 42306 : bool IsIncumbentScript() { return mType == eIncumbentScript; }
154 :
155 : protected:
156 : enum Type {
157 : eEntryScript,
158 : eIncumbentScript,
159 : eJSAPI,
160 : eNoJSAPI
161 : };
162 :
163 : ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
164 : Type aEntryType);
165 :
166 : nsCOMPtr<nsIGlobalObject> mGlobalObject;
167 : Type mType;
168 :
169 : private:
170 : ScriptSettingsStackEntry *mOlder;
171 : };
172 :
173 : /*
174 : * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
175 : * must be on the stack.
176 : *
177 : * This base class should be instantiated as-is when the caller wants to use
178 : * JSAPI but doesn't expect to run script. The caller must then call one of its
179 : * Init functions before being able to access the JSContext through cx().
180 : * Its current duties are as-follows (see individual Init comments for details):
181 : *
182 : * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
183 : * the JSContext stack.
184 : * * Entering an initial (possibly null) compartment, to ensure that the
185 : * previously entered compartment for that JSContext is not used by mistake.
186 : * * Reporting any exceptions left on the JSRuntime, unless the caller steals
187 : * or silences them.
188 : * * On main thread, entering a JSAutoRequest.
189 : *
190 : * Additionally, the following duties are planned, but not yet implemented:
191 : *
192 : * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
193 : * implementing the poisoning first. For now, this de-poisoning
194 : * effectively corresponds to having a non-null cx on the stack.
195 : *
196 : * In situations where the consumer expects to run script, AutoEntryScript
197 : * should be used, which does additional manipulation of the script settings
198 : * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
199 : * any attempt to run script without an AutoEntryScript on the stack will
200 : * fail. This prevents system code from accidentally triggering script
201 : * execution at inopportune moments via surreptitious getters and proxies.
202 : */
203 : class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
204 : public:
205 : // Trivial constructor. One of the Init functions must be called before
206 : // accessing the JSContext through cx().
207 : AutoJSAPI();
208 :
209 : ~AutoJSAPI();
210 :
211 : // This uses the SafeJSContext (or worker equivalent), and enters a null
212 : // compartment, so that the consumer is forced to select a compartment to
213 : // enter before manipulating objects.
214 : //
215 : // This variant will ensure that any errors reported by this AutoJSAPI as it
216 : // comes off the stack will not fire error events or be associated with any
217 : // particular web-visible global.
218 : void Init();
219 :
220 : // This uses the SafeJSContext (or worker equivalent), and enters the
221 : // 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 : //
225 : // If aGlobalObject represents a web-visible global, errors reported by this
226 : // AutoJSAPI as it comes off the stack will fire the relevant error events and
227 : // show up in the corresponding web console.
228 : MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
229 :
230 : // This is a helper that grabs the native global associated with aObject and
231 : // invokes the above Init() with that.
232 : MOZ_MUST_USE bool Init(JSObject* aObject);
233 :
234 : // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
235 : // If aGlobalObject or its associated JS global are null then it returns
236 : // false and use of cx() will cause an assertion.
237 : // If aCx is null it will cause an assertion.
238 : //
239 : // If aGlobalObject represents a web-visible global, errors reported by this
240 : // AutoJSAPI as it comes off the stack will fire the relevant error events and
241 : // show up in the corresponding web console.
242 : MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
243 :
244 : // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*,
245 : // when it is more easily available than an nsIGlobalObject.
246 : MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
247 : MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
248 :
249 : MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow);
250 : MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
251 :
252 29639 : JSContext* cx() const {
253 29639 : MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
254 29639 : MOZ_ASSERT(IsStackTop());
255 29639 : return mCx;
256 : }
257 :
258 : #ifdef DEBUG
259 : bool IsStackTop() const;
260 : #endif
261 :
262 : // If HasException, report it. Otherwise, a no-op.
263 : void ReportException();
264 :
265 7711 : bool HasException() const {
266 7711 : MOZ_ASSERT(IsStackTop());
267 7711 : return JS_IsExceptionPending(cx());
268 : };
269 :
270 : // Transfers ownership of the current exception from the JS engine to the
271 : // caller. Callers must ensure that HasException() is true, and that cx()
272 : // is in a non-null compartment.
273 : //
274 : // Note that this fails if and only if we OOM while wrapping the exception
275 : // into the current compartment.
276 : MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
277 :
278 : // Peek the current exception from the JS engine, without stealing it.
279 : // Callers must ensure that HasException() is true, and that cx() is in a
280 : // non-null compartment.
281 : //
282 : // Note that this fails if and only if we OOM while wrapping the exception
283 : // into the current compartment.
284 : MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
285 :
286 4 : void ClearException() {
287 4 : MOZ_ASSERT(IsStackTop());
288 4 : JS_ClearPendingException(cx());
289 4 : }
290 :
291 : protected:
292 : // Protected constructor for subclasses. This constructor initialises the
293 : // AutoJSAPI, so Init must NOT be called on subclasses that use this.
294 : AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
295 :
296 : private:
297 : mozilla::Maybe<JSAutoRequest> mAutoRequest;
298 : mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
299 : JSContext *mCx;
300 :
301 : // Whether we're mainthread or not; set when we're initialized.
302 : bool mIsMainThread;
303 : Maybe<JS::WarningReporter> mOldWarningReporter;
304 :
305 : void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
306 : JSContext* aCx, bool aIsMainThread);
307 :
308 : AutoJSAPI(const AutoJSAPI&) = delete;
309 : AutoJSAPI& operator= (const AutoJSAPI&) = delete;
310 : };
311 :
312 : /*
313 : * A class that represents a new script entry point.
314 : *
315 : * |aReason| should be a statically-allocated C string naming the reason we're
316 : * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use
317 : * these strings to label JS execution in timeline and profiling displays.
318 : */
319 : class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
320 : public:
321 : AutoEntryScript(nsIGlobalObject* aGlobalObject,
322 : const char *aReason,
323 47 : bool aIsMainThread = NS_IsMainThread());
324 :
325 : AutoEntryScript(JSObject* aObject, // Any object from the relevant global
326 : const char *aReason,
327 601 : bool aIsMainThread = NS_IsMainThread());
328 :
329 : ~AutoEntryScript();
330 :
331 294 : void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
332 294 : mWebIDLCallerPrincipal = aPrincipal;
333 294 : }
334 :
335 : private:
336 : // A subclass of AutoEntryMonitor that notifies the docshell.
337 0 : class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor
338 : {
339 : public:
340 : DocshellEntryMonitor(JSContext* aCx, const char* aReason);
341 :
342 : // Please note that |aAsyncCause| here is owned by the caller, and its
343 : // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
344 : // In practice, |aAsyncCause| is identical to |aReason| passed into
345 : // the AutoEntryScript constructor, so the lifetime requirements are
346 : // trivially satisfied by |aReason| being a statically allocated string.
347 0 : void Entry(JSContext* aCx, JSFunction* aFunction,
348 : JS::Handle<JS::Value> aAsyncStack,
349 : const char* aAsyncCause) override
350 : {
351 0 : Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
352 0 : }
353 :
354 0 : void Entry(JSContext* aCx, JSScript* aScript,
355 : JS::Handle<JS::Value> aAsyncStack,
356 : const char* aAsyncCause) override
357 : {
358 0 : Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
359 0 : }
360 :
361 : void Exit(JSContext* aCx) override;
362 :
363 : private:
364 : void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
365 : JS::Handle<JS::Value> aAsyncStack,
366 : const char* aAsyncCause);
367 :
368 : const char* mReason;
369 : };
370 :
371 : // It's safe to make this a weak pointer, since it's the subject principal
372 : // when we go on the stack, so can't go away until after we're gone. In
373 : // particular, this is only used from the CallSetup constructor, and only in
374 : // the aIsJSImplementedWebIDL case. And in that case, the subject principal
375 : // is the principal of the callee function that is part of the CallArgs just a
376 : // bit up the stack, and which will outlive us. So we know the principal
377 : // can't go away until then either.
378 : nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
379 : friend nsIPrincipal* GetWebIDLCallerPrincipal();
380 :
381 : Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
382 : JS::AutoHideScriptedCaller mCallerOverride;
383 : };
384 :
385 : /*
386 : * A class that can be used to force a particular incumbent script on the stack.
387 : */
388 : class AutoIncumbentScript : protected ScriptSettingsStackEntry {
389 : public:
390 : explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
391 : ~AutoIncumbentScript();
392 :
393 : private:
394 : JS::AutoHideScriptedCaller mCallerOverride;
395 : };
396 :
397 : /*
398 : * A class to put the JS engine in an unusable state. The subject principal
399 : * will become System, the information on the script settings stack is
400 : * rendered inaccessible, and JSAPI may not be manipulated until the class is
401 : * either popped or an AutoJSAPI instance is subsequently pushed.
402 : *
403 : * This class may not be instantiated if an exception is pending.
404 : */
405 : class AutoNoJSAPI : protected ScriptSettingsStackEntry {
406 : public:
407 : explicit AutoNoJSAPI();
408 : ~AutoNoJSAPI();
409 : };
410 :
411 : } // namespace dom
412 :
413 : /**
414 : * Use AutoJSContext when you need a JS context on the stack but don't have one
415 : * passed as a parameter. AutoJSContext will take care of finding the most
416 : * appropriate JS context and release it when leaving the stack.
417 : */
418 119136 : class MOZ_RAII AutoJSContext {
419 : public:
420 : explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
421 : operator JSContext*() const;
422 :
423 : protected:
424 : JSContext* mCx;
425 : dom::AutoJSAPI mJSAPI;
426 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
427 : };
428 :
429 : /**
430 : * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
431 : * JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
432 : *
433 : * Note - This is deprecated. Please use AutoJSAPI instead.
434 : */
435 44 : class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
436 : public:
437 : explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
438 150 : operator JSContext*() const
439 : {
440 150 : return cx();
441 : }
442 :
443 : private:
444 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
445 : };
446 :
447 : /**
448 : * Use AutoSlowOperation when native side calls many JS callbacks in a row
449 : * and slow script dialog should be activated if too much time is spent going
450 : * through those callbacks.
451 : * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue
452 : * to reset the watchdog and CheckForInterrupt can be then used to check whether
453 : * JS execution should be interrupted.
454 : */
455 25 : class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI
456 : {
457 : public:
458 : explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
459 : void CheckForInterrupt();
460 : private:
461 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
462 : };
463 :
464 : } // namespace mozilla
465 :
466 : #endif // mozilla_dom_ScriptSettings_h
|