Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "base/basictypes.h"
8 :
9 : #include "jsfriendapi.h"
10 : #include "jswrapper.h"
11 :
12 : #include "nsAutoPtr.h"
13 : #include "nsIInterfaceRequestorUtils.h"
14 : #include "nsJSNPRuntime.h"
15 : #include "nsNPAPIPlugin.h"
16 : #include "nsNPAPIPluginInstance.h"
17 : #include "nsIGlobalObject.h"
18 : #include "nsIScriptGlobalObject.h"
19 : #include "nsIScriptContext.h"
20 : #include "nsDOMJSUtils.h"
21 : #include "nsJSUtils.h"
22 : #include "nsIDocument.h"
23 : #include "nsIXPConnect.h"
24 : #include "xpcpublic.h"
25 : #include "nsIDOMElement.h"
26 : #include "nsIContent.h"
27 : #include "nsPluginInstanceOwner.h"
28 : #include "nsWrapperCacheInlines.h"
29 : #include "js/GCHashTable.h"
30 : #include "js/TracingAPI.h"
31 : #include "mozilla/HashFunctions.h"
32 : #include "mozilla/dom/ScriptSettings.h"
33 :
34 : #define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class"
35 :
36 : using namespace mozilla::plugins::parent;
37 : using namespace mozilla;
38 :
39 : #include "mozilla/plugins/PluginScriptableObjectParent.h"
40 : using mozilla::plugins::PluginScriptableObjectParent;
41 : using mozilla::plugins::ParentNPObject;
42 :
43 : struct JSObjWrapperHasher
44 : {
45 : typedef nsJSObjWrapperKey Key;
46 : typedef Key Lookup;
47 :
48 0 : static uint32_t hash(const Lookup &l) {
49 0 : return js::MovableCellHasher<JS::Heap<JSObject*>>::hash(l.mJSObj) ^
50 0 : HashGeneric(l.mNpp);
51 : }
52 :
53 0 : static bool match(const Key& k, const Lookup &l) {
54 0 : return js::MovableCellHasher<JS::Heap<JSObject*>>::match(k.mJSObj, l.mJSObj) &&
55 0 : k.mNpp == l.mNpp;
56 : }
57 : };
58 :
59 : namespace JS {
60 : template <>
61 : struct GCPolicy<nsJSObjWrapper*> {
62 0 : static void trace(JSTracer* trc, nsJSObjWrapper** wrapper, const char* name) {
63 0 : MOZ_ASSERT(wrapper);
64 0 : MOZ_ASSERT(*wrapper);
65 0 : (*wrapper)->trace(trc);
66 0 : }
67 : };
68 : } // namespace JS
69 :
70 : class NPObjWrapperHashEntry : public PLDHashEntryHdr
71 : {
72 : public:
73 : NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work
74 : JS::TenuredHeap<JSObject*> mJSObj;
75 : NPP mNpp;
76 : };
77 :
78 : // Hash of JSObject wrappers that wraps JSObjects as NPObjects. There
79 : // will be one wrapper per JSObject per plugin instance, i.e. if two
80 : // plugins access the JSObject x, two wrappers for x will be
81 : // created. This is needed to be able to properly drop the wrappers
82 : // when a plugin is torn down in case there's a leak in the plugin (we
83 : // don't want to leak the world just because a plugin leaks an
84 : // NPObject).
85 : typedef JS::GCHashMap<nsJSObjWrapperKey,
86 : nsJSObjWrapper*,
87 : JSObjWrapperHasher,
88 : js::SystemAllocPolicy> JSObjWrapperTable;
89 3 : static JSObjWrapperTable sJSObjWrappers;
90 :
91 : // Whether it's safe to iterate sJSObjWrappers. Set to true when sJSObjWrappers
92 : // has been initialized and is not currently being enumerated.
93 : static bool sJSObjWrappersAccessible = false;
94 :
95 : // Hash of NPObject wrappers that wrap NPObjects as JSObjects.
96 : static PLDHashTable* sNPObjWrappers;
97 :
98 : // Global wrapper count. This includes JSObject wrappers *and*
99 : // NPObject wrappers. When this count goes to zero, there are no more
100 : // wrappers and we can kill off hash tables etc.
101 : static int32_t sWrapperCount;
102 :
103 : static bool sCallbackIsRegistered = false;
104 :
105 : static nsTArray<NPObject*>* sDelayedReleases;
106 :
107 : namespace {
108 :
109 : inline bool
110 0 : NPObjectIsOutOfProcessProxy(NPObject *obj)
111 : {
112 0 : return obj->_class == PluginScriptableObjectParent::GetClass();
113 : }
114 :
115 : } // namespace
116 :
117 : // Helper class that suppresses any JS exceptions that were thrown while
118 : // the plugin executed JS, if the nsJSObjWrapper has a destroy pending.
119 : // Note that this class is the product (vestige?) of a long evolution in how
120 : // error reporting worked, and hence the mIsDestroyPending check, and hence this
121 : // class in general, may or may not actually be necessary.
122 :
123 : class MOZ_STACK_CLASS AutoJSExceptionSuppressor
124 : {
125 : public:
126 0 : AutoJSExceptionSuppressor(dom::AutoEntryScript& aes, nsJSObjWrapper* aWrapper)
127 0 : : mAes(aes)
128 0 : , mIsDestroyPending(aWrapper->mDestroyPending)
129 : {
130 0 : }
131 :
132 0 : ~AutoJSExceptionSuppressor()
133 0 : {
134 0 : if (mIsDestroyPending) {
135 0 : mAes.ClearException();
136 : }
137 0 : }
138 :
139 : protected:
140 : dom::AutoEntryScript& mAes;
141 : bool mIsDestroyPending;
142 : };
143 :
144 :
145 : NPClass nsJSObjWrapper::sJSObjWrapperNPClass =
146 : {
147 : NP_CLASS_STRUCT_VERSION,
148 : nsJSObjWrapper::NP_Allocate,
149 : nsJSObjWrapper::NP_Deallocate,
150 : nsJSObjWrapper::NP_Invalidate,
151 : nsJSObjWrapper::NP_HasMethod,
152 : nsJSObjWrapper::NP_Invoke,
153 : nsJSObjWrapper::NP_InvokeDefault,
154 : nsJSObjWrapper::NP_HasProperty,
155 : nsJSObjWrapper::NP_GetProperty,
156 : nsJSObjWrapper::NP_SetProperty,
157 : nsJSObjWrapper::NP_RemoveProperty,
158 : nsJSObjWrapper::NP_Enumerate,
159 : nsJSObjWrapper::NP_Construct
160 : };
161 :
162 : static bool
163 : NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v);
164 :
165 : static bool
166 : NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
167 : JS::ObjectOpResult &result);
168 :
169 : static bool
170 : NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
171 : JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result);
172 :
173 : static bool
174 : NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp);
175 :
176 : static bool
177 : NPObjWrapper_NewEnumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties,
178 : bool enumerableOnly);
179 :
180 : static bool
181 : NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
182 : bool *resolvedp);
183 :
184 : static void
185 : NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj);
186 :
187 : static void
188 : NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old);
189 :
190 : static bool
191 : NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp);
192 :
193 : static bool
194 : NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp);
195 :
196 : static bool
197 : NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
198 :
199 : static bool
200 : CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
201 : JS::Handle<jsid> id, NPVariant* getPropertyResult,
202 : JS::MutableHandle<JS::Value> vp);
203 :
204 : const static js::ClassOps sNPObjectJSWrapperClassOps = {
205 : NPObjWrapper_AddProperty,
206 : NPObjWrapper_DelProperty,
207 : NPObjWrapper_GetProperty,
208 : NPObjWrapper_SetProperty,
209 : nullptr, /* enumerate */
210 : NPObjWrapper_NewEnumerate,
211 : NPObjWrapper_Resolve,
212 : nullptr, /* mayResolve */
213 : NPObjWrapper_Finalize,
214 : NPObjWrapper_Call,
215 : nullptr, /* hasInstance */
216 : NPObjWrapper_Construct,
217 : nullptr, /* trace */
218 : };
219 :
220 : const static js::ClassExtension sNPObjectJSWrapperClassExtension = {
221 : nullptr, /* weakmapKeyDelegateOp */
222 : NPObjWrapper_ObjectMoved
223 : };
224 :
225 : const static js::Class sNPObjectJSWrapperClass = {
226 : NPRUNTIME_JSCLASS_NAME,
227 : JSCLASS_HAS_PRIVATE |
228 : JSCLASS_FOREGROUND_FINALIZE,
229 : &sNPObjectJSWrapperClassOps,
230 : JS_NULL_CLASS_SPEC,
231 : &sNPObjectJSWrapperClassExtension,
232 : JS_NULL_OBJECT_OPS
233 : };
234 :
235 : typedef struct NPObjectMemberPrivate {
236 : JS::Heap<JSObject *> npobjWrapper;
237 : JS::Heap<JS::Value> fieldValue;
238 : JS::Heap<jsid> methodName;
239 : NPP npp;
240 : } NPObjectMemberPrivate;
241 :
242 : static bool
243 : NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
244 : JS::MutableHandleValue vp);
245 :
246 : static void
247 : NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj);
248 :
249 : static bool
250 : NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp);
251 :
252 : static void
253 : NPObjectMember_Trace(JSTracer *trc, JSObject *obj);
254 :
255 : static bool
256 : NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
257 :
258 : static const JSClassOps sNPObjectMemberClassOps = {
259 : nullptr, nullptr, NPObjectMember_GetProperty, nullptr,
260 : nullptr, nullptr, nullptr, nullptr,
261 : NPObjectMember_Finalize, NPObjectMember_Call,
262 : nullptr, nullptr, NPObjectMember_Trace
263 : };
264 :
265 : static const JSClass sNPObjectMemberClass = {
266 : "NPObject Ambiguous Member class",
267 : JSCLASS_HAS_PRIVATE |
268 : JSCLASS_FOREGROUND_FINALIZE,
269 : &sNPObjectMemberClassOps
270 : };
271 :
272 : static void
273 : OnWrapperDestroyed();
274 :
275 : static void
276 0 : TraceJSObjWrappers(JSTracer *trc, void *data)
277 : {
278 0 : if (sJSObjWrappers.initialized()) {
279 0 : sJSObjWrappers.trace(trc);
280 : }
281 0 : }
282 :
283 : static void
284 0 : DelayedReleaseGCCallback(JSGCStatus status)
285 : {
286 0 : if (JSGC_END == status) {
287 : // Take ownership of sDelayedReleases and null it out now. The
288 : // _releaseobject call below can reenter GC and double-free these objects.
289 0 : nsAutoPtr<nsTArray<NPObject*> > delayedReleases(sDelayedReleases);
290 0 : sDelayedReleases = nullptr;
291 :
292 0 : if (delayedReleases) {
293 0 : for (uint32_t i = 0; i < delayedReleases->Length(); ++i) {
294 0 : NPObject* obj = (*delayedReleases)[i];
295 0 : if (obj)
296 0 : _releaseobject(obj);
297 0 : OnWrapperDestroyed();
298 : }
299 : }
300 : }
301 0 : }
302 :
303 : static bool
304 0 : RegisterGCCallbacks()
305 : {
306 0 : if (sCallbackIsRegistered) {
307 0 : return true;
308 : }
309 :
310 : // Register a callback to trace wrapped JSObjects.
311 0 : JSContext* cx = dom::danger::GetJSContext();
312 0 : if (!JS_AddExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr)) {
313 0 : return false;
314 : }
315 :
316 : // Register our GC callback to perform delayed destruction of finalized
317 : // NPObjects.
318 0 : xpc::AddGCCallback(DelayedReleaseGCCallback);
319 :
320 0 : sCallbackIsRegistered = true;
321 :
322 0 : return true;
323 : }
324 :
325 : static void
326 0 : UnregisterGCCallbacks()
327 : {
328 0 : MOZ_ASSERT(sCallbackIsRegistered);
329 :
330 : // Remove tracing callback.
331 0 : JSContext* cx = dom::danger::GetJSContext();
332 0 : JS_RemoveExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr);
333 :
334 : // Remove delayed destruction callback.
335 0 : if (sCallbackIsRegistered) {
336 0 : xpc::RemoveGCCallback(DelayedReleaseGCCallback);
337 0 : sCallbackIsRegistered = false;
338 : }
339 0 : }
340 :
341 : static bool
342 0 : CreateJSObjWrapperTable()
343 : {
344 0 : MOZ_ASSERT(!sJSObjWrappersAccessible);
345 0 : MOZ_ASSERT(!sJSObjWrappers.initialized());
346 :
347 0 : if (!RegisterGCCallbacks()) {
348 0 : return false;
349 : }
350 :
351 0 : if (!sJSObjWrappers.init(16)) {
352 0 : NS_ERROR("Error initializing PLDHashTable sJSObjWrappers!");
353 0 : return false;
354 : }
355 :
356 0 : sJSObjWrappersAccessible = true;
357 0 : return true;
358 : }
359 :
360 : static void
361 0 : DestroyJSObjWrapperTable()
362 : {
363 0 : MOZ_ASSERT(sJSObjWrappersAccessible);
364 0 : MOZ_ASSERT(sJSObjWrappers.initialized());
365 0 : MOZ_ASSERT(sJSObjWrappers.count() == 0);
366 :
367 : // No more wrappers, and our hash was initialized. Finish the
368 : // hash to prevent leaking it.
369 0 : sJSObjWrappers.finish();
370 0 : sJSObjWrappersAccessible = false;
371 0 : }
372 :
373 : static bool
374 0 : CreateNPObjWrapperTable()
375 : {
376 0 : MOZ_ASSERT(!sNPObjWrappers);
377 :
378 0 : if (!RegisterGCCallbacks()) {
379 0 : return false;
380 : }
381 :
382 0 : sNPObjWrappers =
383 0 : new PLDHashTable(PLDHashTable::StubOps(), sizeof(NPObjWrapperHashEntry));
384 0 : return true;
385 : }
386 :
387 : static void
388 0 : DestroyNPObjWrapperTable()
389 : {
390 0 : MOZ_ASSERT(sNPObjWrappers->EntryCount() == 0);
391 :
392 0 : delete sNPObjWrappers;
393 0 : sNPObjWrappers = nullptr;
394 0 : }
395 :
396 : static void
397 0 : OnWrapperCreated()
398 : {
399 0 : ++sWrapperCount;
400 0 : }
401 :
402 : static void
403 0 : OnWrapperDestroyed()
404 : {
405 0 : NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!");
406 :
407 0 : if (--sWrapperCount == 0) {
408 0 : if (sJSObjWrappersAccessible) {
409 0 : DestroyJSObjWrapperTable();
410 : }
411 :
412 0 : if (sNPObjWrappers) {
413 : // No more wrappers, and our hash was initialized. Finish the
414 : // hash to prevent leaking it.
415 0 : DestroyNPObjWrapperTable();
416 : }
417 :
418 0 : UnregisterGCCallbacks();
419 : }
420 0 : }
421 :
422 : namespace mozilla {
423 : namespace plugins {
424 : namespace parent {
425 :
426 : static nsIGlobalObject*
427 0 : GetGlobalObject(NPP npp)
428 : {
429 0 : NS_ENSURE_TRUE(npp, nullptr);
430 :
431 0 : nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata;
432 0 : NS_ENSURE_TRUE(inst, nullptr);
433 :
434 0 : RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner();
435 0 : NS_ENSURE_TRUE(owner, nullptr);
436 :
437 0 : nsCOMPtr<nsIDocument> doc;
438 0 : owner->GetDocument(getter_AddRefs(doc));
439 0 : NS_ENSURE_TRUE(doc, nullptr);
440 :
441 0 : return doc->GetScopeObject();
442 : }
443 :
444 : } // namespace parent
445 : } // namespace plugins
446 : } // namespace mozilla
447 :
448 : static NPP
449 : LookupNPP(NPObject *npobj);
450 :
451 :
452 : static JS::Value
453 0 : NPVariantToJSVal(NPP npp, JSContext *cx, const NPVariant *variant)
454 : {
455 0 : switch (variant->type) {
456 : case NPVariantType_Void :
457 0 : return JS::UndefinedValue();
458 : case NPVariantType_Null :
459 0 : return JS::NullValue();
460 : case NPVariantType_Bool :
461 0 : return JS::BooleanValue(NPVARIANT_TO_BOOLEAN(*variant));
462 : case NPVariantType_Int32 :
463 : {
464 : // Don't use INT_TO_JSVAL directly to prevent bugs when dealing
465 : // with ints larger than what fits in a integer JS::Value.
466 0 : return ::JS_NumberValue(NPVARIANT_TO_INT32(*variant));
467 : }
468 : case NPVariantType_Double :
469 : {
470 0 : return ::JS_NumberValue(NPVARIANT_TO_DOUBLE(*variant));
471 : }
472 : case NPVariantType_String :
473 : {
474 0 : const NPString *s = &NPVARIANT_TO_STRING(*variant);
475 0 : NS_ConvertUTF8toUTF16 utf16String(s->UTF8Characters, s->UTF8Length);
476 :
477 : JSString *str =
478 0 : ::JS_NewUCStringCopyN(cx, utf16String.get(), utf16String.Length());
479 :
480 0 : if (str) {
481 0 : return JS::StringValue(str);
482 : }
483 :
484 0 : break;
485 : }
486 : case NPVariantType_Object:
487 : {
488 0 : if (npp) {
489 : JSObject *obj =
490 0 : nsNPObjWrapper::GetNewOrUsed(npp, cx, NPVARIANT_TO_OBJECT(*variant));
491 :
492 0 : if (obj) {
493 0 : return JS::ObjectValue(*obj);
494 : }
495 : }
496 :
497 0 : NS_ERROR("Error wrapping NPObject!");
498 :
499 0 : break;
500 : }
501 : default:
502 0 : NS_ERROR("Unknown NPVariant type!");
503 : }
504 :
505 0 : NS_ERROR("Unable to convert NPVariant to jsval!");
506 :
507 0 : return JS::UndefinedValue();
508 : }
509 :
510 : bool
511 0 : JSValToNPVariant(NPP npp, JSContext *cx, const JS::Value& val, NPVariant *variant)
512 : {
513 0 : NS_ASSERTION(npp, "Must have an NPP to wrap a jsval!");
514 :
515 0 : if (val.isPrimitive()) {
516 0 : if (val.isUndefined()) {
517 0 : VOID_TO_NPVARIANT(*variant);
518 0 : } else if (val.isNull()) {
519 0 : NULL_TO_NPVARIANT(*variant);
520 0 : } else if (val.isBoolean()) {
521 0 : BOOLEAN_TO_NPVARIANT(val.toBoolean(), *variant);
522 0 : } else if (val.isInt32()) {
523 0 : INT32_TO_NPVARIANT(val.toInt32(), *variant);
524 0 : } else if (val.isDouble()) {
525 0 : double d = val.toDouble();
526 : int i;
527 0 : if (JS_DoubleIsInt32(d, &i)) {
528 0 : INT32_TO_NPVARIANT(i, *variant);
529 : } else {
530 0 : DOUBLE_TO_NPVARIANT(d, *variant);
531 : }
532 0 : } else if (val.isString()) {
533 0 : JSString *jsstr = val.toString();
534 :
535 0 : nsAutoJSString str;
536 0 : if (!str.init(cx, jsstr)) {
537 0 : return false;
538 : }
539 :
540 : uint32_t len;
541 0 : char *p = ToNewUTF8String(str, &len);
542 :
543 0 : if (!p) {
544 0 : return false;
545 : }
546 :
547 0 : STRINGN_TO_NPVARIANT(p, len, *variant);
548 : } else {
549 0 : NS_ERROR("Unknown primitive type!");
550 :
551 0 : return false;
552 : }
553 :
554 0 : return true;
555 : }
556 :
557 : // The reflected plugin object may be in another compartment if the plugin
558 : // element has since been adopted into a new document. We don't bother
559 : // transplanting the plugin objects, and just do a unwrap with security
560 : // checks if we encounter one of them as an argument. If the unwrap fails,
561 : // we run with the original wrapped object, since sometimes there are
562 : // legitimate cases where a security wrapper ends up here (for example,
563 : // Location objects, which are _always_ behind security wrappers).
564 0 : JS::Rooted<JSObject*> obj(cx, val.toObjectOrNull());
565 0 : obj = js::CheckedUnwrap(obj);
566 0 : if (!obj) {
567 0 : obj = val.toObjectOrNull();
568 : }
569 :
570 0 : NPObject* npobj = nsJSObjWrapper::GetNewOrUsed(npp, obj);
571 0 : if (!npobj) {
572 0 : return false;
573 : }
574 :
575 : // Pass over ownership of npobj to *variant
576 0 : OBJECT_TO_NPVARIANT(npobj, *variant);
577 :
578 0 : return true;
579 : }
580 :
581 : static void
582 0 : ThrowJSExceptionASCII(JSContext *cx, const char *message)
583 : {
584 0 : const char *ex = PeekException();
585 :
586 0 : if (ex) {
587 0 : nsAutoString ucex;
588 :
589 0 : if (message) {
590 0 : AppendASCIItoUTF16(message, ucex);
591 :
592 0 : AppendASCIItoUTF16(" [plugin exception: ", ucex);
593 : }
594 :
595 0 : AppendUTF8toUTF16(ex, ucex);
596 :
597 0 : if (message) {
598 0 : AppendASCIItoUTF16("].", ucex);
599 : }
600 :
601 0 : JSString *str = ::JS_NewUCStringCopyN(cx, ucex.get(), ucex.Length());
602 :
603 0 : if (str) {
604 0 : JS::Rooted<JS::Value> exn(cx, JS::StringValue(str));
605 0 : ::JS_SetPendingException(cx, exn);
606 : }
607 :
608 0 : PopException();
609 : } else {
610 0 : ::JS_ReportErrorASCII(cx, "%s", message);
611 : }
612 0 : }
613 :
614 : static bool
615 0 : ReportExceptionIfPending(JSContext *cx)
616 : {
617 0 : const char *ex = PeekException();
618 :
619 0 : if (!ex) {
620 0 : return true;
621 : }
622 :
623 0 : ThrowJSExceptionASCII(cx, nullptr);
624 :
625 0 : return false;
626 : }
627 :
628 0 : nsJSObjWrapper::nsJSObjWrapper(NPP npp)
629 0 : : mJSObj(nullptr), mNpp(npp), mDestroyPending(false)
630 : {
631 0 : MOZ_COUNT_CTOR(nsJSObjWrapper);
632 0 : OnWrapperCreated();
633 0 : }
634 :
635 0 : nsJSObjWrapper::~nsJSObjWrapper()
636 : {
637 0 : MOZ_COUNT_DTOR(nsJSObjWrapper);
638 :
639 : // Invalidate first, since it relies on sJSObjWrappers.
640 0 : NP_Invalidate(this);
641 :
642 0 : OnWrapperDestroyed();
643 0 : }
644 :
645 : // static
646 : NPObject *
647 0 : nsJSObjWrapper::NP_Allocate(NPP npp, NPClass *aClass)
648 : {
649 0 : NS_ASSERTION(aClass == &sJSObjWrapperNPClass,
650 : "Huh, wrong class passed to NP_Allocate()!!!");
651 :
652 0 : return new nsJSObjWrapper(npp);
653 : }
654 :
655 : // static
656 : void
657 0 : nsJSObjWrapper::NP_Deallocate(NPObject *npobj)
658 : {
659 : // nsJSObjWrapper::~nsJSObjWrapper() will call NP_Invalidate().
660 0 : delete (nsJSObjWrapper *)npobj;
661 0 : }
662 :
663 : // static
664 : void
665 0 : nsJSObjWrapper::NP_Invalidate(NPObject *npobj)
666 : {
667 0 : nsJSObjWrapper *jsnpobj = (nsJSObjWrapper *)npobj;
668 :
669 0 : if (jsnpobj && jsnpobj->mJSObj) {
670 :
671 0 : if (sJSObjWrappersAccessible) {
672 : // Remove the wrapper from the hash
673 0 : nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp);
674 0 : JSObjWrapperTable::Ptr ptr = sJSObjWrappers.lookup(key);
675 0 : MOZ_ASSERT(ptr.found());
676 0 : sJSObjWrappers.remove(ptr);
677 : }
678 :
679 : // Forget our reference to the JSObject.
680 0 : jsnpobj->mJSObj = nullptr;
681 : }
682 0 : }
683 :
684 : static bool
685 0 : GetProperty(JSContext *cx, JSObject *objArg, NPIdentifier npid, JS::MutableHandle<JS::Value> rval)
686 : {
687 0 : NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
688 : "id must be either string or int!\n");
689 0 : JS::Rooted<JSObject *> obj(cx, objArg);
690 0 : JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
691 0 : return ::JS_GetPropertyById(cx, obj, id, rval);
692 : }
693 :
694 : static void
695 0 : MarkCrossZoneNPIdentifier(JSContext* cx, NPIdentifier npid)
696 : {
697 0 : JS_MarkCrossZoneId(cx, NPIdentifierToJSId(npid));
698 0 : }
699 :
700 : // static
701 : bool
702 0 : nsJSObjWrapper::NP_HasMethod(NPObject *npobj, NPIdentifier id)
703 : {
704 0 : NPP npp = NPPStack::Peek();
705 0 : nsIGlobalObject* globalObject = GetGlobalObject(npp);
706 0 : if (NS_WARN_IF(!globalObject)) {
707 0 : return false;
708 : }
709 :
710 0 : dom::AutoEntryScript aes(globalObject, "NPAPI HasMethod");
711 0 : JSContext *cx = aes.cx();
712 :
713 0 : if (!npobj) {
714 : ThrowJSExceptionASCII(cx,
715 0 : "Null npobj in nsJSObjWrapper::NP_HasMethod!");
716 :
717 0 : return false;
718 : }
719 :
720 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
721 :
722 0 : JSAutoCompartment ac(cx, npjsobj->mJSObj);
723 0 : MarkCrossZoneNPIdentifier(cx, id);
724 :
725 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
726 :
727 0 : JS::Rooted<JS::Value> v(cx);
728 0 : bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v);
729 :
730 0 : return ok && !v.isPrimitive() &&
731 0 : ::JS_ObjectIsFunction(cx, v.toObjectOrNull());
732 : }
733 :
734 : static bool
735 0 : doInvoke(NPObject *npobj, NPIdentifier method, const NPVariant *args,
736 : uint32_t argCount, bool ctorCall, NPVariant *result)
737 : {
738 0 : NPP npp = NPPStack::Peek();
739 :
740 0 : nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp);
741 0 : if (NS_WARN_IF(!globalObject)) {
742 0 : return false;
743 : }
744 :
745 : // We're about to run script via JS_CallFunctionValue, so we need an
746 : // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec.
747 0 : dom::AutoEntryScript aes(globalObject, "NPAPI doInvoke");
748 0 : JSContext *cx = aes.cx();
749 :
750 0 : if (!npobj || !result) {
751 0 : ThrowJSExceptionASCII(cx, "Null npobj, or result in doInvoke!");
752 :
753 0 : return false;
754 : }
755 :
756 : // Initialize *result
757 0 : VOID_TO_NPVARIANT(*result);
758 :
759 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
760 :
761 0 : JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
762 0 : JSAutoCompartment ac(cx, jsobj);
763 0 : MarkCrossZoneNPIdentifier(cx, method);
764 0 : JS::Rooted<JS::Value> fv(cx);
765 :
766 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
767 :
768 0 : if (method != NPIdentifier_VOID) {
769 0 : if (!GetProperty(cx, jsobj, method, &fv) ||
770 0 : ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) {
771 0 : return false;
772 : }
773 : } else {
774 0 : fv.setObject(*jsobj);
775 : }
776 :
777 : // Convert args
778 0 : JS::AutoValueVector jsargs(cx);
779 0 : if (!jsargs.reserve(argCount)) {
780 0 : ::JS_ReportOutOfMemory(cx);
781 0 : return false;
782 : }
783 0 : for (uint32_t i = 0; i < argCount; ++i) {
784 0 : jsargs.infallibleAppend(NPVariantToJSVal(npp, cx, args + i));
785 : }
786 :
787 0 : JS::Rooted<JS::Value> v(cx);
788 0 : bool ok = false;
789 :
790 0 : if (ctorCall) {
791 : JSObject *newObj =
792 0 : ::JS_New(cx, jsobj, jsargs);
793 :
794 0 : if (newObj) {
795 0 : v.setObject(*newObj);
796 0 : ok = true;
797 : }
798 : } else {
799 0 : ok = ::JS_CallFunctionValue(cx, jsobj, fv, jsargs, &v);
800 : }
801 :
802 0 : if (ok)
803 0 : ok = JSValToNPVariant(npp, cx, v, result);
804 :
805 0 : return ok;
806 : }
807 :
808 : // static
809 : bool
810 0 : nsJSObjWrapper::NP_Invoke(NPObject *npobj, NPIdentifier method,
811 : const NPVariant *args, uint32_t argCount,
812 : NPVariant *result)
813 : {
814 0 : if (method == NPIdentifier_VOID) {
815 0 : return false;
816 : }
817 :
818 0 : return doInvoke(npobj, method, args, argCount, false, result);
819 : }
820 :
821 : // static
822 : bool
823 0 : nsJSObjWrapper::NP_InvokeDefault(NPObject *npobj, const NPVariant *args,
824 : uint32_t argCount, NPVariant *result)
825 : {
826 0 : return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false,
827 0 : result);
828 : }
829 :
830 : // static
831 : bool
832 0 : nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid)
833 : {
834 0 : NPP npp = NPPStack::Peek();
835 0 : nsIGlobalObject* globalObject = GetGlobalObject(npp);
836 0 : if (NS_WARN_IF(!globalObject)) {
837 0 : return false;
838 : }
839 :
840 0 : dom::AutoEntryScript aes(globalObject, "NPAPI HasProperty");
841 0 : JSContext *cx = aes.cx();
842 :
843 0 : if (!npobj) {
844 : ThrowJSExceptionASCII(cx,
845 0 : "Null npobj in nsJSObjWrapper::NP_HasProperty!");
846 :
847 0 : return false;
848 : }
849 :
850 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
851 0 : bool found, ok = false;
852 :
853 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
854 0 : JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
855 0 : JSAutoCompartment ac(cx, jsobj);
856 0 : MarkCrossZoneNPIdentifier(cx, npid);
857 :
858 0 : NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
859 : "id must be either string or int!\n");
860 0 : JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
861 0 : ok = ::JS_HasPropertyById(cx, jsobj, id, &found);
862 0 : return ok && found;
863 : }
864 :
865 : // static
866 : bool
867 0 : nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id,
868 : NPVariant *result)
869 : {
870 0 : NPP npp = NPPStack::Peek();
871 :
872 0 : nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp);
873 0 : if (NS_WARN_IF(!globalObject)) {
874 0 : return false;
875 : }
876 :
877 : // We're about to run script via JS_CallFunctionValue, so we need an
878 : // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec.
879 0 : dom::AutoEntryScript aes(globalObject, "NPAPI get");
880 0 : JSContext *cx = aes.cx();
881 :
882 0 : if (!npobj) {
883 : ThrowJSExceptionASCII(cx,
884 0 : "Null npobj in nsJSObjWrapper::NP_GetProperty!");
885 :
886 0 : return false;
887 : }
888 :
889 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
890 :
891 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
892 0 : JSAutoCompartment ac(cx, npjsobj->mJSObj);
893 0 : MarkCrossZoneNPIdentifier(cx, id);
894 :
895 0 : JS::Rooted<JS::Value> v(cx);
896 0 : return (GetProperty(cx, npjsobj->mJSObj, id, &v) &&
897 0 : JSValToNPVariant(npp, cx, v, result));
898 : }
899 :
900 : // static
901 : bool
902 0 : nsJSObjWrapper::NP_SetProperty(NPObject *npobj, NPIdentifier npid,
903 : const NPVariant *value)
904 : {
905 0 : NPP npp = NPPStack::Peek();
906 :
907 0 : nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp);
908 0 : if (NS_WARN_IF(!globalObject)) {
909 0 : return false;
910 : }
911 :
912 : // We're about to run script via JS_CallFunctionValue, so we need an
913 : // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec.
914 0 : dom::AutoEntryScript aes(globalObject, "NPAPI set");
915 0 : JSContext *cx = aes.cx();
916 :
917 0 : if (!npobj) {
918 : ThrowJSExceptionASCII(cx,
919 0 : "Null npobj in nsJSObjWrapper::NP_SetProperty!");
920 :
921 0 : return false;
922 : }
923 :
924 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
925 0 : bool ok = false;
926 :
927 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
928 0 : JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj);
929 0 : JSAutoCompartment ac(cx, jsObj);
930 0 : MarkCrossZoneNPIdentifier(cx, npid);
931 :
932 0 : JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value));
933 :
934 0 : NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
935 : "id must be either string or int!\n");
936 0 : JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
937 0 : ok = ::JS_SetPropertyById(cx, jsObj, id, v);
938 :
939 0 : return ok;
940 : }
941 :
942 : // static
943 : bool
944 0 : nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid)
945 : {
946 0 : NPP npp = NPPStack::Peek();
947 0 : nsIGlobalObject* globalObject = GetGlobalObject(npp);
948 0 : if (NS_WARN_IF(!globalObject)) {
949 0 : return false;
950 : }
951 :
952 0 : dom::AutoEntryScript aes(globalObject, "NPAPI RemoveProperty");
953 0 : JSContext *cx = aes.cx();
954 :
955 0 : if (!npobj) {
956 : ThrowJSExceptionASCII(cx,
957 0 : "Null npobj in nsJSObjWrapper::NP_RemoveProperty!");
958 :
959 0 : return false;
960 : }
961 :
962 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
963 :
964 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
965 0 : JS::ObjectOpResult result;
966 0 : JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj);
967 0 : JSAutoCompartment ac(cx, obj);
968 0 : MarkCrossZoneNPIdentifier(cx, npid);
969 :
970 0 : NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
971 : "id must be either string or int!\n");
972 0 : JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
973 0 : if (!::JS_DeletePropertyById(cx, obj, id, result))
974 0 : return false;
975 :
976 0 : if (result) {
977 : // FIXME: See bug 425823, we shouldn't need to do this, and once
978 : // that bug is fixed we can remove this code.
979 : bool hasProp;
980 0 : if (!::JS_HasPropertyById(cx, obj, id, &hasProp))
981 0 : return false;
982 0 : if (!hasProp)
983 0 : return true;
984 :
985 : // The property might have been deleted, but it got
986 : // re-resolved, so no, it's not really deleted.
987 0 : result.failCantDelete();
988 : }
989 :
990 0 : return result.reportError(cx, obj, id);
991 : }
992 :
993 : //static
994 : bool
995 0 : nsJSObjWrapper::NP_Enumerate(NPObject *npobj, NPIdentifier **idarray,
996 : uint32_t *count)
997 : {
998 0 : NPP npp = NPPStack::Peek();
999 0 : nsIGlobalObject* globalObject = GetGlobalObject(npp);
1000 0 : if (NS_WARN_IF(!globalObject)) {
1001 0 : return false;
1002 : }
1003 :
1004 0 : dom::AutoEntryScript aes(globalObject, "NPAPI Enumerate");
1005 0 : JSContext *cx = aes.cx();
1006 :
1007 0 : *idarray = 0;
1008 0 : *count = 0;
1009 :
1010 0 : if (!npobj) {
1011 : ThrowJSExceptionASCII(cx,
1012 0 : "Null npobj in nsJSObjWrapper::NP_Enumerate!");
1013 :
1014 0 : return false;
1015 : }
1016 :
1017 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
1018 :
1019 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
1020 0 : JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
1021 0 : JSAutoCompartment ac(cx, jsobj);
1022 :
1023 0 : JS::Rooted<JS::IdVector> ida(cx, JS::IdVector(cx));
1024 0 : if (!JS_Enumerate(cx, jsobj, &ida)) {
1025 0 : return false;
1026 : }
1027 :
1028 0 : *count = ida.length();
1029 0 : *idarray = (NPIdentifier*) malloc(*count * sizeof(NPIdentifier));
1030 0 : if (!*idarray) {
1031 0 : ThrowJSExceptionASCII(cx, "Memory allocation failed for NPIdentifier!");
1032 0 : return false;
1033 : }
1034 :
1035 0 : for (uint32_t i = 0; i < *count; i++) {
1036 0 : JS::Rooted<JS::Value> v(cx);
1037 0 : if (!JS_IdToValue(cx, ida[i], &v)) {
1038 0 : free(*idarray);
1039 0 : return false;
1040 : }
1041 :
1042 : NPIdentifier id;
1043 0 : if (v.isString()) {
1044 0 : JS::Rooted<JSString*> str(cx, v.toString());
1045 0 : str = JS_AtomizeAndPinJSString(cx, str);
1046 0 : if (!str) {
1047 0 : free(*idarray);
1048 0 : return false;
1049 : }
1050 0 : id = StringToNPIdentifier(cx, str);
1051 : } else {
1052 0 : NS_ASSERTION(v.isInt32(),
1053 : "The element in ida must be either string or int!\n");
1054 0 : id = IntToNPIdentifier(v.toInt32());
1055 : }
1056 :
1057 0 : (*idarray)[i] = id;
1058 : }
1059 :
1060 0 : return true;
1061 : }
1062 :
1063 : //static
1064 : bool
1065 0 : nsJSObjWrapper::NP_Construct(NPObject *npobj, const NPVariant *args,
1066 : uint32_t argCount, NPVariant *result)
1067 : {
1068 0 : return doInvoke(npobj, NPIdentifier_VOID, args, argCount, true, result);
1069 : }
1070 :
1071 : // Look up or create an NPObject that wraps the JSObject obj.
1072 :
1073 : // static
1074 : NPObject *
1075 0 : nsJSObjWrapper::GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj)
1076 : {
1077 0 : if (!npp) {
1078 0 : NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!");
1079 :
1080 0 : return nullptr;
1081 : }
1082 :
1083 : // No need to enter the right compartment here as we only get the
1084 : // class and private from the JSObject, neither of which cares about
1085 : // compartments.
1086 :
1087 0 : if (nsNPObjWrapper::IsWrapper(obj)) {
1088 : // obj is one of our own, its private data is the NPObject we're
1089 : // looking for.
1090 :
1091 0 : NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
1092 :
1093 : // If the private is null, that means that the object has already been torn
1094 : // down, possible because the owning plugin was destroyed (there can be
1095 : // multiple plugins, so the fact that it was destroyed does not prevent one
1096 : // of its dead JS objects from being passed to another plugin). There's not
1097 : // much use in wrapping such a dead object, so we just return null, causing
1098 : // us to throw.
1099 0 : if (!npobj)
1100 0 : return nullptr;
1101 :
1102 0 : if (LookupNPP(npobj) == npp)
1103 0 : return _retainobject(npobj);
1104 : }
1105 :
1106 0 : if (!sJSObjWrappers.initialized()) {
1107 : // No hash yet (or any more), initialize it.
1108 0 : if (!CreateJSObjWrapperTable())
1109 0 : return nullptr;
1110 : }
1111 0 : MOZ_ASSERT(sJSObjWrappersAccessible);
1112 :
1113 0 : JSObjWrapperTable::Ptr p = sJSObjWrappers.lookupForAdd(nsJSObjWrapperKey(obj, npp));
1114 0 : if (p) {
1115 0 : MOZ_ASSERT(p->value());
1116 : // Found a live nsJSObjWrapper, return it.
1117 :
1118 0 : return _retainobject(p->value());
1119 : }
1120 :
1121 : // No existing nsJSObjWrapper, create one.
1122 :
1123 : nsJSObjWrapper *wrapper =
1124 0 : (nsJSObjWrapper *)_createobject(npp, &sJSObjWrapperNPClass);
1125 :
1126 0 : if (!wrapper) {
1127 : // Out of memory, entry not yet added to table.
1128 0 : return nullptr;
1129 : }
1130 :
1131 0 : wrapper->mJSObj = obj;
1132 :
1133 : // Insert the new wrapper into the hashtable, rooting the JSObject. Its
1134 : // lifetime is now tied to that of the NPObject.
1135 0 : if (!sJSObjWrappers.putNew(nsJSObjWrapperKey(obj, npp), wrapper)) {
1136 : // Out of memory, free the wrapper we created.
1137 0 : _releaseobject(wrapper);
1138 0 : return nullptr;
1139 : }
1140 :
1141 0 : return wrapper;
1142 : }
1143 :
1144 : // Climb the prototype chain, unwrapping as necessary until we find an NP object
1145 : // wrapper.
1146 : //
1147 : // Because this function unwraps, its return value must be wrapped for the cx
1148 : // compartment for callers that plan to hold onto the result or do anything
1149 : // substantial with it.
1150 : static JSObject *
1151 0 : GetNPObjectWrapper(JSContext *cx, JSObject *aObj, bool wrapResult = true)
1152 : {
1153 0 : JS::Rooted<JSObject*> obj(cx, aObj);
1154 0 : while (obj && (obj = js::CheckedUnwrap(obj))) {
1155 0 : if (nsNPObjWrapper::IsWrapper(obj)) {
1156 0 : if (wrapResult && !JS_WrapObject(cx, &obj)) {
1157 0 : return nullptr;
1158 : }
1159 0 : return obj;
1160 : }
1161 :
1162 0 : JSAutoCompartment ac(cx, obj);
1163 0 : if (!::JS_GetPrototype(cx, obj, &obj)) {
1164 0 : return nullptr;
1165 : }
1166 : }
1167 0 : return nullptr;
1168 : }
1169 :
1170 : static NPObject *
1171 0 : GetNPObject(JSContext *cx, JSObject *obj)
1172 : {
1173 0 : obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false);
1174 0 : if (!obj) {
1175 0 : return nullptr;
1176 : }
1177 :
1178 0 : return (NPObject *)::JS_GetPrivate(obj);
1179 : }
1180 :
1181 :
1182 : // Does not actually add a property because this is always followed by a
1183 : // SetProperty call.
1184 : static bool
1185 0 : NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v)
1186 : {
1187 0 : NPObject *npobj = GetNPObject(cx, obj);
1188 :
1189 0 : if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
1190 0 : !npobj->_class->hasMethod) {
1191 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1192 :
1193 0 : return false;
1194 : }
1195 :
1196 0 : if (NPObjectIsOutOfProcessProxy(npobj)) {
1197 0 : return true;
1198 : }
1199 :
1200 0 : PluginDestructionGuard pdg(LookupNPP(npobj));
1201 :
1202 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
1203 0 : bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
1204 0 : if (!ReportExceptionIfPending(cx))
1205 0 : return false;
1206 :
1207 0 : if (hasProperty)
1208 0 : return true;
1209 :
1210 : // We must permit methods here since JS_DefineUCFunction() will add
1211 : // the function as a property
1212 0 : bool hasMethod = npobj->_class->hasMethod(npobj, identifier);
1213 0 : if (!ReportExceptionIfPending(cx))
1214 0 : return false;
1215 :
1216 0 : if (!hasMethod) {
1217 0 : ThrowJSExceptionASCII(cx, "Trying to add unsupported property on NPObject!");
1218 :
1219 0 : return false;
1220 : }
1221 :
1222 0 : return true;
1223 : }
1224 :
1225 : static bool
1226 0 : NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
1227 : JS::ObjectOpResult &result)
1228 : {
1229 0 : NPObject *npobj = GetNPObject(cx, obj);
1230 :
1231 0 : if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
1232 0 : !npobj->_class->removeProperty) {
1233 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1234 :
1235 0 : return false;
1236 : }
1237 :
1238 0 : PluginDestructionGuard pdg(LookupNPP(npobj));
1239 :
1240 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
1241 :
1242 0 : if (!NPObjectIsOutOfProcessProxy(npobj)) {
1243 0 : bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
1244 0 : if (!ReportExceptionIfPending(cx))
1245 0 : return false;
1246 :
1247 0 : if (!hasProperty)
1248 0 : return result.succeed();
1249 : }
1250 :
1251 : // This removeProperty hook may throw an exception and return false; or just
1252 : // return false without an exception pending, which behaves like `delete
1253 : // obj.prop` returning false: in strict mode it becomes a TypeError. Legacy
1254 : // code---nothing else that uses the JSAPI works this way anymore.
1255 0 : bool succeeded = npobj->_class->removeProperty(npobj, identifier);
1256 0 : if (!ReportExceptionIfPending(cx))
1257 0 : return false;
1258 0 : return succeeded ? result.succeed() : result.failCantDelete();
1259 : }
1260 :
1261 : static bool
1262 0 : NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
1263 : JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result)
1264 : {
1265 0 : NPObject *npobj = GetNPObject(cx, obj);
1266 :
1267 0 : if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
1268 0 : !npobj->_class->setProperty) {
1269 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1270 :
1271 0 : return false;
1272 : }
1273 :
1274 : // Find out what plugin (NPP) is the owner of the object we're
1275 : // manipulating, and make it own any JSObject wrappers created here.
1276 0 : NPP npp = LookupNPP(npobj);
1277 :
1278 0 : if (!npp) {
1279 0 : ThrowJSExceptionASCII(cx, "No NPP found for NPObject!");
1280 :
1281 0 : return false;
1282 : }
1283 :
1284 0 : PluginDestructionGuard pdg(npp);
1285 :
1286 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
1287 :
1288 0 : if (!NPObjectIsOutOfProcessProxy(npobj)) {
1289 0 : bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
1290 0 : if (!ReportExceptionIfPending(cx))
1291 0 : return false;
1292 :
1293 0 : if (!hasProperty) {
1294 0 : ThrowJSExceptionASCII(cx, "Trying to set unsupported property on NPObject!");
1295 :
1296 0 : return false;
1297 : }
1298 : }
1299 :
1300 : NPVariant npv;
1301 0 : if (!JSValToNPVariant(npp, cx, vp, &npv)) {
1302 0 : ThrowJSExceptionASCII(cx, "Error converting jsval to NPVariant!");
1303 :
1304 0 : return false;
1305 : }
1306 :
1307 0 : bool ok = npobj->_class->setProperty(npobj, identifier, &npv);
1308 0 : _releasevariantvalue(&npv); // Release the variant
1309 0 : if (!ReportExceptionIfPending(cx))
1310 0 : return false;
1311 :
1312 0 : if (!ok) {
1313 0 : ThrowJSExceptionASCII(cx, "Error setting property on NPObject!");
1314 :
1315 0 : return false;
1316 : }
1317 :
1318 0 : return result.succeed();
1319 : }
1320 :
1321 : static bool
1322 0 : NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp)
1323 : {
1324 0 : NPObject *npobj = GetNPObject(cx, obj);
1325 :
1326 0 : if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
1327 0 : !npobj->_class->hasMethod || !npobj->_class->getProperty) {
1328 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1329 :
1330 0 : return false;
1331 : }
1332 :
1333 0 : if (JSID_IS_SYMBOL(id)) {
1334 0 : JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
1335 0 : if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
1336 0 : JS::RootedObject obj(cx, JS_GetFunctionObject(
1337 : JS_NewFunction(
1338 : cx, NPObjWrapper_toPrimitive, 1, 0,
1339 0 : "Symbol.toPrimitive")));
1340 0 : if (!obj)
1341 0 : return false;
1342 0 : vp.setObject(*obj);
1343 0 : return true;
1344 : }
1345 :
1346 0 : if (JS::GetSymbolCode(sym) == JS::SymbolCode::toStringTag) {
1347 0 : JS::RootedString tag(cx, JS_NewStringCopyZ(cx, NPRUNTIME_JSCLASS_NAME));
1348 0 : if (!tag) {
1349 0 : return false;
1350 : }
1351 :
1352 0 : vp.setString(tag);
1353 0 : return true;
1354 : }
1355 :
1356 0 : vp.setUndefined();
1357 0 : return true;
1358 : }
1359 :
1360 : // Find out what plugin (NPP) is the owner of the object we're
1361 : // manipulating, and make it own any JSObject wrappers created here.
1362 0 : NPP npp = LookupNPP(npobj);
1363 0 : if (!npp) {
1364 0 : ThrowJSExceptionASCII(cx, "No NPP found for NPObject!");
1365 :
1366 0 : return false;
1367 : }
1368 :
1369 0 : PluginDestructionGuard pdg(npp);
1370 :
1371 : bool hasProperty, hasMethod;
1372 :
1373 : NPVariant npv;
1374 0 : VOID_TO_NPVARIANT(npv);
1375 :
1376 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
1377 :
1378 0 : if (NPObjectIsOutOfProcessProxy(npobj)) {
1379 : PluginScriptableObjectParent* actor =
1380 0 : static_cast<ParentNPObject*>(npobj)->parent;
1381 :
1382 : // actor may be null if the plugin crashed.
1383 0 : if (!actor)
1384 0 : return false;
1385 :
1386 : bool success = actor->GetPropertyHelper(identifier, &hasProperty,
1387 0 : &hasMethod, &npv);
1388 :
1389 0 : if (!ReportExceptionIfPending(cx)) {
1390 0 : if (success)
1391 0 : _releasevariantvalue(&npv);
1392 0 : return false;
1393 : }
1394 :
1395 0 : if (success) {
1396 : // We return NPObject Member class here to support ambiguous members.
1397 0 : if (hasProperty && hasMethod)
1398 0 : return CreateNPObjectMember(npp, cx, obj, npobj, id, &npv, vp);
1399 :
1400 0 : if (hasProperty) {
1401 0 : vp.set(NPVariantToJSVal(npp, cx, &npv));
1402 0 : _releasevariantvalue(&npv);
1403 :
1404 0 : if (!ReportExceptionIfPending(cx))
1405 0 : return false;
1406 : }
1407 : }
1408 0 : return true;
1409 : }
1410 :
1411 0 : hasProperty = npobj->_class->hasProperty(npobj, identifier);
1412 0 : if (!ReportExceptionIfPending(cx))
1413 0 : return false;
1414 :
1415 0 : hasMethod = npobj->_class->hasMethod(npobj, identifier);
1416 0 : if (!ReportExceptionIfPending(cx))
1417 0 : return false;
1418 :
1419 : // We return NPObject Member class here to support ambiguous members.
1420 0 : if (hasProperty && hasMethod)
1421 0 : return CreateNPObjectMember(npp, cx, obj, npobj, id, nullptr, vp);
1422 :
1423 0 : if (hasProperty) {
1424 0 : if (npobj->_class->getProperty(npobj, identifier, &npv))
1425 0 : vp.set(NPVariantToJSVal(npp, cx, &npv));
1426 :
1427 0 : _releasevariantvalue(&npv);
1428 :
1429 0 : if (!ReportExceptionIfPending(cx))
1430 0 : return false;
1431 : }
1432 :
1433 0 : return true;
1434 : }
1435 :
1436 : static bool
1437 0 : CallNPMethodInternal(JSContext *cx, JS::Handle<JSObject*> obj, unsigned argc,
1438 : JS::Value *argv, JS::Value *rval, bool ctorCall)
1439 : {
1440 0 : NPObject *npobj = GetNPObject(cx, obj);
1441 :
1442 0 : if (!npobj || !npobj->_class) {
1443 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1444 :
1445 0 : return false;
1446 : }
1447 :
1448 : // Find out what plugin (NPP) is the owner of the object we're
1449 : // manipulating, and make it own any JSObject wrappers created here.
1450 0 : NPP npp = LookupNPP(npobj);
1451 :
1452 0 : if (!npp) {
1453 0 : ThrowJSExceptionASCII(cx, "Error finding NPP for NPObject!");
1454 :
1455 0 : return false;
1456 : }
1457 :
1458 0 : PluginDestructionGuard pdg(npp);
1459 :
1460 : NPVariant npargs_buf[8];
1461 0 : NPVariant *npargs = npargs_buf;
1462 :
1463 0 : if (argc > (sizeof(npargs_buf) / sizeof(NPVariant))) {
1464 : // Our stack buffer isn't large enough to hold all arguments,
1465 : // malloc a buffer.
1466 0 : npargs = (NPVariant*) malloc(argc * sizeof(NPVariant));
1467 :
1468 0 : if (!npargs) {
1469 0 : ThrowJSExceptionASCII(cx, "Out of memory!");
1470 :
1471 0 : return false;
1472 : }
1473 : }
1474 :
1475 : // Convert arguments
1476 : uint32_t i;
1477 0 : for (i = 0; i < argc; ++i) {
1478 0 : if (!JSValToNPVariant(npp, cx, argv[i], npargs + i)) {
1479 0 : ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!");
1480 :
1481 0 : if (npargs != npargs_buf) {
1482 0 : free(npargs);
1483 : }
1484 :
1485 0 : return false;
1486 : }
1487 : }
1488 :
1489 : NPVariant v;
1490 0 : VOID_TO_NPVARIANT(v);
1491 :
1492 0 : JSObject *funobj = argv[-2].toObjectOrNull();
1493 : bool ok;
1494 0 : const char *msg = "Error calling method on NPObject!";
1495 :
1496 0 : if (ctorCall) {
1497 : // construct a new NPObject based on the NPClass in npobj. Fail if
1498 : // no construct method is available.
1499 :
1500 0 : if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) &&
1501 0 : npobj->_class->construct) {
1502 0 : ok = npobj->_class->construct(npobj, npargs, argc, &v);
1503 : } else {
1504 0 : ok = false;
1505 :
1506 0 : msg = "Attempt to construct object from class with no constructor.";
1507 : }
1508 0 : } else if (funobj != obj) {
1509 : // A obj.function() style call is made, get the method name from
1510 : // the function object.
1511 :
1512 0 : if (npobj->_class->invoke) {
1513 0 : JSFunction *fun = ::JS_GetObjectFunction(funobj);
1514 0 : JS::Rooted<JSString*> funId(cx, ::JS_GetFunctionId(fun));
1515 0 : JSString *name = ::JS_AtomizeAndPinJSString(cx, funId);
1516 0 : NPIdentifier id = StringToNPIdentifier(cx, name);
1517 :
1518 0 : ok = npobj->_class->invoke(npobj, id, npargs, argc, &v);
1519 : } else {
1520 0 : ok = false;
1521 :
1522 0 : msg = "Attempt to call a method on object with no invoke method.";
1523 : }
1524 : } else {
1525 0 : if (npobj->_class->invokeDefault) {
1526 : // obj is a callable object that is being called, no method name
1527 : // available then. Invoke the default method.
1528 :
1529 0 : ok = npobj->_class->invokeDefault(npobj, npargs, argc, &v);
1530 : } else {
1531 0 : ok = false;
1532 :
1533 0 : msg = "Attempt to call a default method on object with no "
1534 : "invokeDefault method.";
1535 : }
1536 : }
1537 :
1538 : // Release arguments.
1539 0 : for (i = 0; i < argc; ++i) {
1540 0 : _releasevariantvalue(npargs + i);
1541 : }
1542 :
1543 0 : if (npargs != npargs_buf) {
1544 0 : free(npargs);
1545 : }
1546 :
1547 0 : if (!ok) {
1548 : // ReportExceptionIfPending returns a return value, which is true
1549 : // if no exception was thrown. In that case, throw our own.
1550 0 : if (ReportExceptionIfPending(cx))
1551 0 : ThrowJSExceptionASCII(cx, msg);
1552 :
1553 0 : return false;
1554 : }
1555 :
1556 0 : *rval = NPVariantToJSVal(npp, cx, &v);
1557 :
1558 : // *rval now owns the value, release our reference.
1559 0 : _releasevariantvalue(&v);
1560 :
1561 0 : return ReportExceptionIfPending(cx);
1562 : }
1563 :
1564 : static bool
1565 0 : CallNPMethod(JSContext *cx, unsigned argc, JS::Value *vp)
1566 : {
1567 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1568 0 : JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp));
1569 0 : if (!obj)
1570 0 : return false;
1571 :
1572 0 : return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false);
1573 : }
1574 :
1575 : static bool
1576 0 : NPObjWrapper_NewEnumerate(JSContext *cx, JS::Handle<JSObject*> obj,
1577 : JS::AutoIdVector &properties, bool enumerableOnly)
1578 : {
1579 0 : NPObject *npobj = GetNPObject(cx, obj);
1580 0 : if (!npobj || !npobj->_class) {
1581 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1582 0 : return false;
1583 : }
1584 :
1585 0 : PluginDestructionGuard pdg(LookupNPP(npobj));
1586 :
1587 0 : if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) ||
1588 0 : !npobj->_class->enumerate) {
1589 0 : return true;
1590 : }
1591 :
1592 : NPIdentifier *identifiers;
1593 : uint32_t length;
1594 0 : if (!npobj->_class->enumerate(npobj, &identifiers, &length)) {
1595 0 : if (ReportExceptionIfPending(cx)) {
1596 : // ReportExceptionIfPending returns a return value, which is true
1597 : // if no exception was thrown. In that case, throw our own.
1598 : ThrowJSExceptionASCII(cx, "Error enumerating properties on scriptable "
1599 0 : "plugin object");
1600 : }
1601 0 : return false;
1602 : }
1603 :
1604 0 : if (!properties.reserve(length))
1605 0 : return false;
1606 :
1607 0 : JS::Rooted<jsid> id(cx);
1608 0 : for (uint32_t i = 0; i < length; i++) {
1609 0 : id = NPIdentifierToJSId(identifiers[i]);
1610 0 : properties.infallibleAppend(id);
1611 : }
1612 :
1613 0 : free(identifiers);
1614 0 : return true;
1615 : }
1616 :
1617 : static bool
1618 0 : NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
1619 : bool *resolvedp)
1620 : {
1621 0 : if (JSID_IS_SYMBOL(id))
1622 0 : return true;
1623 :
1624 0 : AUTO_PROFILER_LABEL("NPObjWrapper_Resolve", JS);
1625 :
1626 0 : NPObject *npobj = GetNPObject(cx, obj);
1627 :
1628 0 : if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
1629 0 : !npobj->_class->hasMethod) {
1630 0 : ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
1631 :
1632 0 : return false;
1633 : }
1634 :
1635 0 : PluginDestructionGuard pdg(LookupNPP(npobj));
1636 :
1637 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
1638 :
1639 0 : bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
1640 0 : if (!ReportExceptionIfPending(cx))
1641 0 : return false;
1642 :
1643 0 : if (hasProperty) {
1644 0 : NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id),
1645 : "id must be either string or int!\n");
1646 0 : if (!::JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue,
1647 : JSPROP_ENUMERATE | JSPROP_SHARED)) {
1648 0 : return false;
1649 : }
1650 :
1651 0 : *resolvedp = true;
1652 :
1653 0 : return true;
1654 : }
1655 :
1656 0 : bool hasMethod = npobj->_class->hasMethod(npobj, identifier);
1657 0 : if (!ReportExceptionIfPending(cx))
1658 0 : return false;
1659 :
1660 0 : if (hasMethod) {
1661 0 : NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id),
1662 : "id must be either string or int!\n");
1663 :
1664 : JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallNPMethod, 0,
1665 0 : JSPROP_ENUMERATE);
1666 :
1667 0 : *resolvedp = true;
1668 :
1669 0 : return fnc != nullptr;
1670 : }
1671 :
1672 : // no property or method
1673 0 : return true;
1674 : }
1675 :
1676 : static void
1677 0 : NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj)
1678 : {
1679 0 : JS::AutoAssertGCCallback inCallback;
1680 :
1681 0 : NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
1682 0 : if (npobj) {
1683 0 : if (sNPObjWrappers) {
1684 : // If the sNPObjWrappers map contains an entry that refers to this
1685 : // wrapper, remove it.
1686 : auto entry =
1687 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
1688 0 : if (entry && entry->mJSObj == obj) {
1689 0 : sNPObjWrappers->Remove(npobj);
1690 : }
1691 : }
1692 : }
1693 :
1694 0 : if (!sDelayedReleases)
1695 0 : sDelayedReleases = new nsTArray<NPObject*>;
1696 0 : sDelayedReleases->AppendElement(npobj);
1697 0 : }
1698 :
1699 : static void
1700 0 : NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old)
1701 : {
1702 : // The wrapper JSObject has been moved, so we need to update the entry in the
1703 : // sNPObjWrappers hash table, if present.
1704 :
1705 0 : if (!sNPObjWrappers) {
1706 0 : return;
1707 : }
1708 :
1709 0 : NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
1710 0 : if (!npobj) {
1711 0 : return;
1712 : }
1713 :
1714 : // Calling PLDHashTable::Search() will not result in GC.
1715 0 : JS::AutoSuppressGCAnalysis nogc;
1716 :
1717 : auto entry =
1718 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
1719 0 : MOZ_ASSERT(entry && entry->mJSObj);
1720 0 : MOZ_ASSERT(entry->mJSObj == old);
1721 0 : entry->mJSObj = obj;
1722 : }
1723 :
1724 : static bool
1725 0 : NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp)
1726 : {
1727 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1728 0 : JS::Rooted<JSObject*> obj(cx, &args.callee());
1729 0 : return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false);
1730 : }
1731 :
1732 : static bool
1733 0 : NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp)
1734 : {
1735 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1736 0 : JS::Rooted<JSObject*> obj(cx, &args.callee());
1737 0 : return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true);
1738 : }
1739 :
1740 : static bool
1741 0 : NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
1742 : {
1743 : // Plugins do not simply use the default OrdinaryToPrimitive behavior,
1744 : // because that behavior involves calling toString or valueOf on objects
1745 : // which weren't designed to accommodate this. Usually this wouldn't be a
1746 : // problem, because the absence of either property, or the presence of either
1747 : // property with a value that isn't callable, will cause that property to
1748 : // simply be ignored. But there is a problem in one specific case: Java,
1749 : // specifically java.lang.Integer. The Integer class has static valueOf
1750 : // methods, none of which are nullary, so the JS-reflected method will behave
1751 : // poorly when called with no arguments. We work around this problem by
1752 : // giving plugins a [Symbol.toPrimitive]() method which uses only toString
1753 : // and not valueOf.
1754 :
1755 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
1756 0 : JS::RootedValue thisv(cx, args.thisv());
1757 0 : if (thisv.isPrimitive())
1758 0 : return true;
1759 :
1760 0 : JS::RootedObject obj(cx, &thisv.toObject());
1761 0 : JS::RootedValue v(cx);
1762 0 : if (!JS_GetProperty(cx, obj, "toString", &v))
1763 0 : return false;
1764 0 : if (v.isObject() && JS::IsCallable(&v.toObject())) {
1765 0 : if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval()))
1766 0 : return false;
1767 0 : if (args.rval().isPrimitive())
1768 0 : return true;
1769 : }
1770 :
1771 : JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
1772 : JSMSG_CANT_CONVERT_TO,
1773 0 : JS_GetClass(obj)->name, "primitive type");
1774 0 : return false;
1775 : }
1776 :
1777 : bool
1778 0 : nsNPObjWrapper::IsWrapper(JSObject *obj)
1779 : {
1780 0 : return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass;
1781 : }
1782 :
1783 : // An NPObject is going away, make sure we null out the JS object's
1784 : // private data in case this is an NPObject that came from a plugin
1785 : // and it's destroyed prematurely.
1786 :
1787 : // static
1788 : void
1789 0 : nsNPObjWrapper::OnDestroy(NPObject *npobj)
1790 : {
1791 0 : if (!npobj) {
1792 0 : return;
1793 : }
1794 :
1795 0 : if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) {
1796 : // npobj is one of our own, no private data to clean up here.
1797 :
1798 0 : return;
1799 : }
1800 :
1801 0 : if (!sNPObjWrappers) {
1802 : // No hash yet (or any more), no used wrappers available.
1803 :
1804 0 : return;
1805 : }
1806 :
1807 : auto entry =
1808 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
1809 :
1810 0 : if (entry && entry->mJSObj) {
1811 : // Found a live NPObject wrapper, null out its JSObjects' private
1812 : // data.
1813 :
1814 0 : ::JS_SetPrivate(entry->mJSObj, nullptr);
1815 :
1816 : // Remove the npobj from the hash now that it went away.
1817 0 : sNPObjWrappers->RawRemove(entry);
1818 :
1819 : // The finalize hook will call OnWrapperDestroyed().
1820 : }
1821 : }
1822 :
1823 : // Look up or create a JSObject that wraps the NPObject npobj.
1824 :
1825 : // static
1826 : JSObject *
1827 0 : nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj)
1828 : {
1829 0 : if (!npobj) {
1830 0 : NS_ERROR("Null NPObject passed to nsNPObjWrapper::GetNewOrUsed()!");
1831 :
1832 0 : return nullptr;
1833 : }
1834 :
1835 0 : if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) {
1836 : // npobj is one of our own, return its existing JSObject.
1837 :
1838 0 : JS::Rooted<JSObject*> obj(cx, ((nsJSObjWrapper *)npobj)->mJSObj);
1839 0 : if (!JS_WrapObject(cx, &obj)) {
1840 0 : return nullptr;
1841 : }
1842 0 : return obj;
1843 : }
1844 :
1845 0 : if (!npp) {
1846 0 : NS_ERROR("No npp passed to nsNPObjWrapper::GetNewOrUsed()!");
1847 :
1848 0 : return nullptr;
1849 : }
1850 :
1851 0 : if (!sNPObjWrappers) {
1852 : // No hash yet (or any more), initialize it.
1853 0 : if (!CreateNPObjWrapperTable()) {
1854 0 : return nullptr;
1855 : }
1856 : }
1857 :
1858 : auto entry =
1859 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible));
1860 :
1861 0 : if (!entry) {
1862 : // Out of memory
1863 0 : JS_ReportOutOfMemory(cx);
1864 :
1865 0 : return nullptr;
1866 : }
1867 :
1868 0 : if (entry->mJSObj) {
1869 : // Found a NPObject wrapper. First check it is still alive.
1870 0 : JSObject* obj = entry->mJSObj;
1871 0 : if (js::gc::EdgeNeedsSweepUnbarriered(&obj)) {
1872 : // The object is dead (finalization will happen at a later time). By the
1873 : // time we leave this function, this entry will either be updated with a
1874 : // new wrapper or removed if that fails. Clear it anyway to make sure
1875 : // nothing touches the dead object.
1876 0 : entry->mJSObj = nullptr;
1877 : } else {
1878 : // It may not be in the same compartment as cx, so we need to wrap it
1879 : // before returning it.
1880 0 : JS::Rooted<JSObject*> obj(cx, entry->mJSObj);
1881 0 : if (!JS_WrapObject(cx, &obj)) {
1882 0 : return nullptr;
1883 : }
1884 0 : return obj;
1885 : }
1886 : }
1887 :
1888 0 : entry->mNPObj = npobj;
1889 0 : entry->mNpp = npp;
1890 :
1891 0 : uint32_t generation = sNPObjWrappers->Generation();
1892 :
1893 : // No existing JSObject, create one.
1894 :
1895 0 : JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, js::Jsvalify(&sNPObjectJSWrapperClass)));
1896 :
1897 0 : if (generation != sNPObjWrappers->Generation()) {
1898 : // Reload entry if the JS_NewObject call caused a GC and reallocated
1899 : // the table (see bug 445229). This is guaranteed to succeed.
1900 :
1901 : entry =
1902 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
1903 0 : NS_ASSERTION(entry, "Hashtable didn't find what we just added?");
1904 : }
1905 :
1906 0 : if (!obj) {
1907 : // OOM? Remove the stale entry from the hash.
1908 :
1909 0 : sNPObjWrappers->RawRemove(entry);
1910 :
1911 0 : return nullptr;
1912 : }
1913 :
1914 0 : OnWrapperCreated();
1915 :
1916 0 : entry->mJSObj = obj;
1917 :
1918 0 : ::JS_SetPrivate(obj, npobj);
1919 :
1920 : // The new JSObject now holds on to npobj
1921 0 : _retainobject(npobj);
1922 :
1923 0 : return obj;
1924 : }
1925 :
1926 : // static
1927 : void
1928 0 : nsJSNPRuntime::OnPluginDestroy(NPP npp)
1929 : {
1930 0 : if (sJSObjWrappersAccessible) {
1931 :
1932 : // Prevent modification of sJSObjWrappers table if we go reentrant.
1933 0 : sJSObjWrappersAccessible = false;
1934 :
1935 0 : for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) {
1936 0 : nsJSObjWrapper *npobj = e.front().value();
1937 0 : MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass);
1938 0 : if (npobj->mNpp == npp) {
1939 0 : if (npobj->_class && npobj->_class->invalidate) {
1940 0 : npobj->_class->invalidate(npobj);
1941 : }
1942 :
1943 0 : _releaseobject(npobj);
1944 :
1945 0 : e.removeFront();
1946 : }
1947 : }
1948 :
1949 0 : sJSObjWrappersAccessible = true;
1950 : }
1951 :
1952 0 : if (sNPObjWrappers) {
1953 0 : for (auto i = sNPObjWrappers->Iter(); !i.Done(); i.Next()) {
1954 0 : auto entry = static_cast<NPObjWrapperHashEntry*>(i.Get());
1955 :
1956 0 : if (entry->mNpp == npp) {
1957 : // HACK: temporarily hide the table we're enumerating so that
1958 : // invalidate() and deallocate() don't touch it.
1959 0 : PLDHashTable *tmp = sNPObjWrappers;
1960 0 : sNPObjWrappers = nullptr;
1961 :
1962 0 : NPObject *npobj = entry->mNPObj;
1963 :
1964 0 : if (npobj->_class && npobj->_class->invalidate) {
1965 0 : npobj->_class->invalidate(npobj);
1966 : }
1967 :
1968 : #ifdef NS_BUILD_REFCNT_LOGGING
1969 : {
1970 0 : int32_t refCnt = npobj->referenceCount;
1971 0 : while (refCnt) {
1972 0 : --refCnt;
1973 0 : NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject");
1974 : }
1975 : }
1976 : #endif
1977 :
1978 : // Force deallocation of plugin objects since the plugin they came
1979 : // from is being torn down.
1980 0 : if (npobj->_class && npobj->_class->deallocate) {
1981 0 : npobj->_class->deallocate(npobj);
1982 : } else {
1983 0 : free(npobj);
1984 : }
1985 :
1986 0 : ::JS_SetPrivate(entry->mJSObj, nullptr);
1987 :
1988 0 : sNPObjWrappers = tmp;
1989 :
1990 0 : if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) {
1991 0 : OnWrapperDestroyed();
1992 : }
1993 :
1994 0 : i.Remove();
1995 : }
1996 : }
1997 : }
1998 0 : }
1999 :
2000 : // static
2001 : void
2002 0 : nsJSNPRuntime::OnPluginDestroyPending(NPP npp)
2003 : {
2004 0 : if (sJSObjWrappersAccessible) {
2005 : // Prevent modification of sJSObjWrappers table if we go reentrant.
2006 0 : sJSObjWrappersAccessible = false;
2007 0 : for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) {
2008 0 : nsJSObjWrapper *npobj = e.front().value();
2009 0 : MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass);
2010 0 : if (npobj->mNpp == npp) {
2011 0 : npobj->mDestroyPending = true;
2012 : }
2013 : }
2014 0 : sJSObjWrappersAccessible = true;
2015 : }
2016 0 : }
2017 :
2018 : // Find the NPP for a NPObject.
2019 : static NPP
2020 0 : LookupNPP(NPObject *npobj)
2021 : {
2022 0 : if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) {
2023 0 : nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj);
2024 0 : return o->mNpp;
2025 : }
2026 :
2027 : auto entry =
2028 0 : static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible));
2029 :
2030 0 : if (!entry) {
2031 0 : return nullptr;
2032 : }
2033 :
2034 0 : NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!");
2035 :
2036 0 : return entry->mNpp;
2037 : }
2038 :
2039 : static bool
2040 0 : CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
2041 : JS::Handle<jsid> id, NPVariant* getPropertyResult,
2042 : JS::MutableHandle<JS::Value> vp)
2043 : {
2044 0 : if (!npobj || !npobj->_class || !npobj->_class->getProperty ||
2045 0 : !npobj->_class->invoke) {
2046 0 : ThrowJSExceptionASCII(cx, "Bad NPObject");
2047 :
2048 0 : return false;
2049 : }
2050 :
2051 : NPObjectMemberPrivate* memberPrivate =
2052 0 : (NPObjectMemberPrivate*) malloc(sizeof(NPObjectMemberPrivate));
2053 0 : if (!memberPrivate)
2054 0 : return false;
2055 :
2056 : // Make sure to clear all members in case something fails here
2057 : // during initialization.
2058 0 : memset(memberPrivate, 0, sizeof(NPObjectMemberPrivate));
2059 :
2060 0 : JSObject *memobj = ::JS_NewObject(cx, &sNPObjectMemberClass);
2061 0 : if (!memobj) {
2062 0 : free(memberPrivate);
2063 0 : return false;
2064 : }
2065 :
2066 0 : vp.setObject(*memobj);
2067 :
2068 0 : ::JS_SetPrivate(memobj, (void *)memberPrivate);
2069 :
2070 0 : NPIdentifier identifier = JSIdToNPIdentifier(id);
2071 :
2072 0 : JS::Rooted<JS::Value> fieldValue(cx);
2073 : NPVariant npv;
2074 :
2075 0 : if (getPropertyResult) {
2076 : // Plugin has already handed us the value we want here.
2077 0 : npv = *getPropertyResult;
2078 : }
2079 : else {
2080 0 : VOID_TO_NPVARIANT(npv);
2081 :
2082 0 : NPBool hasProperty = npobj->_class->getProperty(npobj, identifier,
2083 0 : &npv);
2084 0 : if (!ReportExceptionIfPending(cx) || !hasProperty) {
2085 0 : return false;
2086 : }
2087 : }
2088 :
2089 0 : fieldValue = NPVariantToJSVal(npp, cx, &npv);
2090 :
2091 : // npobjWrapper is the JSObject through which we make sure we don't
2092 : // outlive the underlying NPObject, so make sure it points to the
2093 : // real JSObject wrapper for the NPObject.
2094 0 : obj = GetNPObjectWrapper(cx, obj);
2095 :
2096 0 : memberPrivate->npobjWrapper = obj;
2097 :
2098 0 : memberPrivate->fieldValue = fieldValue;
2099 0 : memberPrivate->methodName = id;
2100 0 : memberPrivate->npp = npp;
2101 :
2102 0 : return true;
2103 : }
2104 :
2105 : static bool
2106 0 : NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
2107 : JS::MutableHandleValue vp)
2108 : {
2109 0 : AUTO_PROFILER_LABEL("NPObjectMember_GetProperty", OTHER);
2110 :
2111 0 : if (JSID_IS_SYMBOL(id)) {
2112 0 : JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
2113 0 : if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
2114 0 : JS::RootedObject obj(cx, JS_GetFunctionObject(
2115 : JS_NewFunction(
2116 : cx, NPObjectMember_toPrimitive, 1, 0,
2117 0 : "Symbol.toPrimitive")));
2118 0 : if (!obj)
2119 0 : return false;
2120 0 : vp.setObject(*obj);
2121 0 : return true;
2122 : }
2123 : }
2124 :
2125 0 : return true;
2126 : }
2127 :
2128 : static void
2129 0 : NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj)
2130 : {
2131 : NPObjectMemberPrivate *memberPrivate;
2132 :
2133 0 : memberPrivate = (NPObjectMemberPrivate *)::JS_GetPrivate(obj);
2134 0 : if (!memberPrivate)
2135 0 : return;
2136 :
2137 0 : free(memberPrivate);
2138 : }
2139 :
2140 : static bool
2141 0 : NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp)
2142 : {
2143 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2144 0 : JS::Rooted<JSObject*> memobj(cx, &args.callee());
2145 0 : NS_ENSURE_TRUE(memobj, false);
2146 :
2147 : NPObjectMemberPrivate *memberPrivate =
2148 0 : (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, memobj,
2149 : &sNPObjectMemberClass,
2150 0 : &args);
2151 0 : if (!memberPrivate || !memberPrivate->npobjWrapper)
2152 0 : return false;
2153 :
2154 0 : NPObject *npobj = GetNPObject(cx, memberPrivate->npobjWrapper);
2155 0 : if (!npobj) {
2156 0 : ThrowJSExceptionASCII(cx, "Call on invalid member object");
2157 :
2158 0 : return false;
2159 : }
2160 :
2161 : NPVariant npargs_buf[8];
2162 0 : NPVariant *npargs = npargs_buf;
2163 :
2164 0 : if (args.length() > (sizeof(npargs_buf) / sizeof(NPVariant))) {
2165 : // Our stack buffer isn't large enough to hold all arguments,
2166 : // malloc a buffer.
2167 0 : npargs = (NPVariant*) malloc(args.length() * sizeof(NPVariant));
2168 :
2169 0 : if (!npargs) {
2170 0 : ThrowJSExceptionASCII(cx, "Out of memory!");
2171 :
2172 0 : return false;
2173 : }
2174 : }
2175 :
2176 : // Convert arguments
2177 0 : for (uint32_t i = 0; i < args.length(); ++i) {
2178 0 : if (!JSValToNPVariant(memberPrivate->npp, cx, args[i], npargs + i)) {
2179 0 : ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!");
2180 :
2181 0 : if (npargs != npargs_buf) {
2182 0 : free(npargs);
2183 : }
2184 :
2185 0 : return false;
2186 : }
2187 : }
2188 :
2189 :
2190 : NPVariant npv;
2191 0 : bool ok = npobj->_class->invoke(npobj,
2192 0 : JSIdToNPIdentifier(memberPrivate->methodName),
2193 0 : npargs, args.length(), &npv);
2194 :
2195 : // Release arguments.
2196 0 : for (uint32_t i = 0; i < args.length(); ++i) {
2197 0 : _releasevariantvalue(npargs + i);
2198 : }
2199 :
2200 0 : if (npargs != npargs_buf) {
2201 0 : free(npargs);
2202 : }
2203 :
2204 0 : if (!ok) {
2205 : // ReportExceptionIfPending returns a return value, which is true
2206 : // if no exception was thrown. In that case, throw our own.
2207 0 : if (ReportExceptionIfPending(cx))
2208 0 : ThrowJSExceptionASCII(cx, "Error calling method on NPObject!");
2209 :
2210 0 : return false;
2211 : }
2212 :
2213 0 : args.rval().set(NPVariantToJSVal(memberPrivate->npp, cx, &npv));
2214 :
2215 : // *vp now owns the value, release our reference.
2216 0 : _releasevariantvalue(&npv);
2217 :
2218 0 : return ReportExceptionIfPending(cx);
2219 : }
2220 :
2221 : static void
2222 0 : NPObjectMember_Trace(JSTracer *trc, JSObject *obj)
2223 : {
2224 : NPObjectMemberPrivate *memberPrivate =
2225 0 : (NPObjectMemberPrivate *)::JS_GetPrivate(obj);
2226 0 : if (!memberPrivate)
2227 0 : return;
2228 :
2229 : // Our NPIdentifier is not always interned, so we must trace it.
2230 0 : JS::TraceEdge(trc, &memberPrivate->methodName, "NPObjectMemberPrivate.methodName");
2231 :
2232 0 : JS::TraceEdge(trc, &memberPrivate->fieldValue, "NPObject Member => fieldValue");
2233 :
2234 : // There's no strong reference from our private data to the
2235 : // NPObject, so make sure to mark the NPObject wrapper to keep the
2236 : // NPObject alive as long as this NPObjectMember is alive.
2237 0 : JS::TraceEdge(trc, &memberPrivate->npobjWrapper,
2238 0 : "NPObject Member => npobjWrapper");
2239 : }
2240 :
2241 : static bool
2242 0 : NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
2243 : {
2244 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2245 0 : JS::RootedValue thisv(cx, args.thisv());
2246 0 : if (thisv.isPrimitive()) {
2247 0 : args.rval().set(thisv);
2248 0 : return true;
2249 : }
2250 :
2251 0 : JS::RootedObject obj(cx, &thisv.toObject());
2252 : NPObjectMemberPrivate *memberPrivate =
2253 0 : (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
2254 : &sNPObjectMemberClass,
2255 0 : &args);
2256 0 : if (!memberPrivate)
2257 0 : return false;
2258 :
2259 : JSType hint;
2260 0 : if (!JS::GetFirstArgumentAsTypeHint(cx, args, &hint))
2261 0 : return false;
2262 :
2263 0 : args.rval().set(memberPrivate->fieldValue);
2264 0 : if (args.rval().isObject()) {
2265 0 : JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject());
2266 0 : return JS::ToPrimitive(cx, objVal, hint, args.rval());
2267 : }
2268 0 : return true;
2269 : }
2270 :
2271 : // static
2272 : bool
2273 0 : nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid)
2274 : {
2275 0 : NPP npp = NPPStack::Peek();
2276 0 : nsIGlobalObject* globalObject = GetGlobalObject(npp);
2277 0 : if (NS_WARN_IF(!globalObject)) {
2278 0 : return false;
2279 : }
2280 :
2281 0 : dom::AutoEntryScript aes(globalObject, "NPAPI HasOwnProperty");
2282 0 : JSContext *cx = aes.cx();
2283 :
2284 0 : if (!npobj) {
2285 : ThrowJSExceptionASCII(cx,
2286 0 : "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!");
2287 :
2288 0 : return false;
2289 : }
2290 :
2291 0 : nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
2292 0 : bool found, ok = false;
2293 :
2294 0 : AutoJSExceptionSuppressor suppressor(aes, npjsobj);
2295 0 : JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
2296 0 : JSAutoCompartment ac(cx, jsobj);
2297 0 : MarkCrossZoneNPIdentifier(cx, npid);
2298 :
2299 0 : NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
2300 : "id must be either string or int!\n");
2301 0 : JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
2302 0 : ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found);
2303 0 : return ok && found;
2304 : }
|