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