Line data Source code
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 : #ifndef mozilla_dom_DOMJSProxyHandler_h
8 : #define mozilla_dom_DOMJSProxyHandler_h
9 :
10 : #include "mozilla/Attributes.h"
11 : #include "mozilla/Likely.h"
12 :
13 : #include "jsapi.h"
14 : #include "js/Proxy.h"
15 : #include "nsString.h"
16 :
17 : namespace mozilla {
18 : namespace dom {
19 :
20 : /**
21 : * DOM proxies store the expando object in the private slot.
22 : *
23 : * The expando object is a plain JSObject whose properties correspond to
24 : * "expandos" (custom properties set by the script author).
25 : *
26 : * The exact value stored in the proxy's private slot depends on whether the
27 : * interface is annotated with the [OverrideBuiltins] extended attribute.
28 : *
29 : * If it is, the proxy is initialized with a PrivateValue, which contains a
30 : * pointer to a js::ExpandoAndGeneration object; this contains a pointer to
31 : * the actual expando object as well as the "generation" of the object. The
32 : * proxy handler will trace the expando object stored in the
33 : * js::ExpandoAndGeneration while the proxy itself is alive.
34 : *
35 : * If it is not, the proxy is initialized with an UndefinedValue. In
36 : * EnsureExpandoObject, it is set to an ObjectValue that points to the
37 : * expando object directly. (It is set back to an UndefinedValue only when
38 : * the object is about to die.)
39 : */
40 :
41 : template<typename T> struct Prefable;
42 :
43 : class BaseDOMProxyHandler : public js::BaseProxyHandler
44 : {
45 : public:
46 : explicit constexpr BaseDOMProxyHandler(const void* aProxyFamily, bool aHasPrototype = false)
47 : : js::BaseProxyHandler(aProxyFamily, aHasPrototype)
48 : {}
49 :
50 : // Implementations of methods that can be implemented in terms of
51 : // other lower-level methods.
52 : bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy,
53 : JS::Handle<jsid> id,
54 : JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
55 : virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
56 : JS::AutoIdVector &props) const override;
57 :
58 : virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
59 : bool* isOrdinary,
60 : JS::MutableHandle<JSObject*> proto) const override;
61 :
62 : // We override getOwnEnumerablePropertyKeys() and implement it directly
63 : // instead of using the default implementation, which would call
64 : // ownPropertyKeys and then filter out the non-enumerable ones. This avoids
65 : // unnecessary work during enumeration.
66 : virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
67 : JS::AutoIdVector &props) const override;
68 :
69 : bool watch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
70 : JS::Handle<JSObject*> callable) const override;
71 : bool unwatch(JSContext* cx, JS::Handle<JSObject*> proxy,
72 : JS::Handle<jsid> id) const override;
73 :
74 : protected:
75 : // Hook for subclasses to implement shared ownPropertyKeys()/keys()
76 : // functionality. The "flags" argument is either JSITER_OWNONLY (for keys())
77 : // or JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS (for
78 : // ownPropertyKeys()).
79 : virtual bool ownPropNames(JSContext* cx, JS::Handle<JSObject*> proxy,
80 : unsigned flags,
81 : JS::AutoIdVector& props) const = 0;
82 :
83 : // Hook for subclasses to allow set() to ignore named props while other things
84 : // that look at property descriptors see them. This is intentionally not
85 : // named getOwnPropertyDescriptor to avoid subclasses that override it hiding
86 : // our public getOwnPropertyDescriptor.
87 : virtual bool getOwnPropDescriptor(JSContext* cx,
88 : JS::Handle<JSObject*> proxy,
89 : JS::Handle<jsid> id,
90 : bool ignoreNamedProps,
91 : JS::MutableHandle<JS::PropertyDescriptor> desc) const = 0;
92 : };
93 :
94 : class DOMProxyHandler : public BaseDOMProxyHandler
95 : {
96 : public:
97 : constexpr DOMProxyHandler()
98 : : BaseDOMProxyHandler(&family)
99 : {}
100 :
101 0 : bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
102 : JS::Handle<JS::PropertyDescriptor> desc,
103 : JS::ObjectOpResult &result) const override
104 : {
105 : bool unused;
106 0 : return defineProperty(cx, proxy, id, desc, result, &unused);
107 : }
108 : virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
109 : JS::Handle<JS::PropertyDescriptor> desc,
110 : JS::ObjectOpResult &result, bool *defined) const;
111 : bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
112 : JS::ObjectOpResult &result) const override;
113 : bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
114 : JS::ObjectOpResult& result) const override;
115 : bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
116 : const override;
117 : bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
118 : JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver, JS::ObjectOpResult &result)
119 : const override;
120 :
121 : /*
122 : * If assigning to proxy[id] hits a named setter with OverrideBuiltins or
123 : * an indexed setter, call it and set *done to true on success. Otherwise, set
124 : * *done to false.
125 : */
126 : virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
127 : JS::Handle<JS::Value> v, bool *done) const;
128 :
129 : /*
130 : * Get the expando object for the given DOM proxy.
131 : */
132 : static JSObject* GetExpandoObject(JSObject* obj);
133 :
134 : /*
135 : * Clear the expando object for the given DOM proxy and return it. This
136 : * function will ensure that the returned object is exposed to active JS if
137 : * the given object is exposed.
138 : *
139 : * GetAndClearExpandoObject does not DROP or clear the preserving wrapper
140 : * flag.
141 : */
142 : static JSObject* GetAndClearExpandoObject(JSObject* obj);
143 :
144 : /*
145 : * Ensure that the given proxy (obj) has an expando object, and return it.
146 : * Returns null on failure.
147 : */
148 : static JSObject* EnsureExpandoObject(JSContext* cx,
149 : JS::Handle<JSObject*> obj);
150 :
151 : static const char family;
152 : };
153 :
154 : // Class used by shadowing handlers (the ones that have [OverrideBuiltins].
155 : // This handles tracing the expando in ExpandoAndGeneration.
156 : class ShadowingDOMProxyHandler : public DOMProxyHandler
157 : {
158 : virtual void trace(JSTracer* trc, JSObject* proxy) const override;
159 : };
160 :
161 91 : inline bool IsDOMProxy(JSObject *obj)
162 : {
163 91 : const js::Class* clasp = js::GetObjectClass(obj);
164 182 : return clasp->isProxy() &&
165 182 : js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
166 : }
167 :
168 : inline const DOMProxyHandler*
169 0 : GetDOMProxyHandler(JSObject* obj)
170 : {
171 0 : MOZ_ASSERT(IsDOMProxy(obj));
172 0 : return static_cast<const DOMProxyHandler*>(js::GetProxyHandler(obj));
173 : }
174 :
175 : extern jsid s_length_id;
176 :
177 : // A return value of UINT32_MAX indicates "not an array index". Note, in
178 : // particular, that UINT32_MAX itself is not a valid array index in general.
179 : inline uint32_t
180 1447 : GetArrayIndexFromId(JSContext* cx, JS::Handle<jsid> id)
181 : {
182 : // Much like js::IdIsIndex, except with a fast path for "length" and another
183 : // fast path for starting with a lowercase ascii char. Is that second one
184 : // really needed? I guess it is because StringIsArrayIndex is out of line...
185 1447 : if (MOZ_LIKELY(JSID_IS_INT(id))) {
186 76 : return JSID_TO_INT(id);
187 : }
188 1371 : if (MOZ_LIKELY(id == s_length_id)) {
189 54 : return UINT32_MAX;
190 : }
191 1317 : if (MOZ_UNLIKELY(!JSID_IS_ATOM(id))) {
192 14 : return UINT32_MAX;
193 : }
194 :
195 1303 : JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id));
196 : char16_t s;
197 : {
198 2606 : JS::AutoCheckCannotGC nogc;
199 1303 : if (js::LinearStringHasLatin1Chars(str)) {
200 1303 : s = *js::GetLatin1LinearStringChars(nogc, str);
201 : } else {
202 0 : s = *js::GetTwoByteLinearStringChars(nogc, str);
203 : }
204 : }
205 1303 : if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z'))
206 966 : return UINT32_MAX;
207 :
208 : uint32_t i;
209 337 : return js::StringIsArrayIndex(str, &i) ? i : UINT32_MAX;
210 : }
211 :
212 : inline bool
213 1448 : IsArrayIndex(uint32_t index)
214 : {
215 1448 : return index < UINT32_MAX;
216 : }
217 :
218 : inline void
219 14 : FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc,
220 : JSObject* obj, bool readonly, bool enumerable = true)
221 : {
222 14 : desc.object().set(obj);
223 28 : desc.setAttributes((readonly ? JSPROP_READONLY : 0) |
224 28 : (enumerable ? JSPROP_ENUMERATE : 0));
225 14 : desc.setGetter(nullptr);
226 14 : desc.setSetter(nullptr);
227 14 : }
228 :
229 : inline void
230 14 : FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc,
231 : JSObject* obj, const JS::Value& v,
232 : bool readonly, bool enumerable = true)
233 : {
234 14 : desc.value().set(v);
235 14 : FillPropertyDescriptor(desc, obj, readonly, enumerable);
236 14 : }
237 :
238 : inline void
239 8 : FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc,
240 : JSObject* obj, unsigned attributes, const JS::Value& v)
241 : {
242 8 : desc.object().set(obj);
243 8 : desc.value().set(v);
244 8 : desc.setAttributes(attributes);
245 8 : desc.setGetter(nullptr);
246 8 : desc.setSetter(nullptr);
247 8 : }
248 :
249 : } // namespace dom
250 : } // namespace mozilla
251 :
252 : #endif /* mozilla_dom_DOMProxyHandler_h */
|