/src/mozilla-central/dom/script/ScriptSettings.h
Line | Count | Source (jump to first uncovered line) |
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 | | #include "xpcpublic.h" |
16 | | |
17 | | #include "mozilla/Maybe.h" |
18 | | |
19 | | #include "jsapi.h" |
20 | | #include "js/Debug.h" |
21 | | |
22 | | class nsPIDOMWindowInner; |
23 | | class nsGlobalWindowInner; |
24 | | class nsIScriptContext; |
25 | | class nsIDocument; |
26 | | class nsIDocShell; |
27 | | |
28 | | namespace mozilla { |
29 | | namespace dom { |
30 | | |
31 | | /* |
32 | | * System-wide setup/teardown routines. Init and Destroy should be invoked |
33 | | * once each, at startup and shutdown (respectively). |
34 | | */ |
35 | | void InitScriptSettings(); |
36 | | void DestroyScriptSettings(); |
37 | | bool ScriptSettingsInitialized(); |
38 | | |
39 | | /* |
40 | | * Static helpers in ScriptSettings which track the number of listeners |
41 | | * of Javascript RunToCompletion events. These should be used by the code in |
42 | | * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script |
43 | | * settings that script run-to-completion needs to be monitored. |
44 | | * SHOULD BE CALLED ONLY BY MAIN THREAD. |
45 | | */ |
46 | | void UseEntryScriptProfiling(); |
47 | | void UnuseEntryScriptProfiling(); |
48 | | |
49 | | // To implement a web-compatible browser, it is often necessary to obtain the |
50 | | // global object that is "associated" with the currently-running code. This |
51 | | // process is made more complicated by the fact that, historically, different |
52 | | // algorithms have operated with different definitions of the "associated" |
53 | | // global. |
54 | | // |
55 | | // HTML5 formalizes this into two concepts: the "incumbent global" and the |
56 | | // "entry global". The incumbent global corresponds to the global of the |
57 | | // current script being executed, whereas the entry global corresponds to the |
58 | | // global of the script where the current JS execution began. |
59 | | // |
60 | | // There is also a potentially-distinct third global that is determined by the |
61 | | // current compartment. This roughly corresponds with the notion of Realms in |
62 | | // ECMAScript. |
63 | | // |
64 | | // Suppose some event triggers an event listener in window |A|, which invokes a |
65 | | // scripted function in window |B|, which invokes the |window.location.href| |
66 | | // setter in window |C|. The entry global would be |A|, the incumbent global |
67 | | // would be |B|, and the current compartment would be that of |C|. |
68 | | // |
69 | | // In general, it's best to use to use the most-closely-associated global |
70 | | // unless the spec says to do otherwise. In 95% of the cases, the global of |
71 | | // the current compartment (GetCurrentGlobal()) is the right thing. For |
72 | | // example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with |
73 | | // the global of the current compartment (i.e. |C|). |
74 | | // |
75 | | // The incumbent global is very similar, but differs in a few edge cases. For |
76 | | // example, if window |B| does |C.location.href = "..."|, the incumbent global |
77 | | // used for the navigation algorithm is B, because no script from |C| was ever run. |
78 | | // |
79 | | // The entry global is used for various things like computing base URIs, mostly |
80 | | // for historical reasons. |
81 | | // |
82 | | // Note that all of these functions return bonafide global objects. This means |
83 | | // that, for Windows, they always return the inner. |
84 | | |
85 | | // Returns the global associated with the top-most Candidate Entry Point on |
86 | | // the Script Settings Stack. See the HTML spec. This may be null. |
87 | | nsIGlobalObject* GetEntryGlobal(); |
88 | | |
89 | | // If the entry global is a window, returns its extant document. Otherwise, |
90 | | // returns null. |
91 | | nsIDocument* GetEntryDocument(); |
92 | | |
93 | | // Returns the global associated with the top-most entry of the the Script |
94 | | // Settings Stack. See the HTML spec. This may be null. |
95 | | nsIGlobalObject* GetIncumbentGlobal(); |
96 | | |
97 | | // Returns the global associated with the current compartment. This may be null. |
98 | | nsIGlobalObject* GetCurrentGlobal(); |
99 | | |
100 | | // JS-implemented WebIDL presents an interesting situation with respect to the |
101 | | // subject principal. A regular C++-implemented API can simply examine the |
102 | | // compartment of the most-recently-executed script, and use that to infer the |
103 | | // responsible party. However, JS-implemented APIs are run with system |
104 | | // principal, and thus clobber the subject principal of the script that |
105 | | // invoked the API. So we have to do some extra work to keep track of this |
106 | | // information. |
107 | | // |
108 | | // We therefore implement the following behavior: |
109 | | // * Each Script Settings Object has an optional WebIDL Caller Principal field. |
110 | | // This defaults to null. |
111 | | // * When we push an Entry Point in preparation to run a JS-implemented WebIDL |
112 | | // callback, we grab the subject principal at the time of invocation, and |
113 | | // store that as the WebIDL Caller Principal. |
114 | | // * When non-null, callers can query this principal from script via an API on |
115 | | // Components.utils. |
116 | | nsIPrincipal* GetWebIDLCallerPrincipal(); |
117 | | |
118 | | // This may be used by callers that know that their incumbent global is non- |
119 | | // null (i.e. they know there have been no System Caller pushes since the |
120 | | // inner-most script execution). |
121 | | inline JSObject& IncumbentJSGlobal() |
122 | | { |
123 | | return *GetIncumbentGlobal()->GetGlobalJSObject(); |
124 | | } |
125 | | |
126 | | // Returns whether JSAPI is active right now. If it is not, working with a |
127 | | // JSContext you grab from somewhere random is not OK and you should be doing |
128 | | // AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext. |
129 | | bool IsJSAPIActive(); |
130 | | |
131 | | namespace danger { |
132 | | |
133 | | // Get the JSContext for this thread. This is in the "danger" namespace because |
134 | | // we generally want people using AutoJSAPI instead, unless they really know |
135 | | // what they're doing. |
136 | | JSContext* GetJSContext(); |
137 | | |
138 | | } // namespace danger |
139 | | |
140 | | JS::RootingContext* RootingCx(); |
141 | | |
142 | | class ScriptSettingsStack; |
143 | | class ScriptSettingsStackEntry { |
144 | | friend class ScriptSettingsStack; |
145 | | |
146 | | public: |
147 | | ~ScriptSettingsStackEntry(); |
148 | | |
149 | 60.0M | bool NoJSAPI() const { return mType == eNoJSAPI; } |
150 | 0 | bool IsEntryCandidate() const { |
151 | 0 | return mType == eEntryScript || mType == eNoJSAPI; |
152 | 0 | } |
153 | 0 | bool IsIncumbentCandidate() { return mType != eJSAPI; } |
154 | | bool IsIncumbentScript() { return mType == eIncumbentScript; } |
155 | | |
156 | | protected: |
157 | | enum Type { |
158 | | eEntryScript, |
159 | | eIncumbentScript, |
160 | | eJSAPI, |
161 | | eNoJSAPI |
162 | | }; |
163 | | |
164 | | ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, |
165 | | Type aEntryType); |
166 | | |
167 | | nsCOMPtr<nsIGlobalObject> mGlobalObject; |
168 | | Type mType; |
169 | | |
170 | | private: |
171 | | ScriptSettingsStackEntry *mOlder; |
172 | | }; |
173 | | |
174 | | /* |
175 | | * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses) |
176 | | * must be on the stack. |
177 | | * |
178 | | * This base class should be instantiated as-is when the caller wants to use |
179 | | * JSAPI but doesn't expect to run script. The caller must then call one of its |
180 | | * Init functions before being able to access the JSContext through cx(). |
181 | | * Its current duties are as-follows (see individual Init comments for details): |
182 | | * |
183 | | * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto |
184 | | * the JSContext stack. |
185 | | * * Entering an initial (possibly null) compartment, to ensure that the |
186 | | * previously entered compartment for that JSContext is not used by mistake. |
187 | | * * Reporting any exceptions left on the JSRuntime, unless the caller steals |
188 | | * or silences them. |
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. aObject must not be a cross-compartment |
232 | | // wrapper: CCWs are not associated with a single global. |
233 | | MOZ_MUST_USE bool Init(JSObject* aObject); |
234 | | |
235 | | // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject. |
236 | | // If aGlobalObject or its associated JS global are null then it returns |
237 | | // false and use of cx() will cause an assertion. |
238 | | // If aCx is null it will cause an assertion. |
239 | | // |
240 | | // If aGlobalObject represents a web-visible global, errors reported by this |
241 | | // AutoJSAPI as it comes off the stack will fire the relevant error events and |
242 | | // show up in the corresponding web console. |
243 | | MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx); |
244 | | |
245 | | // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*, |
246 | | // when it is more easily available than an nsIGlobalObject. |
247 | | MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow); |
248 | | MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx); |
249 | | |
250 | | MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow); |
251 | | MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow, JSContext* aCx); |
252 | | |
253 | | JSContext* cx() const { |
254 | | MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI"); |
255 | | MOZ_ASSERT(IsStackTop()); |
256 | | return mCx; |
257 | | } |
258 | | |
259 | | #ifdef DEBUG |
260 | | bool IsStackTop() const; |
261 | | #endif |
262 | | |
263 | | // If HasException, report it. Otherwise, a no-op. |
264 | | void ReportException(); |
265 | | |
266 | | bool HasException() const { |
267 | | MOZ_ASSERT(IsStackTop()); |
268 | | return JS_IsExceptionPending(cx()); |
269 | | }; |
270 | | |
271 | | // Transfers ownership of the current exception from the JS engine to the |
272 | | // caller. Callers must ensure that HasException() is true, and that cx() |
273 | | // is in a 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 | | MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal); |
278 | | |
279 | | // Peek the current exception from the JS engine, without stealing it. |
280 | | // Callers must ensure that HasException() is true, and that cx() is in a |
281 | | // non-null compartment. |
282 | | // |
283 | | // Note that this fails if and only if we OOM while wrapping the exception |
284 | | // into the current compartment. |
285 | | MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal); |
286 | | |
287 | | void ClearException() { |
288 | | MOZ_ASSERT(IsStackTop()); |
289 | | JS_ClearPendingException(cx()); |
290 | | } |
291 | | |
292 | | protected: |
293 | | // Protected constructor for subclasses. This constructor initialises the |
294 | | // AutoJSAPI, so Init must NOT be called on subclasses that use this. |
295 | | AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType); |
296 | | |
297 | | mozilla::Maybe<JSAutoNullableRealm> mAutoNullableRealm; |
298 | | JSContext *mCx; |
299 | | |
300 | | // Whether we're mainthread or not; set when we're initialized. |
301 | | bool mIsMainThread; |
302 | | Maybe<JS::WarningReporter> mOldWarningReporter; |
303 | | |
304 | | private: |
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 | | bool aIsMainThread = NS_IsMainThread()); |
324 | | |
325 | | // aObject can be any object from the relevant global. It must not be a |
326 | | // cross-compartment wrapper because CCWs are not associated with a single |
327 | | // global. |
328 | | AutoEntryScript(JSObject* aObject, |
329 | | const char *aReason, |
330 | | bool aIsMainThread = NS_IsMainThread()); |
331 | | |
332 | | ~AutoEntryScript(); |
333 | | |
334 | | void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { |
335 | | mWebIDLCallerPrincipal = aPrincipal; |
336 | | } |
337 | | |
338 | | private: |
339 | | // A subclass of AutoEntryMonitor that notifies the docshell. |
340 | | class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor |
341 | | { |
342 | | public: |
343 | | DocshellEntryMonitor(JSContext* aCx, const char* aReason); |
344 | | |
345 | | // Please note that |aAsyncCause| here is owned by the caller, and its |
346 | | // lifetime must outlive the lifetime of the DocshellEntryMonitor object. |
347 | | // In practice, |aAsyncCause| is identical to |aReason| passed into |
348 | | // the AutoEntryScript constructor, so the lifetime requirements are |
349 | | // trivially satisfied by |aReason| being a statically allocated string. |
350 | | void Entry(JSContext* aCx, JSFunction* aFunction, |
351 | | JS::Handle<JS::Value> aAsyncStack, |
352 | | const char* aAsyncCause) override |
353 | 0 | { |
354 | 0 | Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause); |
355 | 0 | } |
356 | | |
357 | | void Entry(JSContext* aCx, JSScript* aScript, |
358 | | JS::Handle<JS::Value> aAsyncStack, |
359 | | const char* aAsyncCause) override |
360 | 0 | { |
361 | 0 | Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause); |
362 | 0 | } |
363 | | |
364 | | void Exit(JSContext* aCx) override; |
365 | | |
366 | | private: |
367 | | void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript, |
368 | | JS::Handle<JS::Value> aAsyncStack, |
369 | | const char* aAsyncCause); |
370 | | |
371 | | const char* mReason; |
372 | | }; |
373 | | |
374 | | // It's safe to make this a weak pointer, since it's the subject principal |
375 | | // when we go on the stack, so can't go away until after we're gone. In |
376 | | // particular, this is only used from the CallSetup constructor, and only in |
377 | | // the aIsJSImplementedWebIDL case. And in that case, the subject principal |
378 | | // is the principal of the callee function that is part of the CallArgs just a |
379 | | // bit up the stack, and which will outlive us. So we know the principal |
380 | | // can't go away until then either. |
381 | | nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; |
382 | | friend nsIPrincipal* GetWebIDLCallerPrincipal(); |
383 | | |
384 | | Maybe<DocshellEntryMonitor> mDocShellEntryMonitor; |
385 | | Maybe<xpc::AutoScriptActivity> mScriptActivity; |
386 | | JS::AutoHideScriptedCaller mCallerOverride; |
387 | | #ifdef MOZ_GECKO_PROFILER |
388 | | AutoProfilerLabel mAutoProfilerLabel; |
389 | | #endif |
390 | | }; |
391 | | |
392 | | /* |
393 | | * A class that can be used to force a particular incumbent script on the stack. |
394 | | */ |
395 | | class AutoIncumbentScript : protected ScriptSettingsStackEntry { |
396 | | public: |
397 | | explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject); |
398 | | ~AutoIncumbentScript(); |
399 | | |
400 | | private: |
401 | | JS::AutoHideScriptedCaller mCallerOverride; |
402 | | }; |
403 | | |
404 | | /* |
405 | | * A class to put the JS engine in an unusable state. The subject principal |
406 | | * will become System, the information on the script settings stack is |
407 | | * rendered inaccessible, and JSAPI may not be manipulated until the class is |
408 | | * either popped or an AutoJSAPI instance is subsequently pushed. |
409 | | * |
410 | | * This class may not be instantiated if an exception is pending. |
411 | | */ |
412 | | class AutoNoJSAPI : protected ScriptSettingsStackEntry { |
413 | | public: |
414 | | explicit AutoNoJSAPI(); |
415 | | ~AutoNoJSAPI(); |
416 | | }; |
417 | | |
418 | | } // namespace dom |
419 | | |
420 | | /** |
421 | | * Use AutoJSContext when you need a JS context on the stack but don't have one |
422 | | * passed as a parameter. AutoJSContext will take care of finding the most |
423 | | * appropriate JS context and release it when leaving the stack. |
424 | | */ |
425 | | class MOZ_RAII AutoJSContext { |
426 | | public: |
427 | | explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); |
428 | | operator JSContext*() const; |
429 | | |
430 | | protected: |
431 | | JSContext* mCx; |
432 | | dom::AutoJSAPI mJSAPI; |
433 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
434 | | }; |
435 | | |
436 | | /** |
437 | | * AutoSafeJSContext is similar to AutoJSContext but will only return the safe |
438 | | * JS context. That means it will never call nsContentUtils::GetCurrentJSContext(). |
439 | | * |
440 | | * Note - This is deprecated. Please use AutoJSAPI instead. |
441 | | */ |
442 | | class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI { |
443 | | public: |
444 | | explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); |
445 | | operator JSContext*() const |
446 | | { |
447 | | return cx(); |
448 | | } |
449 | | |
450 | | private: |
451 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
452 | | }; |
453 | | |
454 | | /** |
455 | | * Use AutoSlowOperation when native side calls many JS callbacks in a row |
456 | | * and slow script dialog should be activated if too much time is spent going |
457 | | * through those callbacks. |
458 | | * AutoSlowOperation puts an AutoScriptActivity on the stack so that we don't |
459 | | * continue to reset the watchdog. CheckForInterrupt can then be used to check |
460 | | * whether JS execution should be interrupted. |
461 | | * This class (including CheckForInterrupt) is a no-op when used off the main |
462 | | * thread. |
463 | | */ |
464 | | class MOZ_RAII AutoSlowOperation |
465 | | { |
466 | | public: |
467 | | explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); |
468 | | void CheckForInterrupt(); |
469 | | private: |
470 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
471 | | bool mIsMainThread; |
472 | | Maybe<xpc::AutoScriptActivity> mScriptActivity; |
473 | | }; |
474 | | |
475 | | /** |
476 | | * A class to disable interrupt callback temporary. |
477 | | */ |
478 | | class MOZ_RAII AutoDisableJSInterruptCallback |
479 | | { |
480 | | public: |
481 | | explicit AutoDisableJSInterruptCallback(JSContext* aCx) |
482 | | : mCx(aCx) |
483 | | , mOld(JS_DisableInterruptCallback(aCx)) |
484 | | { } |
485 | | |
486 | | ~AutoDisableJSInterruptCallback() { |
487 | | JS_ResetInterruptCallback(mCx, mOld); |
488 | | } |
489 | | |
490 | | private: |
491 | | JSContext* mCx; |
492 | | bool mOld; |
493 | | }; |
494 | | |
495 | | } // namespace mozilla |
496 | | |
497 | | #endif // mozilla_dom_ScriptSettings_h |