Bug 1707290 [wpt PR 28671] - Auto-expand details elements for find-in-page, a=testonly
[gecko.git] / dom / bindings / DOMJSProxyHandler.cpp
blobef4aa8f71d6ebc42d789fc156bc95f8dee33a8e6
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"
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"
15 #include "jsapi.h"
16 #include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsResult, JS::ExpandoAndGeneration, JS::SetDOMProxyInformation
17 #include "js/Object.h" // JS::GetCompartment
19 using namespace JS;
21 namespace mozilla::dom {
23 jsid s_length_id = JSID_VOID;
25 bool DefineStaticJSVals(JSContext* cx) {
26 return AtomizeAndPinJSString(cx, s_length_id, "length");
29 const char DOMProxyHandler::family = 0;
31 JS::DOMProxyShadowsResult DOMProxyShadows(JSContext* cx,
32 JS::Handle<JSObject*> proxy,
33 JS::Handle<jsid> id) {
34 using DOMProxyShadowsResult = JS::DOMProxyShadowsResult;
36 JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
37 JS::Value v = js::GetProxyPrivate(proxy);
38 bool isOverrideBuiltins = !v.isObject() && !v.isUndefined();
39 if (expando) {
40 bool hasOwn;
41 if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn))
42 return DOMProxyShadowsResult::ShadowCheckFailed;
44 if (hasOwn) {
45 return isOverrideBuiltins
46 ? DOMProxyShadowsResult::ShadowsViaIndirectExpando
47 : DOMProxyShadowsResult::ShadowsViaDirectExpando;
51 if (!isOverrideBuiltins) {
52 // Our expando, if any, didn't shadow, so we're not shadowing at all.
53 return DOMProxyShadowsResult::DoesntShadow;
56 bool hasOwn;
57 if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn))
58 return DOMProxyShadowsResult::ShadowCheckFailed;
60 return hasOwn ? DOMProxyShadowsResult::Shadows
61 : DOMProxyShadowsResult::DoesntShadowUnique;
64 // Store the information for the specialized ICs.
65 struct SetDOMProxyInformation {
66 SetDOMProxyInformation() {
67 JS::SetDOMProxyInformation((const void*)&DOMProxyHandler::family,
68 DOMProxyShadows,
69 &RemoteObjectProxyBase::sCrossOriginProxyFamily);
73 SetDOMProxyInformation gSetDOMProxyInformation;
75 static inline void CheckExpandoObject(JSObject* proxy,
76 const JS::Value& expando) {
77 #ifdef DEBUG
78 JSObject* obj = &expando.toObject();
79 MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj));
80 MOZ_ASSERT(JS::GetCompartment(proxy) == JS::GetCompartment(obj));
82 // When we create an expando object in EnsureExpandoObject below, we preserve
83 // the wrapper. The wrapper is released when the object is unlinked, but we
84 // should never call these functions after that point.
85 nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
86 nsWrapperCache* cache;
87 // QueryInterface to nsWrapperCache will not GC.
88 JS::AutoSuppressGCAnalysis suppress;
89 CallQueryInterface(native, &cache);
90 MOZ_ASSERT(cache->PreservingWrapper());
91 #endif
94 static inline void CheckExpandoAndGeneration(
95 JSObject* proxy, JS::ExpandoAndGeneration* expandoAndGeneration) {
96 #ifdef DEBUG
97 JS::Value value = expandoAndGeneration->expando;
98 if (!value.isUndefined()) CheckExpandoObject(proxy, value);
99 #endif
102 static inline void CheckDOMProxy(JSObject* proxy) {
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
116 // static
117 JSObject* DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) {
118 CheckDOMProxy(obj);
120 JS::Value v = js::GetProxyPrivate(obj);
121 if (v.isUndefined()) {
122 return nullptr;
125 if (v.isObject()) {
126 js::SetProxyPrivate(obj, UndefinedValue());
127 } else {
128 auto* expandoAndGeneration =
129 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
130 v = expandoAndGeneration->expando;
131 if (v.isUndefined()) {
132 return nullptr;
134 expandoAndGeneration->expando = UndefinedValue();
137 CheckExpandoObject(obj, v);
139 return &v.toObject();
142 // static
143 JSObject* DOMProxyHandler::EnsureExpandoObject(JSContext* cx,
144 JS::Handle<JSObject*> obj) {
145 CheckDOMProxy(obj);
147 JS::Value v = js::GetProxyPrivate(obj);
148 if (v.isObject()) {
149 CheckExpandoObject(obj, v);
150 return &v.toObject();
153 JS::ExpandoAndGeneration* expandoAndGeneration = nullptr;
154 if (!v.isUndefined()) {
155 expandoAndGeneration =
156 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
157 CheckExpandoAndGeneration(obj, expandoAndGeneration);
158 if (expandoAndGeneration->expando.isObject()) {
159 return &expandoAndGeneration->expando.toObject();
163 JS::Rooted<JSObject*> expando(
164 cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
165 if (!expando) {
166 return nullptr;
169 nsISupports* native = UnwrapDOMObject<nsISupports>(obj);
170 nsWrapperCache* cache;
171 CallQueryInterface(native, &cache);
172 cache->PreserveWrapper(native);
174 if (expandoAndGeneration) {
175 expandoAndGeneration->expando.setObject(*expando);
176 return expando;
179 js::SetProxyPrivate(obj, ObjectValue(*expando));
181 return expando;
184 bool DOMProxyHandler::preventExtensions(JSContext* cx,
185 JS::Handle<JSObject*> proxy,
186 JS::ObjectOpResult& result) const {
187 // always extensible per WebIDL
188 return result.failCantPreventExtensions();
191 bool DOMProxyHandler::isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
192 bool* extensible) const {
193 *extensible = true;
194 return true;
197 bool BaseDOMProxyHandler::getOwnPropertyDescriptor(
198 JSContext* cx, Handle<JSObject*> proxy, Handle<jsid> id,
199 MutableHandle<Maybe<PropertyDescriptor>> desc) const {
200 return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false,
201 desc);
204 bool DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
205 JS::Handle<jsid> id,
206 Handle<PropertyDescriptor> desc,
207 JS::ObjectOpResult& result,
208 bool* done) const {
209 if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
210 return result.succeed();
213 JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy));
214 if (!expando) {
215 return false;
218 if (!JS_DefinePropertyById(cx, expando, id, desc, result)) {
219 return false;
221 *done = true;
222 return true;
225 bool DOMProxyHandler::set(JSContext* cx, Handle<JSObject*> proxy,
226 Handle<jsid> id, Handle<JS::Value> v,
227 Handle<JS::Value> receiver,
228 ObjectOpResult& result) const {
229 MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
230 "Should not have a XrayWrapper here");
231 bool done;
232 if (!setCustom(cx, proxy, id, v, &done)) {
233 return false;
235 if (done) {
236 return result.succeed();
239 // Make sure to ignore our named properties when checking for own
240 // property descriptors for a set.
241 Rooted<Maybe<PropertyDescriptor>> ownDesc(cx);
242 if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true,
243 &ownDesc)) {
244 return false;
247 return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
248 result);
251 bool DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
252 JS::Handle<jsid> id,
253 JS::ObjectOpResult& result) const {
254 JS::Rooted<JSObject*> expando(cx);
255 if (!xpc::WrapperFactory::IsXrayWrapper(proxy) &&
256 (expando = GetExpandoObject(proxy))) {
257 return JS_DeletePropertyById(cx, expando, id, result);
260 return result.succeed();
263 bool BaseDOMProxyHandler::ownPropertyKeys(
264 JSContext* cx, JS::Handle<JSObject*> proxy,
265 JS::MutableHandleVector<jsid> props) const {
266 return ownPropNames(cx, proxy,
267 JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
270 bool BaseDOMProxyHandler::getPrototypeIfOrdinary(
271 JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
272 JS::MutableHandle<JSObject*> proto) const {
273 *isOrdinary = true;
274 proto.set(GetStaticPrototype(proxy));
275 return true;
278 bool BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(
279 JSContext* cx, JS::Handle<JSObject*> proxy,
280 JS::MutableHandleVector<jsid> props) const {
281 return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
284 bool DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy,
285 JS::Handle<jsid> id, JS::Handle<JS::Value> v,
286 bool* done) const {
287 *done = false;
288 return true;
291 // static
292 JSObject* DOMProxyHandler::GetExpandoObject(JSObject* obj) {
293 CheckDOMProxy(obj);
295 JS::Value v = js::GetProxyPrivate(obj);
296 if (v.isObject()) {
297 CheckExpandoObject(obj, v);
298 return &v.toObject();
301 if (v.isUndefined()) {
302 return nullptr;
305 auto* expandoAndGeneration =
306 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
307 CheckExpandoAndGeneration(obj, expandoAndGeneration);
309 v = expandoAndGeneration->expando;
310 return v.isUndefined() ? nullptr : &v.toObject();
313 void ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {
314 DOMProxyHandler::trace(trc, proxy);
316 MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
317 JS::Value v = js::GetProxyPrivate(proxy);
318 MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
320 // The proxy's private slot is set when we allocate the proxy,
321 // so it cannot be |undefined|.
322 MOZ_ASSERT(!v.isUndefined());
324 auto* expandoAndGeneration =
325 static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
326 JS::TraceEdge(trc, &expandoAndGeneration->expando,
327 "Shadowing DOM proxy expando");
330 } // namespace mozilla::dom