1/*
2 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(NETSCAPE_PLUGIN_API)
29
30#include "NP_jsobject.h"
31
32#include "IdentifierRep.h"
33#include "JSDOMBinding.h"
34#include "c_instance.h"
35#include "c_utility.h"
36#include "npruntime_priv.h"
37#include "runtime_root.h"
38#include <JavaScriptCore/CatchScope.h>
39#include <JavaScriptCore/Completion.h>
40#include <JavaScriptCore/Error.h>
41#include <JavaScriptCore/JSGlobalObject.h>
42#include <JavaScriptCore/JSLock.h>
43#include <JavaScriptCore/PropertyNameArray.h>
44#include <JavaScriptCore/SourceCode.h>
45#include <wtf/NeverDestroyed.h>
46#include <wtf/text/WTFString.h>
47
48#pragma GCC visibility push(default)
49#include "npruntime_impl.h"
50#pragma GCC visibility pop
51
52namespace JSC {
53using namespace Bindings;
54using namespace WebCore;
55
56class ObjectMap {
57public:
58 NPObject* get(RootObject* rootObject, JSObject* jsObject)
59 {
60 return m_map.get(rootObject).get(jsObject);
61 }
62
63 void add(RootObject* rootObject, JSObject* jsObject, NPObject* npObject)
64 {
65 HashMap<RootObject*, JSToNPObjectMap>::iterator iter = m_map.find(rootObject);
66 if (iter == m_map.end()) {
67 rootObject->addInvalidationCallback(&m_invalidationCallback);
68 iter = m_map.add(rootObject, JSToNPObjectMap()).iterator;
69 }
70
71 ASSERT(iter->value.find(jsObject) == iter->value.end());
72 iter->value.add(jsObject, npObject);
73 }
74
75 void remove(RootObject* rootObject)
76 {
77 ASSERT(m_map.contains(rootObject));
78 m_map.remove(rootObject);
79 }
80
81 void remove(RootObject* rootObject, JSObject* jsObject)
82 {
83 HashMap<RootObject*, JSToNPObjectMap>::iterator iter = m_map.find(rootObject);
84 ASSERT(iter != m_map.end());
85 ASSERT(iter->value.find(jsObject) != iter->value.end());
86
87 iter->value.remove(jsObject);
88 }
89
90private:
91 struct RootObjectInvalidationCallback : public RootObject::InvalidationCallback {
92 void operator()(RootObject*) override;
93 };
94 RootObjectInvalidationCallback m_invalidationCallback;
95
96 // JSObjects are protected by RootObject.
97 typedef HashMap<JSObject*, NPObject*> JSToNPObjectMap;
98 HashMap<RootObject*, JSToNPObjectMap> m_map;
99};
100
101
102static ObjectMap& objectMap()
103{
104 static NeverDestroyed<ObjectMap> map;
105 return map;
106}
107
108void ObjectMap::RootObjectInvalidationCallback::operator()(RootObject* rootObject)
109{
110 objectMap().remove(rootObject);
111}
112
113static void getListFromVariantArgs(ExecState* exec, const NPVariant* args, unsigned argCount, RootObject* rootObject, MarkedArgumentBuffer& aList)
114{
115 for (unsigned i = 0; i < argCount; ++i)
116 aList.append(convertNPVariantToValue(exec, &args[i], rootObject));
117}
118
119static NPObject* jsAllocate(NPP, NPClass*)
120{
121 return static_cast<NPObject*>(malloc(sizeof(JavaScriptObject)));
122}
123
124static void jsDeallocate(NPObject* npObj)
125{
126 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(npObj);
127
128 if (obj->rootObject && obj->rootObject->isValid()) {
129 objectMap().remove(obj->rootObject, obj->imp);
130 obj->rootObject->gcUnprotect(obj->imp);
131 }
132
133 if (obj->rootObject)
134 obj->rootObject->deref();
135
136 free(obj);
137}
138
139static NPClass javascriptClass = { 1, jsAllocate, jsDeallocate, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
140static NPClass noScriptClass = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
141
142extern "C" {
143NPClass* NPScriptObjectClass = &javascriptClass;
144static NPClass* NPNoScriptObjectClass = &noScriptClass;
145
146NPObject* _NPN_CreateScriptObject(NPP npp, JSObject* imp, RefPtr<RootObject>&& rootObject)
147{
148 if (NPObject* object = objectMap().get(rootObject.get(), imp))
149 return _NPN_RetainObject(object);
150
151 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(_NPN_CreateObject(npp, NPScriptObjectClass));
152
153 obj->rootObject = rootObject.leakRef();
154
155 if (obj->rootObject) {
156 obj->rootObject->gcProtect(imp);
157 objectMap().add(obj->rootObject, imp, reinterpret_cast<NPObject*>(obj));
158 }
159
160 obj->imp = imp;
161
162 return reinterpret_cast<NPObject*>(obj);
163}
164
165NPObject* _NPN_CreateNoScriptObject(void)
166{
167 return _NPN_CreateObject(0, NPNoScriptObjectClass);
168}
169
170bool _NPN_InvokeDefault(NPP, NPObject* o, const NPVariant* args, uint32_t argCount, NPVariant* result)
171{
172 if (o->_class == NPScriptObjectClass) {
173 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
174
175 VOID_TO_NPVARIANT(*result);
176
177 // Lookup the function object.
178 RootObject* rootObject = obj->rootObject;
179 if (!rootObject || !rootObject->isValid())
180 return false;
181
182 auto globalObject = rootObject->globalObject();
183 VM& vm = globalObject->vm();
184 JSLockHolder lock(vm);
185 auto scope = DECLARE_CATCH_SCOPE(vm);
186
187 ExecState* exec = globalObject->globalExec();
188
189 // Call the function object.
190 JSValue function = obj->imp;
191 CallData callData;
192 CallType callType = getCallData(vm, function, callData);
193 if (callType == CallType::None)
194 return false;
195
196 MarkedArgumentBuffer argList;
197 getListFromVariantArgs(exec, args, argCount, rootObject, argList);
198 RELEASE_ASSERT(!argList.hasOverflowed());
199 JSValue resultV = JSC::call(exec, function, callType, callData, function, argList);
200
201 // Convert and return the result of the function call.
202 convertValueToNPVariant(exec, resultV, result);
203 scope.clearException();
204 return true;
205 }
206
207 if (o->_class->invokeDefault)
208 return o->_class->invokeDefault(o, args, argCount, result);
209 VOID_TO_NPVARIANT(*result);
210 return true;
211}
212
213bool _NPN_Invoke(NPP npp, NPObject* o, NPIdentifier methodName, const NPVariant* args, uint32_t argCount, NPVariant* result)
214{
215 if (o->_class == NPScriptObjectClass) {
216 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
217
218 IdentifierRep* i = static_cast<IdentifierRep*>(methodName);
219 if (!i->isString())
220 return false;
221
222 // Special case the "eval" method.
223 if (methodName == _NPN_GetStringIdentifier("eval")) {
224 if (argCount != 1)
225 return false;
226 if (args[0].type != NPVariantType_String)
227 return false;
228 return _NPN_Evaluate(npp, o, const_cast<NPString*>(&args[0].value.stringValue), result);
229 }
230
231 // Look up the function object.
232 RootObject* rootObject = obj->rootObject;
233 if (!rootObject || !rootObject->isValid())
234 return false;
235
236 auto globalObject = rootObject->globalObject();
237 VM& vm = globalObject->vm();
238 JSLockHolder lock(vm);
239 auto scope = DECLARE_CATCH_SCOPE(vm);
240
241 ExecState* exec = globalObject->globalExec();
242 JSValue function = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string()));
243 CallData callData;
244 CallType callType = getCallData(vm, function, callData);
245 if (callType == CallType::None)
246 return false;
247
248 // Call the function object.
249 MarkedArgumentBuffer argList;
250 getListFromVariantArgs(exec, args, argCount, rootObject, argList);
251 RELEASE_ASSERT(!argList.hasOverflowed());
252 JSValue resultV = JSC::call(exec, function, callType, callData, obj->imp, argList);
253
254 // Convert and return the result of the function call.
255 convertValueToNPVariant(exec, resultV, result);
256 scope.clearException();
257 return true;
258 }
259
260 if (o->_class->invoke)
261 return o->_class->invoke(o, methodName, args, argCount, result);
262
263 VOID_TO_NPVARIANT(*result);
264 return true;
265}
266
267bool _NPN_Evaluate(NPP, NPObject* o, NPString* s, NPVariant* variant)
268{
269 if (o->_class == NPScriptObjectClass) {
270 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
271
272 RootObject* rootObject = obj->rootObject;
273 if (!rootObject || !rootObject->isValid())
274 return false;
275
276 auto globalObject = rootObject->globalObject();
277 VM& vm = globalObject->vm();
278 JSLockHolder lock(vm);
279 auto scope = DECLARE_CATCH_SCOPE(vm);
280
281 ExecState* exec = globalObject->globalExec();
282 String scriptString = convertNPStringToUTF16(s);
283
284 JSValue returnValue = JSC::evaluate(exec, JSC::makeSource(scriptString, { }), JSC::JSValue());
285
286 convertValueToNPVariant(exec, returnValue, variant);
287 scope.clearException();
288 return true;
289 }
290
291 VOID_TO_NPVARIANT(*variant);
292 return false;
293}
294
295bool _NPN_GetProperty(NPP, NPObject* o, NPIdentifier propertyName, NPVariant* variant)
296{
297 if (o->_class == NPScriptObjectClass) {
298 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
299
300 RootObject* rootObject = obj->rootObject;
301 if (!rootObject || !rootObject->isValid())
302 return false;
303
304 auto globalObject = rootObject->globalObject();
305 VM& vm = globalObject->vm();
306 JSLockHolder lock(vm);
307 auto scope = DECLARE_CATCH_SCOPE(vm);
308
309 ExecState* exec = globalObject->globalExec();
310 IdentifierRep* i = static_cast<IdentifierRep*>(propertyName);
311
312 JSValue result;
313 if (i->isString())
314 result = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string()));
315 else
316 result = obj->imp->get(exec, i->number());
317
318 convertValueToNPVariant(exec, result, variant);
319 scope.clearException();
320 return true;
321 }
322
323 if (o->_class->hasProperty && o->_class->getProperty) {
324 if (o->_class->hasProperty(o, propertyName))
325 return o->_class->getProperty(o, propertyName, variant);
326 return false;
327 }
328
329 VOID_TO_NPVARIANT(*variant);
330 return false;
331}
332
333bool _NPN_SetProperty(NPP, NPObject* o, NPIdentifier propertyName, const NPVariant* variant)
334{
335 if (o->_class == NPScriptObjectClass) {
336 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
337
338 RootObject* rootObject = obj->rootObject;
339 if (!rootObject || !rootObject->isValid())
340 return false;
341
342 auto globalObject = rootObject->globalObject();
343 VM& vm = globalObject->vm();
344 JSLockHolder lock(vm);
345 auto scope = DECLARE_CATCH_SCOPE(vm);
346
347 ExecState* exec = globalObject->globalExec();
348 IdentifierRep* i = static_cast<IdentifierRep*>(propertyName);
349
350 if (i->isString()) {
351 PutPropertySlot slot(obj->imp);
352 obj->imp->methodTable(vm)->put(obj->imp, exec, identifierFromNPIdentifier(exec, i->string()), convertNPVariantToValue(exec, variant, rootObject), slot);
353 } else
354 obj->imp->methodTable(vm)->putByIndex(obj->imp, exec, i->number(), convertNPVariantToValue(exec, variant, rootObject), false);
355 scope.clearException();
356 return true;
357 }
358
359 if (o->_class->setProperty)
360 return o->_class->setProperty(o, propertyName, variant);
361
362 return false;
363}
364
365bool _NPN_RemoveProperty(NPP, NPObject* o, NPIdentifier propertyName)
366{
367 if (o->_class == NPScriptObjectClass) {
368 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
369
370 RootObject* rootObject = obj->rootObject;
371 if (!rootObject || !rootObject->isValid())
372 return false;
373
374 auto globalObject = rootObject->globalObject();
375 VM& vm = globalObject->vm();
376 JSLockHolder lock(vm);
377 auto scope = DECLARE_CATCH_SCOPE(vm);
378
379 ExecState* exec = globalObject->globalExec();
380
381 IdentifierRep* i = static_cast<IdentifierRep*>(propertyName);
382 if (i->isString()) {
383 if (!obj->imp->hasProperty(exec, identifierFromNPIdentifier(exec, i->string()))) {
384 scope.clearException();
385 return false;
386 }
387 } else {
388 if (!obj->imp->hasProperty(exec, i->number())) {
389 scope.clearException();
390 return false;
391 }
392 }
393
394 if (i->isString())
395 obj->imp->methodTable(vm)->deleteProperty(obj->imp, exec, identifierFromNPIdentifier(exec, i->string()));
396 else
397 obj->imp->methodTable(vm)->deletePropertyByIndex(obj->imp, exec, i->number());
398
399 scope.clearException();
400 return true;
401 }
402 return false;
403}
404
405bool _NPN_HasProperty(NPP, NPObject* o, NPIdentifier propertyName)
406{
407 if (o->_class == NPScriptObjectClass) {
408 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
409
410 RootObject* rootObject = obj->rootObject;
411 if (!rootObject || !rootObject->isValid())
412 return false;
413
414 auto globalObject = rootObject->globalObject();
415 VM& vm = globalObject->vm();
416 JSLockHolder lock(vm);
417 auto scope = DECLARE_CATCH_SCOPE(vm);
418
419 ExecState* exec = globalObject->globalExec();
420 IdentifierRep* i = static_cast<IdentifierRep*>(propertyName);
421 if (i->isString()) {
422 bool result = obj->imp->hasProperty(exec, identifierFromNPIdentifier(exec, i->string()));
423 scope.clearException();
424 return result;
425 }
426
427 bool result = obj->imp->hasProperty(exec, i->number());
428 scope.clearException();
429 return result;
430 }
431
432 if (o->_class->hasProperty)
433 return o->_class->hasProperty(o, propertyName);
434
435 return false;
436}
437
438bool _NPN_HasMethod(NPP, NPObject* o, NPIdentifier methodName)
439{
440 if (o->_class == NPScriptObjectClass) {
441 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
442
443 IdentifierRep* i = static_cast<IdentifierRep*>(methodName);
444 if (!i->isString())
445 return false;
446
447 RootObject* rootObject = obj->rootObject;
448 if (!rootObject || !rootObject->isValid())
449 return false;
450
451 auto globalObject = rootObject->globalObject();
452 VM& vm = globalObject->vm();
453 JSLockHolder lock(vm);
454 auto scope = DECLARE_CATCH_SCOPE(vm);
455
456 ExecState* exec = globalObject->globalExec();
457 JSValue func = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string()));
458 scope.clearException();
459 return !func.isUndefined();
460 }
461
462 if (o->_class->hasMethod)
463 return o->_class->hasMethod(o, methodName);
464
465 return false;
466}
467
468void _NPN_SetException(NPObject*, const NPUTF8* message)
469{
470 // Ignoring the NPObject param is consistent with the Mozilla implementation.
471 String exception(message);
472 CInstance::setGlobalException(exception);
473}
474
475bool _NPN_Enumerate(NPP, NPObject* o, NPIdentifier** identifier, uint32_t* count)
476{
477 if (o->_class == NPScriptObjectClass) {
478 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
479
480 RootObject* rootObject = obj->rootObject;
481 if (!rootObject || !rootObject->isValid())
482 return false;
483
484 auto globalObject = rootObject->globalObject();
485 VM& vm = globalObject->vm();
486 JSLockHolder lock(vm);
487 auto scope = DECLARE_CATCH_SCOPE(vm);
488
489 ExecState* exec = globalObject->globalExec();
490 PropertyNameArray propertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
491
492 obj->imp->methodTable(vm)->getPropertyNames(obj->imp, exec, propertyNames, EnumerationMode());
493 unsigned size = static_cast<unsigned>(propertyNames.size());
494 // FIXME: This should really call NPN_MemAlloc but that's in WebKit
495 NPIdentifier* identifiers = static_cast<NPIdentifier*>(malloc(sizeof(NPIdentifier) * size));
496
497 for (unsigned i = 0; i < size; ++i)
498 identifiers[i] = _NPN_GetStringIdentifier(propertyNames[i].string().utf8().data());
499
500 *identifier = identifiers;
501 *count = size;
502
503 scope.clearException();
504 return true;
505 }
506
507 if (NP_CLASS_STRUCT_VERSION_HAS_ENUM(o->_class) && o->_class->enumerate)
508 return o->_class->enumerate(o, identifier, count);
509
510 return false;
511}
512
513bool _NPN_Construct(NPP, NPObject* o, const NPVariant* args, uint32_t argCount, NPVariant* result)
514{
515 if (o->_class == NPScriptObjectClass) {
516 JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o);
517
518 VOID_TO_NPVARIANT(*result);
519
520 // Lookup the constructor object.
521 RootObject* rootObject = obj->rootObject;
522 if (!rootObject || !rootObject->isValid())
523 return false;
524
525 auto globalObject = rootObject->globalObject();
526 VM& vm = globalObject->vm();
527 JSLockHolder lock(vm);
528 auto scope = DECLARE_CATCH_SCOPE(vm);
529
530 ExecState* exec = globalObject->globalExec();
531
532 // Call the constructor object.
533 JSValue constructor = obj->imp;
534 ConstructData constructData;
535 ConstructType constructType = getConstructData(vm, constructor, constructData);
536 if (constructType == ConstructType::None)
537 return false;
538
539 MarkedArgumentBuffer argList;
540 getListFromVariantArgs(exec, args, argCount, rootObject, argList);
541 RELEASE_ASSERT(!argList.hasOverflowed());
542 JSValue resultV = JSC::construct(exec, constructor, constructType, constructData, argList);
543
544 // Convert and return the result.
545 convertValueToNPVariant(exec, resultV, result);
546 scope.clearException();
547 return true;
548 }
549
550 if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(o->_class) && o->_class->construct)
551 return o->_class->construct(o, args, argCount, result);
552
553 return false;
554}
555
556} // extern "C"
557
558} // namespace JSC
559
560#endif // ENABLE(NETSCAPE_PLUGIN_API)
561