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 #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
19 
20 using namespace JS;
21 
22 namespace mozilla::dom {
23 
24 jsid s_length_id = JS::PropertyKey::Void();
25 
DefineStaticJSVals(JSContext * cx)26 bool DefineStaticJSVals(JSContext* cx) {
27   return AtomizeAndPinJSString(cx, s_length_id, "length");
28 }
29 
30 const char DOMProxyHandler::family = 0;
31 
DOMProxyShadows(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id)32 JS::DOMProxyShadowsResult DOMProxyShadows(JSContext* cx,
33                                           JS::Handle<JSObject*> proxy,
34                                           JS::Handle<jsid> id) {
35   using DOMProxyShadowsResult = JS::DOMProxyShadowsResult;
36 
37   JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
38   JS::Value v = js::GetProxyPrivate(proxy);
39   bool isOverrideBuiltins = !v.isObject() && !v.isUndefined();
40   if (expando) {
41     bool hasOwn;
42     if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn))
43       return DOMProxyShadowsResult::ShadowCheckFailed;
44 
45     if (hasOwn) {
46       return isOverrideBuiltins
47                  ? DOMProxyShadowsResult::ShadowsViaIndirectExpando
48                  : DOMProxyShadowsResult::ShadowsViaDirectExpando;
49     }
50   }
51 
52   if (!isOverrideBuiltins) {
53     // Our expando, if any, didn't shadow, so we're not shadowing at all.
54     return DOMProxyShadowsResult::DoesntShadow;
55   }
56 
57   bool hasOwn;
58   if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn))
59     return DOMProxyShadowsResult::ShadowCheckFailed;
60 
61   return hasOwn ? DOMProxyShadowsResult::Shadows
62                 : DOMProxyShadowsResult::DoesntShadowUnique;
63 }
64 
65 // Store the information for the specialized ICs.
66 struct SetDOMProxyInformation {
SetDOMProxyInformationmozilla::dom::SetDOMProxyInformation67   SetDOMProxyInformation() {
68     JS::SetDOMProxyInformation((const void*)&DOMProxyHandler::family,
69                                DOMProxyShadows,
70                                &RemoteObjectProxyBase::sCrossOriginProxyFamily);
71   }
72 };
73 
74 SetDOMProxyInformation gSetDOMProxyInformation;
75 
CheckExpandoObject(JSObject * proxy,const JS::Value & expando)76 static inline void CheckExpandoObject(JSObject* proxy,
77                                       const JS::Value& expando) {
78 #ifdef DEBUG
79   JSObject* obj = &expando.toObject();
80   MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj));
81   MOZ_ASSERT(JS::GetCompartment(proxy) == JS::GetCompartment(obj));
82 
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());
92 #endif
93 }
94 
CheckExpandoAndGeneration(JSObject * proxy,JS::ExpandoAndGeneration * expandoAndGeneration)95 static inline void CheckExpandoAndGeneration(
96     JSObject* proxy, JS::ExpandoAndGeneration* expandoAndGeneration) {
97 #ifdef DEBUG
98   JS::Value value = expandoAndGeneration->expando;
99   if (!value.isUndefined()) CheckExpandoObject(proxy, value);
100 #endif
101 }
102 
CheckDOMProxy(JSObject * proxy)103 static inline void CheckDOMProxy(JSObject* proxy) {
104 #ifdef DEBUG
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);
114 #endif
115 }
116 
117 // static
GetAndClearExpandoObject(JSObject * obj)118 JSObject* DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) {
119   CheckDOMProxy(obj);
120 
121   JS::Value v = js::GetProxyPrivate(obj);
122   if (v.isUndefined()) {
123     return nullptr;
124   }
125 
126   if (v.isObject()) {
127     js::SetProxyPrivate(obj, UndefinedValue());
128   } else {
129     auto* expandoAndGeneration =
130         static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
131     v = expandoAndGeneration->expando;
132     if (v.isUndefined()) {
133       return nullptr;
134     }
135     expandoAndGeneration->expando = UndefinedValue();
136   }
137 
138   CheckExpandoObject(obj, v);
139 
140   return &v.toObject();
141 }
142 
143 // static
EnsureExpandoObject(JSContext * cx,JS::Handle<JSObject * > obj)144 JSObject* DOMProxyHandler::EnsureExpandoObject(JSContext* cx,
145                                                JS::Handle<JSObject*> obj) {
146   CheckDOMProxy(obj);
147 
148   JS::Value v = js::GetProxyPrivate(obj);
149   if (v.isObject()) {
150     CheckExpandoObject(obj, v);
151     return &v.toObject();
152   }
153 
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();
161     }
162   }
163 
164   JS::Rooted<JSObject*> expando(
165       cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
166   if (!expando) {
167     return nullptr;
168   }
169 
170   nsISupports* native = UnwrapDOMObject<nsISupports>(obj);
171   nsWrapperCache* cache;
172   CallQueryInterface(native, &cache);
173   cache->PreserveWrapper(native);
174 
175   if (expandoAndGeneration) {
176     expandoAndGeneration->expando.setObject(*expando);
177     return expando;
178   }
179 
180   js::SetProxyPrivate(obj, ObjectValue(*expando));
181 
182   return expando;
183 }
184 
preventExtensions(JSContext * cx,JS::Handle<JSObject * > proxy,JS::ObjectOpResult & result) const185 bool DOMProxyHandler::preventExtensions(JSContext* cx,
186                                         JS::Handle<JSObject*> proxy,
187                                         JS::ObjectOpResult& result) const {
188   // always extensible per WebIDL
189   return result.failCantPreventExtensions();
190 }
191 
isExtensible(JSContext * cx,JS::Handle<JSObject * > proxy,bool * extensible) const192 bool DOMProxyHandler::isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
193                                    bool* extensible) const {
194   *extensible = true;
195   return true;
196 }
197 
getOwnPropertyDescriptor(JSContext * cx,Handle<JSObject * > proxy,Handle<jsid> id,MutableHandle<Maybe<PropertyDescriptor>> desc) const198 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,
202                               desc);
203 }
204 
defineProperty(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,Handle<PropertyDescriptor> desc,JS::ObjectOpResult & result,bool * done) const205 bool DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
206                                      JS::Handle<jsid> id,
207                                      Handle<PropertyDescriptor> desc,
208                                      JS::ObjectOpResult& result,
209                                      bool* done) const {
210   if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
211     return result.succeed();
212   }
213 
214   JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy));
215   if (!expando) {
216     return false;
217   }
218 
219   if (!JS_DefinePropertyById(cx, expando, id, desc, result)) {
220     return false;
221   }
222   *done = true;
223   return true;
224 }
225 
set(JSContext * cx,Handle<JSObject * > proxy,Handle<jsid> id,Handle<JS::Value> v,Handle<JS::Value> receiver,ObjectOpResult & result) const226 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");
232   bool done;
233   if (!setCustom(cx, proxy, id, v, &done)) {
234     return false;
235   }
236   if (done) {
237     return result.succeed();
238   }
239 
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,
244                             &ownDesc)) {
245     return false;
246   }
247 
248   return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
249                                             result);
250 }
251 
delete_(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,JS::ObjectOpResult & result) const252 bool DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
253                               JS::Handle<jsid> id,
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);
259   }
260 
261   return result.succeed();
262 }
263 
ownPropertyKeys(JSContext * cx,JS::Handle<JSObject * > proxy,JS::MutableHandleVector<jsid> props) const264 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);
269 }
270 
getPrototypeIfOrdinary(JSContext * cx,JS::Handle<JSObject * > proxy,bool * isOrdinary,JS::MutableHandle<JSObject * > proto) const271 bool BaseDOMProxyHandler::getPrototypeIfOrdinary(
272     JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
273     JS::MutableHandle<JSObject*> proto) const {
274   *isOrdinary = true;
275   proto.set(GetStaticPrototype(proxy));
276   return true;
277 }
278 
getOwnEnumerablePropertyKeys(JSContext * cx,JS::Handle<JSObject * > proxy,JS::MutableHandleVector<jsid> props) const279 bool BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(
280     JSContext* cx, JS::Handle<JSObject*> proxy,
281     JS::MutableHandleVector<jsid> props) const {
282   return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
283 }
284 
setCustom(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,JS::Handle<JS::Value> v,bool * done) const285 bool DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy,
286                                 JS::Handle<jsid> id, JS::Handle<JS::Value> v,
287                                 bool* done) const {
288   *done = false;
289   return true;
290 }
291 
292 // static
GetExpandoObject(JSObject * obj)293 JSObject* DOMProxyHandler::GetExpandoObject(JSObject* obj) {
294   CheckDOMProxy(obj);
295 
296   JS::Value v = js::GetProxyPrivate(obj);
297   if (v.isObject()) {
298     CheckExpandoObject(obj, v);
299     return &v.toObject();
300   }
301 
302   if (v.isUndefined()) {
303     return nullptr;
304   }
305 
306   auto* expandoAndGeneration =
307       static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
308   CheckExpandoAndGeneration(obj, expandoAndGeneration);
309 
310   v = expandoAndGeneration->expando;
311   return v.isUndefined() ? nullptr : &v.toObject();
312 }
313 
trace(JSTracer * trc,JSObject * proxy) const314 void ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {
315   DOMProxyHandler::trace(trc, proxy);
316 
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!");
320 
321   // The proxy's private slot is set when we allocate the proxy,
322   // so it cannot be |undefined|.
323   MOZ_ASSERT(!v.isUndefined());
324 
325   auto* expandoAndGeneration =
326       static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
327   JS::TraceEdge(trc, &expandoAndGeneration->expando,
328                 "Shadowing DOM proxy expando");
329 }
330 
331 }  // namespace mozilla::dom
332