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#include "JSNPMethod.h"
30#include "NPJSObject.h"
31#include "NPRuntimeObjectMap.h"
32#include "NPRuntimeUtilities.h"
33#include <JavaScriptCore/Error.h>
34#include <JavaScriptCore/JSGlobalObject.h>
35#include <JavaScriptCore/JSLock.h>
36#include <JavaScriptCore/ObjectPrototype.h>
37#include <WebCore/IdentifierRep.h>
38#include <wtf/text/WTFString.h>
39
40using namespace JSC;
41using namespace WebCore;
42
43namespace WebKit {
44
45static NPIdentifier npIdentifierFromIdentifier(const Identifier& identifier)
46{
47    return static_cast<NPIdentifier>(IdentifierRep::get(identifier.ustring().utf8().data()));
48}
49
50const ClassInfo JSNPObject::s_info = { "NPObject", &JSObjectWithGlobalObject::s_info, 0, 0 };
51
52JSNPObject::JSNPObject(JSGlobalObject* globalObject, NPRuntimeObjectMap* objectMap, NPObject* npObject)
53    : JSObjectWithGlobalObject(globalObject, createStructure(globalObject->globalData(), globalObject->objectPrototype()))
54    , m_objectMap(objectMap)
55    , m_npObject(npObject)
56{
57    ASSERT(inherits(&s_info));
58
59    // We should never have an NPJSObject inside a JSNPObject.
60    ASSERT(!NPJSObject::isNPJSObject(m_npObject));
61
62    retainNPObject(m_npObject);
63}
64
65JSNPObject::~JSNPObject()
66{
67    if (!m_npObject)
68        return;
69
70    m_objectMap->jsNPObjectDestroyed(this);
71    releaseNPObject(m_npObject);
72}
73
74void JSNPObject::invalidate()
75{
76    ASSERT(m_npObject);
77
78    releaseNPObject(m_npObject);
79    m_npObject = 0;
80}
81
82JSValue JSNPObject::callMethod(ExecState* exec, NPIdentifier methodName)
83{
84    if (!m_npObject)
85        return throwInvalidAccessError(exec);
86
87    size_t argumentCount = exec->argumentCount();
88    Vector<NPVariant, 8> arguments(argumentCount);
89
90    // Convert all arguments to NPVariants.
91    for (size_t i = 0; i < argumentCount; ++i)
92        m_objectMap->convertJSValueToNPVariant(exec, exec->argument(i), arguments[i]);
93
94    // Calling NPClass::invoke will call into plug-in code, and there's no telling what the plug-in can do.
95    // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
96    // the call has finished.
97    NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
98
99    bool returnValue;
100    NPVariant result;
101    VOID_TO_NPVARIANT(result);
102
103    {
104        JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly);
105        returnValue = m_npObject->_class->invoke(m_npObject, methodName, arguments.data(), argumentCount, &result);
106        NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
107    }
108
109    // Release all arguments;
110    for (size_t i = 0; i < argumentCount; ++i)
111        releaseNPVariantValue(&arguments[i]);
112
113    if (!returnValue)
114        throwError(exec, createError(exec, "Error calling method on NPObject."));
115
116    JSValue propertyValue = m_objectMap->convertNPVariantToJSValue(exec, globalObject(), result);
117    releaseNPVariantValue(&result);
118    return propertyValue;
119}
120
121JSC::JSValue JSNPObject::callObject(JSC::ExecState* exec)
122{
123    if (!m_npObject)
124        return throwInvalidAccessError(exec);
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->argument(i), arguments[i]);
132
133    // Calling NPClass::invokeDefault 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(SilenceAssertionsOnly);
144        returnValue = m_npObject->_class->invokeDefault(m_npObject, 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        throwError(exec, 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
160JSValue JSNPObject::callConstructor(ExecState* exec)
161{
162    if (!m_npObject)
163        return throwInvalidAccessError(exec);
164
165    size_t argumentCount = exec->argumentCount();
166    Vector<NPVariant, 8> arguments(argumentCount);
167
168    // Convert all arguments to NPVariants.
169    for (size_t i = 0; i < argumentCount; ++i)
170        m_objectMap->convertJSValueToNPVariant(exec, exec->argument(i), arguments[i]);
171
172    // Calling NPClass::construct will call into plug-in code, and there's no telling what the plug-in can do.
173    // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
174    // the call has finished.
175    NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
176
177    bool returnValue;
178    NPVariant result;
179    VOID_TO_NPVARIANT(result);
180
181    {
182        JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly);
183        returnValue = m_npObject->_class->construct(m_npObject, arguments.data(), argumentCount, &result);
184        NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
185    }
186
187    if (!returnValue)
188        throwError(exec, createError(exec, "Error calling method on NPObject."));
189
190    JSValue value = m_objectMap->convertNPVariantToJSValue(exec, globalObject(), result);
191    releaseNPVariantValue(&result);
192    return value;
193}
194
195static EncodedJSValue JSC_HOST_CALL callNPJSObject(ExecState* exec)
196{
197    JSObject* object = exec->callee();
198    ASSERT(object->inherits(&JSNPObject::s_info));
199
200    return JSValue::encode(static_cast<JSNPObject*>(object)->callObject(exec));
201}
202
203JSC::CallType JSNPObject::getCallData(JSC::CallData& callData)
204{
205    if (!m_npObject || !m_npObject->_class->invokeDefault)
206        return CallTypeNone;
207
208    callData.native.function = callNPJSObject;
209    return CallTypeHost;
210}
211
212static EncodedJSValue JSC_HOST_CALL constructWithConstructor(ExecState* exec)
213{
214    JSObject* constructor = exec->callee();
215    ASSERT(constructor->inherits(&JSNPObject::s_info));
216
217    return JSValue::encode(static_cast<JSNPObject*>(constructor)->callConstructor(exec));
218}
219
220ConstructType JSNPObject::getConstructData(ConstructData& constructData)
221{
222    if (!m_npObject || !m_npObject->_class->construct)
223        return ConstructTypeNone;
224
225    constructData.native.function = constructWithConstructor;
226    return ConstructTypeHost;
227}
228
229bool JSNPObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
230{
231    if (!m_npObject) {
232        throwInvalidAccessError(exec);
233        return false;
234    }
235
236    NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
237
238    // First, check if the NPObject has a property with this name.
239    if (m_npObject->_class->hasProperty && m_npObject->_class->hasProperty(m_npObject, npIdentifier)) {
240        slot.setCustom(this, propertyGetter);
241        return true;
242    }
243
244    // Second, check if the NPObject has a method with this name.
245    if (m_npObject->_class->hasMethod && m_npObject->_class->hasMethod(m_npObject, npIdentifier)) {
246        slot.setCustom(this, methodGetter);
247        return true;
248    }
249
250    return false;
251}
252
253bool JSNPObject::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
254{
255    if (!m_npObject) {
256        throwInvalidAccessError(exec);
257        return false;
258    }
259
260    NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
261
262    // First, check if the NPObject has a property with this name.
263    if (m_npObject->_class->hasProperty && m_npObject->_class->hasProperty(m_npObject, npIdentifier)) {
264        PropertySlot slot;
265        slot.setCustom(this, propertyGetter);
266        descriptor.setDescriptor(slot.getValue(exec, propertyName), DontDelete);
267        return true;
268    }
269
270    // Second, check if the NPObject has a method with this name.
271    if (m_npObject->_class->hasMethod && m_npObject->_class->hasMethod(m_npObject, npIdentifier)) {
272        PropertySlot slot;
273        slot.setCustom(this, methodGetter);
274        descriptor.setDescriptor(slot.getValue(exec, propertyName), DontDelete | ReadOnly);
275        return true;
276    }
277
278    return false;
279}
280
281void JSNPObject::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot&)
282{
283    if (!m_npObject) {
284        throwInvalidAccessError(exec);
285        return;
286    }
287
288    NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
289
290    if (!m_npObject->_class->hasProperty || !m_npObject->_class->hasProperty(m_npObject, npIdentifier)) {
291        // FIXME: Should we throw an exception here?
292        return;
293    }
294
295    if (!m_npObject->_class->setProperty)
296        return;
297
298    NPVariant variant;
299    m_objectMap->convertJSValueToNPVariant(exec, value, variant);
300
301    // Calling NPClass::setProperty will call into plug-in code, and there's no telling what the plug-in can do.
302    // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
303    // the call has finished.
304    NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
305
306    {
307        JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly);
308        m_npObject->_class->setProperty(m_npObject, npIdentifier, &variant);
309
310        NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
311
312        // FIXME: Should we throw an exception if setProperty returns false?
313    }
314
315    releaseNPVariantValue(&variant);
316}
317
318void JSNPObject::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode mode)
319{
320    if (!m_npObject) {
321        throwInvalidAccessError(exec);
322        return;
323    }
324
325    if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(m_npObject->_class) || !m_npObject->_class->enumerate)
326        return;
327
328    NPIdentifier* identifiers = 0;
329    uint32_t identifierCount = 0;
330
331    // Calling NPClass::enumerate will call into plug-in code, and there's no telling what the plug-in can do.
332    // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
333    // the call has finished.
334    NPRuntimeObjectMap::PluginProtector protector(m_objectMap);
335
336    {
337        JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly);
338
339        // FIXME: Should we throw an exception if enumerate returns false?
340        if (!m_npObject->_class->enumerate(m_npObject, &identifiers, &identifierCount))
341            return;
342
343        NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
344    }
345
346    for (uint32_t i = 0; i < identifierCount; ++i) {
347        IdentifierRep* identifierRep = static_cast<IdentifierRep*>(identifiers[i]);
348
349        Identifier identifier;
350        if (identifierRep->isString()) {
351            const char* string = identifierRep->string();
352            int length = strlen(string);
353
354            identifier = Identifier(exec, String::fromUTF8WithLatin1Fallback(string, length).impl());
355        } else
356            identifier = Identifier::from(exec, identifierRep->number());
357
358        propertyNameArray.add(identifier);
359    }
360
361    npnMemFree(identifiers);
362}
363
364JSValue JSNPObject::propertyGetter(ExecState* exec, JSValue slotBase, const Identifier& propertyName)
365{
366    JSNPObject* thisObj = static_cast<JSNPObject*>(asObject(slotBase));
367
368    if (!thisObj->m_npObject)
369        return throwInvalidAccessError(exec);
370
371    if (!thisObj->m_npObject->_class->getProperty)
372        return jsUndefined();
373
374    NPVariant result;
375    VOID_TO_NPVARIANT(result);
376
377    // Calling NPClass::getProperty will call into plug-in code, and there's no telling what the plug-in can do.
378    // (including destroying the plug-in). Because of this, we make sure to keep the plug-in alive until
379    // the call has finished.
380    NPRuntimeObjectMap::PluginProtector protector(thisObj->m_objectMap);
381
382    bool returnValue;
383    {
384        JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly);
385        NPIdentifier npIdentifier = npIdentifierFromIdentifier(propertyName);
386        returnValue = thisObj->m_npObject->_class->getProperty(thisObj->m_npObject, npIdentifier, &result);
387
388        NPRuntimeObjectMap::moveGlobalExceptionToExecState(exec);
389    }
390
391    if (!returnValue)
392        return jsUndefined();
393
394    JSValue propertyValue = thisObj->m_objectMap->convertNPVariantToJSValue(exec, thisObj->globalObject(), result);
395    releaseNPVariantValue(&result);
396    return propertyValue;
397}
398
399JSValue JSNPObject::methodGetter(ExecState* exec, JSValue slotBase, const Identifier& methodName)
400{
401    JSNPObject* thisObj = static_cast<JSNPObject*>(asObject(slotBase));
402
403    if (!thisObj->m_npObject)
404        return throwInvalidAccessError(exec);
405
406    NPIdentifier npIdentifier = npIdentifierFromIdentifier(methodName);
407    return new (exec) JSNPMethod(exec, thisObj->globalObject(), methodName, npIdentifier);
408}
409
410JSObject* JSNPObject::throwInvalidAccessError(ExecState* exec)
411{
412    return throwError(exec, createReferenceError(exec, "Trying to access object from destroyed plug-in."));
413}
414
415} // namespace WebKit
416