1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,195 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "ChromeObjectWrapper.h" 1.11 +#include "jsapi.h" 1.12 + 1.13 +using namespace JS; 1.14 + 1.15 +namespace xpc { 1.16 + 1.17 +// When creating wrappers for chrome objects in content, we detect if the 1.18 +// prototype of the wrapped chrome object is a prototype for a standard class 1.19 +// (like Array.prototype). If it is, we use the corresponding standard prototype 1.20 +// from the wrapper's scope, rather than the wrapped standard prototype 1.21 +// from the wrappee's scope. 1.22 +// 1.23 +// One of the reasons for doing this is to allow standard operations like 1.24 +// chromeArray.forEach(..) to Just Work without explicitly listing them in 1.25 +// __exposedProps__. Since proxies don't automatically inherit behavior from 1.26 +// their prototype, we have to instrument the traps to do this manually. 1.27 +ChromeObjectWrapper ChromeObjectWrapper::singleton; 1.28 + 1.29 +using js::assertEnteredPolicy; 1.30 + 1.31 +static bool 1.32 +AllowedByBase(JSContext *cx, HandleObject wrapper, HandleId id, 1.33 + js::Wrapper::Action act) 1.34 +{ 1.35 + MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == 1.36 + &ChromeObjectWrapper::singleton); 1.37 + bool bp; 1.38 + ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; 1.39 + return handler->ChromeObjectWrapperBase::enter(cx, wrapper, id, act, &bp); 1.40 +} 1.41 + 1.42 +static bool 1.43 +PropIsFromStandardPrototype(JSContext *cx, JS::MutableHandle<JSPropertyDescriptor> desc) 1.44 +{ 1.45 + MOZ_ASSERT(desc.object()); 1.46 + RootedObject unwrapped(cx, js::UncheckedUnwrap(desc.object())); 1.47 + JSAutoCompartment ac(cx, unwrapped); 1.48 + return IdentifyStandardPrototype(unwrapped) != JSProto_Null; 1.49 +} 1.50 + 1.51 +// Note that we're past the policy enforcement stage, here, so we can query 1.52 +// ChromeObjectWrapperBase and get an unfiltered view of the underlying object. 1.53 +// This lets us determine whether the property we would have found (given a 1.54 +// transparent wrapper) would have come off a standard prototype. 1.55 +static bool 1.56 +PropIsFromStandardPrototype(JSContext *cx, HandleObject wrapper, 1.57 + HandleId id) 1.58 +{ 1.59 + MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) == 1.60 + &ChromeObjectWrapper::singleton); 1.61 + Rooted<JSPropertyDescriptor> desc(cx); 1.62 + ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton; 1.63 + if (!handler->ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, 1.64 + &desc) || 1.65 + !desc.object()) 1.66 + { 1.67 + return false; 1.68 + } 1.69 + return PropIsFromStandardPrototype(cx, &desc); 1.70 +} 1.71 + 1.72 +bool 1.73 +ChromeObjectWrapper::getPropertyDescriptor(JSContext *cx, 1.74 + HandleObject wrapper, 1.75 + HandleId id, 1.76 + JS::MutableHandle<JSPropertyDescriptor> desc) 1.77 +{ 1.78 + assertEnteredPolicy(cx, wrapper, id, GET | SET); 1.79 + // First, try a lookup on the base wrapper if permitted. 1.80 + desc.object().set(nullptr); 1.81 + if (AllowedByBase(cx, wrapper, id, Wrapper::GET) && 1.82 + !ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, 1.83 + desc)) { 1.84 + return false; 1.85 + } 1.86 + 1.87 + // If the property is something that can be found on a standard prototype, 1.88 + // prefer the one we'll get via the prototype chain in the content 1.89 + // compartment. 1.90 + if (desc.object() && PropIsFromStandardPrototype(cx, desc)) 1.91 + desc.object().set(nullptr); 1.92 + 1.93 + // If we found something or have no proto, we're done. 1.94 + RootedObject wrapperProto(cx); 1.95 + if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) 1.96 + return false; 1.97 + if (desc.object() || !wrapperProto) 1.98 + return true; 1.99 + 1.100 + // If not, try doing the lookup on the prototype. 1.101 + MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); 1.102 + return JS_GetPropertyDescriptorById(cx, wrapperProto, id, desc); 1.103 +} 1.104 + 1.105 +bool 1.106 +ChromeObjectWrapper::has(JSContext *cx, HandleObject wrapper, 1.107 + HandleId id, bool *bp) 1.108 +{ 1.109 + assertEnteredPolicy(cx, wrapper, id, GET); 1.110 + // Try the lookup on the base wrapper if permitted. 1.111 + if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && 1.112 + !ChromeObjectWrapperBase::has(cx, wrapper, id, bp)) 1.113 + { 1.114 + return false; 1.115 + } 1.116 + 1.117 + // If we found something or have no prototype, we're done. 1.118 + RootedObject wrapperProto(cx); 1.119 + if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) 1.120 + return false; 1.121 + if (*bp || !wrapperProto) 1.122 + return true; 1.123 + 1.124 + // Try the prototype if that failed. 1.125 + MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); 1.126 + Rooted<JSPropertyDescriptor> desc(cx); 1.127 + if (!JS_GetPropertyDescriptorById(cx, wrapperProto, id, &desc)) 1.128 + return false; 1.129 + *bp = !!desc.object(); 1.130 + return true; 1.131 +} 1.132 + 1.133 +bool 1.134 +ChromeObjectWrapper::get(JSContext *cx, HandleObject wrapper, 1.135 + HandleObject receiver, HandleId id, 1.136 + MutableHandleValue vp) 1.137 +{ 1.138 + assertEnteredPolicy(cx, wrapper, id, GET); 1.139 + vp.setUndefined(); 1.140 + // Only call through to the get trap on the underlying object if we're 1.141 + // allowed to see the property, and if what we'll find is not on a standard 1.142 + // prototype. 1.143 + if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) && 1.144 + !PropIsFromStandardPrototype(cx, wrapper, id)) 1.145 + { 1.146 + // Call the get trap. 1.147 + if (!ChromeObjectWrapperBase::get(cx, wrapper, receiver, id, vp)) 1.148 + return false; 1.149 + // If we found something, we're done. 1.150 + if (!vp.isUndefined()) 1.151 + return true; 1.152 + } 1.153 + 1.154 + // If we have no proto, we're done. 1.155 + RootedObject wrapperProto(cx); 1.156 + if (!JS_GetPrototype(cx, wrapper, &wrapperProto)) 1.157 + return false; 1.158 + if (!wrapperProto) 1.159 + return true; 1.160 + 1.161 + // Try the prototype. 1.162 + MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); 1.163 + return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address()); 1.164 +} 1.165 + 1.166 +// SecurityWrapper categorically returns false for objectClassIs, but the 1.167 +// contacts API depends on Array.isArray returning true for COW-implemented 1.168 +// contacts. This isn't really ideal, but make it work for now. 1.169 +bool 1.170 +ChromeObjectWrapper::objectClassIs(HandleObject obj, js::ESClassValue classValue, 1.171 + JSContext *cx) 1.172 +{ 1.173 + return CrossCompartmentWrapper::objectClassIs(obj, classValue, cx); 1.174 +} 1.175 + 1.176 +// This mechanism isn't ideal because we end up calling enter() on the base class 1.177 +// twice (once during enter() here and once during the trap itself), and policy 1.178 +// enforcement or COWs isn't cheap. But it results in the cleanest code, and this 1.179 +// whole proto remapping thing for COWs is going to be phased out anyway. 1.180 +bool 1.181 +ChromeObjectWrapper::enter(JSContext *cx, HandleObject wrapper, 1.182 + HandleId id, js::Wrapper::Action act, bool *bp) 1.183 +{ 1.184 + if (AllowedByBase(cx, wrapper, id, act)) 1.185 + return true; 1.186 + // COWs fail silently for GETs, and that also happens to be the only case 1.187 + // where we might want to redirect the lookup to the home prototype chain. 1.188 + *bp = act == Wrapper::GET || act == Wrapper::ENUMERATE; 1.189 + if (!*bp || id == JSID_VOID) 1.190 + return false; 1.191 + 1.192 + // Note that PropIsFromStandardPrototype needs to invoke getPropertyDescriptor 1.193 + // before we've fully entered the policy. Waive our policy. 1.194 + js::AutoWaivePolicy policy(cx, wrapper, id, act); 1.195 + return PropIsFromStandardPrototype(cx, wrapper, id); 1.196 +} 1.197 + 1.198 +}