Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
7 #include "ChromeObjectWrapper.h"
8 #include "jsapi.h"
10 using namespace JS;
12 namespace xpc {
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;
26 using js::assertEnteredPolicy;
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 }
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 }
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 }
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 }
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);
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;
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 }
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 }
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;
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 }
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 }
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;
158 // Try the prototype.
159 MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
160 return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address());
161 }
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 }
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;
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 }
195 }