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