|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */ |
|
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 |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "ChromeObjectWrapper.h" |
|
8 #include "jsapi.h" |
|
9 |
|
10 using namespace JS; |
|
11 |
|
12 namespace xpc { |
|
13 |
|
14 // When creating wrappers for chrome objects in content, we detect if the |
|
15 // prototype of the wrapped chrome object is a prototype for a standard class |
|
16 // (like Array.prototype). If it is, we use the corresponding standard prototype |
|
17 // from the wrapper's scope, rather than the wrapped standard prototype |
|
18 // from the wrappee's scope. |
|
19 // |
|
20 // One of the reasons for doing this is to allow standard operations like |
|
21 // chromeArray.forEach(..) to Just Work without explicitly listing them in |
|
22 // __exposedProps__. Since proxies don't automatically inherit behavior from |
|
23 // their prototype, we have to instrument the traps to do this manually. |
|
24 ChromeObjectWrapper ChromeObjectWrapper::singleton; |
|
25 |
|
26 using js::assertEnteredPolicy; |
|
27 |
|
28 static bool |
|
29 AllowedByBase(JSContext *cx, HandleObject wrapper, HandleId id, |
|
30 js::Wrapper::Action act) |
|
31 { |
|
32 MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == |
|
33 &ChromeObjectWrapper::singleton); |
|
34 bool bp; |
|
35 ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; |
|
36 return handler->ChromeObjectWrapperBase::enter(cx, wrapper, id, act, &bp); |
|
37 } |
|
38 |
|
39 static bool |
|
40 PropIsFromStandardPrototype(JSContext *cx, JS::MutableHandle<JSPropertyDescriptor> desc) |
|
41 { |
|
42 MOZ_ASSERT(desc.object()); |
|
43 RootedObject unwrapped(cx, js::UncheckedUnwrap(desc.object())); |
|
44 JSAutoCompartment ac(cx, unwrapped); |
|
45 return IdentifyStandardPrototype(unwrapped) != JSProto_Null; |
|
46 } |
|
47 |
|
48 // Note that we're past the policy enforcement stage, here, so we can query |
|
49 // ChromeObjectWrapperBase and get an unfiltered view of the underlying object. |
|
50 // This lets us determine whether the property we would have found (given a |
|
51 // transparent wrapper) would have come off a standard prototype. |
|
52 static bool |
|
53 PropIsFromStandardPrototype(JSContext *cx, HandleObject wrapper, |
|
54 HandleId id) |
|
55 { |
|
56 MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == |
|
57 &ChromeObjectWrapper::singleton); |
|
58 Rooted<JSPropertyDescriptor> desc(cx); |
|
59 ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; |
|
60 if (!handler->ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, |
|
61 &desc) || |
|
62 !desc.object()) |
|
63 { |
|
64 return false; |
|
65 } |
|
66 return PropIsFromStandardPrototype(cx, &desc); |
|
67 } |
|
68 |
|
69 bool |
|
70 ChromeObjectWrapper::getPropertyDescriptor(JSContext *cx, |
|
71 HandleObject wrapper, |
|
72 HandleId id, |
|
73 JS::MutableHandle<JSPropertyDescriptor> desc) |
|
74 { |
|
75 assertEnteredPolicy(cx, wrapper, id, GET | SET); |
|
76 // First, try a lookup on the base wrapper if permitted. |
|
77 desc.object().set(nullptr); |
|
78 if (AllowedByBase(cx, wrapper, id, Wrapper::GET) && |
|
79 !ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, |
|
80 desc)) { |
|
81 return false; |
|
82 } |
|
83 |
|
84 // If the property is something that can be found on a standard prototype, |
|
85 // prefer the one we'll get via the prototype chain in the content |
|
86 // compartment. |
|
87 if (desc.object() && PropIsFromStandardPrototype(cx, desc)) |
|
88 desc.object().set(nullptr); |
|
89 |
|
90 // If we found something or have no proto, we're done. |
|
91 RootedObject wrapperProto(cx); |
|
92 if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) |
|
93 return false; |
|
94 if (desc.object() || !wrapperProto) |
|
95 return true; |
|
96 |
|
97 // If not, try doing the lookup on the prototype. |
|
98 MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); |
|
99 return JS_GetPropertyDescriptorById(cx, wrapperProto, id, desc); |
|
100 } |
|
101 |
|
102 bool |
|
103 ChromeObjectWrapper::has(JSContext *cx, HandleObject wrapper, |
|
104 HandleId id, bool *bp) |
|
105 { |
|
106 assertEnteredPolicy(cx, wrapper, id, GET); |
|
107 // Try the lookup on the base wrapper if permitted. |
|
108 if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && |
|
109 !ChromeObjectWrapperBase::has(cx, wrapper, id, bp)) |
|
110 { |
|
111 return false; |
|
112 } |
|
113 |
|
114 // If we found something or have no prototype, we're done. |
|
115 RootedObject wrapperProto(cx); |
|
116 if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) |
|
117 return false; |
|
118 if (*bp || !wrapperProto) |
|
119 return true; |
|
120 |
|
121 // Try the prototype if that failed. |
|
122 MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); |
|
123 Rooted<JSPropertyDescriptor> desc(cx); |
|
124 if (!JS_GetPropertyDescriptorById(cx, wrapperProto, id, &desc)) |
|
125 return false; |
|
126 *bp = !!desc.object(); |
|
127 return true; |
|
128 } |
|
129 |
|
130 bool |
|
131 ChromeObjectWrapper::get(JSContext *cx, HandleObject wrapper, |
|
132 HandleObject receiver, HandleId id, |
|
133 MutableHandleValue vp) |
|
134 { |
|
135 assertEnteredPolicy(cx, wrapper, id, GET); |
|
136 vp.setUndefined(); |
|
137 // Only call through to the get trap on the underlying object if we're |
|
138 // allowed to see the property, and if what we'll find is not on a standard |
|
139 // prototype. |
|
140 if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && |
|
141 !PropIsFromStandardPrototype(cx, wrapper, id)) |
|
142 { |
|
143 // Call the get trap. |
|
144 if (!ChromeObjectWrapperBase::get(cx, wrapper, receiver, id, vp)) |
|
145 return false; |
|
146 // If we found something, we're done. |
|
147 if (!vp.isUndefined()) |
|
148 return true; |
|
149 } |
|
150 |
|
151 // If we have no proto, we're done. |
|
152 RootedObject wrapperProto(cx); |
|
153 if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) |
|
154 return false; |
|
155 if (!wrapperProto) |
|
156 return true; |
|
157 |
|
158 // Try the prototype. |
|
159 MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); |
|
160 return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address()); |
|
161 } |
|
162 |
|
163 // SecurityWrapper categorically returns false for objectClassIs, but the |
|
164 // contacts API depends on Array.isArray returning true for COW-implemented |
|
165 // contacts. This isn't really ideal, but make it work for now. |
|
166 bool |
|
167 ChromeObjectWrapper::objectClassIs(HandleObject obj, js::ESClassValue classValue, |
|
168 JSContext *cx) |
|
169 { |
|
170 return CrossCompartmentWrapper::objectClassIs(obj, classValue, cx); |
|
171 } |
|
172 |
|
173 // This mechanism isn't ideal because we end up calling enter() on the base class |
|
174 // twice (once during enter() here and once during the trap itself), and policy |
|
175 // enforcement or COWs isn't cheap. But it results in the cleanest code, and this |
|
176 // whole proto remapping thing for COWs is going to be phased out anyway. |
|
177 bool |
|
178 ChromeObjectWrapper::enter(JSContext *cx, HandleObject wrapper, |
|
179 HandleId id, js::Wrapper::Action act, bool *bp) |
|
180 { |
|
181 if (AllowedByBase(cx, wrapper, id, act)) |
|
182 return true; |
|
183 // COWs fail silently for GETs, and that also happens to be the only case |
|
184 // where we might want to redirect the lookup to the home prototype chain. |
|
185 *bp = act == Wrapper::GET || act == Wrapper::ENUMERATE; |
|
186 if (!*bp || id == JSID_VOID) |
|
187 return false; |
|
188 |
|
189 // Note that PropIsFromStandardPrototype needs to invoke getPropertyDescriptor |
|
190 // before we've fully entered the policy. Waive our policy. |
|
191 js::AutoWaivePolicy policy(cx, wrapper, id, act); |
|
192 return PropIsFromStandardPrototype(cx, wrapper, id); |
|
193 } |
|
194 |
|
195 } |