|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 // vim: ft=cpp tw=78 sw=2 et ts=2 |
|
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 "nsCxPusher.h" |
|
13 #include "MainThreadUtils.h" |
|
14 #include "nsIGlobalObject.h" |
|
15 #include "nsIPrincipal.h" |
|
16 |
|
17 #include "mozilla/Maybe.h" |
|
18 |
|
19 class nsIGlobalObject; |
|
20 |
|
21 namespace mozilla { |
|
22 namespace dom { |
|
23 |
|
24 /* |
|
25 * System-wide setup/teardown routines. Init and Destroy should be invoked |
|
26 * once each, at startup and shutdown (respectively). |
|
27 */ |
|
28 void InitScriptSettings(); |
|
29 void DestroyScriptSettings(); |
|
30 |
|
31 // This mostly gets the entry global, but doesn't entirely match the spec in |
|
32 // certain edge cases. It's good enough for some purposes, but not others. If |
|
33 // you want to call this function, ping bholley and describe your use-case. |
|
34 nsIGlobalObject* BrokenGetEntryGlobal(); |
|
35 |
|
36 // Note: We don't yet expose GetEntryGlobal, because in order for it to be |
|
37 // correct, we first need to replace a bunch of explicit cx pushing in the |
|
38 // browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it |
|
39 // can mostly be inferred from the JS stack. |
|
40 nsIGlobalObject* GetIncumbentGlobal(); |
|
41 |
|
42 // JS-implemented WebIDL presents an interesting situation with respect to the |
|
43 // subject principal. A regular C++-implemented API can simply examine the |
|
44 // compartment of the most-recently-executed script, and use that to infer the |
|
45 // responsible party. However, JS-implemented APIs are run with system |
|
46 // principal, and thus clobber the subject principal of the script that |
|
47 // invoked the API. So we have to do some extra work to keep track of this |
|
48 // information. |
|
49 // |
|
50 // We therefore implement the following behavior: |
|
51 // * Each Script Settings Object has an optional WebIDL Caller Principal field. |
|
52 // This defaults to null. |
|
53 // * When we push an Entry Point in preparation to run a JS-implemented WebIDL |
|
54 // callback, we grab the subject principal at the time of invocation, and |
|
55 // store that as the WebIDL Caller Principal. |
|
56 // * When non-null, callers can query this principal from script via an API on |
|
57 // Components.utils. |
|
58 nsIPrincipal* GetWebIDLCallerPrincipal(); |
|
59 |
|
60 // This may be used by callers that know that their incumbent global is non- |
|
61 // null (i.e. they know there have been no System Caller pushes since the |
|
62 // inner-most script execution). |
|
63 inline JSObject& IncumbentJSGlobal() |
|
64 { |
|
65 return *GetIncumbentGlobal()->GetGlobalJSObject(); |
|
66 } |
|
67 |
|
68 class ScriptSettingsStack; |
|
69 struct ScriptSettingsStackEntry { |
|
70 nsCOMPtr<nsIGlobalObject> mGlobalObject; |
|
71 bool mIsCandidateEntryPoint; |
|
72 |
|
73 ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate) |
|
74 : mGlobalObject(aGlobal) |
|
75 , mIsCandidateEntryPoint(aCandidate) |
|
76 { |
|
77 MOZ_ASSERT(mGlobalObject); |
|
78 MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(), |
|
79 "Must have an actual JS global for the duration on the stack"); |
|
80 MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()), |
|
81 "No outer windows allowed"); |
|
82 } |
|
83 |
|
84 ~ScriptSettingsStackEntry() { |
|
85 // We must have an actual JS global for the entire time this is on the stack. |
|
86 MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject()); |
|
87 } |
|
88 |
|
89 bool NoJSAPI() { return this == &NoJSAPISingleton; } |
|
90 static ScriptSettingsStackEntry NoJSAPISingleton; |
|
91 |
|
92 private: |
|
93 ScriptSettingsStackEntry() : mGlobalObject(nullptr) |
|
94 , mIsCandidateEntryPoint(true) |
|
95 {} |
|
96 }; |
|
97 |
|
98 /* |
|
99 * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses) |
|
100 * must be on the stack. |
|
101 * |
|
102 * This base class should be instantiated as-is when the caller wants to use |
|
103 * JSAPI but doesn't expect to run script. Its current duties are as-follows: |
|
104 * |
|
105 * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto |
|
106 * the JSContext stack. |
|
107 * * Entering a null compartment, so that the consumer is forced to select a |
|
108 * compartment to enter before manipulating objects. |
|
109 * |
|
110 * Additionally, the following duties are planned, but not yet implemented: |
|
111 * |
|
112 * * De-poisoning the JSRuntime to allow manipulation of JSAPI. We can't |
|
113 * actually implement this poisoning until all the JSContext pushing in the |
|
114 * system goes through AutoJSAPI (see bug 951991). For now, this de-poisoning |
|
115 * effectively corresponds to having a non-null cx on the stack. |
|
116 * * Reporting any exceptions left on the JSRuntime, unless the caller steals |
|
117 * or silences them. |
|
118 * * Entering a JSAutoRequest. At present, this is handled by the cx pushing |
|
119 * on the main thread, and by other code on workers. Depending on the order |
|
120 * in which various cleanup lands, this may never be necessary, because |
|
121 * JSAutoRequests may go away. |
|
122 * |
|
123 * In situations where the consumer expects to run script, AutoEntryScript |
|
124 * should be used, which does additional manipulation of the script settings |
|
125 * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that |
|
126 * any attempt to run script without an AutoEntryScript on the stack will |
|
127 * fail. This prevents system code from accidentally triggering script |
|
128 * execution at inopportune moments via surreptitious getters and proxies. |
|
129 */ |
|
130 class AutoJSAPI { |
|
131 public: |
|
132 // Public constructor for use when the base class is constructed as-is. It |
|
133 // uses the SafeJSContext (or worker equivalent), and enters a null |
|
134 // compartment. |
|
135 AutoJSAPI(); |
|
136 JSContext* cx() const { return mCx; } |
|
137 |
|
138 bool CxPusherIsStackTop() { return mCxPusher.ref().IsStackTop(); } |
|
139 |
|
140 protected: |
|
141 // Protected constructor, allowing subclasses to specify a particular cx to |
|
142 // be used. |
|
143 AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAC = false); |
|
144 |
|
145 private: |
|
146 mozilla::Maybe<AutoCxPusher> mCxPusher; |
|
147 mozilla::Maybe<JSAutoNullCompartment> mNullAc; |
|
148 JSContext *mCx; |
|
149 }; |
|
150 |
|
151 // Note - the ideal way to implement this is with an accessor on AutoJSAPI |
|
152 // that lets us select the error reporting target. But at present, |
|
153 // implementing it that way would require us to destroy and reconstruct |
|
154 // mCxPusher, which is pretty wasteful. So we do this for now, since it should |
|
155 // be pretty easy to switch things over later. |
|
156 // |
|
157 // This should only be used on the main thread. |
|
158 class AutoJSAPIWithErrorsReportedToWindow : public AutoJSAPI { |
|
159 public: |
|
160 AutoJSAPIWithErrorsReportedToWindow(nsIScriptContext* aScx); |
|
161 // Equivalent to AutoJSAPI if aGlobal is not a Window. |
|
162 AutoJSAPIWithErrorsReportedToWindow(nsIGlobalObject* aGlobalObject); |
|
163 }; |
|
164 |
|
165 /* |
|
166 * A class that represents a new script entry point. |
|
167 */ |
|
168 class AutoEntryScript : public AutoJSAPI, |
|
169 protected ScriptSettingsStackEntry { |
|
170 public: |
|
171 AutoEntryScript(nsIGlobalObject* aGlobalObject, |
|
172 bool aIsMainThread = NS_IsMainThread(), |
|
173 // Note: aCx is mandatory off-main-thread. |
|
174 JSContext* aCx = nullptr); |
|
175 ~AutoEntryScript(); |
|
176 |
|
177 void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { |
|
178 mWebIDLCallerPrincipal = aPrincipal; |
|
179 } |
|
180 |
|
181 private: |
|
182 JSAutoCompartment mAc; |
|
183 dom::ScriptSettingsStack& mStack; |
|
184 nsCOMPtr<nsIPrincipal> mWebIDLCallerPrincipal; |
|
185 friend nsIPrincipal* GetWebIDLCallerPrincipal(); |
|
186 }; |
|
187 |
|
188 /* |
|
189 * A class that can be used to force a particular incumbent script on the stack. |
|
190 */ |
|
191 class AutoIncumbentScript : protected ScriptSettingsStackEntry { |
|
192 public: |
|
193 AutoIncumbentScript(nsIGlobalObject* aGlobalObject); |
|
194 ~AutoIncumbentScript(); |
|
195 private: |
|
196 dom::ScriptSettingsStack& mStack; |
|
197 JS::AutoHideScriptedCaller mCallerOverride; |
|
198 }; |
|
199 |
|
200 /* |
|
201 * A class to put the JS engine in an unusable state. The subject principal |
|
202 * will become System, the information on the script settings stack is |
|
203 * rendered inaccessible, and JSAPI may not be manipulated until the class is |
|
204 * either popped or an AutoJSAPI instance is subsequently pushed. |
|
205 * |
|
206 * This class may not be instantiated if an exception is pending. |
|
207 */ |
|
208 class AutoNoJSAPI { |
|
209 public: |
|
210 AutoNoJSAPI(bool aIsMainThread = NS_IsMainThread()); |
|
211 ~AutoNoJSAPI(); |
|
212 private: |
|
213 dom::ScriptSettingsStack& mStack; |
|
214 mozilla::Maybe<AutoCxPusher> mCxPusher; |
|
215 }; |
|
216 |
|
217 } // namespace dom |
|
218 } // namespace mozilla |
|
219 |
|
220 #endif // mozilla_dom_ScriptSettings_h |