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/. */
7 #include "mozilla/dom/DOMJSProxyHandler.h"
9 #include "xpcprivate.h"
10 #include "XPCWrapper.h"
11 #include "WrapperFactory.h"
12 #include "nsWrapperCacheInlines.h"
13 #include "mozilla/dom/BindingUtils.h"
16 #include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsResult, JS::ExpandoAndGeneration, JS::SetDOMProxyInformation
17 #include "js/PropertyAndElement.h" // JS_AlreadyHasOwnPropertyById, JS_DefineProperty, JS_DefinePropertyById, JS_DeleteProperty, JS_DeletePropertyById
18 #include "js/Object.h" // JS::GetCompartment
22 namespace mozilla::dom
{
24 jsid s_length_id
= JS::PropertyKey::Void();
26 bool DefineStaticJSVals(JSContext
* cx
) {
27 return AtomizeAndPinJSString(cx
, s_length_id
, "length");
30 const char DOMProxyHandler::family
= 0;
32 JS::DOMProxyShadowsResult
DOMProxyShadows(JSContext
* cx
,
33 JS::Handle
<JSObject
*> proxy
,
34 JS::Handle
<jsid
> id
) {
35 using DOMProxyShadowsResult
= JS::DOMProxyShadowsResult
;
37 JS::Rooted
<JSObject
*> expando(cx
, DOMProxyHandler::GetExpandoObject(proxy
));
38 JS::Value v
= js::GetProxyPrivate(proxy
);
39 bool isOverrideBuiltins
= !v
.isObject() && !v
.isUndefined();
42 if (!JS_AlreadyHasOwnPropertyById(cx
, expando
, id
, &hasOwn
))
43 return DOMProxyShadowsResult::ShadowCheckFailed
;
46 return isOverrideBuiltins
47 ? DOMProxyShadowsResult::ShadowsViaIndirectExpando
48 : DOMProxyShadowsResult::ShadowsViaDirectExpando
;
52 if (!isOverrideBuiltins
) {
53 // Our expando, if any, didn't shadow, so we're not shadowing at all.
54 return DOMProxyShadowsResult::DoesntShadow
;
58 if (!GetProxyHandler(proxy
)->hasOwn(cx
, proxy
, id
, &hasOwn
))
59 return DOMProxyShadowsResult::ShadowCheckFailed
;
61 return hasOwn
? DOMProxyShadowsResult::Shadows
62 : DOMProxyShadowsResult::DoesntShadowUnique
;
65 // Store the information for the specialized ICs.
66 struct SetDOMProxyInformation
{
67 SetDOMProxyInformation() {
68 JS::SetDOMProxyInformation((const void*)&DOMProxyHandler::family
,
70 &RemoteObjectProxyBase::sCrossOriginProxyFamily
);
74 MOZ_RUNINIT SetDOMProxyInformation gSetDOMProxyInformation
;
76 static inline void CheckExpandoObject(JSObject
* proxy
,
77 const JS::Value
& expando
) {
79 JSObject
* obj
= &expando
.toObject();
80 MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj
));
81 MOZ_ASSERT(JS::GetCompartment(proxy
) == JS::GetCompartment(obj
));
83 // When we create an expando object in EnsureExpandoObject below, we preserve
84 // the wrapper. The wrapper is released when the object is unlinked, but we
85 // should never call these functions after that point.
86 nsISupports
* native
= UnwrapDOMObject
<nsISupports
>(proxy
);
87 nsWrapperCache
* cache
;
88 // QueryInterface to nsWrapperCache will not GC.
89 JS::AutoSuppressGCAnalysis suppress
;
90 CallQueryInterface(native
, &cache
);
91 MOZ_ASSERT(cache
->PreservingWrapper());
95 static inline void CheckExpandoAndGeneration(
96 JSObject
* proxy
, JS::ExpandoAndGeneration
* expandoAndGeneration
) {
98 JS::Value value
= expandoAndGeneration
->expando
;
99 if (!value
.isUndefined()) CheckExpandoObject(proxy
, value
);
103 static inline void CheckDOMProxy(JSObject
* proxy
) {
105 MOZ_ASSERT(IsDOMProxy(proxy
), "expected a DOM proxy object");
106 MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy
));
107 nsISupports
* native
= UnwrapDOMObject
<nsISupports
>(proxy
);
108 nsWrapperCache
* cache
;
109 // QI to nsWrapperCache cannot GC for very non-obvious reasons; see
110 // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548
111 JS::AutoSuppressGCAnalysis nogc
;
112 CallQueryInterface(native
, &cache
);
113 MOZ_ASSERT(cache
->GetWrapperPreserveColor() == proxy
);
118 JSObject
* DOMProxyHandler::GetAndClearExpandoObject(JSObject
* obj
) {
121 JS::Value v
= js::GetProxyPrivate(obj
);
122 if (v
.isUndefined()) {
127 js::SetProxyPrivate(obj
, UndefinedValue());
129 auto* expandoAndGeneration
=
130 static_cast<JS::ExpandoAndGeneration
*>(v
.toPrivate());
131 v
= expandoAndGeneration
->expando
;
132 if (v
.isUndefined()) {
135 expandoAndGeneration
->expando
= UndefinedValue();
138 CheckExpandoObject(obj
, v
);
140 return &v
.toObject();
144 JSObject
* DOMProxyHandler::EnsureExpandoObject(JSContext
* cx
,
145 JS::Handle
<JSObject
*> obj
) {
148 JS::Value v
= js::GetProxyPrivate(obj
);
150 CheckExpandoObject(obj
, v
);
151 return &v
.toObject();
154 JS::ExpandoAndGeneration
* expandoAndGeneration
= nullptr;
155 if (!v
.isUndefined()) {
156 expandoAndGeneration
=
157 static_cast<JS::ExpandoAndGeneration
*>(v
.toPrivate());
158 CheckExpandoAndGeneration(obj
, expandoAndGeneration
);
159 if (expandoAndGeneration
->expando
.isObject()) {
160 return &expandoAndGeneration
->expando
.toObject();
164 JS::Rooted
<JSObject
*> expando(
165 cx
, JS_NewObjectWithGivenProto(cx
, nullptr, nullptr));
170 nsISupports
* native
= UnwrapDOMObject
<nsISupports
>(obj
);
171 nsWrapperCache
* cache
;
172 CallQueryInterface(native
, &cache
);
173 cache
->PreserveWrapper(native
);
175 if (expandoAndGeneration
) {
176 expandoAndGeneration
->expando
.setObject(*expando
);
180 js::SetProxyPrivate(obj
, ObjectValue(*expando
));
185 bool DOMProxyHandler::preventExtensions(JSContext
* cx
,
186 JS::Handle
<JSObject
*> proxy
,
187 JS::ObjectOpResult
& result
) const {
188 // always extensible per WebIDL
189 return result
.failCantPreventExtensions();
192 bool DOMProxyHandler::isExtensible(JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
193 bool* extensible
) const {
198 bool BaseDOMProxyHandler::getOwnPropertyDescriptor(
199 JSContext
* cx
, Handle
<JSObject
*> proxy
, Handle
<jsid
> id
,
200 MutableHandle
<Maybe
<PropertyDescriptor
>> desc
) const {
201 return getOwnPropDescriptor(cx
, proxy
, id
, /* ignoreNamedProps = */ false,
205 bool DOMProxyHandler::defineProperty(JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
207 Handle
<PropertyDescriptor
> desc
,
208 JS::ObjectOpResult
& result
,
210 if (xpc::WrapperFactory::IsXrayWrapper(proxy
)) {
211 return result
.succeed();
214 JS::Rooted
<JSObject
*> expando(cx
, EnsureExpandoObject(cx
, proxy
));
219 if (!JS_DefinePropertyById(cx
, expando
, id
, desc
, result
)) {
226 bool DOMProxyHandler::set(JSContext
* cx
, Handle
<JSObject
*> proxy
,
227 Handle
<jsid
> id
, Handle
<JS::Value
> v
,
228 Handle
<JS::Value
> receiver
,
229 ObjectOpResult
& result
) const {
230 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy
),
231 "Should not have a XrayWrapper here");
233 if (!setCustom(cx
, proxy
, id
, v
, &done
)) {
237 return result
.succeed();
240 // Make sure to ignore our named properties when checking for own
241 // property descriptors for a set.
242 Rooted
<Maybe
<PropertyDescriptor
>> ownDesc(cx
);
243 if (!getOwnPropDescriptor(cx
, proxy
, id
, /* ignoreNamedProps = */ true,
248 return js::SetPropertyIgnoringNamedGetter(cx
, proxy
, id
, v
, receiver
, ownDesc
,
252 bool DOMProxyHandler::delete_(JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
254 JS::ObjectOpResult
& result
) const {
255 JS::Rooted
<JSObject
*> expando(cx
);
256 if (!xpc::WrapperFactory::IsXrayWrapper(proxy
) &&
257 (expando
= GetExpandoObject(proxy
))) {
258 return JS_DeletePropertyById(cx
, expando
, id
, result
);
261 return result
.succeed();
264 bool BaseDOMProxyHandler::ownPropertyKeys(
265 JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
266 JS::MutableHandleVector
<jsid
> props
) const {
267 return ownPropNames(cx
, proxy
,
268 JSITER_OWNONLY
| JSITER_HIDDEN
| JSITER_SYMBOLS
, props
);
271 bool BaseDOMProxyHandler::getPrototypeIfOrdinary(
272 JSContext
* cx
, JS::Handle
<JSObject
*> proxy
, bool* isOrdinary
,
273 JS::MutableHandle
<JSObject
*> proto
) const {
275 proto
.set(GetStaticPrototype(proxy
));
279 bool BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(
280 JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
281 JS::MutableHandleVector
<jsid
> props
) const {
282 return ownPropNames(cx
, proxy
, JSITER_OWNONLY
, props
);
285 bool DOMProxyHandler::setCustom(JSContext
* cx
, JS::Handle
<JSObject
*> proxy
,
286 JS::Handle
<jsid
> id
, JS::Handle
<JS::Value
> v
,
293 JSObject
* DOMProxyHandler::GetExpandoObject(JSObject
* obj
) {
296 JS::Value v
= js::GetProxyPrivate(obj
);
298 CheckExpandoObject(obj
, v
);
299 return &v
.toObject();
302 if (v
.isUndefined()) {
306 auto* expandoAndGeneration
=
307 static_cast<JS::ExpandoAndGeneration
*>(v
.toPrivate());
308 CheckExpandoAndGeneration(obj
, expandoAndGeneration
);
310 v
= expandoAndGeneration
->expando
;
311 return v
.isUndefined() ? nullptr : &v
.toObject();
314 void ShadowingDOMProxyHandler::trace(JSTracer
* trc
, JSObject
* proxy
) const {
315 DOMProxyHandler::trace(trc
, proxy
);
317 MOZ_ASSERT(IsDOMProxy(proxy
), "expected a DOM proxy object");
318 JS::Value v
= js::GetProxyPrivate(proxy
);
319 MOZ_ASSERT(!v
.isObject(), "Should not have expando object directly!");
321 // The proxy's private slot is set when we allocate the proxy,
322 // so it cannot be |undefined|.
323 MOZ_ASSERT(!v
.isUndefined());
325 auto* expandoAndGeneration
=
326 static_cast<JS::ExpandoAndGeneration
*>(v
.toPrivate());
327 JS::TraceEdge(trc
, &expandoAndGeneration
->expando
,
328 "Shadowing DOM proxy expando");
331 } // namespace mozilla::dom