|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * vim: set ts=2 sw=2 et tw=99 ft=cpp: */ |
|
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 "XPCQuickStubs.h" |
|
11 #include "XPCWrapper.h" |
|
12 #include "WrapperFactory.h" |
|
13 #include "nsDOMClassInfo.h" |
|
14 #include "nsWrapperCacheInlines.h" |
|
15 #include "mozilla/dom/BindingUtils.h" |
|
16 |
|
17 #include "jsapi.h" |
|
18 |
|
19 using namespace JS; |
|
20 |
|
21 namespace mozilla { |
|
22 namespace dom { |
|
23 |
|
24 jsid s_length_id = JSID_VOID; |
|
25 |
|
26 bool |
|
27 DefineStaticJSVals(JSContext* cx) |
|
28 { |
|
29 return InternJSString(cx, s_length_id, "length"); |
|
30 } |
|
31 |
|
32 |
|
33 const char HandlerFamily = 0; |
|
34 |
|
35 js::DOMProxyShadowsResult |
|
36 DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) |
|
37 { |
|
38 JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO); |
|
39 if (v.isObject()) { |
|
40 bool hasOwn; |
|
41 Rooted<JSObject*> object(cx, &v.toObject()); |
|
42 if (!JS_AlreadyHasOwnPropertyById(cx, object, id, &hasOwn)) |
|
43 return js::ShadowCheckFailed; |
|
44 |
|
45 return hasOwn ? js::Shadows : js::DoesntShadow; |
|
46 } |
|
47 |
|
48 if (v.isUndefined()) { |
|
49 return js::DoesntShadow; |
|
50 } |
|
51 |
|
52 bool hasOwn; |
|
53 if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn)) |
|
54 return js::ShadowCheckFailed; |
|
55 |
|
56 return hasOwn ? js::Shadows : js::DoesntShadowUnique; |
|
57 } |
|
58 |
|
59 // Store the information for the specialized ICs. |
|
60 struct SetDOMProxyInformation |
|
61 { |
|
62 SetDOMProxyInformation() { |
|
63 js::SetDOMProxyInformation((const void*) &HandlerFamily, |
|
64 js::PROXY_EXTRA_SLOT + JSPROXYSLOT_EXPANDO, DOMProxyShadows); |
|
65 } |
|
66 }; |
|
67 |
|
68 SetDOMProxyInformation gSetDOMProxyInformation; |
|
69 |
|
70 // static |
|
71 JSObject* |
|
72 DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) |
|
73 { |
|
74 MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); |
|
75 JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); |
|
76 if (v.isUndefined()) { |
|
77 return nullptr; |
|
78 } |
|
79 |
|
80 if (v.isObject()) { |
|
81 js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue()); |
|
82 XPCWrappedNativeScope* scope = xpc::MaybeGetObjectScope(obj); |
|
83 if (scope) { |
|
84 scope->RemoveDOMExpandoObject(obj); |
|
85 } |
|
86 } else { |
|
87 js::ExpandoAndGeneration* expandoAndGeneration = |
|
88 static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
|
89 v = expandoAndGeneration->expando; |
|
90 if (v.isUndefined()) { |
|
91 return nullptr; |
|
92 } |
|
93 expandoAndGeneration->expando = UndefinedValue(); |
|
94 } |
|
95 |
|
96 |
|
97 return &v.toObject(); |
|
98 } |
|
99 |
|
100 // static |
|
101 JSObject* |
|
102 DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj) |
|
103 { |
|
104 NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object"); |
|
105 JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); |
|
106 if (v.isObject()) { |
|
107 return &v.toObject(); |
|
108 } |
|
109 |
|
110 js::ExpandoAndGeneration* expandoAndGeneration; |
|
111 if (!v.isUndefined()) { |
|
112 expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); |
|
113 if (expandoAndGeneration->expando.isObject()) { |
|
114 return &expandoAndGeneration->expando.toObject(); |
|
115 } |
|
116 } else { |
|
117 expandoAndGeneration = nullptr; |
|
118 } |
|
119 |
|
120 JS::Rooted<JSObject*> parent(cx, js::GetObjectParent(obj)); |
|
121 JS::Rooted<JSObject*> expando(cx, |
|
122 JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), parent)); |
|
123 if (!expando) { |
|
124 return nullptr; |
|
125 } |
|
126 |
|
127 nsISupports* native = UnwrapDOMObject<nsISupports>(obj); |
|
128 nsWrapperCache* cache; |
|
129 CallQueryInterface(native, &cache); |
|
130 if (expandoAndGeneration) { |
|
131 cache->PreserveWrapper(native); |
|
132 expandoAndGeneration->expando.setObject(*expando); |
|
133 |
|
134 return expando; |
|
135 } |
|
136 |
|
137 XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj); |
|
138 if (!scope->RegisterDOMExpandoObject(obj)) { |
|
139 return nullptr; |
|
140 } |
|
141 |
|
142 cache->SetPreservingWrapper(true); |
|
143 js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando)); |
|
144 |
|
145 return expando; |
|
146 } |
|
147 |
|
148 bool |
|
149 DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) |
|
150 { |
|
151 // always extensible per WebIDL |
|
152 *extensible = true; |
|
153 return true; |
|
154 } |
|
155 |
|
156 bool |
|
157 DOMProxyHandler::preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy) |
|
158 { |
|
159 // Throw a TypeError, per WebIDL. |
|
160 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
161 JSMSG_CANT_CHANGE_EXTENSIBILITY); |
|
162 return false; |
|
163 } |
|
164 |
|
165 bool |
|
166 BaseDOMProxyHandler::getPropertyDescriptor(JSContext* cx, |
|
167 JS::Handle<JSObject*> proxy, |
|
168 JS::Handle<jsid> id, |
|
169 MutableHandle<JSPropertyDescriptor> desc) |
|
170 { |
|
171 if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) { |
|
172 return false; |
|
173 } |
|
174 if (desc.object()) { |
|
175 return true; |
|
176 } |
|
177 |
|
178 JS::Rooted<JSObject*> proto(cx); |
|
179 if (!js::GetObjectProto(cx, proxy, &proto)) { |
|
180 return false; |
|
181 } |
|
182 if (!proto) { |
|
183 desc.object().set(nullptr); |
|
184 return true; |
|
185 } |
|
186 |
|
187 return JS_GetPropertyDescriptorById(cx, proto, id, desc); |
|
188 } |
|
189 |
|
190 bool |
|
191 BaseDOMProxyHandler::getOwnPropertyDescriptor(JSContext* cx, |
|
192 JS::Handle<JSObject*> proxy, |
|
193 JS::Handle<jsid> id, |
|
194 MutableHandle<JSPropertyDescriptor> desc) |
|
195 { |
|
196 return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false, |
|
197 desc); |
|
198 } |
|
199 |
|
200 bool |
|
201 DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, |
|
202 MutableHandle<JSPropertyDescriptor> desc, bool* defined) |
|
203 { |
|
204 if (desc.hasGetterObject() && desc.setter() == JS_StrictPropertyStub) { |
|
205 return JS_ReportErrorFlagsAndNumber(cx, |
|
206 JSREPORT_WARNING | JSREPORT_STRICT | |
|
207 JSREPORT_STRICT_MODE_ERROR, |
|
208 js_GetErrorMessage, nullptr, |
|
209 JSMSG_GETTER_ONLY); |
|
210 } |
|
211 |
|
212 if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { |
|
213 return true; |
|
214 } |
|
215 |
|
216 JSObject* expando = EnsureExpandoObject(cx, proxy); |
|
217 if (!expando) { |
|
218 return false; |
|
219 } |
|
220 |
|
221 bool dummy; |
|
222 return js_DefineOwnProperty(cx, expando, id, desc, &dummy); |
|
223 } |
|
224 |
|
225 bool |
|
226 DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<JSObject*> receiver, |
|
227 Handle<jsid> id, bool strict, MutableHandle<JS::Value> vp) |
|
228 { |
|
229 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), |
|
230 "Should not have a XrayWrapper here"); |
|
231 bool done; |
|
232 if (!setCustom(cx, proxy, id, vp, &done)) { |
|
233 return false; |
|
234 } |
|
235 if (done) { |
|
236 return true; |
|
237 } |
|
238 |
|
239 // Make sure to ignore our named properties when checking for own |
|
240 // property descriptors for a set. |
|
241 JS::Rooted<JSPropertyDescriptor> desc(cx); |
|
242 if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true, |
|
243 &desc)) { |
|
244 return false; |
|
245 } |
|
246 bool descIsOwn = desc.object() != nullptr; |
|
247 if (!desc.object()) { |
|
248 // Don't just use getPropertyDescriptor, unlike BaseProxyHandler::set, |
|
249 // because that would call getOwnPropertyDescriptor on ourselves. Instead, |
|
250 // directly delegate to the proto, if any. |
|
251 JS::Rooted<JSObject*> proto(cx); |
|
252 if (!js::GetObjectProto(cx, proxy, &proto)) { |
|
253 return false; |
|
254 } |
|
255 if (proto && !JS_GetPropertyDescriptorById(cx, proto, id, &desc)) { |
|
256 return false; |
|
257 } |
|
258 } |
|
259 |
|
260 return js::SetPropertyIgnoringNamedGetter(cx, this, proxy, receiver, id, |
|
261 &desc, descIsOwn, strict, vp); |
|
262 } |
|
263 |
|
264 bool |
|
265 DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy, |
|
266 JS::Handle<jsid> id, bool* bp) |
|
267 { |
|
268 JS::Rooted<JSObject*> expando(cx); |
|
269 if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { |
|
270 return JS_DeletePropertyById2(cx, expando, id, bp); |
|
271 } |
|
272 |
|
273 *bp = true; |
|
274 return true; |
|
275 } |
|
276 |
|
277 bool |
|
278 BaseDOMProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, |
|
279 AutoIdVector& props) |
|
280 { |
|
281 JS::Rooted<JSObject*> proto(cx); |
|
282 if (!JS_GetPrototype(cx, proxy, &proto)) { |
|
283 return false; |
|
284 } |
|
285 return keys(cx, proxy, props) && |
|
286 (!proto || js::GetPropertyNames(cx, proto, 0, &props)); |
|
287 } |
|
288 |
|
289 bool |
|
290 BaseDOMProxyHandler::watch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, |
|
291 JS::Handle<JSObject*> callable) |
|
292 { |
|
293 return js::WatchGuts(cx, proxy, id, callable); |
|
294 } |
|
295 |
|
296 bool |
|
297 BaseDOMProxyHandler::unwatch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) |
|
298 { |
|
299 return js::UnwatchGuts(cx, proxy, id); |
|
300 } |
|
301 |
|
302 bool |
|
303 BaseDOMProxyHandler::getOwnPropertyNames(JSContext* cx, |
|
304 JS::Handle<JSObject*> proxy, |
|
305 JS::AutoIdVector& props) |
|
306 { |
|
307 return ownPropNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN, props); |
|
308 } |
|
309 |
|
310 bool |
|
311 BaseDOMProxyHandler::keys(JSContext* cx, |
|
312 JS::Handle<JSObject*> proxy, |
|
313 JS::AutoIdVector& props) |
|
314 { |
|
315 return ownPropNames(cx, proxy, JSITER_OWNONLY, props); |
|
316 } |
|
317 |
|
318 bool |
|
319 DOMProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) |
|
320 { |
|
321 if (!hasOwn(cx, proxy, id, bp)) { |
|
322 return false; |
|
323 } |
|
324 |
|
325 if (*bp) { |
|
326 // We have the property ourselves; no need to worry about our prototype |
|
327 // chain. |
|
328 return true; |
|
329 } |
|
330 |
|
331 // OK, now we have to look at the proto |
|
332 JS::Rooted<JSObject*> proto(cx); |
|
333 if (!js::GetObjectProto(cx, proxy, &proto)) { |
|
334 return false; |
|
335 } |
|
336 if (!proto) { |
|
337 return true; |
|
338 } |
|
339 bool protoHasProp; |
|
340 bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp); |
|
341 if (ok) { |
|
342 *bp = protoHasProp; |
|
343 } |
|
344 return ok; |
|
345 } |
|
346 |
|
347 int32_t |
|
348 IdToInt32(JSContext* cx, JS::Handle<jsid> id) |
|
349 { |
|
350 JS::Rooted<JS::Value> idval(cx); |
|
351 double array_index; |
|
352 int32_t i; |
|
353 if (!::JS_IdToValue(cx, id, &idval) || |
|
354 !JS::ToNumber(cx, idval, &array_index) || |
|
355 !::JS_DoubleIsInt32(array_index, &i)) { |
|
356 return -1; |
|
357 } |
|
358 |
|
359 return i; |
|
360 } |
|
361 |
|
362 bool |
|
363 DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, |
|
364 JS::MutableHandle<JS::Value> vp, bool *done) |
|
365 { |
|
366 *done = false; |
|
367 return true; |
|
368 } |
|
369 |
|
370 } // namespace dom |
|
371 } // namespace mozilla |