1/*
2 * Copyright (C) 2010-2019 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "JSNPObject.h"
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30
31#include "JSNPMethod.h"
32#include "NPJSObject.h"
33#include "NPRuntimeObjectMap.h"
34#include "NPRuntimeUtilities.h"
35#include <JavaScriptCore/AuxiliaryBarrierInlines.h>
36#include <JavaScriptCore/Error.h>
37#include <JavaScriptCore/IdentifierInlines.h>
38#include <JavaScriptCore/IsoSubspacePerVM.h>
39#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h>
40#include <JavaScriptCore/JSGlobalObject.h>
41#include <JavaScriptCore/JSLock.h>
42#include <JavaScriptCore/ObjectPrototype.h>
43#include <WebCore/CommonVM.h>
44#include <WebCore/DOMWindow.h>
45#include <WebCore/IdentifierRep.h>
46#include <WebCore/JSDOMWindowBase.h>
47#include <wtf/Assertions.h>
48#include <wtf/text/WTFString.h>
49
50namespace WebKit {
51using namespace JSC;
52using namespace WebCore;
53
54static NPIdentifier npIdentifierFromIdentifier(PropertyName propertyName)
55{
56 String name(propertyName.publicName());
57 // If the propertyName is Symbol.
58 if (name.isNull())
59 return nullptr;
60 return static_cast<NPIdentifier>(IdentifierRep::get(name.utf8().data()));
61}
62
63const ClassInfo JSNPObject::s_info = { "NPObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNPObject) };
64
65JSNPObject::JSNPObject(JSGlobalObject* globalObject, Structure* structure, NPRuntimeObjectMap* objectMap, NPObject* npObject)
66 : JSDestructibleObject(globalObject->vm(), structure)
67 , m_objectMap(objectMap)
68 , m_npObject(npObject)
69{
70 ASSERT(globalObject == structure->globalObject());
71}
72
73void JSNPObject::finishCreation(JSGlobalObject* globalObject)
74{
75 VM& vm = globalObject->vm();
76 Base::finishCreation(vm);
77 ASSERT(inherits(vm, info()));
78
79 // We should never have an NPJSObject inside a JSNPObject.
80 ASSERT(!NPJSObject::isNPJSObject(m_npObject));
81
82 retainNPObject(m_npObject);
83}
84
85JSNPObject::~JSNPObject()
86{
87 if (m_npObject)
88 invalidate();
89}
90
91void JSNPObject::destroy(JSCell* cell)
92{
93 static_cast<JSNPObject*>(cell)->JSNPObject::~JSNPObject();
94}
95
96void JSNPObject::invalidate()
97{
98 ASSERT(m_npObject);
99
100 releaseNPObject(m_npObject);
101 m_npObject = 0;
102}
103
104NPObject* JSNPObject::leakNPObject()
105{
106 ASSERT(m_npObject);
107
108 NPObject* object = m_npObject;
109 m_npObject = 0;
110 return object;
111}
112
113JSValue JSNPObject::callMethod(ExecState* exec, NPIdentifier methodName)
114{
115 VM& vm = exec->vm();
116 auto scope = DECLARE_THROW_SCOPE(vm);
117
118 ASSERT_THIS_GC_OBJECT_INHERITS(info());
119 if (!m_npObject)
120 return throwInvalidAccessError(exec, scope);
121
122 // If the propertyName is symbol.
123 if (!methodName)
124 return jsUndefined();
125
126 size_t argumentCount = exec->argumentCount();
127 Vector<NPVariant, 8> arguments(argumentCount);
128
129 // Convert all arguments to NPVariants.
130 for (size_t i = 0; i < argumentCount; ++i)
131 m_objectMap->convertJSValueToNPVariant(exec, exec->uncheckedArgument(i), arguments[i]);
132
133 // Calling NPClass::invoke will call into plug-in code, and there's no telling what the plug-in can do.
134 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
135 // the call has finished.
136 NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
137
138 bool returnValue;
139 NPVariant result;
140 VOID_TO_NPVARIANT(result);
141
142 {
143 JSLock::DropAllLocks dropAllLocks(commonVM());
144 returnValue = m_npObject->_class->invoke(m_npObject, methodName, arguments.data(), argumentCount, &result);
145 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
146 }
147
148 // Release all arguments.
149 for (size_t i = 0; i < argumentCount; ++i)
150 releaseNPVariantValue(&arguments[i]);
151
152 if (!returnValue)
153 throwException(exec, scope, createError(exec, "Error calling method on NPObject."));
154
155 JSValue propertyValue = m_objectMap->convertNPVariantToJSValue(exec, globalObject(), result);
156 releaseNPVariantValue(&result);
157 return propertyValue;
158}
159
160JSC::JSValue JSNPObject::callObject(JSC::ExecState* exec)
161{
162 VM& vm = exec->vm();
163 auto scope = DECLARE_THROW_SCOPE(vm);
164
165 ASSERT_THIS_GC_OBJECT_INHERITS(info());
166 if (!m_npObject)
167 return throwInvalidAccessError(exec, scope);
168
169 size_t argumentCount = exec->argumentCount();
170 Vector<NPVariant, 8> arguments(argumentCount);
171
172 // Convert all arguments to NPVariants.
173 for (size_t i = 0; i < argumentCount; ++i)
174 m_objectMap->convertJSValueToNPVariant(exec, exec->uncheckedArgument(i), arguments[i]);
175
176 // Calling NPClass::invokeDefault will call into plug-in code, and there's no telling what the plug-in can do.
177 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
178 // the call has finished.
179 NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
180
181 bool returnValue;
182 NPVariant result;
183 VOID_TO_NPVARIANT(result);
184
185 {
186 JSLock::DropAllLocks dropAllLocks(commonVM());
187 returnValue = m_npObject->_class->invokeDefault(m_npObject, arguments.data(), argumentCount, &result);
188 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
189 }
190
191 // Release all arguments;
192 for (size_t i = 0; i < argumentCount; ++i)
193 releaseNPVariantValue(&arguments[i]);
194
195 if (!returnValue)
196 throwException(exec, scope, createError(exec, "Error calling method on NPObject."));
197
198 JSValue propertyValue = m_objectMap->convertNPVariantToJSValue(exec, globalObject(), result);
199 releaseNPVariantValue(&result);
200 return propertyValue;
201}
202
203JSValue JSNPObject::callConstructor(ExecState* exec)
204{
205 VM& vm = exec->vm();
206 auto scope = DECLARE_THROW_SCOPE(vm);
207
208 ASSERT_THIS_GC_OBJECT_INHERITS(info());
209 if (!m_npObject)
210 return throwInvalidAccessError(exec, scope);
211
212 size_t argumentCount = exec->argumentCount();
213 Vector<NPVariant, 8> arguments(argumentCount);
214
215 // Convert all arguments to NPVariants.
216 for (size_t i = 0; i < argumentCount; ++i)
217 m_objectMap->convertJSValueToNPVariant(exec, exec->uncheckedArgument(i), arguments[i]);
218
219 // Calling NPClass::construct will call into plug-in code, and there's no telling what the plug-in can do.
220 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
221 // the call has finished.
222 NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
223
224 bool returnValue;
225 NPVariant result;
226 VOID_TO_NPVARIANT(result);
227
228 {
229 JSLock::DropAllLocks dropAllLocks(commonVM());
230 returnValue = m_npObject->_class->construct(m_npObject, arguments.data(), argumentCount, &result);
231 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
232 }
233
234 if (!returnValue)
235 throwException(exec, scope, createError(exec, "Error calling method on NPObject."));
236
237 JSValue value = m_objectMap->convertNPVariantToJSValue(exec, globalObject(), result);
238 releaseNPVariantValue(&result);
239 return value;
240}
241
242static EncodedJSValue JSC_HOST_CALL callNPJSObject(ExecState* exec)
243{
244 JSObject* object = exec->jsCallee();
245 ASSERT(object->inherits<JSNPObject>(exec->vm()));
246
247 return JSValue::encode(jsCast<JSNPObject*>(object)->callObject(exec));
248}
249
250JSC::CallType JSNPObject::getCallData(JSC::JSCell* cell, JSC::CallData& callData)
251{
252 JSNPObject* thisObject = JSC::jsCast<JSNPObject*>(cell);
253 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
254 if (!thisObject->m_npObject || !thisObject->m_npObject->_class->invokeDefault)
255 return CallType::None;
256
257 callData.native.function = callNPJSObject;
258 return CallType::Host;
259}
260
261static EncodedJSValue JSC_HOST_CALL constructWithConstructor(ExecState* exec)
262{
263 JSObject* constructor = exec->jsCallee();
264 ASSERT(constructor->inherits<JSNPObject>(exec->vm()));
265
266 return JSValue::encode(jsCast<JSNPObject*>(constructor)->callConstructor(exec));
267}
268
269ConstructType JSNPObject::getConstructData(JSCell* cell, ConstructData& constructData)
270{
271 JSNPObject* thisObject = JSC::jsCast<JSNPObject*>(cell);
272 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
273 if (!thisObject->m_npObject || !thisObject->m_npObject->_class->construct)
274 return ConstructType::None;
275
276 constructData.native.function = constructWithConstructor;
277 return ConstructType::Host;
278}
279
280bool JSNPObject::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
281{
282 VM& vm = exec->vm();
283 auto scope = DECLARE_THROW_SCOPE(vm);
284
285 JSNPObject* thisObject = JSC::jsCast<JSNPObject*>(object);
286 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
287 if (!thisObject->m_npObject) {
288 throwInvalidAccessError(exec, scope);
289 return false;
290 }
291
292 NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
293 // If the propertyName is symbol.
294 if (!npIdentifier)
295 return false;
296
297 // Calling NPClass::invoke will call into plug-in code, and there's no telling what the plug-in can do.
298 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
299 // the call has finished.
300 NPRuntimeObjectMap::PluginProtector protector(thisObject->m_objectMap);
301
302 // First, check if the NPObject has a property with this name.
303 if (thisObject->m_npObject->_class->hasProperty && thisObject->m_npObject->_class->hasProperty(thisObject->m_npObject, npIdentifier)) {
304 slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete), thisObject->propertyGetter);
305 return true;
306 }
307
308 // Second, check if the NPObject has a method with this name.
309 if (thisObject->m_npObject->_class->hasMethod && thisObject->m_npObject->_class->hasMethod(thisObject->m_npObject, npIdentifier)) {
310 slot.setCustom(thisObject, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly, thisObject->methodGetter);
311 return true;
312 }
313
314 return false;
315}
316
317bool JSNPObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot&)
318{
319 VM& vm = exec->vm();
320 auto scope = DECLARE_THROW_SCOPE(vm);
321
322 JSNPObject* thisObject = JSC::jsCast<JSNPObject*>(cell);
323 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
324 if (!thisObject->m_npObject) {
325 throwInvalidAccessError(exec, scope);
326 return false;
327 }
328
329 NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
330 // If the propertyName is symbol.
331 if (!npIdentifier)
332 return false;
333
334 if (!thisObject->m_npObject->_class->hasProperty || !thisObject->m_npObject->_class->hasProperty(thisObject->m_npObject, npIdentifier)) {
335 // FIXME: Should we throw an exception here?
336 return false;
337 }
338
339 if (!thisObject->m_npObject->_class->setProperty)
340 return false;
341
342 NPVariant variant;
343 thisObject->m_objectMap->convertJSValueToNPVariant(exec, value, variant);
344
345 // Calling NPClass::setProperty will call into plug-in code, and there's no telling what the plug-in can do.
346 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
347 // the call has finished.
348 NPRuntimeObjectMap::PluginProtector protector(thisObject->m_objectMap);
349
350 bool result = false;
351 {
352 JSLock::DropAllLocks dropAllLocks(commonVM());
353 result = thisObject->m_npObject->_class->setProperty(thisObject->m_npObject, npIdentifier, &variant);
354
355 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
356
357 // FIXME: Should we throw an exception if setProperty returns false?
358 }
359
360 releaseNPVariantValue(&variant);
361 return result;
362}
363
364bool JSNPObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
365{
366 return jsCast<JSNPObject*>(cell)->deleteProperty(exec, npIdentifierFromIdentifier(propertyName));
367}
368
369bool JSNPObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
370{
371 return jsCast<JSNPObject*>(cell)->deleteProperty(exec, static_cast<NPIdentifier>(IdentifierRep::get(propertyName)));
372}
373
374bool JSNPObject::deleteProperty(ExecState* exec, NPIdentifier propertyName)
375{
376 VM& vm = exec->vm();
377 auto scope = DECLARE_THROW_SCOPE(vm);
378
379 ASSERT_THIS_GC_OBJECT_INHERITS(info());
380
381 // If the propertyName is symbol.
382 if (!propertyName)
383 return false;
384
385 if (!m_npObject) {
386 throwInvalidAccessError(exec, scope);
387 return false;
388 }
389
390 if (!m_npObject->_class->removeProperty) {
391 // FIXME: Should we throw an exception here?
392 return false;
393 }
394
395 // Calling NPClass::setProperty will call into plug-in code, and there's no telling what the plug-in can do.
396 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
397 // the call has finished.
398 NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
399
400 {
401 JSLock::DropAllLocks dropAllLocks(commonVM());
402
403 // FIXME: Should we throw an exception if removeProperty returns false?
404 if (!m_npObject->_class->removeProperty(m_npObject, propertyName))
405 return false;
406
407 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
408 }
409
410 return true;
411}
412
413void JSNPObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode)
414{
415 VM& vm = exec->vm();
416 auto scope = DECLARE_THROW_SCOPE(vm);
417
418 JSNPObject* thisObject = jsCast<JSNPObject*>(object);
419 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
420 if (!thisObject->m_npObject) {
421 throwInvalidAccessError(exec, scope);
422 return;
423 }
424
425 if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(thisObject->m_npObject->_class) || !thisObject->m_npObject->_class->enumerate)
426 return;
427
428 NPIdentifier* identifiers = 0;
429 uint32_t identifierCount = 0;
430
431 // Calling NPClass::enumerate will call into plug-in code, and there's no telling what the plug-in can do.
432 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
433 // the call has finished.
434 NPRuntimeObjectMap::PluginProtector protector(thisObject->m_objectMap);
435
436 {
437 JSLock::DropAllLocks dropAllLocks(commonVM());
438
439 // FIXME: Should we throw an exception if enumerate returns false?
440 if (!thisObject->m_npObject->_class->enumerate(thisObject->m_npObject, &identifiers, &identifierCount))
441 return;
442
443 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
444 }
445
446 for (uint32_t i = 0; i < identifierCount; ++i) {
447 IdentifierRep* identifierRep = static_cast<IdentifierRep*>(identifiers[i]);
448
449 Identifier identifier;
450 if (identifierRep->isString()) {
451 const char* string = identifierRep->string();
452 int length = strlen(string);
453
454 identifier = Identifier::fromString(exec, String::fromUTF8WithLatin1Fallback(string, length));
455 } else
456 identifier = Identifier::from(exec, identifierRep->number());
457
458 propertyNameArray.add(identifier);
459 }
460
461 npnMemFree(identifiers);
462}
463
464EncodedJSValue JSNPObject::propertyGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName propertyName)
465{
466 VM& vm = exec->vm();
467 auto scope = DECLARE_THROW_SCOPE(vm);
468
469 JSNPObject* thisObj = jsCast<JSNPObject*>(JSValue::decode(thisValue));
470 ASSERT_GC_OBJECT_INHERITS(thisObj, info());
471
472 if (!thisObj->m_npObject)
473 return JSValue::encode(throwInvalidAccessError(exec, scope));
474
475 if (!thisObj->m_npObject->_class->getProperty)
476 return JSValue::encode(jsUndefined());
477
478 NPVariant result;
479 VOID_TO_NPVARIANT(result);
480
481 // Calling NPClass::getProperty will call into plug-in code, and there's no telling what the plug-in can do.
482 // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
483 // the call has finished.
484 NPRuntimeObjectMap::PluginProtector protector(thisObj->m_objectMap);
485
486 bool returnValue;
487 {
488 JSLock::DropAllLocks dropAllLocks(commonVM());
489 NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
490 // If the propertyName is symbol.
491 if (!npIdentifier)
492 return JSValue::encode(jsUndefined());
493
494 returnValue = thisObj->m_npObject->_class->getProperty(thisObj->m_npObject, npIdentifier, &result);
495
496 NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
497 }
498
499 if (!returnValue)
500 return JSValue::encode(jsUndefined());
501
502 JSValue propertyValue = thisObj->m_objectMap->convertNPVariantToJSValue(exec, thisObj->globalObject(), result);
503 releaseNPVariantValue(&result);
504 return JSValue::encode(propertyValue);
505}
506
507EncodedJSValue JSNPObject::methodGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName propertyName)
508{
509 VM& vm = exec->vm();
510 auto scope = DECLARE_THROW_SCOPE(vm);
511
512 JSNPObject* thisObj = jsCast<JSNPObject*>(JSValue::decode(thisValue));
513 ASSERT_GC_OBJECT_INHERITS(thisObj, info());
514
515 if (!thisObj->m_npObject)
516 return JSValue::encode(throwInvalidAccessError(exec, scope));
517
518 NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
519 // If the propertyName is symbol.
520 if (!npIdentifier)
521 return JSValue::encode(throwInvalidAccessError(exec, scope));
522
523 return JSValue::encode(JSNPMethod::create(exec, thisObj->globalObject(), propertyName.publicName(), npIdentifier));
524}
525
526JSC::Exception* JSNPObject::throwInvalidAccessError(ExecState* exec, ThrowScope& scope)
527{
528 return throwException(exec, scope, createReferenceError(exec, "Trying to access object from destroyed plug-in."));
529}
530
531IsoSubspace* JSNPObject::subspaceForImpl(VM& vm)
532{
533 static NeverDestroyed<IsoSubspacePerVM> perVM([] (VM& vm) { return ISO_SUBSPACE_PARAMETERS(vm.destructibleObjectHeapCellType.get(), JSNPObject); });
534 return &perVM.get().forVM(vm);
535}
536
537} // namespace WebKit
538
539#endif // ENABLE(NETSCAPE_PLUGIN_API)
540