/src/mozilla-central/dom/bindings/DOMJSProxyHandler.cpp
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 file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/DOMJSProxyHandler.h" |
8 | | #include "xpcpublic.h" |
9 | | #include "xpcprivate.h" |
10 | | #include "XPCWrapper.h" |
11 | | #include "WrapperFactory.h" |
12 | | #include "nsWrapperCacheInlines.h" |
13 | | #include "mozilla/dom/BindingUtils.h" |
14 | | |
15 | | #include "jsapi.h" |
16 | | |
17 | | using namespace JS; |
18 | | |
19 | | namespace mozilla { |
20 | | namespace dom { |
21 | | |
22 | | jsid s_length_id = JSID_VOID; |
23 | | |
24 | | bool |
25 | | DefineStaticJSVals(JSContext* cx) |
26 | 3 | { |
27 | 3 | return AtomizeAndPinJSString(cx, s_length_id, "length"); |
28 | 3 | } |
29 | | |
30 | | const char DOMProxyHandler::family = 0; |
31 | | |
32 | | js::DOMProxyShadowsResult |
33 | | DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) |
34 | 0 | { |
35 | 0 | JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); |
36 | 0 | JS::Value v = js::GetProxyPrivate(proxy); |
37 | 0 | bool isOverrideBuiltins = !v.isObject() && !v.isUndefined(); |
38 | 0 | if (expando) { |
39 | 0 | bool hasOwn; |
40 | 0 | if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn)) |
41 | 0 | return js::ShadowCheckFailed; |
42 | 0 | |
43 | 0 | if (hasOwn) { |
44 | 0 | return isOverrideBuiltins ? |
45 | 0 | js::ShadowsViaIndirectExpando : js::ShadowsViaDirectExpando; |
46 | 0 | } |
47 | 0 | } |
48 | 0 |
|
49 | 0 | if (!isOverrideBuiltins) { |
50 | 0 | // Our expando, if any, didn't shadow, so we're not shadowing at all. |
51 | 0 | return js::DoesntShadow; |
52 | 0 | } |
53 | 0 | |
54 | 0 | bool hasOwn; |
55 | 0 | if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn)) |
56 | 0 | return js::ShadowCheckFailed; |
57 | 0 | |
58 | 0 | return hasOwn ? js::Shadows : js::DoesntShadowUnique; |
59 | 0 | } |
60 | | |
61 | | // Store the information for the specialized ICs. |
62 | | struct SetDOMProxyInformation |
63 | | { |
64 | 3 | SetDOMProxyInformation() { |
65 | 3 | js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family, |
66 | 3 | DOMProxyShadows); |
67 | 3 | } |
68 | | }; |
69 | | |
70 | | SetDOMProxyInformation gSetDOMProxyInformation; |
71 | | |
72 | | static inline void |
73 | | CheckExpandoObject(JSObject* proxy, const JS::Value& expando) |
74 | 0 | { |
75 | | #ifdef DEBUG |
76 | | JSObject* obj = &expando.toObject(); |
77 | | MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj)); |
78 | | MOZ_ASSERT(js::GetObjectCompartment(proxy) == js::GetObjectCompartment(obj)); |
79 | | |
80 | | // When we create an expando object in EnsureExpandoObject below, we preserve |
81 | | // the wrapper. The wrapper is released when the object is unlinked, but we |
82 | | // should never call these functions after that point. |
83 | | nsISupports* native = UnwrapDOMObject<nsISupports>(proxy); |
84 | | nsWrapperCache* cache; |
85 | | CallQueryInterface(native, &cache); |
86 | | MOZ_ASSERT(cache->PreservingWrapper()); |
87 | | #endif |
88 | | } |
89 | | |
90 | | static inline void |
91 | | CheckExpandoAndGeneration(JSObject* proxy, js::ExpandoAndGeneration* expandoAndGeneration) |
92 | 0 | { |
93 | | #ifdef DEBUG |
94 | | JS::Value value = expandoAndGeneration->expando; |
95 | | if (!value.isUndefined()) |
96 | | CheckExpandoObject(proxy, value); |
97 | | #endif |
98 | | } |
99 | | |
100 | | static inline void |
101 | | CheckDOMProxy(JSObject* proxy) |
102 | 0 | { |
103 | | #ifdef DEBUG |
104 | | MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object"); |
105 | | MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy)); |
106 | | nsISupports* native = UnwrapDOMObject<nsISupports>(proxy); |
107 | | nsWrapperCache* cache; |
108 | | // QI to nsWrapperCache cannot GC for very non-obvious reasons; see |
109 | | // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548 |
110 | | JS::AutoSuppressGCAnalysis nogc; |
111 | | CallQueryInterface(native, &cache); |
112 | | MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy); |
113 | | #endif |
114 | | } |
115 | | |
116 | | // static |
117 | | JSObject* |
118 | | DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) |
119 | 0 | { |
120 | 0 | CheckDOMProxy(obj); |
121 | 0 |
|
122 | 0 | JS::Value v = js::GetProxyPrivate(obj); |
123 | 0 | if (v.isUndefined()) { |
124 | 0 | return nullptr; |
125 | 0 | } |
126 | 0 | |
127 | 0 | if (v.isObject()) { |
128 | 0 | js::SetProxyPrivate(obj, UndefinedValue()); |
129 | 0 | } else { |
130 | 0 | js::ExpandoAndGeneration* expandoAndGeneration = |
131 | 0 | static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
132 | 0 | v = expandoAndGeneration->expando; |
133 | 0 | if (v.isUndefined()) { |
134 | 0 | return nullptr; |
135 | 0 | } |
136 | 0 | // We have to expose v to active JS here. The reason for that is that we |
137 | 0 | // might be in the middle of a GC right now. If our proxy hasn't been |
138 | 0 | // traced yet, when it _does_ get traced it won't trace the expando, since |
139 | 0 | // we're breaking that link. But the Rooted we're presumably being placed |
140 | 0 | // into is also not going to trace us, because Rooted marking is done at |
141 | 0 | // the very beginning of the GC. In that situation, we need to manually |
142 | 0 | // mark the expando as live here. JS::ExposeValueToActiveJS will do just |
143 | 0 | // that for us. |
144 | 0 | // |
145 | 0 | // We don't need to do this in the non-expandoAndGeneration case, because |
146 | 0 | // in that case our value is stored in a slot and slots will already mark |
147 | 0 | // the old thing live when the value in the slot changes. |
148 | 0 | JS::ExposeValueToActiveJS(v); |
149 | 0 | expandoAndGeneration->expando = UndefinedValue(); |
150 | 0 | } |
151 | 0 |
|
152 | 0 | CheckExpandoObject(obj, v); |
153 | 0 |
|
154 | 0 | return &v.toObject(); |
155 | 0 | } |
156 | | |
157 | | // static |
158 | | JSObject* |
159 | | DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj) |
160 | 0 | { |
161 | 0 | CheckDOMProxy(obj); |
162 | 0 |
|
163 | 0 | JS::Value v = js::GetProxyPrivate(obj); |
164 | 0 | if (v.isObject()) { |
165 | 0 | CheckExpandoObject(obj, v); |
166 | 0 | return &v.toObject(); |
167 | 0 | } |
168 | 0 | |
169 | 0 | js::ExpandoAndGeneration* expandoAndGeneration; |
170 | 0 | if (!v.isUndefined()) { |
171 | 0 | expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
172 | 0 | CheckExpandoAndGeneration(obj, expandoAndGeneration); |
173 | 0 | if (expandoAndGeneration->expando.isObject()) { |
174 | 0 | return &expandoAndGeneration->expando.toObject(); |
175 | 0 | } |
176 | 0 | } else { |
177 | 0 | expandoAndGeneration = nullptr; |
178 | 0 | } |
179 | 0 |
|
180 | 0 | JS::Rooted<JSObject*> expando(cx, |
181 | 0 | JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); |
182 | 0 | if (!expando) { |
183 | 0 | return nullptr; |
184 | 0 | } |
185 | 0 | |
186 | 0 | nsISupports* native = UnwrapDOMObject<nsISupports>(obj); |
187 | 0 | nsWrapperCache* cache; |
188 | 0 | CallQueryInterface(native, &cache); |
189 | 0 | cache->PreserveWrapper(native); |
190 | 0 |
|
191 | 0 | if (expandoAndGeneration) { |
192 | 0 | expandoAndGeneration->expando.setObject(*expando); |
193 | 0 | return expando; |
194 | 0 | } |
195 | 0 | |
196 | 0 | js::SetProxyPrivate(obj, ObjectValue(*expando)); |
197 | 0 |
|
198 | 0 | return expando; |
199 | 0 | } |
200 | | |
201 | | bool |
202 | | DOMProxyHandler::preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy, |
203 | | JS::ObjectOpResult& result) const |
204 | 0 | { |
205 | 0 | // always extensible per WebIDL |
206 | 0 | return result.failCantPreventExtensions(); |
207 | 0 | } |
208 | | |
209 | | bool |
210 | | DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) const |
211 | 0 | { |
212 | 0 | *extensible = true; |
213 | 0 | return true; |
214 | 0 | } |
215 | | |
216 | | bool |
217 | | BaseDOMProxyHandler::getOwnPropertyDescriptor(JSContext* cx, |
218 | | JS::Handle<JSObject*> proxy, |
219 | | JS::Handle<jsid> id, |
220 | | MutableHandle<PropertyDescriptor> desc) const |
221 | 0 | { |
222 | 0 | return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false, |
223 | 0 | desc); |
224 | 0 | } |
225 | | |
226 | | bool |
227 | | DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, |
228 | | Handle<PropertyDescriptor> desc, |
229 | | JS::ObjectOpResult &result, bool *defined) const |
230 | 0 | { |
231 | 0 | if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { |
232 | 0 | return result.succeed(); |
233 | 0 | } |
234 | 0 | |
235 | 0 | JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy)); |
236 | 0 | if (!expando) { |
237 | 0 | return false; |
238 | 0 | } |
239 | 0 | |
240 | 0 | if (!JS_DefinePropertyById(cx, expando, id, desc, result)) { |
241 | 0 | return false; |
242 | 0 | } |
243 | 0 | *defined = true; |
244 | 0 | return true; |
245 | 0 | } |
246 | | |
247 | | bool |
248 | | DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<jsid> id, |
249 | | Handle<JS::Value> v, Handle<JS::Value> receiver, |
250 | | ObjectOpResult &result) const |
251 | 0 | { |
252 | 0 | MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), |
253 | 0 | "Should not have a XrayWrapper here"); |
254 | 0 | bool done; |
255 | 0 | if (!setCustom(cx, proxy, id, v, &done)) { |
256 | 0 | return false; |
257 | 0 | } |
258 | 0 | if (done) { |
259 | 0 | return result.succeed(); |
260 | 0 | } |
261 | 0 | |
262 | 0 | // Make sure to ignore our named properties when checking for own |
263 | 0 | // property descriptors for a set. |
264 | 0 | JS::Rooted<PropertyDescriptor> ownDesc(cx); |
265 | 0 | if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true, |
266 | 0 | &ownDesc)) { |
267 | 0 | return false; |
268 | 0 | } |
269 | 0 | return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result); |
270 | 0 | } |
271 | | |
272 | | bool |
273 | | DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy, |
274 | | JS::Handle<jsid> id, JS::ObjectOpResult &result) const |
275 | 0 | { |
276 | 0 | JS::Rooted<JSObject*> expando(cx); |
277 | 0 | if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { |
278 | 0 | return JS_DeletePropertyById(cx, expando, id, result); |
279 | 0 | } |
280 | 0 | |
281 | 0 | return result.succeed(); |
282 | 0 | } |
283 | | |
284 | | bool |
285 | | BaseDOMProxyHandler::ownPropertyKeys(JSContext* cx, |
286 | | JS::Handle<JSObject*> proxy, |
287 | | JS::AutoIdVector& props) const |
288 | 0 | { |
289 | 0 | return ownPropNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props); |
290 | 0 | } |
291 | | |
292 | | bool |
293 | | BaseDOMProxyHandler::getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy, |
294 | | bool* isOrdinary, |
295 | | JS::MutableHandle<JSObject*> proto) const |
296 | 0 | { |
297 | 0 | *isOrdinary = true; |
298 | 0 | proto.set(GetStaticPrototype(proxy)); |
299 | 0 | return true; |
300 | 0 | } |
301 | | |
302 | | bool |
303 | | BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, |
304 | | JS::Handle<JSObject*> proxy, |
305 | | JS::AutoIdVector& props) const |
306 | 0 | { |
307 | 0 | return ownPropNames(cx, proxy, JSITER_OWNONLY, props); |
308 | 0 | } |
309 | | |
310 | | bool |
311 | | DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, |
312 | | JS::Handle<JS::Value> v, bool *done) const |
313 | 0 | { |
314 | 0 | *done = false; |
315 | 0 | return true; |
316 | 0 | } |
317 | | |
318 | | //static |
319 | | JSObject * |
320 | | DOMProxyHandler::GetExpandoObject(JSObject *obj) |
321 | 0 | { |
322 | 0 | CheckDOMProxy(obj); |
323 | 0 |
|
324 | 0 | JS::Value v = js::GetProxyPrivate(obj); |
325 | 0 | if (v.isObject()) { |
326 | 0 | CheckExpandoObject(obj, v); |
327 | 0 | return &v.toObject(); |
328 | 0 | } |
329 | 0 | |
330 | 0 | if (v.isUndefined()) { |
331 | 0 | return nullptr; |
332 | 0 | } |
333 | 0 | |
334 | 0 | js::ExpandoAndGeneration* expandoAndGeneration = |
335 | 0 | static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
336 | 0 | CheckExpandoAndGeneration(obj, expandoAndGeneration); |
337 | 0 |
|
338 | 0 | v = expandoAndGeneration->expando; |
339 | 0 | return v.isUndefined() ? nullptr : &v.toObject(); |
340 | 0 | } |
341 | | |
342 | | void |
343 | | ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const |
344 | 0 | { |
345 | 0 | DOMProxyHandler::trace(trc, proxy); |
346 | 0 |
|
347 | 0 | MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object"); |
348 | 0 | JS::Value v = js::GetProxyPrivate(proxy); |
349 | 0 | MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!"); |
350 | 0 |
|
351 | 0 | // The proxy's private slot is set when we allocate the proxy, |
352 | 0 | // so it cannot be |undefined|. |
353 | 0 | MOZ_ASSERT(!v.isUndefined()); |
354 | 0 |
|
355 | 0 | js::ExpandoAndGeneration* expandoAndGeneration = |
356 | 0 | static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
357 | 0 | JS::TraceEdge(trc, &expandoAndGeneration->expando, |
358 | 0 | "Shadowing DOM proxy expando"); |
359 | 0 | } |
360 | | |
361 | | } // namespace dom |
362 | | } // namespace mozilla |