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 | #ifndef jswrapper_h |
michael@0 | 8 | #define jswrapper_h |
michael@0 | 9 | |
michael@0 | 10 | #include "mozilla/Attributes.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "jsproxy.h" |
michael@0 | 13 | |
michael@0 | 14 | namespace js { |
michael@0 | 15 | |
michael@0 | 16 | class DummyFrameGuard; |
michael@0 | 17 | |
michael@0 | 18 | /* |
michael@0 | 19 | * Helper for Wrapper::New default options. |
michael@0 | 20 | * |
michael@0 | 21 | * Callers of Wrapper::New() who wish to specify a prototype for the created |
michael@0 | 22 | * Wrapper, *MUST* construct a WrapperOptions with a JSContext. |
michael@0 | 23 | */ |
michael@0 | 24 | class MOZ_STACK_CLASS WrapperOptions : public ProxyOptions { |
michael@0 | 25 | public: |
michael@0 | 26 | WrapperOptions() : ProxyOptions(false, nullptr), |
michael@0 | 27 | proto_() |
michael@0 | 28 | {} |
michael@0 | 29 | |
michael@0 | 30 | WrapperOptions(JSContext *cx) : ProxyOptions(false, nullptr), |
michael@0 | 31 | proto_() |
michael@0 | 32 | { |
michael@0 | 33 | proto_.construct(cx); |
michael@0 | 34 | } |
michael@0 | 35 | |
michael@0 | 36 | inline JSObject *proto() const; |
michael@0 | 37 | WrapperOptions &setProto(JSObject *protoArg) { |
michael@0 | 38 | JS_ASSERT(!proto_.empty()); |
michael@0 | 39 | proto_.ref() = protoArg; |
michael@0 | 40 | return *this; |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | private: |
michael@0 | 44 | mozilla::Maybe<JS::RootedObject> proto_; |
michael@0 | 45 | }; |
michael@0 | 46 | |
michael@0 | 47 | /* |
michael@0 | 48 | * A wrapper is a proxy with a target object to which it generally forwards |
michael@0 | 49 | * operations, but may restrict access to certain operations or instrument |
michael@0 | 50 | * the trap operations in various ways. A wrapper is distinct from a Direct Proxy |
michael@0 | 51 | * Handler in the sense that it can be "unwrapped" in C++, exposing the underlying |
michael@0 | 52 | * object (Direct Proxy Handlers have an underlying target object, but don't |
michael@0 | 53 | * expect to expose this object via any kind of unwrapping operation). Callers |
michael@0 | 54 | * should be careful to avoid unwrapping security wrappers in the wrong context. |
michael@0 | 55 | */ |
michael@0 | 56 | class JS_FRIEND_API(Wrapper) : public DirectProxyHandler |
michael@0 | 57 | { |
michael@0 | 58 | unsigned mFlags; |
michael@0 | 59 | |
michael@0 | 60 | public: |
michael@0 | 61 | using BaseProxyHandler::Action; |
michael@0 | 62 | |
michael@0 | 63 | enum Flags { |
michael@0 | 64 | CROSS_COMPARTMENT = 1 << 0, |
michael@0 | 65 | LAST_USED_FLAG = CROSS_COMPARTMENT |
michael@0 | 66 | }; |
michael@0 | 67 | |
michael@0 | 68 | virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, |
michael@0 | 69 | MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 70 | |
michael@0 | 71 | static JSObject *New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler, |
michael@0 | 72 | const WrapperOptions *options = nullptr); |
michael@0 | 73 | |
michael@0 | 74 | static JSObject *Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler); |
michael@0 | 75 | |
michael@0 | 76 | static Wrapper *wrapperHandler(JSObject *wrapper); |
michael@0 | 77 | |
michael@0 | 78 | static JSObject *wrappedObject(JSObject *wrapper); |
michael@0 | 79 | |
michael@0 | 80 | unsigned flags() const { |
michael@0 | 81 | return mFlags; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | explicit Wrapper(unsigned flags, bool hasPrototype = false); |
michael@0 | 85 | |
michael@0 | 86 | virtual ~Wrapper(); |
michael@0 | 87 | |
michael@0 | 88 | virtual bool finalizeInBackground(Value priv) MOZ_OVERRIDE; |
michael@0 | 89 | |
michael@0 | 90 | static Wrapper singleton; |
michael@0 | 91 | static Wrapper singletonWithPrototype; |
michael@0 | 92 | |
michael@0 | 93 | static JSObject *defaultProto; |
michael@0 | 94 | }; |
michael@0 | 95 | |
michael@0 | 96 | inline JSObject * |
michael@0 | 97 | WrapperOptions::proto() const |
michael@0 | 98 | { |
michael@0 | 99 | return proto_.empty() ? Wrapper::defaultProto : proto_.ref(); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | /* Base class for all cross compartment wrapper handlers. */ |
michael@0 | 103 | class JS_FRIEND_API(CrossCompartmentWrapper) : public Wrapper |
michael@0 | 104 | { |
michael@0 | 105 | public: |
michael@0 | 106 | CrossCompartmentWrapper(unsigned flags, bool hasPrototype = false); |
michael@0 | 107 | |
michael@0 | 108 | virtual ~CrossCompartmentWrapper(); |
michael@0 | 109 | |
michael@0 | 110 | /* ES5 Harmony fundamental wrapper traps. */ |
michael@0 | 111 | virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE; |
michael@0 | 112 | virtual bool getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 113 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 114 | virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 115 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 116 | virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 117 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 118 | virtual bool getOwnPropertyNames(JSContext *cx, HandleObject wrapper, |
michael@0 | 119 | AutoIdVector &props) MOZ_OVERRIDE; |
michael@0 | 120 | virtual bool delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE; |
michael@0 | 121 | virtual bool enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE; |
michael@0 | 122 | |
michael@0 | 123 | /* ES5 Harmony derived wrapper traps. */ |
michael@0 | 124 | virtual bool has(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE; |
michael@0 | 125 | virtual bool hasOwn(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE; |
michael@0 | 126 | virtual bool get(JSContext *cx, HandleObject wrapper, HandleObject receiver, |
michael@0 | 127 | HandleId id, MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 128 | virtual bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, |
michael@0 | 129 | HandleId id, bool strict, MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 130 | virtual bool keys(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE; |
michael@0 | 131 | virtual bool iterate(JSContext *cx, HandleObject wrapper, unsigned flags, |
michael@0 | 132 | MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 133 | |
michael@0 | 134 | /* Spidermonkey extensions. */ |
michael@0 | 135 | virtual bool isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) MOZ_OVERRIDE; |
michael@0 | 136 | virtual bool call(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE; |
michael@0 | 137 | virtual bool construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE; |
michael@0 | 138 | virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, |
michael@0 | 139 | CallArgs args) MOZ_OVERRIDE; |
michael@0 | 140 | virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v, |
michael@0 | 141 | bool *bp) MOZ_OVERRIDE; |
michael@0 | 142 | virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE; |
michael@0 | 143 | virtual JSString *fun_toString(JSContext *cx, HandleObject wrapper, |
michael@0 | 144 | unsigned indent) MOZ_OVERRIDE; |
michael@0 | 145 | virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE; |
michael@0 | 146 | virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint, |
michael@0 | 147 | MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 148 | virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, |
michael@0 | 149 | MutableHandleObject protop) MOZ_OVERRIDE; |
michael@0 | 150 | virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, |
michael@0 | 151 | bool *bp) MOZ_OVERRIDE; |
michael@0 | 152 | |
michael@0 | 153 | static CrossCompartmentWrapper singleton; |
michael@0 | 154 | static CrossCompartmentWrapper singletonWithPrototype; |
michael@0 | 155 | }; |
michael@0 | 156 | |
michael@0 | 157 | /* |
michael@0 | 158 | * Base class for security wrappers. A security wrapper is potentially hiding |
michael@0 | 159 | * all or part of some wrapped object thus SecurityWrapper defaults to denying |
michael@0 | 160 | * access to the wrappee. This is the opposite of Wrapper which tries to be |
michael@0 | 161 | * completely transparent. |
michael@0 | 162 | * |
michael@0 | 163 | * NB: Currently, only a few ProxyHandler operations are overridden to deny |
michael@0 | 164 | * access, relying on derived SecurityWrapper to block access when necessary. |
michael@0 | 165 | */ |
michael@0 | 166 | template <class Base> |
michael@0 | 167 | class JS_FRIEND_API(SecurityWrapper) : public Base |
michael@0 | 168 | { |
michael@0 | 169 | public: |
michael@0 | 170 | SecurityWrapper(unsigned flags); |
michael@0 | 171 | |
michael@0 | 172 | virtual bool isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) MOZ_OVERRIDE; |
michael@0 | 173 | virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE; |
michael@0 | 174 | virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act, |
michael@0 | 175 | bool *bp) MOZ_OVERRIDE; |
michael@0 | 176 | virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, |
michael@0 | 177 | CallArgs args) MOZ_OVERRIDE; |
michael@0 | 178 | virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint, |
michael@0 | 179 | MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 180 | virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, |
michael@0 | 181 | JSContext *cx) MOZ_OVERRIDE; |
michael@0 | 182 | virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE; |
michael@0 | 183 | virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 184 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 185 | |
michael@0 | 186 | virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, |
michael@0 | 187 | bool *bp) MOZ_OVERRIDE; |
michael@0 | 188 | |
michael@0 | 189 | virtual bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, |
michael@0 | 190 | JS::HandleObject callable) MOZ_OVERRIDE; |
michael@0 | 191 | virtual bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id) MOZ_OVERRIDE; |
michael@0 | 192 | |
michael@0 | 193 | /* |
michael@0 | 194 | * Allow our subclasses to select the superclass behavior they want without |
michael@0 | 195 | * needing to specify an exact superclass. |
michael@0 | 196 | */ |
michael@0 | 197 | typedef Base Permissive; |
michael@0 | 198 | typedef SecurityWrapper<Base> Restrictive; |
michael@0 | 199 | }; |
michael@0 | 200 | |
michael@0 | 201 | typedef SecurityWrapper<Wrapper> SameCompartmentSecurityWrapper; |
michael@0 | 202 | typedef SecurityWrapper<CrossCompartmentWrapper> CrossCompartmentSecurityWrapper; |
michael@0 | 203 | |
michael@0 | 204 | class JS_FRIEND_API(DeadObjectProxy) : public BaseProxyHandler |
michael@0 | 205 | { |
michael@0 | 206 | public: |
michael@0 | 207 | // This variable exists solely to provide a unique address for use as an identifier. |
michael@0 | 208 | static const char sDeadObjectFamily; |
michael@0 | 209 | |
michael@0 | 210 | explicit DeadObjectProxy(); |
michael@0 | 211 | |
michael@0 | 212 | /* ES5 Harmony fundamental wrapper traps. */ |
michael@0 | 213 | virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE; |
michael@0 | 214 | virtual bool getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 215 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 216 | virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 217 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 218 | virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, |
michael@0 | 219 | MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE; |
michael@0 | 220 | virtual bool getOwnPropertyNames(JSContext *cx, HandleObject wrapper, |
michael@0 | 221 | AutoIdVector &props) MOZ_OVERRIDE; |
michael@0 | 222 | virtual bool delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE; |
michael@0 | 223 | virtual bool enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE; |
michael@0 | 224 | |
michael@0 | 225 | /* Spidermonkey extensions. */ |
michael@0 | 226 | virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE; |
michael@0 | 227 | virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; |
michael@0 | 228 | virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; |
michael@0 | 229 | virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, |
michael@0 | 230 | CallArgs args) MOZ_OVERRIDE; |
michael@0 | 231 | virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, |
michael@0 | 232 | bool *bp) MOZ_OVERRIDE; |
michael@0 | 233 | virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, |
michael@0 | 234 | JSContext *cx) MOZ_OVERRIDE; |
michael@0 | 235 | virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE; |
michael@0 | 236 | virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE; |
michael@0 | 237 | virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE; |
michael@0 | 238 | virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, |
michael@0 | 239 | MutableHandleValue vp) MOZ_OVERRIDE; |
michael@0 | 240 | virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, |
michael@0 | 241 | MutableHandleObject protop) MOZ_OVERRIDE; |
michael@0 | 242 | |
michael@0 | 243 | static DeadObjectProxy singleton; |
michael@0 | 244 | }; |
michael@0 | 245 | |
michael@0 | 246 | extern JSObject * |
michael@0 | 247 | TransparentObjectWrapper(JSContext *cx, HandleObject existing, HandleObject obj, |
michael@0 | 248 | HandleObject wrappedProto, HandleObject parent, |
michael@0 | 249 | unsigned flags); |
michael@0 | 250 | |
michael@0 | 251 | // Proxy family for wrappers. Public so that IsWrapper() can be fully inlined by |
michael@0 | 252 | // jsfriendapi users. |
michael@0 | 253 | // This variable exists solely to provide a unique address for use as an identifier. |
michael@0 | 254 | extern JS_FRIEND_DATA(const char) sWrapperFamily; |
michael@0 | 255 | |
michael@0 | 256 | inline bool |
michael@0 | 257 | IsWrapper(JSObject *obj) |
michael@0 | 258 | { |
michael@0 | 259 | return IsProxy(obj) && GetProxyHandler(obj)->family() == &sWrapperFamily; |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | // Given a JSObject, returns that object stripped of wrappers. If |
michael@0 | 263 | // stopAtOuter is true, then this returns the outer window if it was |
michael@0 | 264 | // previously wrapped. Otherwise, this returns the first object for |
michael@0 | 265 | // which JSObject::isWrapper returns false. |
michael@0 | 266 | JS_FRIEND_API(JSObject *) |
michael@0 | 267 | UncheckedUnwrap(JSObject *obj, bool stopAtOuter = true, unsigned *flagsp = nullptr); |
michael@0 | 268 | |
michael@0 | 269 | // Given a JSObject, returns that object stripped of wrappers. At each stage, |
michael@0 | 270 | // the security wrapper has the opportunity to veto the unwrap. Since checked |
michael@0 | 271 | // code should never be unwrapping outer window wrappers, we always stop at |
michael@0 | 272 | // outer windows. |
michael@0 | 273 | JS_FRIEND_API(JSObject *) |
michael@0 | 274 | CheckedUnwrap(JSObject *obj, bool stopAtOuter = true); |
michael@0 | 275 | |
michael@0 | 276 | // Unwrap only the outermost security wrapper, with the same semantics as |
michael@0 | 277 | // above. This is the checked version of Wrapper::wrappedObject. |
michael@0 | 278 | JS_FRIEND_API(JSObject *) |
michael@0 | 279 | UnwrapOneChecked(JSObject *obj, bool stopAtOuter = true); |
michael@0 | 280 | |
michael@0 | 281 | JS_FRIEND_API(bool) |
michael@0 | 282 | IsCrossCompartmentWrapper(JSObject *obj); |
michael@0 | 283 | |
michael@0 | 284 | bool |
michael@0 | 285 | IsDeadProxyObject(JSObject *obj); |
michael@0 | 286 | |
michael@0 | 287 | JSObject * |
michael@0 | 288 | NewDeadProxyObject(JSContext *cx, JSObject *parent, |
michael@0 | 289 | const ProxyOptions &options = ProxyOptions()); |
michael@0 | 290 | |
michael@0 | 291 | void |
michael@0 | 292 | NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper); |
michael@0 | 293 | |
michael@0 | 294 | bool |
michael@0 | 295 | RemapWrapper(JSContext *cx, JSObject *wobj, JSObject *newTarget); |
michael@0 | 296 | |
michael@0 | 297 | JS_FRIEND_API(bool) |
michael@0 | 298 | RemapAllWrappersForObject(JSContext *cx, JSObject *oldTarget, |
michael@0 | 299 | JSObject *newTarget); |
michael@0 | 300 | |
michael@0 | 301 | // API to recompute all cross-compartment wrappers whose source and target |
michael@0 | 302 | // match the given filters. |
michael@0 | 303 | JS_FRIEND_API(bool) |
michael@0 | 304 | RecomputeWrappers(JSContext *cx, const CompartmentFilter &sourceFilter, |
michael@0 | 305 | const CompartmentFilter &targetFilter); |
michael@0 | 306 | |
michael@0 | 307 | /* |
michael@0 | 308 | * This auto class should be used around any code, such as brain transplants, |
michael@0 | 309 | * that may touch dead zones. Brain transplants can cause problems |
michael@0 | 310 | * because they operate on all compartments, whether live or dead. A brain |
michael@0 | 311 | * transplant can cause a formerly dead object to be "reanimated" by causing a |
michael@0 | 312 | * read or write barrier to be invoked on it during the transplant. In this way, |
michael@0 | 313 | * a zone becomes a zombie, kept alive by repeatedly consuming |
michael@0 | 314 | * (transplanted) brains. |
michael@0 | 315 | * |
michael@0 | 316 | * To work around this issue, we observe when mark bits are set on objects in |
michael@0 | 317 | * dead zones. If this happens during a brain transplant, we do a full, |
michael@0 | 318 | * non-incremental GC at the end of the brain transplant. This will clean up any |
michael@0 | 319 | * objects that were improperly marked. |
michael@0 | 320 | */ |
michael@0 | 321 | struct JS_FRIEND_API(AutoMaybeTouchDeadZones) |
michael@0 | 322 | { |
michael@0 | 323 | // The version that takes an object just uses it for its runtime. |
michael@0 | 324 | AutoMaybeTouchDeadZones(JSContext *cx); |
michael@0 | 325 | AutoMaybeTouchDeadZones(JSObject *obj); |
michael@0 | 326 | ~AutoMaybeTouchDeadZones(); |
michael@0 | 327 | |
michael@0 | 328 | private: |
michael@0 | 329 | JSRuntime *runtime; |
michael@0 | 330 | unsigned markCount; |
michael@0 | 331 | bool inIncremental; |
michael@0 | 332 | bool manipulatingDeadZones; |
michael@0 | 333 | }; |
michael@0 | 334 | |
michael@0 | 335 | } /* namespace js */ |
michael@0 | 336 | |
michael@0 | 337 | #endif /* jswrapper_h */ |