1/*
2 * Copyright (C) 2008, 2009, 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. ``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#if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
27
28#import "ProxyInstance.h"
29
30#import "NetscapePluginHostProxy.h"
31#import "ProxyRuntimeObject.h"
32#import <WebCore/IdentifierRep.h>
33#import <WebCore/JSDOMWindow.h>
34#import <WebCore/npruntime_impl.h>
35#import <WebCore/runtime_method.h>
36#import <runtime/Error.h>
37#import <runtime/FunctionPrototype.h>
38#import <runtime/PropertyNameArray.h>
39
40extern "C" {
41#import "WebKitPluginHost.h"
42}
43
44using namespace JSC;
45using namespace JSC::Bindings;
46using namespace std;
47using namespace WebCore;
48
49namespace WebKit {
50
51class ProxyClass : public JSC::Bindings::Class {
52private:
53    virtual MethodList methodsNamed(const Identifier&, Instance*) const;
54    virtual Field* fieldNamed(const Identifier&, Instance*) const;
55};
56
57MethodList ProxyClass::methodsNamed(const Identifier& identifier, Instance* instance) const
58{
59    return static_cast<ProxyInstance*>(instance)->methodsNamed(identifier);
60}
61
62Field* ProxyClass::fieldNamed(const Identifier& identifier, Instance* instance) const
63{
64    return static_cast<ProxyInstance*>(instance)->fieldNamed(identifier);
65}
66
67static ProxyClass* proxyClass()
68{
69    DEFINE_STATIC_LOCAL(ProxyClass, proxyClass, ());
70    return &proxyClass;
71}
72
73class ProxyField : public JSC::Bindings::Field {
74public:
75    ProxyField(uint64_t serverIdentifier)
76        : m_serverIdentifier(serverIdentifier)
77    {
78    }
79
80    uint64_t serverIdentifier() const { return m_serverIdentifier; }
81
82private:
83    virtual JSValue valueFromInstance(ExecState*, const Instance*) const;
84    virtual void setValueToInstance(ExecState*, const Instance*, JSValue) const;
85
86    uint64_t m_serverIdentifier;
87};
88
89JSValue ProxyField::valueFromInstance(ExecState* exec, const Instance* instance) const
90{
91    return static_cast<const ProxyInstance*>(instance)->fieldValue(exec, this);
92}
93
94void ProxyField::setValueToInstance(ExecState* exec, const Instance* instance, JSValue value) const
95{
96    static_cast<const ProxyInstance*>(instance)->setFieldValue(exec, this, value);
97}
98
99class ProxyMethod : public JSC::Bindings::Method {
100public:
101    ProxyMethod(uint64_t serverIdentifier)
102        : m_serverIdentifier(serverIdentifier)
103    {
104    }
105
106    uint64_t serverIdentifier() const { return m_serverIdentifier; }
107
108private:
109    virtual int numParameters() const { return 0; }
110
111    uint64_t m_serverIdentifier;
112};
113
114ProxyInstance::ProxyInstance(PassRefPtr<RootObject> rootObject, NetscapePluginInstanceProxy* instanceProxy, uint32_t objectID)
115    : Instance(rootObject)
116    , m_instanceProxy(instanceProxy)
117    , m_objectID(objectID)
118{
119    m_instanceProxy->addInstance(this);
120}
121
122ProxyInstance::~ProxyInstance()
123{
124    deleteAllValues(m_fields);
125    deleteAllValues(m_methods);
126
127    if (!m_instanceProxy)
128        return;
129
130    m_instanceProxy->removeInstance(this);
131
132    invalidate();
133}
134
135RuntimeObject* ProxyInstance::newRuntimeObject(ExecState* exec)
136{
137    return new (exec) ProxyRuntimeObject(exec, exec->lexicalGlobalObject(), this);
138}
139
140JSC::Bindings::Class* ProxyInstance::getClass() const
141{
142    return proxyClass();
143}
144
145JSValue ProxyInstance::invoke(JSC::ExecState* exec, InvokeType type, uint64_t identifier, const ArgList& args)
146{
147    if (!m_instanceProxy)
148        return jsUndefined();
149
150    RetainPtr<NSData*> arguments(m_instanceProxy->marshalValues(exec, args));
151
152    uint32_t requestID = m_instanceProxy->nextRequestID();
153
154    for (unsigned i = 0; i < args.size(); i++)
155        m_instanceProxy->retainLocalObject(args.at(i));
156
157    if (_WKPHNPObjectInvoke(m_instanceProxy->hostProxy()->port(), m_instanceProxy->pluginID(), requestID, m_objectID,
158                            type, identifier, (char*)[arguments.get() bytes], [arguments.get() length]) != KERN_SUCCESS) {
159        if (m_instanceProxy) {
160            for (unsigned i = 0; i < args.size(); i++)
161                m_instanceProxy->releaseLocalObject(args.at(i));
162        }
163        return jsUndefined();
164    }
165
166    auto_ptr<NetscapePluginInstanceProxy::BooleanAndDataReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanAndDataReply>(requestID);
167    NetscapePluginInstanceProxy::moveGlobalExceptionToExecState(exec);
168
169    if (m_instanceProxy) {
170        for (unsigned i = 0; i < args.size(); i++)
171            m_instanceProxy->releaseLocalObject(args.at(i));
172    }
173
174    if (!reply.get() || !reply->m_returnValue)
175        return jsUndefined();
176
177    return m_instanceProxy->demarshalValue(exec, (char*)CFDataGetBytePtr(reply->m_result.get()), CFDataGetLength(reply->m_result.get()));
178}
179
180class ProxyRuntimeMethod : public RuntimeMethod {
181public:
182    ProxyRuntimeMethod(ExecState* exec, JSGlobalObject* globalObject, const Identifier& name, Bindings::MethodList& list)
183        // FIXME: deprecatedGetDOMStructure uses the prototype off of the wrong global object
184        // exec-globalData() is also likely wrong.
185        : RuntimeMethod(exec, globalObject, deprecatedGetDOMStructure<ProxyRuntimeMethod>(exec), name, list)
186    {
187        ASSERT(inherits(&s_info));
188    }
189
190    static Structure* createStructure(JSGlobalData& globalData, JSValue prototype)
191    {
192        return Structure::create(globalData, prototype, TypeInfo(ObjectType, StructureFlags), AnonymousSlotCount, &s_info);
193    }
194
195    static const ClassInfo s_info;
196};
197
198const ClassInfo ProxyRuntimeMethod::s_info = { "ProxyRuntimeMethod", &RuntimeMethod::s_info, 0, 0 };
199
200JSValue ProxyInstance::getMethod(JSC::ExecState* exec, const JSC::Identifier& propertyName)
201{
202    MethodList methodList = getClass()->methodsNamed(propertyName, this);
203    return new (exec) ProxyRuntimeMethod(exec, exec->lexicalGlobalObject(), propertyName, methodList);
204}
205
206JSValue ProxyInstance::invokeMethod(ExecState* exec, JSC::RuntimeMethod* runtimeMethod)
207{
208    if (!asObject(runtimeMethod)->inherits(&ProxyRuntimeMethod::s_info))
209        return throwError(exec, createTypeError(exec, "Attempt to invoke non-plug-in method on plug-in object."));
210
211    const MethodList& methodList = *runtimeMethod->methods();
212
213    ASSERT(methodList.size() == 1);
214
215    ProxyMethod* method = static_cast<ProxyMethod*>(methodList[0]);
216
217    return invoke(exec, Invoke, method->serverIdentifier(), ArgList(exec));
218}
219
220bool ProxyInstance::supportsInvokeDefaultMethod() const
221{
222    if (!m_instanceProxy)
223        return false;
224
225    uint32_t requestID = m_instanceProxy->nextRequestID();
226
227    if (_WKPHNPObjectHasInvokeDefaultMethod(m_instanceProxy->hostProxy()->port(),
228                                            m_instanceProxy->pluginID(), requestID,
229                                            m_objectID) != KERN_SUCCESS)
230        return false;
231
232    auto_ptr<NetscapePluginInstanceProxy::BooleanReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
233    if (reply.get() && reply->m_result)
234        return true;
235
236    return false;
237}
238
239JSValue ProxyInstance::invokeDefaultMethod(ExecState* exec)
240{
241    return invoke(exec, InvokeDefault, 0, ArgList(exec));
242}
243
244bool ProxyInstance::supportsConstruct() const
245{
246    if (!m_instanceProxy)
247        return false;
248
249    uint32_t requestID = m_instanceProxy->nextRequestID();
250
251    if (_WKPHNPObjectHasConstructMethod(m_instanceProxy->hostProxy()->port(),
252                                        m_instanceProxy->pluginID(), requestID,
253                                        m_objectID) != KERN_SUCCESS)
254        return false;
255
256    auto_ptr<NetscapePluginInstanceProxy::BooleanReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
257    if (reply.get() && reply->m_result)
258        return true;
259
260    return false;
261}
262
263JSValue ProxyInstance::invokeConstruct(ExecState* exec, const ArgList& args)
264{
265    return invoke(exec, Construct, 0, args);
266}
267
268JSValue ProxyInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const
269{
270    if (hint == PreferString)
271        return stringValue(exec);
272    if (hint == PreferNumber)
273        return numberValue(exec);
274    return valueOf(exec);
275}
276
277JSValue ProxyInstance::stringValue(ExecState* exec) const
278{
279    // FIXME: Implement something sensible.
280    return jsEmptyString(exec);
281}
282
283JSValue ProxyInstance::numberValue(ExecState*) const
284{
285    // FIXME: Implement something sensible.
286    return jsNumber(0);
287}
288
289JSValue ProxyInstance::booleanValue() const
290{
291    // FIXME: Implement something sensible.
292    return jsBoolean(false);
293}
294
295JSValue ProxyInstance::valueOf(ExecState* exec) const
296{
297    return stringValue(exec);
298}
299
300void ProxyInstance::getPropertyNames(ExecState* exec, PropertyNameArray& nameArray)
301{
302    if (!m_instanceProxy)
303        return;
304
305    uint32_t requestID = m_instanceProxy->nextRequestID();
306
307    if (_WKPHNPObjectEnumerate(m_instanceProxy->hostProxy()->port(), m_instanceProxy->pluginID(), requestID, m_objectID) != KERN_SUCCESS)
308        return;
309
310    auto_ptr<NetscapePluginInstanceProxy::BooleanAndDataReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanAndDataReply>(requestID);
311    NetscapePluginInstanceProxy::moveGlobalExceptionToExecState(exec);
312    if (!reply.get() || !reply->m_returnValue)
313        return;
314
315    RetainPtr<NSArray*> array = [NSPropertyListSerialization propertyListFromData:(NSData *)reply->m_result.get()
316                                                                 mutabilityOption:NSPropertyListImmutable
317                                                                           format:0
318                                                                 errorDescription:0];
319
320    for (NSNumber *number in array.get()) {
321        IdentifierRep* identifier = reinterpret_cast<IdentifierRep*>([number longLongValue]);
322        if (!IdentifierRep::isValid(identifier))
323            continue;
324
325        if (identifier->isString()) {
326            const char* str = identifier->string();
327            nameArray.add(Identifier(JSDOMWindow::commonJSGlobalData(), stringToUString(String::fromUTF8WithLatin1Fallback(str, strlen(str)))));
328        } else
329            nameArray.add(Identifier::from(exec, identifier->number()));
330    }
331}
332
333MethodList ProxyInstance::methodsNamed(const Identifier& identifier)
334{
335    if (!m_instanceProxy)
336        return MethodList();
337
338    // If we already have an entry in the map, use it.
339    MethodMap::iterator existingMapEntry = m_methods.find(identifier.impl());
340    if (existingMapEntry != m_methods.end()) {
341        MethodList methodList;
342        if (existingMapEntry->second)
343            methodList.append(existingMapEntry->second);
344        return methodList;
345    }
346
347    uint64_t methodName = reinterpret_cast<uint64_t>(_NPN_GetStringIdentifier(identifier.ascii().data()));
348    uint32_t requestID = m_instanceProxy->nextRequestID();
349
350    if (_WKPHNPObjectHasMethod(m_instanceProxy->hostProxy()->port(),
351                               m_instanceProxy->pluginID(), requestID,
352                               m_objectID, methodName) != KERN_SUCCESS)
353        return MethodList();
354
355    auto_ptr<NetscapePluginInstanceProxy::BooleanReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
356    if (!reply.get())
357        return MethodList();
358
359    if (!reply->m_result && !m_instanceProxy->hostProxy()->shouldCacheMissingPropertiesAndMethods())
360        return MethodList();
361
362    // Add a new entry to the map unless an entry was added while we were in waitForReply.
363    pair<MethodMap::iterator, bool> mapAddResult = m_methods.add(identifier.impl(), 0);
364    if (mapAddResult.second && reply->m_result)
365        mapAddResult.first->second = new ProxyMethod(methodName);
366
367    MethodList methodList;
368    if (mapAddResult.first->second)
369        methodList.append(mapAddResult.first->second);
370    return methodList;
371}
372
373Field* ProxyInstance::fieldNamed(const Identifier& identifier)
374{
375    if (!m_instanceProxy)
376        return 0;
377
378    // If we already have an entry in the map, use it.
379    FieldMap::iterator existingMapEntry = m_fields.find(identifier.impl());
380    if (existingMapEntry != m_fields.end())
381        return existingMapEntry->second;
382
383    uint64_t propertyName = reinterpret_cast<uint64_t>(_NPN_GetStringIdentifier(identifier.ascii().data()));
384    uint32_t requestID = m_instanceProxy->nextRequestID();
385
386    if (_WKPHNPObjectHasProperty(m_instanceProxy->hostProxy()->port(),
387                                 m_instanceProxy->pluginID(), requestID,
388                                 m_objectID, propertyName) != KERN_SUCCESS)
389        return 0;
390
391    auto_ptr<NetscapePluginInstanceProxy::BooleanReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
392    if (!reply.get())
393        return 0;
394
395    if (!reply->m_result && !m_instanceProxy->hostProxy()->shouldCacheMissingPropertiesAndMethods())
396        return 0;
397
398    // Add a new entry to the map unless an entry was added while we were in waitForReply.
399    pair<FieldMap::iterator, bool> mapAddResult = m_fields.add(identifier.impl(), 0);
400    if (mapAddResult.second && reply->m_result)
401        mapAddResult.first->second = new ProxyField(propertyName);
402    return mapAddResult.first->second;
403}
404
405JSC::JSValue ProxyInstance::fieldValue(ExecState* exec, const Field* field) const
406{
407    if (!m_instanceProxy)
408        return jsUndefined();
409
410    uint64_t serverIdentifier = static_cast<const ProxyField*>(field)->serverIdentifier();
411    uint32_t requestID = m_instanceProxy->nextRequestID();
412
413    if (_WKPHNPObjectGetProperty(m_instanceProxy->hostProxy()->port(),
414                                 m_instanceProxy->pluginID(), requestID,
415                                 m_objectID, serverIdentifier) != KERN_SUCCESS)
416        return jsUndefined();
417
418    auto_ptr<NetscapePluginInstanceProxy::BooleanAndDataReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanAndDataReply>(requestID);
419    NetscapePluginInstanceProxy::moveGlobalExceptionToExecState(exec);
420    if (!reply.get() || !reply->m_returnValue)
421        return jsUndefined();
422
423    return m_instanceProxy->demarshalValue(exec, (char*)CFDataGetBytePtr(reply->m_result.get()), CFDataGetLength(reply->m_result.get()));
424}
425
426void ProxyInstance::setFieldValue(ExecState* exec, const Field* field, JSValue value) const
427{
428    if (!m_instanceProxy)
429        return;
430
431    uint64_t serverIdentifier = static_cast<const ProxyField*>(field)->serverIdentifier();
432    uint32_t requestID = m_instanceProxy->nextRequestID();
433
434    data_t valueData;
435    mach_msg_type_number_t valueLength;
436
437    m_instanceProxy->marshalValue(exec, value, valueData, valueLength);
438    m_instanceProxy->retainLocalObject(value);
439    kern_return_t kr = _WKPHNPObjectSetProperty(m_instanceProxy->hostProxy()->port(),
440                                                m_instanceProxy->pluginID(), requestID,
441                                                m_objectID, serverIdentifier, valueData, valueLength);
442    mig_deallocate(reinterpret_cast<vm_address_t>(valueData), valueLength);
443    if (m_instanceProxy)
444        m_instanceProxy->releaseLocalObject(value);
445    if (kr != KERN_SUCCESS)
446        return;
447
448    auto_ptr<NetscapePluginInstanceProxy::BooleanReply> reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
449    NetscapePluginInstanceProxy::moveGlobalExceptionToExecState(exec);
450}
451
452void ProxyInstance::invalidate()
453{
454    ASSERT(m_instanceProxy);
455
456    if (NetscapePluginHostProxy* hostProxy = m_instanceProxy->hostProxy())
457        _WKPHNPObjectRelease(hostProxy->port(),
458                             m_instanceProxy->pluginID(), m_objectID);
459    m_instanceProxy = 0;
460}
461
462} // namespace WebKit
463
464#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
465
466