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.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=4 et sw=4 tw=99: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "XrayWrapper.h" |
michael@0 | 8 | #include "AccessCheck.h" |
michael@0 | 9 | #include "WrapperFactory.h" |
michael@0 | 10 | |
michael@0 | 11 | #include "nsIContent.h" |
michael@0 | 12 | #include "nsIControllers.h" |
michael@0 | 13 | #include "nsContentUtils.h" |
michael@0 | 14 | |
michael@0 | 15 | #include "XPCWrapper.h" |
michael@0 | 16 | #include "xpcprivate.h" |
michael@0 | 17 | |
michael@0 | 18 | #include "jsapi.h" |
michael@0 | 19 | #include "jsprf.h" |
michael@0 | 20 | #include "nsJSUtils.h" |
michael@0 | 21 | |
michael@0 | 22 | #include "mozilla/dom/BindingUtils.h" |
michael@0 | 23 | #include "mozilla/dom/WindowBinding.h" |
michael@0 | 24 | #include "nsGlobalWindow.h" |
michael@0 | 25 | |
michael@0 | 26 | using namespace mozilla::dom; |
michael@0 | 27 | using namespace JS; |
michael@0 | 28 | using namespace mozilla; |
michael@0 | 29 | |
michael@0 | 30 | using js::Wrapper; |
michael@0 | 31 | using js::BaseProxyHandler; |
michael@0 | 32 | using js::IsCrossCompartmentWrapper; |
michael@0 | 33 | using js::UncheckedUnwrap; |
michael@0 | 34 | using js::CheckedUnwrap; |
michael@0 | 35 | |
michael@0 | 36 | namespace xpc { |
michael@0 | 37 | |
michael@0 | 38 | using namespace XrayUtils; |
michael@0 | 39 | |
michael@0 | 40 | // Whitelist for the standard ES classes we can Xray to. |
michael@0 | 41 | static bool |
michael@0 | 42 | IsJSXraySupported(JSProtoKey key) |
michael@0 | 43 | { |
michael@0 | 44 | switch (key) { |
michael@0 | 45 | case JSProto_Date: |
michael@0 | 46 | return true; |
michael@0 | 47 | default: |
michael@0 | 48 | return false; |
michael@0 | 49 | } |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | XrayType |
michael@0 | 53 | GetXrayType(JSObject *obj) |
michael@0 | 54 | { |
michael@0 | 55 | obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false); |
michael@0 | 56 | if (mozilla::dom::UseDOMXray(obj)) |
michael@0 | 57 | return XrayForDOMObject; |
michael@0 | 58 | |
michael@0 | 59 | const js::Class* clasp = js::GetObjectClass(obj); |
michael@0 | 60 | if (IS_WN_CLASS(clasp) || clasp->ext.innerObject) |
michael@0 | 61 | return XrayForWrappedNative; |
michael@0 | 62 | |
michael@0 | 63 | JSProtoKey standardProto = IdentifyStandardInstanceOrPrototype(obj); |
michael@0 | 64 | if (IsJSXraySupported(standardProto)) |
michael@0 | 65 | return XrayForJSObject; |
michael@0 | 66 | |
michael@0 | 67 | return NotXray; |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | JSObject * |
michael@0 | 71 | XrayAwareCalleeGlobal(JSObject *fun) |
michael@0 | 72 | { |
michael@0 | 73 | MOZ_ASSERT(js::IsFunctionObject(fun)); |
michael@0 | 74 | JSObject *scope = js::GetObjectParent(fun); |
michael@0 | 75 | if (IsXrayWrapper(scope)) |
michael@0 | 76 | scope = js::UncheckedUnwrap(scope); |
michael@0 | 77 | return js::GetGlobalForObjectCrossCompartment(scope); |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | const uint32_t JSSLOT_RESOLVING = 0; |
michael@0 | 81 | ResolvingId::ResolvingId(JSContext *cx, HandleObject wrapper, HandleId id) |
michael@0 | 82 | : mId(id), |
michael@0 | 83 | mHolder(cx, getHolderObject(wrapper)), |
michael@0 | 84 | mPrev(getResolvingId(mHolder)), |
michael@0 | 85 | mXrayShadowing(false) |
michael@0 | 86 | { |
michael@0 | 87 | js::SetReservedSlot(mHolder, JSSLOT_RESOLVING, js::PrivateValue(this)); |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | ResolvingId::~ResolvingId() |
michael@0 | 91 | { |
michael@0 | 92 | MOZ_ASSERT(getResolvingId(mHolder) == this, "unbalanced ResolvingIds"); |
michael@0 | 93 | js::SetReservedSlot(mHolder, JSSLOT_RESOLVING, js::PrivateValue(mPrev)); |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | bool |
michael@0 | 97 | ResolvingId::isXrayShadowing(jsid id) |
michael@0 | 98 | { |
michael@0 | 99 | if (!mXrayShadowing) |
michael@0 | 100 | return false; |
michael@0 | 101 | |
michael@0 | 102 | return mId == id; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | bool |
michael@0 | 106 | ResolvingId::isResolving(jsid id) |
michael@0 | 107 | { |
michael@0 | 108 | for (ResolvingId *cur = this; cur; cur = cur->mPrev) { |
michael@0 | 109 | if (cur->mId == id) |
michael@0 | 110 | return true; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | return false; |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | ResolvingId * |
michael@0 | 117 | ResolvingId::getResolvingId(JSObject *holder) |
michael@0 | 118 | { |
michael@0 | 119 | MOZ_ASSERT(strcmp(JS_GetClass(holder)->name, "NativePropertyHolder") == 0); |
michael@0 | 120 | return (ResolvingId *)js::GetReservedSlot(holder, JSSLOT_RESOLVING).toPrivate(); |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | JSObject * |
michael@0 | 124 | ResolvingId::getHolderObject(JSObject *wrapper) |
michael@0 | 125 | { |
michael@0 | 126 | return &js::GetProxyExtra(wrapper, 0).toObject(); |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | ResolvingId * |
michael@0 | 130 | ResolvingId::getResolvingIdFromWrapper(JSObject *wrapper) |
michael@0 | 131 | { |
michael@0 | 132 | return getResolvingId(getHolderObject(wrapper)); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | class MOZ_STACK_CLASS ResolvingIdDummy |
michael@0 | 136 | { |
michael@0 | 137 | public: |
michael@0 | 138 | ResolvingIdDummy(JSContext *cx, HandleObject wrapper, HandleId id) |
michael@0 | 139 | { |
michael@0 | 140 | } |
michael@0 | 141 | }; |
michael@0 | 142 | |
michael@0 | 143 | class XrayTraits |
michael@0 | 144 | { |
michael@0 | 145 | public: |
michael@0 | 146 | static JSObject* getTargetObject(JSObject *wrapper) { |
michael@0 | 147 | return js::UncheckedUnwrap(wrapper, /* stopAtOuter = */ false); |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 151 | HandleObject holder, HandleId id, |
michael@0 | 152 | MutableHandle<JSPropertyDescriptor> desc) = 0; |
michael@0 | 153 | // NB: resolveOwnProperty may decide whether or not to cache what it finds |
michael@0 | 154 | // on the holder. If the result is not cached, the lookup will happen afresh |
michael@0 | 155 | // for each access, which is the right thing for things like dynamic NodeList |
michael@0 | 156 | // properties. |
michael@0 | 157 | virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, |
michael@0 | 158 | HandleObject wrapper, HandleObject holder, |
michael@0 | 159 | HandleId id, MutableHandle<JSPropertyDescriptor> desc); |
michael@0 | 160 | |
michael@0 | 161 | virtual void preserveWrapper(JSObject *target) = 0; |
michael@0 | 162 | |
michael@0 | 163 | static bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id, |
michael@0 | 164 | bool strict, MutableHandleValue vp); |
michael@0 | 165 | |
michael@0 | 166 | JSObject* getExpandoObject(JSContext *cx, HandleObject target, |
michael@0 | 167 | HandleObject consumer); |
michael@0 | 168 | JSObject* ensureExpandoObject(JSContext *cx, HandleObject wrapper, |
michael@0 | 169 | HandleObject target); |
michael@0 | 170 | |
michael@0 | 171 | JSObject* getHolder(JSObject *wrapper); |
michael@0 | 172 | JSObject* ensureHolder(JSContext *cx, HandleObject wrapper); |
michael@0 | 173 | virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) = 0; |
michael@0 | 174 | |
michael@0 | 175 | JSObject* getExpandoChain(HandleObject obj) { |
michael@0 | 176 | return GetObjectScope(obj)->GetExpandoChain(obj); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | bool setExpandoChain(JSContext *cx, HandleObject obj, HandleObject chain) { |
michael@0 | 180 | return GetObjectScope(obj)->SetExpandoChain(cx, obj, chain); |
michael@0 | 181 | } |
michael@0 | 182 | bool cloneExpandoChain(JSContext *cx, HandleObject dst, HandleObject src); |
michael@0 | 183 | |
michael@0 | 184 | private: |
michael@0 | 185 | bool expandoObjectMatchesConsumer(JSContext *cx, HandleObject expandoObject, |
michael@0 | 186 | nsIPrincipal *consumerOrigin, |
michael@0 | 187 | HandleObject exclusiveGlobal); |
michael@0 | 188 | JSObject* getExpandoObjectInternal(JSContext *cx, HandleObject target, |
michael@0 | 189 | nsIPrincipal *origin, |
michael@0 | 190 | JSObject *exclusiveGlobal); |
michael@0 | 191 | JSObject* attachExpandoObject(JSContext *cx, HandleObject target, |
michael@0 | 192 | nsIPrincipal *origin, |
michael@0 | 193 | HandleObject exclusiveGlobal); |
michael@0 | 194 | }; |
michael@0 | 195 | |
michael@0 | 196 | class XPCWrappedNativeXrayTraits : public XrayTraits |
michael@0 | 197 | { |
michael@0 | 198 | public: |
michael@0 | 199 | enum { |
michael@0 | 200 | HasPrototype = 0 |
michael@0 | 201 | }; |
michael@0 | 202 | |
michael@0 | 203 | static const XrayType Type = XrayForWrappedNative; |
michael@0 | 204 | |
michael@0 | 205 | virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 206 | HandleObject holder, HandleId id, |
michael@0 | 207 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 208 | virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper, |
michael@0 | 209 | HandleObject holder, HandleId id, |
michael@0 | 210 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 211 | static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 212 | MutableHandle<JSPropertyDescriptor> desc, |
michael@0 | 213 | Handle<JSPropertyDescriptor> existingDesc, bool *defined); |
michael@0 | 214 | virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 215 | AutoIdVector &props); |
michael@0 | 216 | static bool call(JSContext *cx, HandleObject wrapper, |
michael@0 | 217 | const JS::CallArgs &args, js::Wrapper& baseInstance); |
michael@0 | 218 | static bool construct(JSContext *cx, HandleObject wrapper, |
michael@0 | 219 | const JS::CallArgs &args, js::Wrapper& baseInstance); |
michael@0 | 220 | |
michael@0 | 221 | static bool isResolving(JSContext *cx, JSObject *holder, jsid id); |
michael@0 | 222 | |
michael@0 | 223 | static bool resolveDOMCollectionProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 224 | HandleObject holder, HandleId id, |
michael@0 | 225 | MutableHandle<JSPropertyDescriptor> desc); |
michael@0 | 226 | |
michael@0 | 227 | static XPCWrappedNative* getWN(JSObject *wrapper) { |
michael@0 | 228 | return XPCWrappedNative::Get(getTargetObject(wrapper)); |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | virtual void preserveWrapper(JSObject *target) MOZ_OVERRIDE; |
michael@0 | 232 | |
michael@0 | 233 | typedef ResolvingId ResolvingIdImpl; |
michael@0 | 234 | |
michael@0 | 235 | virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE; |
michael@0 | 236 | |
michael@0 | 237 | static const JSClass HolderClass; |
michael@0 | 238 | static XPCWrappedNativeXrayTraits singleton; |
michael@0 | 239 | }; |
michael@0 | 240 | |
michael@0 | 241 | const JSClass XPCWrappedNativeXrayTraits::HolderClass = { |
michael@0 | 242 | "NativePropertyHolder", JSCLASS_HAS_RESERVED_SLOTS(2), |
michael@0 | 243 | JS_PropertyStub, JS_DeletePropertyStub, holder_get, holder_set, |
michael@0 | 244 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub |
michael@0 | 245 | }; |
michael@0 | 246 | |
michael@0 | 247 | class DOMXrayTraits : public XrayTraits |
michael@0 | 248 | { |
michael@0 | 249 | public: |
michael@0 | 250 | enum { |
michael@0 | 251 | HasPrototype = 0 |
michael@0 | 252 | }; |
michael@0 | 253 | |
michael@0 | 254 | static const XrayType Type = XrayForDOMObject; |
michael@0 | 255 | |
michael@0 | 256 | virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 257 | HandleObject holder, HandleId id, |
michael@0 | 258 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 259 | virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper, |
michael@0 | 260 | HandleObject holder, HandleId id, |
michael@0 | 261 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 262 | static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 263 | MutableHandle<JSPropertyDescriptor> desc, |
michael@0 | 264 | Handle<JSPropertyDescriptor> existingDesc, bool *defined); |
michael@0 | 265 | static bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id, |
michael@0 | 266 | bool strict, MutableHandleValue vp); |
michael@0 | 267 | virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 268 | AutoIdVector &props); |
michael@0 | 269 | static bool call(JSContext *cx, HandleObject wrapper, |
michael@0 | 270 | const JS::CallArgs &args, js::Wrapper& baseInstance); |
michael@0 | 271 | static bool construct(JSContext *cx, HandleObject wrapper, |
michael@0 | 272 | const JS::CallArgs &args, js::Wrapper& baseInstance); |
michael@0 | 273 | |
michael@0 | 274 | static bool isResolving(JSContext *cx, JSObject *holder, jsid id) |
michael@0 | 275 | { |
michael@0 | 276 | return false; |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | typedef ResolvingIdDummy ResolvingIdImpl; |
michael@0 | 280 | |
michael@0 | 281 | virtual void preserveWrapper(JSObject *target) MOZ_OVERRIDE; |
michael@0 | 282 | |
michael@0 | 283 | virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE; |
michael@0 | 284 | |
michael@0 | 285 | static DOMXrayTraits singleton; |
michael@0 | 286 | }; |
michael@0 | 287 | |
michael@0 | 288 | class JSXrayTraits : public XrayTraits |
michael@0 | 289 | { |
michael@0 | 290 | public: |
michael@0 | 291 | enum { |
michael@0 | 292 | HasPrototype = 1 |
michael@0 | 293 | }; |
michael@0 | 294 | static const XrayType Type = XrayForJSObject; |
michael@0 | 295 | |
michael@0 | 296 | virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 297 | HandleObject holder, HandleId id, |
michael@0 | 298 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE |
michael@0 | 299 | { |
michael@0 | 300 | MOZ_ASSUME_UNREACHABLE("resolveNativeProperty hook should never be called with HasPrototype = 1"); |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper, |
michael@0 | 304 | HandleObject holder, HandleId id, |
michael@0 | 305 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 306 | |
michael@0 | 307 | static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 308 | MutableHandle<JSPropertyDescriptor> desc, |
michael@0 | 309 | Handle<JSPropertyDescriptor> existingDesc, bool *defined) |
michael@0 | 310 | { |
michael@0 | 311 | // There's no useful per-trait work to do here. Punt back up to the common code. |
michael@0 | 312 | *defined = false; |
michael@0 | 313 | return true; |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 317 | AutoIdVector &props); |
michael@0 | 318 | |
michael@0 | 319 | static bool call(JSContext *cx, HandleObject wrapper, |
michael@0 | 320 | const JS::CallArgs &args, js::Wrapper& baseInstance) |
michael@0 | 321 | { |
michael@0 | 322 | // We'll handle this when we start supporting Functions. |
michael@0 | 323 | RootedValue v(cx, ObjectValue(*wrapper)); |
michael@0 | 324 | js_ReportIsNotFunction(cx, v); |
michael@0 | 325 | return false; |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | static bool construct(JSContext *cx, HandleObject wrapper, |
michael@0 | 329 | const JS::CallArgs &args, js::Wrapper& baseInstance) |
michael@0 | 330 | { |
michael@0 | 331 | // We'll handle this when we start supporting Functions. |
michael@0 | 332 | RootedValue v(cx, ObjectValue(*wrapper)); |
michael@0 | 333 | js_ReportIsNotFunction(cx, v); |
michael@0 | 334 | return false; |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | static bool isResolving(JSContext *cx, JSObject *holder, jsid id) |
michael@0 | 338 | { |
michael@0 | 339 | return false; |
michael@0 | 340 | } |
michael@0 | 341 | |
michael@0 | 342 | typedef ResolvingIdDummy ResolvingIdImpl; |
michael@0 | 343 | |
michael@0 | 344 | bool getPrototypeOf(JSContext *cx, JS::HandleObject wrapper, |
michael@0 | 345 | JS::HandleObject target, |
michael@0 | 346 | JS::MutableHandleObject protop) |
michael@0 | 347 | { |
michael@0 | 348 | RootedObject holder(cx, ensureHolder(cx, wrapper)); |
michael@0 | 349 | JSProtoKey key = isPrototype(holder) ? JSProto_Object |
michael@0 | 350 | : getProtoKey(holder); |
michael@0 | 351 | { |
michael@0 | 352 | JSAutoCompartment ac(cx, target); |
michael@0 | 353 | if (!JS_GetClassPrototype(cx, key, protop)) |
michael@0 | 354 | return nullptr; |
michael@0 | 355 | } |
michael@0 | 356 | return JS_WrapObject(cx, protop); |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | virtual void preserveWrapper(JSObject *target) MOZ_OVERRIDE { |
michael@0 | 360 | // In the case of pure JS objects, there is no underlying object, and |
michael@0 | 361 | // the target is the canonical representation of state. If it gets |
michael@0 | 362 | // collected, then expandos and such should be collected too. So there's |
michael@0 | 363 | // nothing to do here. |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | enum { |
michael@0 | 367 | SLOT_PROTOKEY = 0, |
michael@0 | 368 | SLOT_ISPROTOTYPE, |
michael@0 | 369 | SLOT_COUNT |
michael@0 | 370 | }; |
michael@0 | 371 | virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE; |
michael@0 | 372 | |
michael@0 | 373 | static JSProtoKey getProtoKey(JSObject *holder) { |
michael@0 | 374 | int32_t key = js::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32(); |
michael@0 | 375 | return static_cast<JSProtoKey>(key); |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | static bool isPrototype(JSObject *holder) { |
michael@0 | 379 | return js::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean(); |
michael@0 | 380 | } |
michael@0 | 381 | |
michael@0 | 382 | static const JSClass HolderClass; |
michael@0 | 383 | static JSXrayTraits singleton; |
michael@0 | 384 | }; |
michael@0 | 385 | |
michael@0 | 386 | const JSClass JSXrayTraits::HolderClass = { |
michael@0 | 387 | "JSXrayHolder", JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT), |
michael@0 | 388 | JS_PropertyStub, JS_DeletePropertyStub, |
michael@0 | 389 | JS_PropertyStub, JS_StrictPropertyStub, |
michael@0 | 390 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub |
michael@0 | 391 | }; |
michael@0 | 392 | |
michael@0 | 393 | bool |
michael@0 | 394 | JSXrayTraits::resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, |
michael@0 | 395 | HandleObject wrapper, HandleObject holder, |
michael@0 | 396 | HandleId id, |
michael@0 | 397 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 398 | { |
michael@0 | 399 | // Call the common code. |
michael@0 | 400 | bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, |
michael@0 | 401 | id, desc); |
michael@0 | 402 | if (!ok || desc.object()) |
michael@0 | 403 | return ok; |
michael@0 | 404 | |
michael@0 | 405 | // Non-prototypes don't have anything on them yet. |
michael@0 | 406 | if (!isPrototype(holder)) |
michael@0 | 407 | return true; |
michael@0 | 408 | |
michael@0 | 409 | // The non-HasPrototypes semantics implemented by traditional Xrays are kind |
michael@0 | 410 | // of broken with respect to |own|-ness and the holder. The common code |
michael@0 | 411 | // muddles through by only checking the holder for non-|own| lookups, but |
michael@0 | 412 | // that doesn't work for us. So we do an explicit holder check here, and hope |
michael@0 | 413 | // that this mess gets fixed up soon. |
michael@0 | 414 | if (!JS_GetPropertyDescriptorById(cx, holder, id, desc)) |
michael@0 | 415 | return false; |
michael@0 | 416 | if (desc.object()) { |
michael@0 | 417 | desc.object().set(wrapper); |
michael@0 | 418 | return true; |
michael@0 | 419 | } |
michael@0 | 420 | |
michael@0 | 421 | // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. |
michael@0 | 422 | RootedObject target(cx, getTargetObject(wrapper)); |
michael@0 | 423 | const js::Class *clasp = js::GetObjectClass(target); |
michael@0 | 424 | JSProtoKey protoKey = JSCLASS_CACHED_PROTO_KEY(clasp); |
michael@0 | 425 | MOZ_ASSERT(protoKey == getProtoKey(holder)); |
michael@0 | 426 | MOZ_ASSERT(clasp->spec.defined()); |
michael@0 | 427 | |
michael@0 | 428 | // Handle the 'constructor' property. |
michael@0 | 429 | if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_CONSTRUCTOR)) { |
michael@0 | 430 | RootedObject constructor(cx); |
michael@0 | 431 | { |
michael@0 | 432 | JSAutoCompartment ac(cx, target); |
michael@0 | 433 | if (!JS_GetClassObject(cx, protoKey, &constructor)) |
michael@0 | 434 | return false; |
michael@0 | 435 | } |
michael@0 | 436 | if (!JS_WrapObject(cx, &constructor)) |
michael@0 | 437 | return false; |
michael@0 | 438 | desc.object().set(wrapper); |
michael@0 | 439 | desc.setAttributes(0); |
michael@0 | 440 | desc.setGetter(nullptr); |
michael@0 | 441 | desc.setSetter(nullptr); |
michael@0 | 442 | desc.value().setObject(*constructor); |
michael@0 | 443 | return true; |
michael@0 | 444 | } |
michael@0 | 445 | |
michael@0 | 446 | // Find the properties available, if any. |
michael@0 | 447 | const JSFunctionSpec *fs = clasp->spec.prototypeFunctions; |
michael@0 | 448 | if (!fs) |
michael@0 | 449 | return true; |
michael@0 | 450 | |
michael@0 | 451 | // Compute the property name we're looking for. We'll handle indexed |
michael@0 | 452 | // properties when we start supporting arrays. |
michael@0 | 453 | if (!JSID_IS_STRING(id)) |
michael@0 | 454 | return true; |
michael@0 | 455 | Rooted<JSFlatString*> str(cx, JSID_TO_FLAT_STRING(id)); |
michael@0 | 456 | |
michael@0 | 457 | // Scan through the properties. If we don't find anything, we're done. |
michael@0 | 458 | for (; fs->name; ++fs) { |
michael@0 | 459 | // We don't support self-hosted functions yet. See bug 972987. |
michael@0 | 460 | if (fs->selfHostedName) |
michael@0 | 461 | continue; |
michael@0 | 462 | if (JS_FlatStringEqualsAscii(str, fs->name)) |
michael@0 | 463 | break; |
michael@0 | 464 | } |
michael@0 | 465 | if (!fs->name) |
michael@0 | 466 | return true; |
michael@0 | 467 | |
michael@0 | 468 | // Generate an Xrayed version of the method. |
michael@0 | 469 | Rooted<JSFunction*> fun(cx, JS_NewFunctionById(cx, fs->call.op, fs->nargs, |
michael@0 | 470 | 0, wrapper, id)); |
michael@0 | 471 | if (!fun) |
michael@0 | 472 | return false; |
michael@0 | 473 | |
michael@0 | 474 | // The generic Xray machinery only defines non-own properties on the holder. |
michael@0 | 475 | // This is broken, and will be fixed at some point, but for now we need to |
michael@0 | 476 | // cache the value explicitly. See the corresponding call to |
michael@0 | 477 | // JS_GetPropertyById at the top of this function. |
michael@0 | 478 | return JS_DefinePropertyById(cx, holder, id, |
michael@0 | 479 | ObjectValue(*JS_GetFunctionObject(fun)), |
michael@0 | 480 | nullptr, nullptr, 0) && |
michael@0 | 481 | JS_GetPropertyDescriptorById(cx, holder, id, desc); |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | bool |
michael@0 | 485 | JSXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 486 | AutoIdVector &props) |
michael@0 | 487 | { |
michael@0 | 488 | RootedObject holder(cx, ensureHolder(cx, wrapper)); |
michael@0 | 489 | if (!holder) |
michael@0 | 490 | return false; |
michael@0 | 491 | |
michael@0 | 492 | // Non-prototypes don't have anything on them yet. |
michael@0 | 493 | if (!isPrototype(holder)) |
michael@0 | 494 | return true; |
michael@0 | 495 | |
michael@0 | 496 | // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. |
michael@0 | 497 | RootedObject target(cx, getTargetObject(wrapper)); |
michael@0 | 498 | const js::Class *clasp = js::GetObjectClass(target); |
michael@0 | 499 | MOZ_ASSERT(JSCLASS_CACHED_PROTO_KEY(clasp) == getProtoKey(holder)); |
michael@0 | 500 | MOZ_ASSERT(clasp->spec.defined()); |
michael@0 | 501 | |
michael@0 | 502 | // Find the properties available, if any. |
michael@0 | 503 | const JSFunctionSpec *fs = clasp->spec.prototypeFunctions; |
michael@0 | 504 | if (!fs) |
michael@0 | 505 | return true; |
michael@0 | 506 | |
michael@0 | 507 | // Intern all the strings, and pass theme to the caller. |
michael@0 | 508 | for (; fs->name; ++fs) { |
michael@0 | 509 | // We don't support self-hosted functions yet. See bug 972987. |
michael@0 | 510 | if (fs->selfHostedName) |
michael@0 | 511 | continue; |
michael@0 | 512 | RootedString str(cx, JS_InternString(cx, fs->name)); |
michael@0 | 513 | if (!str) |
michael@0 | 514 | return false; |
michael@0 | 515 | if (!props.append(INTERNED_STRING_TO_JSID(cx, str))) |
michael@0 | 516 | return false; |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | // Add the 'constructor' property. |
michael@0 | 520 | return props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_CONSTRUCTOR)); |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | JSObject* |
michael@0 | 524 | JSXrayTraits::createHolder(JSContext *cx, JSObject *wrapper) |
michael@0 | 525 | { |
michael@0 | 526 | RootedObject global(cx, JS_GetGlobalForObject(cx, wrapper)); |
michael@0 | 527 | RootedObject target(cx, getTargetObject(wrapper)); |
michael@0 | 528 | RootedObject holder(cx, JS_NewObjectWithGivenProto(cx, &HolderClass, |
michael@0 | 529 | JS::NullPtr(), global)); |
michael@0 | 530 | if (!holder) |
michael@0 | 531 | return nullptr; |
michael@0 | 532 | |
michael@0 | 533 | // Compute information about the target. |
michael@0 | 534 | bool isPrototype = false; |
michael@0 | 535 | JSProtoKey key = IdentifyStandardInstance(target); |
michael@0 | 536 | if (key == JSProto_Null) { |
michael@0 | 537 | isPrototype = true; |
michael@0 | 538 | key = IdentifyStandardPrototype(target); |
michael@0 | 539 | } |
michael@0 | 540 | MOZ_ASSERT(key != JSProto_Null); |
michael@0 | 541 | |
michael@0 | 542 | // Store it on the holder. |
michael@0 | 543 | RootedValue v(cx); |
michael@0 | 544 | v.setNumber(static_cast<uint32_t>(key)); |
michael@0 | 545 | js::SetReservedSlot(holder, SLOT_PROTOKEY, v); |
michael@0 | 546 | v.setBoolean(isPrototype); |
michael@0 | 547 | js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v); |
michael@0 | 548 | |
michael@0 | 549 | return holder; |
michael@0 | 550 | } |
michael@0 | 551 | |
michael@0 | 552 | XPCWrappedNativeXrayTraits XPCWrappedNativeXrayTraits::singleton; |
michael@0 | 553 | DOMXrayTraits DOMXrayTraits::singleton; |
michael@0 | 554 | JSXrayTraits JSXrayTraits::singleton; |
michael@0 | 555 | |
michael@0 | 556 | XrayTraits* |
michael@0 | 557 | GetXrayTraits(JSObject *obj) |
michael@0 | 558 | { |
michael@0 | 559 | switch (GetXrayType(obj)) { |
michael@0 | 560 | case XrayForDOMObject: |
michael@0 | 561 | return &DOMXrayTraits::singleton; |
michael@0 | 562 | case XrayForWrappedNative: |
michael@0 | 563 | return &XPCWrappedNativeXrayTraits::singleton; |
michael@0 | 564 | case XrayForJSObject: |
michael@0 | 565 | return &JSXrayTraits::singleton; |
michael@0 | 566 | default: |
michael@0 | 567 | return nullptr; |
michael@0 | 568 | } |
michael@0 | 569 | } |
michael@0 | 570 | |
michael@0 | 571 | /* |
michael@0 | 572 | * Xray expando handling. |
michael@0 | 573 | * |
michael@0 | 574 | * We hang expandos for Xray wrappers off a reserved slot on the target object |
michael@0 | 575 | * so that same-origin compartments can share expandos for a given object. We |
michael@0 | 576 | * have a linked list of expando objects, one per origin. The properties on these |
michael@0 | 577 | * objects are generally wrappers pointing back to the compartment that applied |
michael@0 | 578 | * them. |
michael@0 | 579 | * |
michael@0 | 580 | * The expando objects should _never_ be exposed to script. The fact that they |
michael@0 | 581 | * live in the target compartment is a detail of the implementation, and does |
michael@0 | 582 | * not imply that code in the target compartment should be allowed to inspect |
michael@0 | 583 | * them. They are private to the origin that placed them. |
michael@0 | 584 | */ |
michael@0 | 585 | |
michael@0 | 586 | enum ExpandoSlots { |
michael@0 | 587 | JSSLOT_EXPANDO_NEXT = 0, |
michael@0 | 588 | JSSLOT_EXPANDO_ORIGIN, |
michael@0 | 589 | JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL, |
michael@0 | 590 | JSSLOT_EXPANDO_PROTOTYPE, |
michael@0 | 591 | JSSLOT_EXPANDO_COUNT |
michael@0 | 592 | }; |
michael@0 | 593 | |
michael@0 | 594 | static nsIPrincipal* |
michael@0 | 595 | ObjectPrincipal(JSObject *obj) |
michael@0 | 596 | { |
michael@0 | 597 | return GetCompartmentPrincipal(js::GetObjectCompartment(obj)); |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | static nsIPrincipal* |
michael@0 | 601 | GetExpandoObjectPrincipal(JSObject *expandoObject) |
michael@0 | 602 | { |
michael@0 | 603 | Value v = JS_GetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN); |
michael@0 | 604 | return static_cast<nsIPrincipal*>(v.toPrivate()); |
michael@0 | 605 | } |
michael@0 | 606 | |
michael@0 | 607 | static void |
michael@0 | 608 | ExpandoObjectFinalize(JSFreeOp *fop, JSObject *obj) |
michael@0 | 609 | { |
michael@0 | 610 | // Release the principal. |
michael@0 | 611 | nsIPrincipal *principal = GetExpandoObjectPrincipal(obj); |
michael@0 | 612 | NS_RELEASE(principal); |
michael@0 | 613 | } |
michael@0 | 614 | |
michael@0 | 615 | const JSClass ExpandoObjectClass = { |
michael@0 | 616 | "XrayExpandoObject", |
michael@0 | 617 | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_EXPANDO_COUNT), |
michael@0 | 618 | JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
michael@0 | 619 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ExpandoObjectFinalize |
michael@0 | 620 | }; |
michael@0 | 621 | |
michael@0 | 622 | bool |
michael@0 | 623 | XrayTraits::expandoObjectMatchesConsumer(JSContext *cx, |
michael@0 | 624 | HandleObject expandoObject, |
michael@0 | 625 | nsIPrincipal *consumerOrigin, |
michael@0 | 626 | HandleObject exclusiveGlobal) |
michael@0 | 627 | { |
michael@0 | 628 | MOZ_ASSERT(js::IsObjectInContextCompartment(expandoObject, cx)); |
michael@0 | 629 | |
michael@0 | 630 | // First, compare the principals. |
michael@0 | 631 | nsIPrincipal *o = GetExpandoObjectPrincipal(expandoObject); |
michael@0 | 632 | // Note that it's very important here to ignore document.domain. We |
michael@0 | 633 | // pull the principal for the expando object off of the first consumer |
michael@0 | 634 | // for a given origin, and freely share the expandos amongst multiple |
michael@0 | 635 | // same-origin consumers afterwards. However, this means that we have |
michael@0 | 636 | // no way to know whether _all_ consumers have opted in to collaboration |
michael@0 | 637 | // by explicitly setting document.domain. So we just mandate that expando |
michael@0 | 638 | // sharing is unaffected by it. |
michael@0 | 639 | if (!consumerOrigin->Equals(o)) |
michael@0 | 640 | return false; |
michael@0 | 641 | |
michael@0 | 642 | // Sandboxes want exclusive expando objects. |
michael@0 | 643 | JSObject *owner = JS_GetReservedSlot(expandoObject, |
michael@0 | 644 | JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL) |
michael@0 | 645 | .toObjectOrNull(); |
michael@0 | 646 | if (!owner && !exclusiveGlobal) |
michael@0 | 647 | return true; |
michael@0 | 648 | |
michael@0 | 649 | // The exclusive global should always be wrapped in the target's compartment. |
michael@0 | 650 | MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx)); |
michael@0 | 651 | MOZ_ASSERT(!owner || js::IsObjectInContextCompartment(owner, cx)); |
michael@0 | 652 | return owner == exclusiveGlobal; |
michael@0 | 653 | } |
michael@0 | 654 | |
michael@0 | 655 | JSObject * |
michael@0 | 656 | XrayTraits::getExpandoObjectInternal(JSContext *cx, HandleObject target, |
michael@0 | 657 | nsIPrincipal *origin, |
michael@0 | 658 | JSObject *exclusiveGlobalArg) |
michael@0 | 659 | { |
michael@0 | 660 | // The expando object lives in the compartment of the target, so all our |
michael@0 | 661 | // work needs to happen there. |
michael@0 | 662 | RootedObject exclusiveGlobal(cx, exclusiveGlobalArg); |
michael@0 | 663 | JSAutoCompartment ac(cx, target); |
michael@0 | 664 | if (!JS_WrapObject(cx, &exclusiveGlobal)) |
michael@0 | 665 | return nullptr; |
michael@0 | 666 | |
michael@0 | 667 | // Iterate through the chain, looking for a same-origin object. |
michael@0 | 668 | RootedObject head(cx, getExpandoChain(target)); |
michael@0 | 669 | while (head) { |
michael@0 | 670 | if (expandoObjectMatchesConsumer(cx, head, origin, exclusiveGlobal)) |
michael@0 | 671 | return head; |
michael@0 | 672 | head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); |
michael@0 | 673 | } |
michael@0 | 674 | |
michael@0 | 675 | // Not found. |
michael@0 | 676 | return nullptr; |
michael@0 | 677 | } |
michael@0 | 678 | |
michael@0 | 679 | JSObject * |
michael@0 | 680 | XrayTraits::getExpandoObject(JSContext *cx, HandleObject target, HandleObject consumer) |
michael@0 | 681 | { |
michael@0 | 682 | JSObject *consumerGlobal = js::GetGlobalForObjectCrossCompartment(consumer); |
michael@0 | 683 | bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox"); |
michael@0 | 684 | return getExpandoObjectInternal(cx, target, ObjectPrincipal(consumer), |
michael@0 | 685 | isSandbox ? consumerGlobal : nullptr); |
michael@0 | 686 | } |
michael@0 | 687 | |
michael@0 | 688 | JSObject * |
michael@0 | 689 | XrayTraits::attachExpandoObject(JSContext *cx, HandleObject target, |
michael@0 | 690 | nsIPrincipal *origin, HandleObject exclusiveGlobal) |
michael@0 | 691 | { |
michael@0 | 692 | // Make sure the compartments are sane. |
michael@0 | 693 | MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); |
michael@0 | 694 | MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx)); |
michael@0 | 695 | |
michael@0 | 696 | // No duplicates allowed. |
michael@0 | 697 | MOZ_ASSERT(!getExpandoObjectInternal(cx, target, origin, exclusiveGlobal)); |
michael@0 | 698 | |
michael@0 | 699 | // Create the expando object. We parent it directly to the target object. |
michael@0 | 700 | RootedObject expandoObject(cx, JS_NewObjectWithGivenProto(cx, &ExpandoObjectClass, |
michael@0 | 701 | JS::NullPtr(), target)); |
michael@0 | 702 | if (!expandoObject) |
michael@0 | 703 | return nullptr; |
michael@0 | 704 | |
michael@0 | 705 | // AddRef and store the principal. |
michael@0 | 706 | NS_ADDREF(origin); |
michael@0 | 707 | JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN, PRIVATE_TO_JSVAL(origin)); |
michael@0 | 708 | |
michael@0 | 709 | // Note the exclusive global, if any. |
michael@0 | 710 | JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL, |
michael@0 | 711 | OBJECT_TO_JSVAL(exclusiveGlobal)); |
michael@0 | 712 | |
michael@0 | 713 | // If this is our first expando object, take the opportunity to preserve |
michael@0 | 714 | // the wrapper. This keeps our expandos alive even if the Xray wrapper gets |
michael@0 | 715 | // collected. |
michael@0 | 716 | RootedObject chain(cx, getExpandoChain(target)); |
michael@0 | 717 | if (!chain) |
michael@0 | 718 | preserveWrapper(target); |
michael@0 | 719 | |
michael@0 | 720 | // Insert it at the front of the chain. |
michael@0 | 721 | JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_NEXT, OBJECT_TO_JSVAL(chain)); |
michael@0 | 722 | setExpandoChain(cx, target, expandoObject); |
michael@0 | 723 | |
michael@0 | 724 | return expandoObject; |
michael@0 | 725 | } |
michael@0 | 726 | |
michael@0 | 727 | JSObject * |
michael@0 | 728 | XrayTraits::ensureExpandoObject(JSContext *cx, HandleObject wrapper, |
michael@0 | 729 | HandleObject target) |
michael@0 | 730 | { |
michael@0 | 731 | // Expando objects live in the target compartment. |
michael@0 | 732 | JSAutoCompartment ac(cx, target); |
michael@0 | 733 | JSObject *expandoObject = getExpandoObject(cx, target, wrapper); |
michael@0 | 734 | if (!expandoObject) { |
michael@0 | 735 | // If the object is a sandbox, we don't want it to share expandos with |
michael@0 | 736 | // anyone else, so we tag it with the sandbox global. |
michael@0 | 737 | // |
michael@0 | 738 | // NB: We first need to check the class, _then_ wrap for the target's |
michael@0 | 739 | // compartment. |
michael@0 | 740 | RootedObject consumerGlobal(cx, js::GetGlobalForObjectCrossCompartment(wrapper)); |
michael@0 | 741 | bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox"); |
michael@0 | 742 | if (!JS_WrapObject(cx, &consumerGlobal)) |
michael@0 | 743 | return nullptr; |
michael@0 | 744 | expandoObject = attachExpandoObject(cx, target, ObjectPrincipal(wrapper), |
michael@0 | 745 | isSandbox ? (HandleObject)consumerGlobal : NullPtr()); |
michael@0 | 746 | } |
michael@0 | 747 | return expandoObject; |
michael@0 | 748 | } |
michael@0 | 749 | |
michael@0 | 750 | bool |
michael@0 | 751 | XrayTraits::cloneExpandoChain(JSContext *cx, HandleObject dst, HandleObject src) |
michael@0 | 752 | { |
michael@0 | 753 | MOZ_ASSERT(js::IsObjectInContextCompartment(dst, cx)); |
michael@0 | 754 | MOZ_ASSERT(getExpandoChain(dst) == nullptr); |
michael@0 | 755 | |
michael@0 | 756 | RootedObject oldHead(cx, getExpandoChain(src)); |
michael@0 | 757 | while (oldHead) { |
michael@0 | 758 | RootedObject exclusive(cx, JS_GetReservedSlot(oldHead, |
michael@0 | 759 | JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL) |
michael@0 | 760 | .toObjectOrNull()); |
michael@0 | 761 | if (!JS_WrapObject(cx, &exclusive)) |
michael@0 | 762 | return false; |
michael@0 | 763 | RootedObject newHead(cx, attachExpandoObject(cx, dst, GetExpandoObjectPrincipal(oldHead), |
michael@0 | 764 | exclusive)); |
michael@0 | 765 | if (!JS_CopyPropertiesFrom(cx, newHead, oldHead)) |
michael@0 | 766 | return false; |
michael@0 | 767 | oldHead = JS_GetReservedSlot(oldHead, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); |
michael@0 | 768 | } |
michael@0 | 769 | return true; |
michael@0 | 770 | } |
michael@0 | 771 | |
michael@0 | 772 | namespace XrayUtils { |
michael@0 | 773 | bool CloneExpandoChain(JSContext *cx, JSObject *dstArg, JSObject *srcArg) |
michael@0 | 774 | { |
michael@0 | 775 | RootedObject dst(cx, dstArg); |
michael@0 | 776 | RootedObject src(cx, srcArg); |
michael@0 | 777 | return GetXrayTraits(src)->cloneExpandoChain(cx, dst, src); |
michael@0 | 778 | } |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | static JSObject * |
michael@0 | 782 | GetHolder(JSObject *obj) |
michael@0 | 783 | { |
michael@0 | 784 | return &js::GetProxyExtra(obj, 0).toObject(); |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | JSObject* |
michael@0 | 788 | XrayTraits::getHolder(JSObject *wrapper) |
michael@0 | 789 | { |
michael@0 | 790 | MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper)); |
michael@0 | 791 | js::Value v = js::GetProxyExtra(wrapper, 0); |
michael@0 | 792 | return v.isObject() ? &v.toObject() : nullptr; |
michael@0 | 793 | } |
michael@0 | 794 | |
michael@0 | 795 | JSObject* |
michael@0 | 796 | XrayTraits::ensureHolder(JSContext *cx, HandleObject wrapper) |
michael@0 | 797 | { |
michael@0 | 798 | RootedObject holder(cx, getHolder(wrapper)); |
michael@0 | 799 | if (holder) |
michael@0 | 800 | return holder; |
michael@0 | 801 | holder = createHolder(cx, wrapper); // virtual trap. |
michael@0 | 802 | if (holder) |
michael@0 | 803 | js::SetProxyExtra(wrapper, 0, ObjectValue(*holder)); |
michael@0 | 804 | return holder; |
michael@0 | 805 | } |
michael@0 | 806 | |
michael@0 | 807 | bool |
michael@0 | 808 | XPCWrappedNativeXrayTraits::isResolving(JSContext *cx, JSObject *holder, |
michael@0 | 809 | jsid id) |
michael@0 | 810 | { |
michael@0 | 811 | ResolvingId *cur = ResolvingId::getResolvingId(holder); |
michael@0 | 812 | if (!cur) |
michael@0 | 813 | return false; |
michael@0 | 814 | return cur->isResolving(id); |
michael@0 | 815 | } |
michael@0 | 816 | |
michael@0 | 817 | namespace XrayUtils { |
michael@0 | 818 | |
michael@0 | 819 | bool |
michael@0 | 820 | IsXPCWNHolderClass(const JSClass *clasp) |
michael@0 | 821 | { |
michael@0 | 822 | return clasp == &XPCWrappedNativeXrayTraits::HolderClass; |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | } |
michael@0 | 826 | |
michael@0 | 827 | |
michael@0 | 828 | // Some DOM objects have shared properties that don't have an explicit |
michael@0 | 829 | // getter/setter and rely on the class getter/setter. We install a |
michael@0 | 830 | // class getter/setter on the holder object to trigger them. |
michael@0 | 831 | bool |
michael@0 | 832 | holder_get(JSContext *cx, HandleObject wrapper, HandleId id, MutableHandleValue vp) |
michael@0 | 833 | { |
michael@0 | 834 | // JSClass::getProperty is wacky enough that it's hard to be sure someone |
michael@0 | 835 | // can't inherit this getter by prototyping a random object to an |
michael@0 | 836 | // XrayWrapper. Be safe. |
michael@0 | 837 | NS_ENSURE_TRUE(WrapperFactory::IsXrayWrapper(wrapper), true); |
michael@0 | 838 | JSObject *holder = GetHolder(wrapper); |
michael@0 | 839 | |
michael@0 | 840 | XPCWrappedNative *wn = XPCWrappedNativeXrayTraits::getWN(wrapper); |
michael@0 | 841 | if (NATIVE_HAS_FLAG(wn, WantGetProperty)) { |
michael@0 | 842 | JSAutoCompartment ac(cx, holder); |
michael@0 | 843 | bool retval = true; |
michael@0 | 844 | nsresult rv = wn->GetScriptableCallback()->GetProperty(wn, cx, wrapper, |
michael@0 | 845 | id, vp.address(), &retval); |
michael@0 | 846 | if (NS_FAILED(rv) || !retval) { |
michael@0 | 847 | if (retval) |
michael@0 | 848 | XPCThrower::Throw(rv, cx); |
michael@0 | 849 | return false; |
michael@0 | 850 | } |
michael@0 | 851 | } |
michael@0 | 852 | return true; |
michael@0 | 853 | } |
michael@0 | 854 | |
michael@0 | 855 | bool |
michael@0 | 856 | holder_set(JSContext *cx, HandleObject wrapper, HandleId id, bool strict, MutableHandleValue vp) |
michael@0 | 857 | { |
michael@0 | 858 | // JSClass::setProperty is wacky enough that it's hard to be sure someone |
michael@0 | 859 | // can't inherit this getter by prototyping a random object to an |
michael@0 | 860 | // XrayWrapper. Be safe. |
michael@0 | 861 | NS_ENSURE_TRUE(WrapperFactory::IsXrayWrapper(wrapper), true); |
michael@0 | 862 | JSObject *holder = GetHolder(wrapper); |
michael@0 | 863 | if (XPCWrappedNativeXrayTraits::isResolving(cx, holder, id)) { |
michael@0 | 864 | return true; |
michael@0 | 865 | } |
michael@0 | 866 | |
michael@0 | 867 | XPCWrappedNative *wn = XPCWrappedNativeXrayTraits::getWN(wrapper); |
michael@0 | 868 | if (NATIVE_HAS_FLAG(wn, WantSetProperty)) { |
michael@0 | 869 | JSAutoCompartment ac(cx, holder); |
michael@0 | 870 | bool retval = true; |
michael@0 | 871 | nsresult rv = wn->GetScriptableCallback()->SetProperty(wn, cx, wrapper, |
michael@0 | 872 | id, vp.address(), &retval); |
michael@0 | 873 | if (NS_FAILED(rv) || !retval) { |
michael@0 | 874 | if (retval) |
michael@0 | 875 | XPCThrower::Throw(rv, cx); |
michael@0 | 876 | return false; |
michael@0 | 877 | } |
michael@0 | 878 | } |
michael@0 | 879 | return true; |
michael@0 | 880 | } |
michael@0 | 881 | |
michael@0 | 882 | class AutoSetWrapperNotShadowing |
michael@0 | 883 | { |
michael@0 | 884 | public: |
michael@0 | 885 | AutoSetWrapperNotShadowing(ResolvingId *resolvingId MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
michael@0 | 886 | { |
michael@0 | 887 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
michael@0 | 888 | MOZ_ASSERT(resolvingId); |
michael@0 | 889 | mResolvingId = resolvingId; |
michael@0 | 890 | mResolvingId->mXrayShadowing = true; |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | ~AutoSetWrapperNotShadowing() |
michael@0 | 894 | { |
michael@0 | 895 | mResolvingId->mXrayShadowing = false; |
michael@0 | 896 | } |
michael@0 | 897 | |
michael@0 | 898 | private: |
michael@0 | 899 | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
michael@0 | 900 | ResolvingId *mResolvingId; |
michael@0 | 901 | }; |
michael@0 | 902 | |
michael@0 | 903 | // This is called after the resolveNativeProperty could not find any property |
michael@0 | 904 | // with the given id. At this point we can check for DOM specific collections |
michael@0 | 905 | // like document["formName"] because we already know that it is not shadowing |
michael@0 | 906 | // any native property. |
michael@0 | 907 | bool |
michael@0 | 908 | XPCWrappedNativeXrayTraits::resolveDOMCollectionProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 909 | HandleObject holder, HandleId id, |
michael@0 | 910 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 911 | { |
michael@0 | 912 | // If we are not currently resolving this id and resolveNative is called |
michael@0 | 913 | // we don't do anything. (see defineProperty in case of shadowing is forbidden). |
michael@0 | 914 | ResolvingId *rid = ResolvingId::getResolvingId(holder); |
michael@0 | 915 | if (!rid || rid->mId != id) |
michael@0 | 916 | return true; |
michael@0 | 917 | |
michael@0 | 918 | XPCWrappedNative *wn = getWN(wrapper); |
michael@0 | 919 | if (!wn) { |
michael@0 | 920 | // This should NEVER happen, but let's be extra careful here |
michael@0 | 921 | // because of the reported crashes (Bug 832091). |
michael@0 | 922 | XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); |
michael@0 | 923 | return false; |
michael@0 | 924 | } |
michael@0 | 925 | if (!NATIVE_HAS_FLAG(wn, WantNewResolve)) |
michael@0 | 926 | return true; |
michael@0 | 927 | |
michael@0 | 928 | ResolvingId *resolvingId = ResolvingId::getResolvingIdFromWrapper(wrapper); |
michael@0 | 929 | if (!resolvingId) { |
michael@0 | 930 | // This should NEVER happen, but let's be extra careful here |
michael@0 | 931 | // becaue of the reported crashes (Bug 832091). |
michael@0 | 932 | XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); |
michael@0 | 933 | return false; |
michael@0 | 934 | } |
michael@0 | 935 | |
michael@0 | 936 | // Setting the current ResolvingId in non-shadowing mode. So for this id |
michael@0 | 937 | // Xray won't ignore DOM specific collection properties temporarily. |
michael@0 | 938 | AutoSetWrapperNotShadowing asw(resolvingId); |
michael@0 | 939 | |
michael@0 | 940 | bool retval = true; |
michael@0 | 941 | RootedObject pobj(cx); |
michael@0 | 942 | nsresult rv = wn->GetScriptableInfo()->GetCallback()->NewResolve(wn, cx, wrapper, id, |
michael@0 | 943 | pobj.address(), &retval); |
michael@0 | 944 | if (NS_FAILED(rv)) { |
michael@0 | 945 | if (retval) |
michael@0 | 946 | XPCThrower::Throw(rv, cx); |
michael@0 | 947 | return false; |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | if (pobj && !JS_GetPropertyDescriptorById(cx, holder, id, desc)) |
michael@0 | 951 | return false; |
michael@0 | 952 | |
michael@0 | 953 | return true; |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | static nsGlobalWindow* |
michael@0 | 957 | AsWindow(JSContext *cx, JSObject *wrapper) |
michael@0 | 958 | { |
michael@0 | 959 | nsGlobalWindow* win; |
michael@0 | 960 | // We want to use our target object here, since we don't want to be |
michael@0 | 961 | // doing a security check while unwrapping. |
michael@0 | 962 | JSObject* target = XrayTraits::getTargetObject(wrapper); |
michael@0 | 963 | nsresult rv = UNWRAP_OBJECT(Window, target, win); |
michael@0 | 964 | if (NS_SUCCEEDED(rv)) |
michael@0 | 965 | return win; |
michael@0 | 966 | |
michael@0 | 967 | nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface( |
michael@0 | 968 | nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, target)); |
michael@0 | 969 | return static_cast<nsGlobalWindow*>(piWin.get()); |
michael@0 | 970 | } |
michael@0 | 971 | |
michael@0 | 972 | static bool |
michael@0 | 973 | IsWindow(JSContext *cx, JSObject *wrapper) |
michael@0 | 974 | { |
michael@0 | 975 | return !!AsWindow(cx, wrapper); |
michael@0 | 976 | } |
michael@0 | 977 | |
michael@0 | 978 | static nsQueryInterface |
michael@0 | 979 | do_QueryInterfaceNative(JSContext* cx, HandleObject wrapper); |
michael@0 | 980 | |
michael@0 | 981 | void |
michael@0 | 982 | XPCWrappedNativeXrayTraits::preserveWrapper(JSObject *target) |
michael@0 | 983 | { |
michael@0 | 984 | XPCWrappedNative *wn = XPCWrappedNative::Get(target); |
michael@0 | 985 | nsRefPtr<nsXPCClassInfo> ci; |
michael@0 | 986 | CallQueryInterface(wn->Native(), getter_AddRefs(ci)); |
michael@0 | 987 | if (ci) |
michael@0 | 988 | ci->PreserveWrapper(wn->Native()); |
michael@0 | 989 | } |
michael@0 | 990 | |
michael@0 | 991 | bool |
michael@0 | 992 | XPCWrappedNativeXrayTraits::resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 993 | HandleObject holder, HandleId id, |
michael@0 | 994 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 995 | { |
michael@0 | 996 | MOZ_ASSERT(js::GetObjectJSClass(holder) == &HolderClass); |
michael@0 | 997 | |
michael@0 | 998 | desc.object().set(nullptr); |
michael@0 | 999 | |
michael@0 | 1000 | // This will do verification and the method lookup for us. |
michael@0 | 1001 | RootedObject target(cx, getTargetObject(wrapper)); |
michael@0 | 1002 | XPCCallContext ccx(JS_CALLER, cx, target, NullPtr(), id); |
michael@0 | 1003 | |
michael@0 | 1004 | // There are no native numeric properties, so we can shortcut here. We will |
michael@0 | 1005 | // not find the property. However we want to support non shadowing dom |
michael@0 | 1006 | // specific collection properties like window.frames, so we still have to |
michael@0 | 1007 | // check for those. |
michael@0 | 1008 | if (!JSID_IS_STRING(id)) { |
michael@0 | 1009 | /* Not found */ |
michael@0 | 1010 | return resolveDOMCollectionProperty(cx, wrapper, holder, id, desc); |
michael@0 | 1011 | } |
michael@0 | 1012 | |
michael@0 | 1013 | |
michael@0 | 1014 | // The |controllers| property is accessible as a [ChromeOnly] property on |
michael@0 | 1015 | // Window.WebIDL, and [noscript] in XPIDL. Chrome needs to see this over |
michael@0 | 1016 | // Xray, so we need to special-case it until we move |Window| to WebIDL. |
michael@0 | 1017 | nsGlobalWindow *win = nullptr; |
michael@0 | 1018 | if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_CONTROLLERS) && |
michael@0 | 1019 | AccessCheck::isChrome(wrapper) && |
michael@0 | 1020 | (win = AsWindow(cx, wrapper))) |
michael@0 | 1021 | { |
michael@0 | 1022 | nsCOMPtr<nsIControllers> c; |
michael@0 | 1023 | nsresult rv = win->GetControllers(getter_AddRefs(c)); |
michael@0 | 1024 | if (NS_SUCCEEDED(rv) && c) { |
michael@0 | 1025 | rv = nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, CurrentGlobalOrNull(cx), |
michael@0 | 1026 | c, nullptr, nullptr, true, |
michael@0 | 1027 | desc.value()); |
michael@0 | 1028 | } |
michael@0 | 1029 | |
michael@0 | 1030 | if (NS_FAILED(rv) || !c) { |
michael@0 | 1031 | JS_ReportError(cx, "Failed to invoke GetControllers via Xrays"); |
michael@0 | 1032 | return false; |
michael@0 | 1033 | } |
michael@0 | 1034 | |
michael@0 | 1035 | desc.object().set(wrapper); |
michael@0 | 1036 | return true; |
michael@0 | 1037 | } |
michael@0 | 1038 | |
michael@0 | 1039 | XPCNativeInterface *iface; |
michael@0 | 1040 | XPCNativeMember *member; |
michael@0 | 1041 | XPCWrappedNative *wn = getWN(wrapper); |
michael@0 | 1042 | |
michael@0 | 1043 | if (ccx.GetWrapper() != wn || !wn->IsValid()) { |
michael@0 | 1044 | // Something is wrong. If the wrapper is not even valid let's not risk |
michael@0 | 1045 | // calling resolveDOMCollectionProperty. |
michael@0 | 1046 | return true; |
michael@0 | 1047 | } else if (!(iface = ccx.GetInterface()) || |
michael@0 | 1048 | !(member = ccx.GetMember())) { |
michael@0 | 1049 | /* Not found */ |
michael@0 | 1050 | return resolveDOMCollectionProperty(cx, wrapper, holder, id, desc); |
michael@0 | 1051 | } |
michael@0 | 1052 | |
michael@0 | 1053 | desc.object().set(holder); |
michael@0 | 1054 | desc.setAttributes(JSPROP_ENUMERATE); |
michael@0 | 1055 | desc.setGetter(nullptr); |
michael@0 | 1056 | desc.setSetter(nullptr); |
michael@0 | 1057 | desc.value().set(JSVAL_VOID); |
michael@0 | 1058 | |
michael@0 | 1059 | RootedValue fval(cx, JSVAL_VOID); |
michael@0 | 1060 | if (member->IsConstant()) { |
michael@0 | 1061 | if (!member->GetConstantValue(ccx, iface, desc.value().address())) { |
michael@0 | 1062 | JS_ReportError(cx, "Failed to convert constant native property to JS value"); |
michael@0 | 1063 | return false; |
michael@0 | 1064 | } |
michael@0 | 1065 | } else if (member->IsAttribute()) { |
michael@0 | 1066 | // This is a getter/setter. Clone a function for it. |
michael@0 | 1067 | if (!member->NewFunctionObject(ccx, iface, wrapper, fval.address())) { |
michael@0 | 1068 | JS_ReportError(cx, "Failed to clone function object for native getter/setter"); |
michael@0 | 1069 | return false; |
michael@0 | 1070 | } |
michael@0 | 1071 | |
michael@0 | 1072 | unsigned attrs = desc.attributes(); |
michael@0 | 1073 | attrs |= JSPROP_GETTER; |
michael@0 | 1074 | if (member->IsWritableAttribute()) |
michael@0 | 1075 | attrs |= JSPROP_SETTER; |
michael@0 | 1076 | |
michael@0 | 1077 | // Make the property shared on the holder so no slot is allocated |
michael@0 | 1078 | // for it. This avoids keeping garbage alive through that slot. |
michael@0 | 1079 | attrs |= JSPROP_SHARED; |
michael@0 | 1080 | desc.setAttributes(attrs); |
michael@0 | 1081 | } else { |
michael@0 | 1082 | // This is a method. Clone a function for it. |
michael@0 | 1083 | if (!member->NewFunctionObject(ccx, iface, wrapper, desc.value().address())) { |
michael@0 | 1084 | JS_ReportError(cx, "Failed to clone function object for native function"); |
michael@0 | 1085 | return false; |
michael@0 | 1086 | } |
michael@0 | 1087 | |
michael@0 | 1088 | // Without a wrapper the function would live on the prototype. Since we |
michael@0 | 1089 | // don't have one, we have to avoid calling the scriptable helper's |
michael@0 | 1090 | // GetProperty method for this property, so stub out the getter and |
michael@0 | 1091 | // setter here explicitly. |
michael@0 | 1092 | desc.setGetter(JS_PropertyStub); |
michael@0 | 1093 | desc.setSetter(JS_StrictPropertyStub); |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | if (!JS_WrapValue(cx, desc.value()) || !JS_WrapValue(cx, &fval)) |
michael@0 | 1097 | return false; |
michael@0 | 1098 | |
michael@0 | 1099 | if (desc.hasGetterObject()) |
michael@0 | 1100 | desc.setGetterObject(&fval.toObject()); |
michael@0 | 1101 | if (desc.hasSetterObject()) |
michael@0 | 1102 | desc.setSetterObject(&fval.toObject()); |
michael@0 | 1103 | |
michael@0 | 1104 | // Define the property. |
michael@0 | 1105 | return JS_DefinePropertyById(cx, holder, id, desc.value(), |
michael@0 | 1106 | desc.getter(), desc.setter(), desc.attributes()); |
michael@0 | 1107 | } |
michael@0 | 1108 | |
michael@0 | 1109 | static bool |
michael@0 | 1110 | wrappedJSObject_getter(JSContext *cx, HandleObject wrapper, HandleId id, MutableHandleValue vp) |
michael@0 | 1111 | { |
michael@0 | 1112 | if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper)) { |
michael@0 | 1113 | JS_ReportError(cx, "Unexpected object"); |
michael@0 | 1114 | return false; |
michael@0 | 1115 | } |
michael@0 | 1116 | |
michael@0 | 1117 | vp.set(OBJECT_TO_JSVAL(wrapper)); |
michael@0 | 1118 | |
michael@0 | 1119 | return WrapperFactory::WaiveXrayAndWrap(cx, vp); |
michael@0 | 1120 | } |
michael@0 | 1121 | |
michael@0 | 1122 | bool |
michael@0 | 1123 | XrayTraits::resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, |
michael@0 | 1124 | HandleObject wrapper, HandleObject holder, HandleId id, |
michael@0 | 1125 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1126 | { |
michael@0 | 1127 | desc.object().set(nullptr); |
michael@0 | 1128 | RootedObject target(cx, getTargetObject(wrapper)); |
michael@0 | 1129 | RootedObject expando(cx, getExpandoObject(cx, target, wrapper)); |
michael@0 | 1130 | |
michael@0 | 1131 | // Check for expando properties first. Note that the expando object lives |
michael@0 | 1132 | // in the target compartment. |
michael@0 | 1133 | bool found = false; |
michael@0 | 1134 | if (expando) { |
michael@0 | 1135 | JSAutoCompartment ac(cx, expando); |
michael@0 | 1136 | if (!JS_GetPropertyDescriptorById(cx, expando, id, desc)) |
michael@0 | 1137 | return false; |
michael@0 | 1138 | found = !!desc.object(); |
michael@0 | 1139 | } |
michael@0 | 1140 | |
michael@0 | 1141 | // Next, check for ES builtins. |
michael@0 | 1142 | if (!found && JS_IsGlobalObject(target)) { |
michael@0 | 1143 | JSProtoKey key = JS_IdToProtoKey(cx, id); |
michael@0 | 1144 | JSAutoCompartment ac(cx, target); |
michael@0 | 1145 | if (key != JSProto_Null) { |
michael@0 | 1146 | MOZ_ASSERT(key < JSProto_LIMIT); |
michael@0 | 1147 | RootedObject constructor(cx); |
michael@0 | 1148 | if (!JS_GetClassObject(cx, key, &constructor)) |
michael@0 | 1149 | return false; |
michael@0 | 1150 | MOZ_ASSERT(constructor); |
michael@0 | 1151 | desc.value().set(ObjectValue(*constructor)); |
michael@0 | 1152 | found = true; |
michael@0 | 1153 | } else if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_EVAL)) { |
michael@0 | 1154 | RootedObject eval(cx); |
michael@0 | 1155 | if (!js::GetOriginalEval(cx, target, &eval)) |
michael@0 | 1156 | return false; |
michael@0 | 1157 | desc.value().set(ObjectValue(*eval)); |
michael@0 | 1158 | found = true; |
michael@0 | 1159 | } |
michael@0 | 1160 | } |
michael@0 | 1161 | |
michael@0 | 1162 | if (found) { |
michael@0 | 1163 | if (!JS_WrapPropertyDescriptor(cx, desc)) |
michael@0 | 1164 | return false; |
michael@0 | 1165 | // Pretend the property lives on the wrapper. |
michael@0 | 1166 | desc.object().set(wrapper); |
michael@0 | 1167 | return true; |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | // Handle .wrappedJSObject for subsuming callers. This should move once we |
michael@0 | 1171 | // sort out own-ness for the holder. |
michael@0 | 1172 | if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_WRAPPED_JSOBJECT) && |
michael@0 | 1173 | AccessCheck::wrapperSubsumes(wrapper)) |
michael@0 | 1174 | { |
michael@0 | 1175 | if (!JS_AlreadyHasOwnPropertyById(cx, holder, id, &found)) |
michael@0 | 1176 | return false; |
michael@0 | 1177 | if (!found && !JS_DefinePropertyById(cx, holder, id, UndefinedValue(), |
michael@0 | 1178 | wrappedJSObject_getter, nullptr, |
michael@0 | 1179 | JSPROP_ENUMERATE | JSPROP_SHARED)) { |
michael@0 | 1180 | return false; |
michael@0 | 1181 | } |
michael@0 | 1182 | if (!JS_GetPropertyDescriptorById(cx, holder, id, desc)) |
michael@0 | 1183 | return false; |
michael@0 | 1184 | desc.object().set(wrapper); |
michael@0 | 1185 | return true; |
michael@0 | 1186 | } |
michael@0 | 1187 | |
michael@0 | 1188 | return true; |
michael@0 | 1189 | } |
michael@0 | 1190 | |
michael@0 | 1191 | bool |
michael@0 | 1192 | XrayTraits::set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id, |
michael@0 | 1193 | bool strict, MutableHandleValue vp) |
michael@0 | 1194 | { |
michael@0 | 1195 | // Skip our Base if it isn't already BaseProxyHandler. |
michael@0 | 1196 | js::BaseProxyHandler *handler = js::GetProxyHandler(wrapper); |
michael@0 | 1197 | return handler->js::BaseProxyHandler::set(cx, wrapper, receiver, id, strict, vp); |
michael@0 | 1198 | } |
michael@0 | 1199 | |
michael@0 | 1200 | bool |
michael@0 | 1201 | XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, |
michael@0 | 1202 | HandleObject wrapper, HandleObject holder, |
michael@0 | 1203 | HandleId id, |
michael@0 | 1204 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1205 | { |
michael@0 | 1206 | // Call the common code. |
michael@0 | 1207 | bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, |
michael@0 | 1208 | id, desc); |
michael@0 | 1209 | if (!ok || desc.object()) |
michael@0 | 1210 | return ok; |
michael@0 | 1211 | |
michael@0 | 1212 | // Check for indexed access on a window. |
michael@0 | 1213 | int32_t index = GetArrayIndexFromId(cx, id); |
michael@0 | 1214 | if (IsArrayIndex(index)) { |
michael@0 | 1215 | nsGlobalWindow* win = AsWindow(cx, wrapper); |
michael@0 | 1216 | // Note: As() unwraps outer windows to get to the inner window. |
michael@0 | 1217 | if (win) { |
michael@0 | 1218 | bool unused; |
michael@0 | 1219 | nsCOMPtr<nsIDOMWindow> subframe = win->IndexedGetter(index, unused); |
michael@0 | 1220 | if (subframe) { |
michael@0 | 1221 | nsGlobalWindow* global = static_cast<nsGlobalWindow*>(subframe.get()); |
michael@0 | 1222 | global->EnsureInnerWindow(); |
michael@0 | 1223 | JSObject* obj = global->FastGetGlobalJSObject(); |
michael@0 | 1224 | if (MOZ_UNLIKELY(!obj)) { |
michael@0 | 1225 | // It's gone? |
michael@0 | 1226 | return xpc::Throw(cx, NS_ERROR_FAILURE); |
michael@0 | 1227 | } |
michael@0 | 1228 | desc.value().setObject(*obj); |
michael@0 | 1229 | FillPropertyDescriptor(desc, wrapper, true); |
michael@0 | 1230 | return JS_WrapPropertyDescriptor(cx, desc); |
michael@0 | 1231 | } |
michael@0 | 1232 | } |
michael@0 | 1233 | } |
michael@0 | 1234 | |
michael@0 | 1235 | // Xray wrappers don't use the regular wrapper hierarchy, so we should be |
michael@0 | 1236 | // in the wrapper's compartment here, not the wrappee. |
michael@0 | 1237 | MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); |
michael@0 | 1238 | |
michael@0 | 1239 | bool hasProp; |
michael@0 | 1240 | if (!JS_HasPropertyById(cx, holder, id, &hasProp)) { |
michael@0 | 1241 | return false; |
michael@0 | 1242 | } |
michael@0 | 1243 | if (!hasProp) { |
michael@0 | 1244 | XPCWrappedNative *wn = getWN(wrapper); |
michael@0 | 1245 | |
michael@0 | 1246 | // Run the resolve hook of the wrapped native. |
michael@0 | 1247 | if (!NATIVE_HAS_FLAG(wn, WantNewResolve)) { |
michael@0 | 1248 | return true; |
michael@0 | 1249 | } |
michael@0 | 1250 | |
michael@0 | 1251 | bool retval = true; |
michael@0 | 1252 | RootedObject pobj(cx); |
michael@0 | 1253 | nsIXPCScriptable *callback = wn->GetScriptableInfo()->GetCallback(); |
michael@0 | 1254 | nsresult rv = callback->NewResolve(wn, cx, wrapper, id, pobj.address(), |
michael@0 | 1255 | &retval); |
michael@0 | 1256 | if (NS_FAILED(rv)) { |
michael@0 | 1257 | if (retval) |
michael@0 | 1258 | XPCThrower::Throw(rv, cx); |
michael@0 | 1259 | return false; |
michael@0 | 1260 | } |
michael@0 | 1261 | |
michael@0 | 1262 | MOZ_ASSERT(!pobj || (JS_HasPropertyById(cx, holder, id, &hasProp) && |
michael@0 | 1263 | hasProp), "id got defined somewhere else?"); |
michael@0 | 1264 | } |
michael@0 | 1265 | |
michael@0 | 1266 | // resolveOwnProperty must return a non-empty |desc| if and only if an |own| |
michael@0 | 1267 | // property was found on the object. However, given how the NewResolve setup |
michael@0 | 1268 | // works, we can't run the resolve hook if the holder already has a property |
michael@0 | 1269 | // of the same name. So if there was a pre-existing property on the holder, |
michael@0 | 1270 | // we have to use it. But we have no way of knowing if it corresponded to an |
michael@0 | 1271 | // |own| or non-|own| property, since both get cached on the holder and the |
michael@0 | 1272 | // |own|-ness information is lost. |
michael@0 | 1273 | // |
michael@0 | 1274 | // So we just over-zealously call things |own| here. This can cause us to |
michael@0 | 1275 | // return non-|own| properties from Object.getOwnPropertyDescriptor if |
michael@0 | 1276 | // lookups are performed in a certain order, but we can probably live with |
michael@0 | 1277 | // that until XPCWN Xrays go away with the new DOM bindings. |
michael@0 | 1278 | return JS_GetPropertyDescriptorById(cx, holder, id, desc); |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | bool |
michael@0 | 1282 | XPCWrappedNativeXrayTraits::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 1283 | MutableHandle<JSPropertyDescriptor> desc, |
michael@0 | 1284 | Handle<JSPropertyDescriptor> existingDesc, bool *defined) |
michael@0 | 1285 | { |
michael@0 | 1286 | *defined = false; |
michael@0 | 1287 | JSObject *holder = singleton.ensureHolder(cx, wrapper); |
michael@0 | 1288 | if (isResolving(cx, holder, id)) { |
michael@0 | 1289 | if (!desc.hasAttributes(JSPROP_GETTER | JSPROP_SETTER)) { |
michael@0 | 1290 | if (!desc.getter()) |
michael@0 | 1291 | desc.setGetter(holder_get); |
michael@0 | 1292 | if (!desc.setter()) |
michael@0 | 1293 | desc.setSetter(holder_set); |
michael@0 | 1294 | } |
michael@0 | 1295 | |
michael@0 | 1296 | *defined = true; |
michael@0 | 1297 | return JS_DefinePropertyById(cx, holder, id, desc.value(), desc.getter(), desc.setter(), |
michael@0 | 1298 | desc.attributes()); |
michael@0 | 1299 | } |
michael@0 | 1300 | |
michael@0 | 1301 | // Check for an indexed property on a Window. If that's happening, do |
michael@0 | 1302 | // nothing but claim we defined it so it won't get added as an expando. |
michael@0 | 1303 | int32_t index = GetArrayIndexFromId(cx, id); |
michael@0 | 1304 | if (IsArrayIndex(index) && IsWindow(cx, wrapper)) { |
michael@0 | 1305 | *defined = true; |
michael@0 | 1306 | return true; |
michael@0 | 1307 | } |
michael@0 | 1308 | |
michael@0 | 1309 | return true; |
michael@0 | 1310 | } |
michael@0 | 1311 | |
michael@0 | 1312 | bool |
michael@0 | 1313 | XPCWrappedNativeXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 1314 | AutoIdVector &props) |
michael@0 | 1315 | { |
michael@0 | 1316 | // Force all native properties to be materialized onto the wrapped native. |
michael@0 | 1317 | AutoIdVector wnProps(cx); |
michael@0 | 1318 | { |
michael@0 | 1319 | RootedObject target(cx, singleton.getTargetObject(wrapper)); |
michael@0 | 1320 | JSAutoCompartment ac(cx, target); |
michael@0 | 1321 | if (!js::GetPropertyNames(cx, target, flags, &wnProps)) |
michael@0 | 1322 | return false; |
michael@0 | 1323 | } |
michael@0 | 1324 | if (!JS_WrapAutoIdVector(cx, wnProps)) |
michael@0 | 1325 | return false; |
michael@0 | 1326 | |
michael@0 | 1327 | // Go through the properties we got and enumerate all native ones. |
michael@0 | 1328 | for (size_t n = 0; n < wnProps.length(); ++n) { |
michael@0 | 1329 | RootedId id(cx, wnProps[n]); |
michael@0 | 1330 | bool hasProp; |
michael@0 | 1331 | if (!JS_HasPropertyById(cx, wrapper, id, &hasProp)) |
michael@0 | 1332 | return false; |
michael@0 | 1333 | if (hasProp) |
michael@0 | 1334 | props.append(id); |
michael@0 | 1335 | } |
michael@0 | 1336 | return true; |
michael@0 | 1337 | } |
michael@0 | 1338 | |
michael@0 | 1339 | JSObject * |
michael@0 | 1340 | XPCWrappedNativeXrayTraits::createHolder(JSContext *cx, JSObject *wrapper) |
michael@0 | 1341 | { |
michael@0 | 1342 | RootedObject global(cx, JS_GetGlobalForObject(cx, wrapper)); |
michael@0 | 1343 | JSObject *holder = JS_NewObjectWithGivenProto(cx, &HolderClass, JS::NullPtr(), |
michael@0 | 1344 | global); |
michael@0 | 1345 | if (!holder) |
michael@0 | 1346 | return nullptr; |
michael@0 | 1347 | |
michael@0 | 1348 | js::SetReservedSlot(holder, JSSLOT_RESOLVING, PrivateValue(nullptr)); |
michael@0 | 1349 | return holder; |
michael@0 | 1350 | } |
michael@0 | 1351 | |
michael@0 | 1352 | bool |
michael@0 | 1353 | XPCWrappedNativeXrayTraits::call(JSContext *cx, HandleObject wrapper, |
michael@0 | 1354 | const JS::CallArgs &args, |
michael@0 | 1355 | js::Wrapper& baseInstance) |
michael@0 | 1356 | { |
michael@0 | 1357 | // Run the resolve hook of the wrapped native. |
michael@0 | 1358 | XPCWrappedNative *wn = getWN(wrapper); |
michael@0 | 1359 | if (NATIVE_HAS_FLAG(wn, WantCall)) { |
michael@0 | 1360 | XPCCallContext ccx(JS_CALLER, cx, wrapper, NullPtr(), JSID_VOIDHANDLE, args.length(), |
michael@0 | 1361 | args.array(), args.rval().address()); |
michael@0 | 1362 | if (!ccx.IsValid()) |
michael@0 | 1363 | return false; |
michael@0 | 1364 | bool ok = true; |
michael@0 | 1365 | nsresult rv = wn->GetScriptableInfo()->GetCallback()->Call( |
michael@0 | 1366 | wn, cx, wrapper, args, &ok); |
michael@0 | 1367 | if (NS_FAILED(rv)) { |
michael@0 | 1368 | if (ok) |
michael@0 | 1369 | XPCThrower::Throw(rv, cx); |
michael@0 | 1370 | return false; |
michael@0 | 1371 | } |
michael@0 | 1372 | } |
michael@0 | 1373 | |
michael@0 | 1374 | return true; |
michael@0 | 1375 | |
michael@0 | 1376 | } |
michael@0 | 1377 | |
michael@0 | 1378 | bool |
michael@0 | 1379 | XPCWrappedNativeXrayTraits::construct(JSContext *cx, HandleObject wrapper, |
michael@0 | 1380 | const JS::CallArgs &args, |
michael@0 | 1381 | js::Wrapper& baseInstance) |
michael@0 | 1382 | { |
michael@0 | 1383 | // Run the resolve hook of the wrapped native. |
michael@0 | 1384 | XPCWrappedNative *wn = getWN(wrapper); |
michael@0 | 1385 | if (NATIVE_HAS_FLAG(wn, WantConstruct)) { |
michael@0 | 1386 | XPCCallContext ccx(JS_CALLER, cx, wrapper, NullPtr(), JSID_VOIDHANDLE, args.length(), |
michael@0 | 1387 | args.array(), args.rval().address()); |
michael@0 | 1388 | if (!ccx.IsValid()) |
michael@0 | 1389 | return false; |
michael@0 | 1390 | bool ok = true; |
michael@0 | 1391 | nsresult rv = wn->GetScriptableInfo()->GetCallback()->Construct( |
michael@0 | 1392 | wn, cx, wrapper, args, &ok); |
michael@0 | 1393 | if (NS_FAILED(rv)) { |
michael@0 | 1394 | if (ok) |
michael@0 | 1395 | XPCThrower::Throw(rv, cx); |
michael@0 | 1396 | return false; |
michael@0 | 1397 | } |
michael@0 | 1398 | } |
michael@0 | 1399 | |
michael@0 | 1400 | return true; |
michael@0 | 1401 | |
michael@0 | 1402 | } |
michael@0 | 1403 | |
michael@0 | 1404 | bool |
michael@0 | 1405 | DOMXrayTraits::resolveNativeProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 1406 | HandleObject holder, HandleId id, |
michael@0 | 1407 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1408 | { |
michael@0 | 1409 | RootedObject obj(cx, getTargetObject(wrapper)); |
michael@0 | 1410 | if (!XrayResolveNativeProperty(cx, wrapper, obj, id, desc)) |
michael@0 | 1411 | return false; |
michael@0 | 1412 | |
michael@0 | 1413 | MOZ_ASSERT(!desc.object() || desc.object() == wrapper, "What did we resolve this on?"); |
michael@0 | 1414 | |
michael@0 | 1415 | return true; |
michael@0 | 1416 | } |
michael@0 | 1417 | |
michael@0 | 1418 | bool |
michael@0 | 1419 | DOMXrayTraits::resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper, |
michael@0 | 1420 | HandleObject holder, HandleId id, |
michael@0 | 1421 | MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1422 | { |
michael@0 | 1423 | // Call the common code. |
michael@0 | 1424 | bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, id, desc); |
michael@0 | 1425 | if (!ok || desc.object()) |
michael@0 | 1426 | return ok; |
michael@0 | 1427 | |
michael@0 | 1428 | // Check for indexed access on a window. |
michael@0 | 1429 | int32_t index = GetArrayIndexFromId(cx, id); |
michael@0 | 1430 | if (IsArrayIndex(index)) { |
michael@0 | 1431 | nsGlobalWindow* win = AsWindow(cx, wrapper); |
michael@0 | 1432 | // Note: As() unwraps outer windows to get to the inner window. |
michael@0 | 1433 | if (win) { |
michael@0 | 1434 | bool unused; |
michael@0 | 1435 | nsCOMPtr<nsIDOMWindow> subframe = win->IndexedGetter(index, unused); |
michael@0 | 1436 | if (subframe) { |
michael@0 | 1437 | nsGlobalWindow* global = static_cast<nsGlobalWindow*>(subframe.get()); |
michael@0 | 1438 | global->EnsureInnerWindow(); |
michael@0 | 1439 | JSObject* obj = global->FastGetGlobalJSObject(); |
michael@0 | 1440 | if (MOZ_UNLIKELY(!obj)) { |
michael@0 | 1441 | // It's gone? |
michael@0 | 1442 | return xpc::Throw(cx, NS_ERROR_FAILURE); |
michael@0 | 1443 | } |
michael@0 | 1444 | desc.value().setObject(*obj); |
michael@0 | 1445 | FillPropertyDescriptor(desc, wrapper, true); |
michael@0 | 1446 | return JS_WrapPropertyDescriptor(cx, desc); |
michael@0 | 1447 | } |
michael@0 | 1448 | } |
michael@0 | 1449 | } |
michael@0 | 1450 | |
michael@0 | 1451 | RootedObject obj(cx, getTargetObject(wrapper)); |
michael@0 | 1452 | if (!XrayResolveOwnProperty(cx, wrapper, obj, id, desc)) |
michael@0 | 1453 | return false; |
michael@0 | 1454 | |
michael@0 | 1455 | MOZ_ASSERT(!desc.object() || desc.object() == wrapper, "What did we resolve this on?"); |
michael@0 | 1456 | |
michael@0 | 1457 | return true; |
michael@0 | 1458 | } |
michael@0 | 1459 | |
michael@0 | 1460 | bool |
michael@0 | 1461 | DOMXrayTraits::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 1462 | MutableHandle<JSPropertyDescriptor> desc, |
michael@0 | 1463 | Handle<JSPropertyDescriptor> existingDesc, bool *defined) |
michael@0 | 1464 | { |
michael@0 | 1465 | // Check for an indexed property on a Window. If that's happening, do |
michael@0 | 1466 | // nothing but claim we defined it so it won't get added as an expando. |
michael@0 | 1467 | if (IsWindow(cx, wrapper)) { |
michael@0 | 1468 | int32_t index = GetArrayIndexFromId(cx, id); |
michael@0 | 1469 | if (IsArrayIndex(index)) { |
michael@0 | 1470 | *defined = true; |
michael@0 | 1471 | return true; |
michael@0 | 1472 | } |
michael@0 | 1473 | } |
michael@0 | 1474 | |
michael@0 | 1475 | if (!existingDesc.object()) |
michael@0 | 1476 | return true; |
michael@0 | 1477 | |
michael@0 | 1478 | JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper)); |
michael@0 | 1479 | return XrayDefineProperty(cx, wrapper, obj, id, desc, defined); |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | bool |
michael@0 | 1483 | DOMXrayTraits::set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id, |
michael@0 | 1484 | bool strict, MutableHandleValue vp) |
michael@0 | 1485 | { |
michael@0 | 1486 | MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(wrapper)); |
michael@0 | 1487 | RootedObject obj(cx, getTargetObject(wrapper)); |
michael@0 | 1488 | if (IsDOMProxy(obj)) { |
michael@0 | 1489 | DOMProxyHandler* handler = GetDOMProxyHandler(obj); |
michael@0 | 1490 | |
michael@0 | 1491 | bool done; |
michael@0 | 1492 | if (!handler->setCustom(cx, obj, id, vp, &done)) |
michael@0 | 1493 | return false; |
michael@0 | 1494 | if (done) |
michael@0 | 1495 | return true; |
michael@0 | 1496 | } |
michael@0 | 1497 | return XrayTraits::set(cx, wrapper, receiver, id, strict, vp); |
michael@0 | 1498 | } |
michael@0 | 1499 | |
michael@0 | 1500 | bool |
michael@0 | 1501 | DOMXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 1502 | AutoIdVector &props) |
michael@0 | 1503 | { |
michael@0 | 1504 | JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper)); |
michael@0 | 1505 | return XrayEnumerateProperties(cx, wrapper, obj, flags, props); |
michael@0 | 1506 | } |
michael@0 | 1507 | |
michael@0 | 1508 | bool |
michael@0 | 1509 | DOMXrayTraits::call(JSContext *cx, HandleObject wrapper, |
michael@0 | 1510 | const JS::CallArgs &args, js::Wrapper& baseInstance) |
michael@0 | 1511 | { |
michael@0 | 1512 | RootedObject obj(cx, getTargetObject(wrapper)); |
michael@0 | 1513 | const js::Class* clasp = js::GetObjectClass(obj); |
michael@0 | 1514 | // What we have is either a WebIDL interface object, a WebIDL prototype |
michael@0 | 1515 | // object, or a WebIDL instance object. WebIDL prototype objects never have |
michael@0 | 1516 | // a clasp->call. WebIDL interface objects we want to invoke on the xray |
michael@0 | 1517 | // compartment. WebIDL instance objects either don't have a clasp->call or |
michael@0 | 1518 | // are using "legacycaller", which basically means plug-ins. We want to |
michael@0 | 1519 | // call those on the content compartment. |
michael@0 | 1520 | if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) { |
michael@0 | 1521 | if (!clasp->call) { |
michael@0 | 1522 | RootedValue v(cx, ObjectValue(*wrapper)); |
michael@0 | 1523 | js_ReportIsNotFunction(cx, v); |
michael@0 | 1524 | return false; |
michael@0 | 1525 | } |
michael@0 | 1526 | // call it on the Xray compartment |
michael@0 | 1527 | if (!clasp->call(cx, args.length(), args.base())) |
michael@0 | 1528 | return false; |
michael@0 | 1529 | } else { |
michael@0 | 1530 | // This is only reached for WebIDL instance objects, and in practice |
michael@0 | 1531 | // only for plugins. Just call them on the content compartment. |
michael@0 | 1532 | if (!baseInstance.call(cx, wrapper, args)) |
michael@0 | 1533 | return false; |
michael@0 | 1534 | } |
michael@0 | 1535 | return JS_WrapValue(cx, args.rval()); |
michael@0 | 1536 | } |
michael@0 | 1537 | |
michael@0 | 1538 | bool |
michael@0 | 1539 | DOMXrayTraits::construct(JSContext *cx, HandleObject wrapper, |
michael@0 | 1540 | const JS::CallArgs &args, js::Wrapper& baseInstance) |
michael@0 | 1541 | { |
michael@0 | 1542 | RootedObject obj(cx, getTargetObject(wrapper)); |
michael@0 | 1543 | MOZ_ASSERT(mozilla::dom::HasConstructor(obj)); |
michael@0 | 1544 | const js::Class* clasp = js::GetObjectClass(obj); |
michael@0 | 1545 | // See comments in DOMXrayTraits::call() explaining what's going on here. |
michael@0 | 1546 | if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) { |
michael@0 | 1547 | if (!clasp->construct) { |
michael@0 | 1548 | RootedValue v(cx, ObjectValue(*wrapper)); |
michael@0 | 1549 | js_ReportIsNotFunction(cx, v); |
michael@0 | 1550 | return false; |
michael@0 | 1551 | } |
michael@0 | 1552 | if (!clasp->construct(cx, args.length(), args.base())) |
michael@0 | 1553 | return false; |
michael@0 | 1554 | } else { |
michael@0 | 1555 | if (!baseInstance.construct(cx, wrapper, args)) |
michael@0 | 1556 | return false; |
michael@0 | 1557 | } |
michael@0 | 1558 | if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval())) |
michael@0 | 1559 | return false; |
michael@0 | 1560 | return true; |
michael@0 | 1561 | } |
michael@0 | 1562 | |
michael@0 | 1563 | void |
michael@0 | 1564 | DOMXrayTraits::preserveWrapper(JSObject *target) |
michael@0 | 1565 | { |
michael@0 | 1566 | nsISupports *identity = mozilla::dom::UnwrapDOMObjectToISupports(target); |
michael@0 | 1567 | if (!identity) |
michael@0 | 1568 | return; |
michael@0 | 1569 | nsWrapperCache* cache = nullptr; |
michael@0 | 1570 | CallQueryInterface(identity, &cache); |
michael@0 | 1571 | if (cache) |
michael@0 | 1572 | cache->PreserveWrapper(identity); |
michael@0 | 1573 | } |
michael@0 | 1574 | |
michael@0 | 1575 | JSObject* |
michael@0 | 1576 | DOMXrayTraits::createHolder(JSContext *cx, JSObject *wrapper) |
michael@0 | 1577 | { |
michael@0 | 1578 | RootedObject global(cx, JS_GetGlobalForObject(cx, wrapper)); |
michael@0 | 1579 | return JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global); |
michael@0 | 1580 | } |
michael@0 | 1581 | |
michael@0 | 1582 | template <typename Base, typename Traits> |
michael@0 | 1583 | XrayWrapper<Base, Traits>::XrayWrapper(unsigned flags) |
michael@0 | 1584 | : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG) |
michael@0 | 1585 | { |
michael@0 | 1586 | Base::setHasPrototype(Traits::HasPrototype); |
michael@0 | 1587 | } |
michael@0 | 1588 | |
michael@0 | 1589 | template <typename Base, typename Traits> |
michael@0 | 1590 | XrayWrapper<Base, Traits>::~XrayWrapper() |
michael@0 | 1591 | { |
michael@0 | 1592 | } |
michael@0 | 1593 | |
michael@0 | 1594 | namespace XrayUtils { |
michael@0 | 1595 | |
michael@0 | 1596 | JSObject * |
michael@0 | 1597 | GetNativePropertiesObject(JSContext *cx, JSObject *wrapper) |
michael@0 | 1598 | { |
michael@0 | 1599 | MOZ_ASSERT(js::IsWrapper(wrapper) && WrapperFactory::IsXrayWrapper(wrapper), |
michael@0 | 1600 | "bad object passed in"); |
michael@0 | 1601 | |
michael@0 | 1602 | JSObject *holder = GetHolder(wrapper); |
michael@0 | 1603 | MOZ_ASSERT(holder, "uninitialized wrapper being used?"); |
michael@0 | 1604 | return holder; |
michael@0 | 1605 | } |
michael@0 | 1606 | |
michael@0 | 1607 | bool |
michael@0 | 1608 | IsXrayResolving(JSContext *cx, HandleObject wrapper, HandleId id) |
michael@0 | 1609 | { |
michael@0 | 1610 | if (!WrapperFactory::IsXrayWrapper(wrapper) || |
michael@0 | 1611 | GetXrayType(wrapper) != XrayForWrappedNative) |
michael@0 | 1612 | { |
michael@0 | 1613 | return false; |
michael@0 | 1614 | } |
michael@0 | 1615 | JSObject *holder = |
michael@0 | 1616 | XPCWrappedNativeXrayTraits::singleton.ensureHolder(cx, wrapper); |
michael@0 | 1617 | return XPCWrappedNativeXrayTraits::isResolving(cx, holder, id); |
michael@0 | 1618 | } |
michael@0 | 1619 | |
michael@0 | 1620 | bool |
michael@0 | 1621 | HasNativeProperty(JSContext *cx, HandleObject wrapper, HandleId id, bool *hasProp) |
michael@0 | 1622 | { |
michael@0 | 1623 | MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper)); |
michael@0 | 1624 | XrayTraits *traits = GetXrayTraits(wrapper); |
michael@0 | 1625 | MOZ_ASSERT(traits); |
michael@0 | 1626 | RootedObject holder(cx, traits->ensureHolder(cx, wrapper)); |
michael@0 | 1627 | NS_ENSURE_TRUE(holder, false); |
michael@0 | 1628 | *hasProp = false; |
michael@0 | 1629 | Rooted<JSPropertyDescriptor> desc(cx); |
michael@0 | 1630 | Wrapper *handler = Wrapper::wrapperHandler(wrapper); |
michael@0 | 1631 | |
michael@0 | 1632 | // Try resolveOwnProperty. |
michael@0 | 1633 | Maybe<ResolvingId> resolvingId; |
michael@0 | 1634 | if (traits == &XPCWrappedNativeXrayTraits::singleton) |
michael@0 | 1635 | resolvingId.construct(cx, wrapper, id); |
michael@0 | 1636 | if (!traits->resolveOwnProperty(cx, *handler, wrapper, holder, id, &desc)) |
michael@0 | 1637 | return false; |
michael@0 | 1638 | if (desc.object()) { |
michael@0 | 1639 | *hasProp = true; |
michael@0 | 1640 | return true; |
michael@0 | 1641 | } |
michael@0 | 1642 | |
michael@0 | 1643 | // Try the holder. |
michael@0 | 1644 | bool found = false; |
michael@0 | 1645 | if (!JS_AlreadyHasOwnPropertyById(cx, holder, id, &found)) |
michael@0 | 1646 | return false; |
michael@0 | 1647 | if (found) { |
michael@0 | 1648 | *hasProp = true; |
michael@0 | 1649 | return true; |
michael@0 | 1650 | } |
michael@0 | 1651 | |
michael@0 | 1652 | // Try resolveNativeProperty. |
michael@0 | 1653 | if (!traits->resolveNativeProperty(cx, wrapper, holder, id, &desc)) |
michael@0 | 1654 | return false; |
michael@0 | 1655 | *hasProp = !!desc.object(); |
michael@0 | 1656 | return true; |
michael@0 | 1657 | } |
michael@0 | 1658 | |
michael@0 | 1659 | } // namespace XrayUtils |
michael@0 | 1660 | |
michael@0 | 1661 | static bool |
michael@0 | 1662 | XrayToString(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 1663 | { |
michael@0 | 1664 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 1665 | |
michael@0 | 1666 | if (!args.thisv().isObject()) { |
michael@0 | 1667 | JS_ReportError(cx, "XrayToString called on an incompatible object"); |
michael@0 | 1668 | return false; |
michael@0 | 1669 | } |
michael@0 | 1670 | |
michael@0 | 1671 | RootedObject wrapper(cx, &args.thisv().toObject()); |
michael@0 | 1672 | if (!wrapper) |
michael@0 | 1673 | return false; |
michael@0 | 1674 | if (IsWrapper(wrapper) && |
michael@0 | 1675 | GetProxyHandler(wrapper) == &sandboxCallableProxyHandler) { |
michael@0 | 1676 | wrapper = xpc::SandboxCallableProxyHandler::wrappedObject(wrapper); |
michael@0 | 1677 | } |
michael@0 | 1678 | if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper)) { |
michael@0 | 1679 | JS_ReportError(cx, "XrayToString called on an incompatible object"); |
michael@0 | 1680 | return false; |
michael@0 | 1681 | } |
michael@0 | 1682 | |
michael@0 | 1683 | static const char start[] = "[object XrayWrapper "; |
michael@0 | 1684 | static const char end[] = "]"; |
michael@0 | 1685 | |
michael@0 | 1686 | RootedObject obj(cx, XrayTraits::getTargetObject(wrapper)); |
michael@0 | 1687 | XrayType type = GetXrayType(obj); |
michael@0 | 1688 | if (type == XrayForDOMObject) |
michael@0 | 1689 | return NativeToString(cx, wrapper, obj, start, end, args.rval()); |
michael@0 | 1690 | |
michael@0 | 1691 | if (type != XrayForWrappedNative) { |
michael@0 | 1692 | JS_ReportError(cx, "XrayToString called on an incompatible object"); |
michael@0 | 1693 | return false; |
michael@0 | 1694 | } |
michael@0 | 1695 | |
michael@0 | 1696 | nsAutoString result; |
michael@0 | 1697 | result.AppendASCII(start); |
michael@0 | 1698 | |
michael@0 | 1699 | XPCCallContext ccx(JS_CALLER, cx, obj); |
michael@0 | 1700 | XPCWrappedNative *wn = XPCWrappedNativeXrayTraits::getWN(wrapper); |
michael@0 | 1701 | char *wrapperStr = wn->ToString(); |
michael@0 | 1702 | if (!wrapperStr) { |
michael@0 | 1703 | JS_ReportOutOfMemory(cx); |
michael@0 | 1704 | return false; |
michael@0 | 1705 | } |
michael@0 | 1706 | result.AppendASCII(wrapperStr); |
michael@0 | 1707 | JS_smprintf_free(wrapperStr); |
michael@0 | 1708 | |
michael@0 | 1709 | result.AppendASCII(end); |
michael@0 | 1710 | |
michael@0 | 1711 | JSString *str = JS_NewUCStringCopyN(cx, result.get(), result.Length()); |
michael@0 | 1712 | if (!str) |
michael@0 | 1713 | return false; |
michael@0 | 1714 | |
michael@0 | 1715 | args.rval().setString(str); |
michael@0 | 1716 | return true; |
michael@0 | 1717 | } |
michael@0 | 1718 | |
michael@0 | 1719 | #ifdef DEBUG |
michael@0 | 1720 | |
michael@0 | 1721 | static void |
michael@0 | 1722 | DEBUG_CheckXBLCallable(JSContext *cx, JSObject *obj) |
michael@0 | 1723 | { |
michael@0 | 1724 | // In general, we shouldn't have cross-compartment wrappers here, because |
michael@0 | 1725 | // we should be running in an XBL scope, and the content prototype should |
michael@0 | 1726 | // contain wrappers to functions defined in the XBL scope. But if the node |
michael@0 | 1727 | // has been adopted into another compartment, those prototypes will now point |
michael@0 | 1728 | // to a different XBL scope (which is ok). |
michael@0 | 1729 | MOZ_ASSERT_IF(js::IsCrossCompartmentWrapper(obj), |
michael@0 | 1730 | xpc::IsXBLScope(js::GetObjectCompartment(js::UncheckedUnwrap(obj)))); |
michael@0 | 1731 | MOZ_ASSERT(JS_ObjectIsCallable(cx, obj)); |
michael@0 | 1732 | } |
michael@0 | 1733 | |
michael@0 | 1734 | static void |
michael@0 | 1735 | DEBUG_CheckXBLLookup(JSContext *cx, JSPropertyDescriptor *desc) |
michael@0 | 1736 | { |
michael@0 | 1737 | if (!desc->obj) |
michael@0 | 1738 | return; |
michael@0 | 1739 | if (!desc->value.isUndefined()) { |
michael@0 | 1740 | MOZ_ASSERT(desc->value.isObject()); |
michael@0 | 1741 | DEBUG_CheckXBLCallable(cx, &desc->value.toObject()); |
michael@0 | 1742 | } |
michael@0 | 1743 | if (desc->getter) { |
michael@0 | 1744 | MOZ_ASSERT(desc->attrs & JSPROP_GETTER); |
michael@0 | 1745 | DEBUG_CheckXBLCallable(cx, JS_FUNC_TO_DATA_PTR(JSObject *, desc->getter)); |
michael@0 | 1746 | } |
michael@0 | 1747 | if (desc->setter) { |
michael@0 | 1748 | MOZ_ASSERT(desc->attrs & JSPROP_SETTER); |
michael@0 | 1749 | DEBUG_CheckXBLCallable(cx, JS_FUNC_TO_DATA_PTR(JSObject *, desc->setter)); |
michael@0 | 1750 | } |
michael@0 | 1751 | } |
michael@0 | 1752 | #else |
michael@0 | 1753 | #define DEBUG_CheckXBLLookup(a, b) {} |
michael@0 | 1754 | #endif |
michael@0 | 1755 | |
michael@0 | 1756 | template <typename Base, typename Traits> |
michael@0 | 1757 | bool |
michael@0 | 1758 | XrayWrapper<Base, Traits>::isExtensible(JSContext *cx, JS::Handle<JSObject*> wrapper, bool *extensible) |
michael@0 | 1759 | { |
michael@0 | 1760 | // Xray wrappers are supposed to provide a clean view of the target |
michael@0 | 1761 | // reflector, hiding any modifications by script in the target scope. So |
michael@0 | 1762 | // even if that script freezes the reflector, we don't want to make that |
michael@0 | 1763 | // visible to the caller. DOM reflectors are always extensible by default, |
michael@0 | 1764 | // so we can just return true here. |
michael@0 | 1765 | *extensible = true; |
michael@0 | 1766 | return true; |
michael@0 | 1767 | } |
michael@0 | 1768 | |
michael@0 | 1769 | template <typename Base, typename Traits> |
michael@0 | 1770 | bool |
michael@0 | 1771 | XrayWrapper<Base, Traits>::preventExtensions(JSContext *cx, HandleObject wrapper) |
michael@0 | 1772 | { |
michael@0 | 1773 | // See above. |
michael@0 | 1774 | JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY); |
michael@0 | 1775 | return false; |
michael@0 | 1776 | } |
michael@0 | 1777 | |
michael@0 | 1778 | template <typename Base, typename Traits> |
michael@0 | 1779 | bool |
michael@0 | 1780 | XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 1781 | JS::MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1782 | { |
michael@0 | 1783 | assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET); |
michael@0 | 1784 | RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper)); |
michael@0 | 1785 | if (Traits::isResolving(cx, holder, id)) { |
michael@0 | 1786 | desc.object().set(nullptr); |
michael@0 | 1787 | return true; |
michael@0 | 1788 | } |
michael@0 | 1789 | |
michael@0 | 1790 | typename Traits::ResolvingIdImpl resolving(cx, wrapper, id); |
michael@0 | 1791 | |
michael@0 | 1792 | if (!holder) |
michael@0 | 1793 | return false; |
michael@0 | 1794 | |
michael@0 | 1795 | // Ordering is important here. |
michael@0 | 1796 | // |
michael@0 | 1797 | // We first need to call resolveOwnProperty, even before checking the holder, |
michael@0 | 1798 | // because there might be a new dynamic |own| property that appears and |
michael@0 | 1799 | // shadows a previously-resolved non-own property that we cached on the |
michael@0 | 1800 | // holder. This can happen with indexed properties on NodeLists, for example, |
michael@0 | 1801 | // which are |own| value props. |
michael@0 | 1802 | // |
michael@0 | 1803 | // resolveOwnProperty may or may not cache what it finds on the holder, |
michael@0 | 1804 | // depending on how ephemeral it decides the property is. XPCWN |own| |
michael@0 | 1805 | // properties generally end up on the holder via NewResolve, whereas |
michael@0 | 1806 | // NodeList |own| properties don't get defined on the holder, since they're |
michael@0 | 1807 | // supposed to be dynamic. This means that we have to first check the result |
michael@0 | 1808 | // of resolveOwnProperty, and _then_, if that comes up blank, check the |
michael@0 | 1809 | // holder for any cached native properties. |
michael@0 | 1810 | // |
michael@0 | 1811 | // Finally, we call resolveNativeProperty, which checks non-own properties, |
michael@0 | 1812 | // and unconditionally caches what it finds on the holder. |
michael@0 | 1813 | |
michael@0 | 1814 | // Check resolveOwnProperty. |
michael@0 | 1815 | if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc)) |
michael@0 | 1816 | return false; |
michael@0 | 1817 | |
michael@0 | 1818 | // Check the holder. |
michael@0 | 1819 | if (!desc.object() && !JS_GetPropertyDescriptorById(cx, holder, id, desc)) |
michael@0 | 1820 | return false; |
michael@0 | 1821 | if (desc.object()) { |
michael@0 | 1822 | desc.object().set(wrapper); |
michael@0 | 1823 | return true; |
michael@0 | 1824 | } |
michael@0 | 1825 | |
michael@0 | 1826 | // Nothing in the cache. Call through, and cache the result. |
michael@0 | 1827 | if (!Traits::singleton.resolveNativeProperty(cx, wrapper, holder, id, desc)) |
michael@0 | 1828 | return false; |
michael@0 | 1829 | |
michael@0 | 1830 | // We need to handle named access on the Window somewhere other than |
michael@0 | 1831 | // Traits::resolveOwnProperty, because per spec it happens on the Global |
michael@0 | 1832 | // Scope Polluter and thus the resulting properties are non-|own|. However, |
michael@0 | 1833 | // we're set up (above) to cache (on the holder) anything that comes out of |
michael@0 | 1834 | // resolveNativeProperty, which we don't want for something dynamic like |
michael@0 | 1835 | // named access. So we just handle it separately here. |
michael@0 | 1836 | nsGlobalWindow *win = nullptr; |
michael@0 | 1837 | if (!desc.object() && |
michael@0 | 1838 | JSID_IS_STRING(id) && |
michael@0 | 1839 | (win = AsWindow(cx, wrapper))) |
michael@0 | 1840 | { |
michael@0 | 1841 | nsDependentJSString name(id); |
michael@0 | 1842 | nsCOMPtr<nsIDOMWindow> childDOMWin = win->GetChildWindow(name); |
michael@0 | 1843 | if (childDOMWin) { |
michael@0 | 1844 | nsGlobalWindow *cwin = static_cast<nsGlobalWindow*>(childDOMWin.get()); |
michael@0 | 1845 | JSObject *childObj = cwin->FastGetGlobalJSObject(); |
michael@0 | 1846 | if (MOZ_UNLIKELY(!childObj)) |
michael@0 | 1847 | return xpc::Throw(cx, NS_ERROR_FAILURE); |
michael@0 | 1848 | FillPropertyDescriptor(desc, wrapper, ObjectValue(*childObj), |
michael@0 | 1849 | /* readOnly = */ true); |
michael@0 | 1850 | return JS_WrapPropertyDescriptor(cx, desc); |
michael@0 | 1851 | } |
michael@0 | 1852 | } |
michael@0 | 1853 | |
michael@0 | 1854 | if (!desc.object() && |
michael@0 | 1855 | id == nsXPConnect::GetRuntimeInstance()->GetStringID(XPCJSRuntime::IDX_TO_STRING)) |
michael@0 | 1856 | { |
michael@0 | 1857 | |
michael@0 | 1858 | JSFunction *toString = JS_NewFunction(cx, XrayToString, 0, 0, wrapper, "toString"); |
michael@0 | 1859 | if (!toString) |
michael@0 | 1860 | return false; |
michael@0 | 1861 | |
michael@0 | 1862 | desc.object().set(wrapper); |
michael@0 | 1863 | desc.setAttributes(0); |
michael@0 | 1864 | desc.setGetter(nullptr); |
michael@0 | 1865 | desc.setSetter(nullptr); |
michael@0 | 1866 | desc.value().setObject(*JS_GetFunctionObject(toString)); |
michael@0 | 1867 | } |
michael@0 | 1868 | |
michael@0 | 1869 | // If we're a special scope for in-content XBL, our script expects to see |
michael@0 | 1870 | // the bound XBL methods and attributes when accessing content. However, |
michael@0 | 1871 | // these members are implemented in content via custom-spliced prototypes, |
michael@0 | 1872 | // and thus aren't visible through Xray wrappers unless we handle them |
michael@0 | 1873 | // explicitly. So we check if we're running in such a scope, and if so, |
michael@0 | 1874 | // whether the wrappee is a bound element. If it is, we do a lookup via |
michael@0 | 1875 | // specialized XBL machinery. |
michael@0 | 1876 | // |
michael@0 | 1877 | // While we have to do some sketchy walking through content land, we should |
michael@0 | 1878 | // be protected by read-only/non-configurable properties, and any functions |
michael@0 | 1879 | // we end up with should _always_ be living in an XBL scope (usually ours, |
michael@0 | 1880 | // but could be another if the node has been adopted). |
michael@0 | 1881 | // |
michael@0 | 1882 | // Make sure to assert this. |
michael@0 | 1883 | nsCOMPtr<nsIContent> content; |
michael@0 | 1884 | if (!desc.object() && |
michael@0 | 1885 | EnsureCompartmentPrivate(wrapper)->scope->IsXBLScope() && |
michael@0 | 1886 | (content = do_QueryInterfaceNative(cx, wrapper))) |
michael@0 | 1887 | { |
michael@0 | 1888 | if (!nsContentUtils::LookupBindingMember(cx, content, id, desc)) |
michael@0 | 1889 | return false; |
michael@0 | 1890 | DEBUG_CheckXBLLookup(cx, desc.address()); |
michael@0 | 1891 | } |
michael@0 | 1892 | |
michael@0 | 1893 | // If we still have nothing, we're done. |
michael@0 | 1894 | if (!desc.object()) |
michael@0 | 1895 | return true; |
michael@0 | 1896 | |
michael@0 | 1897 | if (!JS_DefinePropertyById(cx, holder, id, desc.value(), desc.getter(), |
michael@0 | 1898 | desc.setter(), desc.attributes()) || |
michael@0 | 1899 | !JS_GetPropertyDescriptorById(cx, holder, id, desc)) |
michael@0 | 1900 | { |
michael@0 | 1901 | return false; |
michael@0 | 1902 | } |
michael@0 | 1903 | MOZ_ASSERT(desc.object()); |
michael@0 | 1904 | desc.object().set(wrapper); |
michael@0 | 1905 | return true; |
michael@0 | 1906 | } |
michael@0 | 1907 | |
michael@0 | 1908 | template <typename Base, typename Traits> |
michael@0 | 1909 | bool |
michael@0 | 1910 | XrayWrapper<Base, Traits>::getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 1911 | JS::MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1912 | { |
michael@0 | 1913 | assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET); |
michael@0 | 1914 | RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper)); |
michael@0 | 1915 | if (Traits::isResolving(cx, holder, id)) { |
michael@0 | 1916 | desc.object().set(nullptr); |
michael@0 | 1917 | return true; |
michael@0 | 1918 | } |
michael@0 | 1919 | |
michael@0 | 1920 | typename Traits::ResolvingIdImpl resolving(cx, wrapper, id); |
michael@0 | 1921 | |
michael@0 | 1922 | // NB: Nothing we do here acts on the wrapped native itself, so we don't |
michael@0 | 1923 | // enter our policy. |
michael@0 | 1924 | |
michael@0 | 1925 | if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc)) |
michael@0 | 1926 | return false; |
michael@0 | 1927 | if (desc.object()) |
michael@0 | 1928 | desc.object().set(wrapper); |
michael@0 | 1929 | return true; |
michael@0 | 1930 | } |
michael@0 | 1931 | |
michael@0 | 1932 | // Consider what happens when chrome does |xray.expando = xray.wrappedJSObject|. |
michael@0 | 1933 | // |
michael@0 | 1934 | // Since the expando comes from the target compartment, wrapping it back into |
michael@0 | 1935 | // the target compartment to define it on the expando object ends up stripping |
michael@0 | 1936 | // off the Xray waiver that gives |xray| and |xray.wrappedJSObject| different |
michael@0 | 1937 | // identities. This is generally the right thing to do when wrapping across |
michael@0 | 1938 | // compartments, but is incorrect in the special case of the Xray expando |
michael@0 | 1939 | // object. Manually re-apply Xrays if necessary. |
michael@0 | 1940 | // |
michael@0 | 1941 | // NB: In order to satisfy the invariants of WaiveXray, we need to pass |
michael@0 | 1942 | // in an object sans security wrapper, which means we need to strip off any |
michael@0 | 1943 | // potential same-compartment security wrapper that may have been applied |
michael@0 | 1944 | // to the content object. This is ok, because the the expando object is only |
michael@0 | 1945 | // ever accessed by code across the compartment boundary. |
michael@0 | 1946 | static bool |
michael@0 | 1947 | RecreateLostWaivers(JSContext *cx, JSPropertyDescriptor *orig, |
michael@0 | 1948 | MutableHandle<JSPropertyDescriptor> wrapped) |
michael@0 | 1949 | { |
michael@0 | 1950 | // Compute whether the original objects were waived, and implicitly, whether |
michael@0 | 1951 | // they were objects at all. |
michael@0 | 1952 | bool valueWasWaived = |
michael@0 | 1953 | orig->value.isObject() && |
michael@0 | 1954 | WrapperFactory::HasWaiveXrayFlag(&orig->value.toObject()); |
michael@0 | 1955 | bool getterWasWaived = |
michael@0 | 1956 | (orig->attrs & JSPROP_GETTER) && |
michael@0 | 1957 | WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->getter)); |
michael@0 | 1958 | bool setterWasWaived = |
michael@0 | 1959 | (orig->attrs & JSPROP_SETTER) && |
michael@0 | 1960 | WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->setter)); |
michael@0 | 1961 | |
michael@0 | 1962 | // Recreate waivers. Note that for value, we need an extra UncheckedUnwrap |
michael@0 | 1963 | // to handle same-compartment security wrappers (see above). This should |
michael@0 | 1964 | // never happen for getters/setters. |
michael@0 | 1965 | |
michael@0 | 1966 | RootedObject rewaived(cx); |
michael@0 | 1967 | if (valueWasWaived && !IsCrossCompartmentWrapper(&wrapped.value().toObject())) { |
michael@0 | 1968 | rewaived = &wrapped.value().toObject(); |
michael@0 | 1969 | rewaived = WrapperFactory::WaiveXray(cx, UncheckedUnwrap(rewaived)); |
michael@0 | 1970 | NS_ENSURE_TRUE(rewaived, false); |
michael@0 | 1971 | wrapped.value().set(ObjectValue(*rewaived)); |
michael@0 | 1972 | } |
michael@0 | 1973 | if (getterWasWaived && !IsCrossCompartmentWrapper(wrapped.getterObject())) { |
michael@0 | 1974 | MOZ_ASSERT(CheckedUnwrap(wrapped.getterObject())); |
michael@0 | 1975 | rewaived = WrapperFactory::WaiveXray(cx, wrapped.getterObject()); |
michael@0 | 1976 | NS_ENSURE_TRUE(rewaived, false); |
michael@0 | 1977 | wrapped.setGetterObject(rewaived); |
michael@0 | 1978 | } |
michael@0 | 1979 | if (setterWasWaived && !IsCrossCompartmentWrapper(wrapped.setterObject())) { |
michael@0 | 1980 | MOZ_ASSERT(CheckedUnwrap(wrapped.setterObject())); |
michael@0 | 1981 | rewaived = WrapperFactory::WaiveXray(cx, wrapped.setterObject()); |
michael@0 | 1982 | NS_ENSURE_TRUE(rewaived, false); |
michael@0 | 1983 | wrapped.setSetterObject(rewaived); |
michael@0 | 1984 | } |
michael@0 | 1985 | |
michael@0 | 1986 | return true; |
michael@0 | 1987 | } |
michael@0 | 1988 | |
michael@0 | 1989 | template <typename Base, typename Traits> |
michael@0 | 1990 | bool |
michael@0 | 1991 | XrayWrapper<Base, Traits>::defineProperty(JSContext *cx, HandleObject wrapper, |
michael@0 | 1992 | HandleId id, MutableHandle<JSPropertyDescriptor> desc) |
michael@0 | 1993 | { |
michael@0 | 1994 | assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET); |
michael@0 | 1995 | |
michael@0 | 1996 | Rooted<JSPropertyDescriptor> existing_desc(cx); |
michael@0 | 1997 | if (!getOwnPropertyDescriptor(cx, wrapper, id, &existing_desc)) |
michael@0 | 1998 | return false; |
michael@0 | 1999 | |
michael@0 | 2000 | if (existing_desc.object() && existing_desc.isPermanent()) |
michael@0 | 2001 | return true; // silently ignore attempt to overwrite native property |
michael@0 | 2002 | |
michael@0 | 2003 | bool defined = false; |
michael@0 | 2004 | if (!Traits::defineProperty(cx, wrapper, id, desc, existing_desc, &defined)) |
michael@0 | 2005 | return false; |
michael@0 | 2006 | if (defined) |
michael@0 | 2007 | return true; |
michael@0 | 2008 | |
michael@0 | 2009 | // We're placing an expando. The expando objects live in the target |
michael@0 | 2010 | // compartment, so we need to enter it. |
michael@0 | 2011 | RootedObject target(cx, Traits::singleton.getTargetObject(wrapper)); |
michael@0 | 2012 | JSAutoCompartment ac(cx, target); |
michael@0 | 2013 | |
michael@0 | 2014 | // Grab the relevant expando object. |
michael@0 | 2015 | RootedObject expandoObject(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, |
michael@0 | 2016 | target)); |
michael@0 | 2017 | if (!expandoObject) |
michael@0 | 2018 | return false; |
michael@0 | 2019 | |
michael@0 | 2020 | // Wrap the property descriptor for the target compartment. |
michael@0 | 2021 | Rooted<JSPropertyDescriptor> wrappedDesc(cx, desc); |
michael@0 | 2022 | if (!JS_WrapPropertyDescriptor(cx, &wrappedDesc)) |
michael@0 | 2023 | return false; |
michael@0 | 2024 | |
michael@0 | 2025 | // Fix up Xray waivers. |
michael@0 | 2026 | if (!RecreateLostWaivers(cx, desc.address(), &wrappedDesc)) |
michael@0 | 2027 | return false; |
michael@0 | 2028 | |
michael@0 | 2029 | return JS_DefinePropertyById(cx, expandoObject, id, wrappedDesc.value(), |
michael@0 | 2030 | wrappedDesc.getter(), wrappedDesc.setter(), |
michael@0 | 2031 | wrappedDesc.get().attrs); |
michael@0 | 2032 | } |
michael@0 | 2033 | |
michael@0 | 2034 | template <typename Base, typename Traits> |
michael@0 | 2035 | bool |
michael@0 | 2036 | XrayWrapper<Base, Traits>::getOwnPropertyNames(JSContext *cx, HandleObject wrapper, |
michael@0 | 2037 | AutoIdVector &props) |
michael@0 | 2038 | { |
michael@0 | 2039 | assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); |
michael@0 | 2040 | return enumerate(cx, wrapper, JSITER_OWNONLY | JSITER_HIDDEN, props); |
michael@0 | 2041 | } |
michael@0 | 2042 | |
michael@0 | 2043 | template <typename Base, typename Traits> |
michael@0 | 2044 | bool |
michael@0 | 2045 | XrayWrapper<Base, Traits>::delete_(JSContext *cx, HandleObject wrapper, |
michael@0 | 2046 | HandleId id, bool *bp) |
michael@0 | 2047 | { |
michael@0 | 2048 | assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET); |
michael@0 | 2049 | |
michael@0 | 2050 | // Check the expando object. |
michael@0 | 2051 | RootedObject target(cx, Traits::getTargetObject(wrapper)); |
michael@0 | 2052 | RootedObject expando(cx, Traits::singleton.getExpandoObject(cx, target, wrapper)); |
michael@0 | 2053 | if (expando) { |
michael@0 | 2054 | JSAutoCompartment ac(cx, expando); |
michael@0 | 2055 | return JS_DeletePropertyById2(cx, expando, id, bp); |
michael@0 | 2056 | } |
michael@0 | 2057 | *bp = true; |
michael@0 | 2058 | return true; |
michael@0 | 2059 | } |
michael@0 | 2060 | |
michael@0 | 2061 | template <typename Base, typename Traits> |
michael@0 | 2062 | bool |
michael@0 | 2063 | XrayWrapper<Base, Traits>::enumerate(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 2064 | AutoIdVector &props) |
michael@0 | 2065 | { |
michael@0 | 2066 | assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); |
michael@0 | 2067 | if (!AccessCheck::wrapperSubsumes(wrapper)) { |
michael@0 | 2068 | JS_ReportError(cx, "Not allowed to enumerate cross origin objects"); |
michael@0 | 2069 | return false; |
michael@0 | 2070 | } |
michael@0 | 2071 | |
michael@0 | 2072 | // Enumerate expando properties first. Note that the expando object lives |
michael@0 | 2073 | // in the target compartment. |
michael@0 | 2074 | RootedObject target(cx, Traits::singleton.getTargetObject(wrapper)); |
michael@0 | 2075 | RootedObject expando(cx, Traits::singleton.getExpandoObject(cx, target, wrapper)); |
michael@0 | 2076 | if (expando) { |
michael@0 | 2077 | JSAutoCompartment ac(cx, expando); |
michael@0 | 2078 | if (!js::GetPropertyNames(cx, expando, flags, &props)) |
michael@0 | 2079 | return false; |
michael@0 | 2080 | } |
michael@0 | 2081 | if (!JS_WrapAutoIdVector(cx, props)) |
michael@0 | 2082 | return false; |
michael@0 | 2083 | |
michael@0 | 2084 | return Traits::singleton.enumerateNames(cx, wrapper, flags, props); |
michael@0 | 2085 | } |
michael@0 | 2086 | |
michael@0 | 2087 | template <typename Base, typename Traits> |
michael@0 | 2088 | bool |
michael@0 | 2089 | XrayWrapper<Base, Traits>::enumerate(JSContext *cx, HandleObject wrapper, |
michael@0 | 2090 | AutoIdVector &props) |
michael@0 | 2091 | { |
michael@0 | 2092 | return enumerate(cx, wrapper, 0, props); |
michael@0 | 2093 | } |
michael@0 | 2094 | |
michael@0 | 2095 | template <typename Base, typename Traits> |
michael@0 | 2096 | bool |
michael@0 | 2097 | XrayWrapper<Base, Traits>::get(JSContext *cx, HandleObject wrapper, |
michael@0 | 2098 | HandleObject receiver, HandleId id, |
michael@0 | 2099 | MutableHandleValue vp) |
michael@0 | 2100 | { |
michael@0 | 2101 | // Skip our Base if it isn't already ProxyHandler. |
michael@0 | 2102 | // NB: None of the functions we call are prepared for the receiver not |
michael@0 | 2103 | // being the wrapper, so ignore the receiver here. |
michael@0 | 2104 | return js::BaseProxyHandler::get(cx, wrapper, Traits::HasPrototype ? receiver : wrapper, id, vp); |
michael@0 | 2105 | } |
michael@0 | 2106 | |
michael@0 | 2107 | template <typename Base, typename Traits> |
michael@0 | 2108 | bool |
michael@0 | 2109 | XrayWrapper<Base, Traits>::set(JSContext *cx, HandleObject wrapper, |
michael@0 | 2110 | HandleObject receiver, HandleId id, |
michael@0 | 2111 | bool strict, MutableHandleValue vp) |
michael@0 | 2112 | { |
michael@0 | 2113 | // Delegate to Traits. |
michael@0 | 2114 | // NB: None of the functions we call are prepared for the receiver not |
michael@0 | 2115 | // being the wrapper, so ignore the receiver here. |
michael@0 | 2116 | return Traits::set(cx, wrapper, Traits::HasPrototype ? receiver : wrapper, id, strict, vp); |
michael@0 | 2117 | } |
michael@0 | 2118 | |
michael@0 | 2119 | template <typename Base, typename Traits> |
michael@0 | 2120 | bool |
michael@0 | 2121 | XrayWrapper<Base, Traits>::has(JSContext *cx, HandleObject wrapper, |
michael@0 | 2122 | HandleId id, bool *bp) |
michael@0 | 2123 | { |
michael@0 | 2124 | // Skip our Base if it isn't already ProxyHandler. |
michael@0 | 2125 | return js::BaseProxyHandler::has(cx, wrapper, id, bp); |
michael@0 | 2126 | } |
michael@0 | 2127 | |
michael@0 | 2128 | template <typename Base, typename Traits> |
michael@0 | 2129 | bool |
michael@0 | 2130 | XrayWrapper<Base, Traits>::hasOwn(JSContext *cx, HandleObject wrapper, |
michael@0 | 2131 | HandleId id, bool *bp) |
michael@0 | 2132 | { |
michael@0 | 2133 | // Skip our Base if it isn't already ProxyHandler. |
michael@0 | 2134 | return js::BaseProxyHandler::hasOwn(cx, wrapper, id, bp); |
michael@0 | 2135 | } |
michael@0 | 2136 | |
michael@0 | 2137 | template <typename Base, typename Traits> |
michael@0 | 2138 | bool |
michael@0 | 2139 | XrayWrapper<Base, Traits>::keys(JSContext *cx, HandleObject wrapper, |
michael@0 | 2140 | AutoIdVector &props) |
michael@0 | 2141 | { |
michael@0 | 2142 | // Skip our Base if it isn't already ProxyHandler. |
michael@0 | 2143 | return js::BaseProxyHandler::keys(cx, wrapper, props); |
michael@0 | 2144 | } |
michael@0 | 2145 | |
michael@0 | 2146 | template <typename Base, typename Traits> |
michael@0 | 2147 | bool |
michael@0 | 2148 | XrayWrapper<Base, Traits>::iterate(JSContext *cx, HandleObject wrapper, |
michael@0 | 2149 | unsigned flags, MutableHandleValue vp) |
michael@0 | 2150 | { |
michael@0 | 2151 | // Skip our Base if it isn't already ProxyHandler. |
michael@0 | 2152 | return js::BaseProxyHandler::iterate(cx, wrapper, flags, vp); |
michael@0 | 2153 | } |
michael@0 | 2154 | |
michael@0 | 2155 | template <typename Base, typename Traits> |
michael@0 | 2156 | bool |
michael@0 | 2157 | XrayWrapper<Base, Traits>::call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args) |
michael@0 | 2158 | { |
michael@0 | 2159 | assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL); |
michael@0 | 2160 | return Traits::call(cx, wrapper, args, Base::singleton); |
michael@0 | 2161 | } |
michael@0 | 2162 | |
michael@0 | 2163 | template <typename Base, typename Traits> |
michael@0 | 2164 | bool |
michael@0 | 2165 | XrayWrapper<Base, Traits>::construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args) |
michael@0 | 2166 | { |
michael@0 | 2167 | assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL); |
michael@0 | 2168 | return Traits::construct(cx, wrapper, args, Base::singleton); |
michael@0 | 2169 | } |
michael@0 | 2170 | |
michael@0 | 2171 | template <typename Base, typename Traits> |
michael@0 | 2172 | bool |
michael@0 | 2173 | XrayWrapper<Base, Traits>::defaultValue(JSContext *cx, HandleObject wrapper, |
michael@0 | 2174 | JSType hint, MutableHandleValue vp) |
michael@0 | 2175 | { |
michael@0 | 2176 | // Even if this isn't a security wrapper, Xray semantics dictate that we |
michael@0 | 2177 | // run the DefaultValue algorithm directly on the Xray wrapper. |
michael@0 | 2178 | // |
michael@0 | 2179 | // NB: We don't have to worry about things with special [[DefaultValue]] |
michael@0 | 2180 | // behavior like Date because we'll never have an XrayWrapper to them. |
michael@0 | 2181 | return js::DefaultValue(cx, wrapper, hint, vp); |
michael@0 | 2182 | } |
michael@0 | 2183 | |
michael@0 | 2184 | template <typename Base, typename Traits> |
michael@0 | 2185 | bool |
michael@0 | 2186 | XrayWrapper<Base, Traits>::getPrototypeOf(JSContext *cx, JS::HandleObject wrapper, |
michael@0 | 2187 | JS::MutableHandleObject protop) |
michael@0 | 2188 | { |
michael@0 | 2189 | // We really only want this override for non-SecurityWrapper-inheriting |
michael@0 | 2190 | // |Base|. But doing that statically with templates requires partial method |
michael@0 | 2191 | // specializations (and therefore a helper class), which is all more trouble |
michael@0 | 2192 | // than it's worth. Do a dynamic check. |
michael@0 | 2193 | if (Base::hasSecurityPolicy()) |
michael@0 | 2194 | return Base::getPrototypeOf(cx, wrapper, protop); |
michael@0 | 2195 | |
michael@0 | 2196 | RootedObject target(cx, Traits::getTargetObject(wrapper)); |
michael@0 | 2197 | RootedObject expando(cx, Traits::singleton.getExpandoObject(cx, target, wrapper)); |
michael@0 | 2198 | |
michael@0 | 2199 | // We want to keep the Xray's prototype distinct from that of content, but |
michael@0 | 2200 | // only if there's been a set. If there's not an expando, or the expando |
michael@0 | 2201 | // slot is |undefined|, hand back the default proto, appropriately wrapped. |
michael@0 | 2202 | |
michael@0 | 2203 | RootedValue v(cx); |
michael@0 | 2204 | if (expando) { |
michael@0 | 2205 | JSAutoCompartment ac(cx, expando); |
michael@0 | 2206 | v = JS_GetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE); |
michael@0 | 2207 | } |
michael@0 | 2208 | if (v.isUndefined()) |
michael@0 | 2209 | return getPrototypeOfHelper(cx, wrapper, target, protop); |
michael@0 | 2210 | |
michael@0 | 2211 | protop.set(v.toObjectOrNull()); |
michael@0 | 2212 | return JS_WrapObject(cx, protop); |
michael@0 | 2213 | } |
michael@0 | 2214 | |
michael@0 | 2215 | template <typename Base, typename Traits> |
michael@0 | 2216 | bool |
michael@0 | 2217 | XrayWrapper<Base, Traits>::setPrototypeOf(JSContext *cx, JS::HandleObject wrapper, |
michael@0 | 2218 | JS::HandleObject proto, bool *bp) |
michael@0 | 2219 | { |
michael@0 | 2220 | // Do this only for non-SecurityWrapper-inheriting |Base|. See the comment |
michael@0 | 2221 | // in getPrototypeOf(). |
michael@0 | 2222 | if (Base::hasSecurityPolicy()) |
michael@0 | 2223 | return Base::setPrototypeOf(cx, wrapper, proto, bp); |
michael@0 | 2224 | |
michael@0 | 2225 | RootedObject target(cx, Traits::getTargetObject(wrapper)); |
michael@0 | 2226 | RootedObject expando(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, target)); |
michael@0 | 2227 | |
michael@0 | 2228 | // The expando lives in the target's compartment, so do our installation there. |
michael@0 | 2229 | JSAutoCompartment ac(cx, target); |
michael@0 | 2230 | |
michael@0 | 2231 | RootedValue v(cx, ObjectOrNullValue(proto)); |
michael@0 | 2232 | if (!JS_WrapValue(cx, &v)) |
michael@0 | 2233 | return false; |
michael@0 | 2234 | JS_SetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE, v); |
michael@0 | 2235 | *bp = true; |
michael@0 | 2236 | return true; |
michael@0 | 2237 | } |
michael@0 | 2238 | |
michael@0 | 2239 | |
michael@0 | 2240 | /* |
michael@0 | 2241 | * The Permissive / Security variants should be used depending on whether the |
michael@0 | 2242 | * compartment of the wrapper is guranteed to subsume the compartment of the |
michael@0 | 2243 | * wrapped object (i.e. - whether it is safe from a security perspective to |
michael@0 | 2244 | * unwrap the wrapper). |
michael@0 | 2245 | */ |
michael@0 | 2246 | |
michael@0 | 2247 | template<> |
michael@0 | 2248 | PermissiveXrayXPCWN PermissiveXrayXPCWN::singleton(0); |
michael@0 | 2249 | template class PermissiveXrayXPCWN; |
michael@0 | 2250 | |
michael@0 | 2251 | template<> |
michael@0 | 2252 | SecurityXrayXPCWN SecurityXrayXPCWN::singleton(0); |
michael@0 | 2253 | template class SecurityXrayXPCWN; |
michael@0 | 2254 | |
michael@0 | 2255 | template<> |
michael@0 | 2256 | PermissiveXrayDOM PermissiveXrayDOM::singleton(0); |
michael@0 | 2257 | template class PermissiveXrayDOM; |
michael@0 | 2258 | |
michael@0 | 2259 | template<> |
michael@0 | 2260 | SecurityXrayDOM SecurityXrayDOM::singleton(0); |
michael@0 | 2261 | template class SecurityXrayDOM; |
michael@0 | 2262 | |
michael@0 | 2263 | template<> |
michael@0 | 2264 | PermissiveXrayJS PermissiveXrayJS::singleton(0); |
michael@0 | 2265 | template class PermissiveXrayJS; |
michael@0 | 2266 | |
michael@0 | 2267 | template<> |
michael@0 | 2268 | SCSecurityXrayXPCWN SCSecurityXrayXPCWN::singleton(0); |
michael@0 | 2269 | template class SCSecurityXrayXPCWN; |
michael@0 | 2270 | |
michael@0 | 2271 | static nsQueryInterface |
michael@0 | 2272 | do_QueryInterfaceNative(JSContext* cx, HandleObject wrapper) |
michael@0 | 2273 | { |
michael@0 | 2274 | nsISupports* nativeSupports = nullptr; |
michael@0 | 2275 | if (IsWrapper(wrapper) && WrapperFactory::IsXrayWrapper(wrapper)) { |
michael@0 | 2276 | RootedObject target(cx, XrayTraits::getTargetObject(wrapper)); |
michael@0 | 2277 | XrayType type = GetXrayType(target); |
michael@0 | 2278 | if (type == XrayForDOMObject) { |
michael@0 | 2279 | nativeSupports = UnwrapDOMObjectToISupports(target); |
michael@0 | 2280 | } else if (type == XrayForWrappedNative) { |
michael@0 | 2281 | XPCWrappedNative *wn = XPCWrappedNative::Get(target); |
michael@0 | 2282 | nativeSupports = wn->Native(); |
michael@0 | 2283 | } |
michael@0 | 2284 | } else { |
michael@0 | 2285 | nsIXPConnect *xpc = nsXPConnect::XPConnect(); |
michael@0 | 2286 | nativeSupports = xpc->GetNativeOfWrapper(cx, wrapper); |
michael@0 | 2287 | } |
michael@0 | 2288 | |
michael@0 | 2289 | return nsQueryInterface(nativeSupports); |
michael@0 | 2290 | } |
michael@0 | 2291 | |
michael@0 | 2292 | } |