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 "NPRuntimeObjectMap.h"
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30
31#include "JSNPObject.h"
32#include "NPJSObject.h"
33#include "NPRuntimeUtilities.h"
34#include "PluginView.h"
35#include "WebProcess.h"
36#include <JavaScriptCore/Completion.h>
37#include <JavaScriptCore/Error.h>
38#include <JavaScriptCore/JSLock.h>
39#include <JavaScriptCore/SourceCode.h>
40#include <JavaScriptCore/Strong.h>
41#include <JavaScriptCore/StrongInlines.h>
42#include <WebCore/AudioHardwareListener.h>
43#include <WebCore/DOMWrapperWorld.h>
44#include <WebCore/Frame.h>
45#include <WebCore/Page.h>
46#include <WebCore/PageThrottler.h>
47#include <WebCore/ScriptController.h>
48#include <wtf/NeverDestroyed.h>
49
50using namespace JSC;
51using namespace WebCore;
52
53namespace WebKit {
54
55
56NPRuntimeObjectMap::NPRuntimeObjectMap(PluginView* pluginView)
57    : m_pluginView(pluginView)
58    , m_finalizationTimer(RunLoop::main(), this, &NPRuntimeObjectMap::invalidateQueuedObjects)
59{
60}
61
62NPRuntimeObjectMap::PluginProtector::PluginProtector(NPRuntimeObjectMap* npRuntimeObjectMap)
63{
64    // If we're already in the plug-in view destructor, we shouldn't try to keep it alive.
65    if (!npRuntimeObjectMap->m_pluginView->isBeingDestroyed())
66        m_pluginView = npRuntimeObjectMap->m_pluginView;
67}
68
69NPRuntimeObjectMap::PluginProtector::~PluginProtector()
70{
71}
72
73NPObject* NPRuntimeObjectMap::getOrCreateNPObject(VM& vm, JSObject* jsObject)
74{
75    // If this is a JSNPObject, we can just get its underlying NPObject.
76    if (jsObject->classInfo() == JSNPObject::info()) {
77        JSNPObject* jsNPObject = jsCast<JSNPObject*>(jsObject);
78        NPObject* npObject = jsNPObject->npObject();
79
80        retainNPObject(npObject);
81        return npObject;
82    }
83
84    // First, check if we already know about this object.
85    if (NPJSObject* npJSObject = m_npJSObjects.get(jsObject)) {
86        retainNPObject(npJSObject);
87        return npJSObject;
88    }
89
90    NPJSObject* npJSObject = NPJSObject::create(vm, this, jsObject);
91    m_npJSObjects.set(jsObject, npJSObject);
92
93    return npJSObject;
94}
95
96void NPRuntimeObjectMap::npJSObjectDestroyed(NPJSObject* npJSObject)
97{
98    // Remove the object from the map.
99    ASSERT(m_npJSObjects.contains(npJSObject->jsObject()));
100    m_npJSObjects.remove(npJSObject->jsObject());
101}
102
103JSObject* NPRuntimeObjectMap::getOrCreateJSObject(JSGlobalObject* globalObject, NPObject* npObject)
104{
105    // If this is an NPJSObject, we can just get the JSObject that it's wrapping.
106    if (NPJSObject::isNPJSObject(npObject))
107        return NPJSObject::toNPJSObject(npObject)->jsObject();
108
109    if (JSNPObject* jsNPObject = m_jsNPObjects.get(npObject))
110        return jsNPObject;
111
112    JSNPObject* jsNPObject = JSNPObject::create(globalObject, this, npObject);
113    weakAdd(m_jsNPObjects, npObject, JSC::Weak<JSNPObject>(jsNPObject, this, npObject));
114    return jsNPObject;
115}
116
117JSValue NPRuntimeObjectMap::convertNPVariantToJSValue(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const NPVariant& variant)
118{
119    switch (variant.type) {
120    case NPVariantType_Void:
121        return jsUndefined();
122
123    case NPVariantType_Null:
124        return jsNull();
125
126    case NPVariantType_Bool:
127        return jsBoolean(variant.value.boolValue);
128
129    case NPVariantType_Int32:
130        return jsNumber(variant.value.intValue);
131
132    case NPVariantType_Double:
133        return jsNumber(variant.value.doubleValue);
134
135    case NPVariantType_String:
136        return jsString(exec, String::fromUTF8WithLatin1Fallback(variant.value.stringValue.UTF8Characters,
137                                                                 variant.value.stringValue.UTF8Length));
138    case NPVariantType_Object:
139        return getOrCreateJSObject(globalObject, variant.value.objectValue);
140    }
141
142    ASSERT_NOT_REACHED();
143    return jsUndefined();
144}
145
146void NPRuntimeObjectMap::convertJSValueToNPVariant(ExecState* exec, JSValue value, NPVariant& variant)
147{
148    JSLockHolder lock(exec);
149
150    VOID_TO_NPVARIANT(variant);
151
152    if (value.isNull()) {
153        NULL_TO_NPVARIANT(variant);
154        return;
155    }
156
157    if (value.isUndefined()) {
158        VOID_TO_NPVARIANT(variant);
159        return;
160    }
161
162    if (value.isBoolean()) {
163        BOOLEAN_TO_NPVARIANT(value.toBoolean(exec), variant);
164        return;
165    }
166
167    if (value.isNumber()) {
168        DOUBLE_TO_NPVARIANT(value.toNumber(exec), variant);
169        return;
170    }
171
172    if (value.isString()) {
173        NPString npString = createNPString(value.toString(exec)->value(exec).utf8());
174        STRINGN_TO_NPVARIANT(npString.UTF8Characters, npString.UTF8Length, variant);
175        return;
176    }
177
178    if (value.isObject()) {
179        NPObject* npObject = getOrCreateNPObject(exec->vm(), asObject(value));
180        OBJECT_TO_NPVARIANT(npObject, variant);
181        return;
182    }
183
184    ASSERT_NOT_REACHED();
185}
186
187bool NPRuntimeObjectMap::evaluate(NPObject* npObject, const String& scriptString, NPVariant* result)
188{
189    Strong<JSGlobalObject> globalObject(this->globalObject()->vm(), this->globalObject());
190    if (!globalObject)
191        return false;
192
193#if PLATFORM(COCOA)
194    if (m_pluginView && !m_pluginView->isBeingDestroyed()) {
195        if (Page* page = m_pluginView->frame()->page()) {
196            if (m_pluginView->audioHardwareActivity() != WebCore::AudioHardwareActivityType::IsInactive && page->pageThrottler())
197                page->pageThrottler()->pluginDidEvaluateWhileAudioIsPlaying();
198        }
199    }
200#endif
201
202    ExecState* exec = globalObject->globalExec();
203
204    JSLockHolder lock(exec);
205    JSValue thisValue = getOrCreateJSObject(globalObject.get(), npObject);
206
207    JSValue resultValue = JSC::evaluate(exec, makeSource(scriptString), thisValue);
208
209    convertJSValueToNPVariant(exec, resultValue, *result);
210    return true;
211}
212
213void NPRuntimeObjectMap::invalidate()
214{
215    Vector<NPJSObject*> npJSObjects;
216    copyValuesToVector(m_npJSObjects, npJSObjects);
217
218    // Deallocate all the object wrappers so we won't leak any JavaScript objects.
219    for (size_t i = 0; i < npJSObjects.size(); ++i)
220        deallocateNPObject(npJSObjects[i]);
221
222    // We shouldn't have any NPJSObjects left now.
223    ASSERT(m_npJSObjects.isEmpty());
224
225    Vector<NPObject*> objects;
226
227    for (HashMap<NPObject*, JSC::Weak<JSNPObject>>::iterator ptr = m_jsNPObjects.begin(), end = m_jsNPObjects.end(); ptr != end; ++ptr) {
228        JSNPObject* jsNPObject = ptr->value.get();
229        if (!jsNPObject) // Skip zombies.
230            continue;
231        objects.append(jsNPObject->leakNPObject());
232    }
233
234    m_jsNPObjects.clear();
235
236    for (size_t i = 0; i < objects.size(); ++i)
237        releaseNPObject(objects[i]);
238
239    // Deal with any objects that were scheduled for delayed destruction
240    if (m_npObjectsToFinalize.isEmpty())
241        return;
242    ASSERT(m_finalizationTimer.isActive());
243    m_finalizationTimer.stop();
244    invalidateQueuedObjects();
245}
246
247JSGlobalObject* NPRuntimeObjectMap::globalObject() const
248{
249    Frame* frame = m_pluginView->frame();
250    if (!frame)
251        return 0;
252
253    return frame->script().globalObject(pluginWorld());
254}
255
256ExecState* NPRuntimeObjectMap::globalExec() const
257{
258    JSGlobalObject* globalObject = this->globalObject();
259    if (!globalObject)
260        return 0;
261
262    return globalObject->globalExec();
263}
264
265static String& globalExceptionString()
266{
267    static NeverDestroyed<String> exceptionString;
268    return exceptionString;
269}
270
271void NPRuntimeObjectMap::setGlobalException(const String& exceptionString)
272{
273    globalExceptionString() = exceptionString;
274}
275
276void NPRuntimeObjectMap::moveGlobalExceptionToExecState(ExecState* exec)
277{
278    if (globalExceptionString().isNull())
279        return;
280
281    {
282        JSLockHolder lock(exec);
283        exec->vm().throwException(exec, createError(exec, globalExceptionString()));
284    }
285
286    globalExceptionString() = String();
287}
288
289void NPRuntimeObjectMap::invalidateQueuedObjects()
290{
291    ASSERT(m_npObjectsToFinalize.size());
292    // We deliberately re-request m_npObjectsToFinalize.size() as custom dealloc
293    // functions may execute JS and so get more objects added to the dealloc queue
294    for (size_t i = 0; i < m_npObjectsToFinalize.size(); ++i)
295        deallocateNPObject(m_npObjectsToFinalize[i]);
296    m_npObjectsToFinalize.clear();
297}
298
299void NPRuntimeObjectMap::addToInvalidationQueue(NPObject* npObject)
300{
301    if (trySafeReleaseNPObject(npObject))
302        return;
303    if (m_npObjectsToFinalize.isEmpty())
304        m_finalizationTimer.startOneShot(0);
305    ASSERT(m_finalizationTimer.isActive());
306    m_npObjectsToFinalize.append(npObject);
307}
308
309void NPRuntimeObjectMap::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
310{
311    JSNPObject* object = jsCast<JSNPObject*>(handle.get().asCell());
312    weakRemove(m_jsNPObjects, static_cast<NPObject*>(context), object);
313    addToInvalidationQueue(object->leakNPObject());
314}
315
316} // namespace WebKit
317
318#endif // ENABLE(NETSCAPE_PLUGIN_API)
319