1/*
2 * Copyright (C) 2008, 2009 Google Inc. All rights reserved.
3 * Copyright (C) 2009 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "ScriptController.h"
34
35#include "PlatformBridge.h"
36#include "CString.h"
37#include "Document.h"
38#include "DOMWindow.h"
39#include "Event.h"
40#include "EventListener.h"
41#include "EventNames.h"
42#include "Frame.h"
43#include "FrameLoaderClient.h"
44#include "Node.h"
45#include "NotImplemented.h"
46#include "npruntime_impl.h"
47#include "npruntime_priv.h"
48#include "NPV8Object.h"
49#include "ScriptSourceCode.h"
50#include "Settings.h"
51#include "V8Binding.h"
52#include "V8BindingState.h"
53#include "V8DOMWindow.h"
54#include "V8Event.h"
55#include "V8HTMLEmbedElement.h"
56#include "V8IsolatedContext.h"
57#include "V8NPObject.h"
58#include "V8Proxy.h"
59#include "Widget.h"
60#include "XSSAuditor.h"
61#include <wtf/StdLibExtras.h>
62
63namespace WebCore {
64
65void ScriptController::initializeThreading()
66{
67    static bool initializedThreading = false;
68    if (!initializedThreading) {
69        WTF::initializeThreading();
70        initializedThreading = true;
71    }
72}
73
74void ScriptController::setFlags(const char* string, int length)
75{
76    v8::V8::SetFlagsFromString(string, length);
77}
78
79Frame* ScriptController::retrieveFrameForEnteredContext()
80{
81    return V8Proxy::retrieveFrameForEnteredContext();
82}
83
84Frame* ScriptController::retrieveFrameForCurrentContext()
85{
86    return V8Proxy::retrieveFrameForCurrentContext();
87}
88
89bool ScriptController::isSafeScript(Frame* target)
90{
91    return V8BindingSecurity::canAccessFrame(V8BindingState::Only(), target, true);
92}
93
94void ScriptController::gcProtectJSWrapper(void* domObject)
95{
96    V8GCController::gcProtect(domObject);
97}
98
99void ScriptController::gcUnprotectJSWrapper(void* domObject)
100{
101    V8GCController::gcUnprotect(domObject);
102}
103
104ScriptController::ScriptController(Frame* frame)
105    : m_frame(frame)
106    , m_sourceURL(0)
107    , m_inExecuteScript(false)
108    , m_processingTimerCallback(false)
109    , m_paused(false)
110    , m_proxy(new V8Proxy(frame))
111#if ENABLE(NETSCAPE_PLUGIN_API)
112    , m_windowScriptNPObject(0)
113#endif
114    , m_XSSAuditor(new XSSAuditor(frame))
115{
116}
117
118ScriptController::~ScriptController()
119{
120    m_proxy->disconnectFrame();
121}
122
123void ScriptController::clearScriptObjects()
124{
125    PluginObjectMap::iterator it = m_pluginObjects.begin();
126    for (; it != m_pluginObjects.end(); ++it) {
127        _NPN_UnregisterObject(it->second);
128        _NPN_ReleaseObject(it->second);
129    }
130    m_pluginObjects.clear();
131
132#if ENABLE(NETSCAPE_PLUGIN_API)
133    if (m_windowScriptNPObject) {
134        // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
135        // script object properly.
136        // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
137        _NPN_DeallocateObject(m_windowScriptNPObject);
138        m_windowScriptNPObject = 0;
139    }
140#endif
141}
142
143void ScriptController::updateSecurityOrigin()
144{
145    m_proxy->windowShell()->updateSecurityOrigin();
146}
147
148void ScriptController::updatePlatformScriptObjects()
149{
150    notImplemented();
151}
152
153bool ScriptController::processingUserGesture(DOMWrapperWorld*) const
154{
155    Frame* activeFrame = V8Proxy::retrieveFrameForEnteredContext();
156    // No script is running, so it must be run by users.
157    if (!activeFrame)
158        return true;
159
160    V8Proxy* activeProxy = activeFrame->script()->proxy();
161
162    v8::HandleScope handleScope;
163    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(activeFrame);
164    // FIXME: find all cases context can be empty:
165    //  1) JS is disabled;
166    //  2) page is NULL;
167    if (v8Context.IsEmpty())
168        return true;
169
170    v8::Context::Scope scope(v8Context);
171
172    v8::Handle<v8::Object> global = v8Context->Global();
173    v8::Handle<v8::Value> jsEvent = global->Get(v8::String::NewSymbol("event"));
174    Event* event = (!jsEvent.IsEmpty() && jsEvent->IsObject()) ? V8Event::toNative(v8::Handle<v8::Object>::Cast(jsEvent)) : 0;
175
176    // Based on code from kjs_bindings.cpp.
177    // Note: This is more liberal than Firefox's implementation.
178    if (event) {
179        if (event->createdByDOM())
180            return false;
181
182        const AtomicString& type = event->type();
183        bool eventOk =
184            // mouse events
185            type == eventNames().clickEvent || type == eventNames().mousedownEvent || type == eventNames().mouseupEvent || type == eventNames().dblclickEvent
186            // keyboard events
187            || type == eventNames().keydownEvent || type == eventNames().keypressEvent || type == eventNames().keyupEvent
188            // other accepted events
189            || type == eventNames().selectEvent || type == eventNames().changeEvent || type == eventNames().focusEvent || type == eventNames().blurEvent || type == eventNames().submitEvent;
190
191        if (eventOk)
192            return true;
193    } else if (activeProxy->inlineCode() && !activeProxy->timerCallback()) {
194        // This is the <a href="javascript:window.open('...')> case -> we let it through.
195        return true;
196    }
197
198    // This is the <script>window.open(...)</script> case or a timer callback -> block it.
199    return false;
200}
201
202bool ScriptController::anyPageIsProcessingUserGesture() const
203{
204    // FIXME: is this right?
205    return processingUserGesture();
206}
207
208void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources)
209{
210    m_proxy->evaluateInIsolatedWorld(worldID, sources, 0);
211}
212
213void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources, int extensionGroup)
214{
215    m_proxy->evaluateInIsolatedWorld(worldID, sources, extensionGroup);
216}
217
218// Evaluate a script file in the environment of this proxy.
219ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
220{
221    String sourceURL = sourceCode.url();
222
223    if (!m_XSSAuditor->canEvaluate(sourceCode.source())) {
224        // This script is not safe to be evaluated.
225        return ScriptValue();
226    }
227
228    v8::HandleScope handleScope;
229    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame());
230    if (v8Context.IsEmpty())
231        return ScriptValue();
232
233    v8::Context::Scope scope(v8Context);
234
235    RefPtr<Frame> protect(m_frame);
236
237    v8::Local<v8::Value> object = m_proxy->evaluate(sourceCode, 0);
238
239    // Evaluating the JavaScript could cause the frame to be deallocated
240    // so we starot the keep alive timer here.
241    m_frame->keepAlive();
242
243    if (object.IsEmpty() || object->IsUndefined())
244        return ScriptValue();
245
246    return ScriptValue(object);
247}
248
249void ScriptController::setEventHandlerLineNumber(int lineNumber)
250{
251    m_proxy->setEventHandlerLineNumber(lineNumber);
252}
253
254void ScriptController::finishedWithEvent(Event* event)
255{
256    m_proxy->finishedWithEvent(event);
257}
258
259// Create a V8 object with an interceptor of NPObjectPropertyGetter.
260void ScriptController::bindToWindowObject(Frame* frame, const String& key, NPObject* object)
261{
262    v8::HandleScope handleScope;
263
264    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
265    if (v8Context.IsEmpty())
266        return;
267
268    v8::Context::Scope scope(v8Context);
269
270    v8::Handle<v8::Object> value = createV8ObjectForNPObject(object, 0);
271
272    // Attach to the global object.
273    v8::Handle<v8::Object> global = v8Context->Global();
274    global->Set(v8String(key), value);
275}
276
277void ScriptController::collectGarbage()
278{
279    v8::HandleScope handleScope;
280    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame());
281    if (v8Context.IsEmpty())
282        return;
283
284    v8::Context::Scope scope(v8Context);
285
286    m_proxy->evaluate(ScriptSourceCode("if (window.gc) void(gc());"), 0);
287}
288
289void ScriptController::lowMemoryNotification()
290{
291    v8::V8::LowMemoryNotification();
292}
293
294bool ScriptController::haveInterpreter() const
295{
296    return m_proxy->windowShell()->isContextInitialized();
297}
298
299PassScriptInstance ScriptController::createScriptInstanceForWidget(Widget* widget)
300{
301    ASSERT(widget);
302
303    if (widget->isFrameView())
304        return 0;
305
306    NPObject* npObject = PlatformBridge::pluginScriptableObject(widget);
307
308    if (!npObject)
309        return 0;
310
311    // Frame Memory Management for NPObjects
312    // -------------------------------------
313    // NPObjects are treated differently than other objects wrapped by JS.
314    // NPObjects can be created either by the browser (e.g. the main
315    // window object) or by the plugin (the main plugin object
316    // for a HTMLEmbedElement). Further, unlike most DOM Objects, the frame
317    // is especially careful to ensure NPObjects terminate at frame teardown because
318    // if a plugin leaks a reference, it could leak its objects (or the browser's objects).
319    //
320    // The Frame maintains a list of plugin objects (m_pluginObjects)
321    // which it can use to quickly find the wrapped embed object.
322    //
323    // Inside the NPRuntime, we've added a few methods for registering
324    // wrapped NPObjects. The purpose of the registration is because
325    // javascript garbage collection is non-deterministic, yet we need to
326    // be able to tear down the plugin objects immediately. When an object
327    // is registered, javascript can use it. When the object is destroyed,
328    // or when the object's "owning" object is destroyed, the object will
329    // be un-registered, and the javascript engine must not use it.
330    //
331    // Inside the javascript engine, the engine can keep a reference to the
332    // NPObject as part of its wrapper. However, before accessing the object
333    // it must consult the _NPN_Registry.
334
335    v8::Local<v8::Object> wrapper = createV8ObjectForNPObject(npObject, 0);
336
337#ifdef ANDROID_FIX
338    // TODO: this should be up streamed.
339    // HTMLEmbedElement::getInstance() will call this function with its closest
340    // ancestor who has the objectTag. So this "widget" may be already in the
341    // HashMap. If it does, even m_pluginObjects.set() is a no-op, we do need to
342    // call _NPN_ReleaseObject on the npObject to balance the reference count.
343    PluginObjectMap::iterator it = m_pluginObjects.find(widget);
344    if (it != m_pluginObjects.end()) {
345        ASSERT(it->second == npObject);
346        _NPN_ReleaseObject(it->second);
347    }
348#endif
349
350    // Track the plugin object. We've been given a reference to the object.
351    m_pluginObjects.set(widget, npObject);
352
353    return V8ScriptInstance::create(wrapper);
354}
355
356void ScriptController::cleanupScriptObjectsForPlugin(Widget* nativeHandle)
357{
358    PluginObjectMap::iterator it = m_pluginObjects.find(nativeHandle);
359    if (it == m_pluginObjects.end())
360        return;
361    _NPN_UnregisterObject(it->second);
362    _NPN_ReleaseObject(it->second);
363    m_pluginObjects.remove(it);
364}
365
366void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
367{
368    worlds.append(mainThreadNormalWorld());
369}
370
371void ScriptController::evaluateInWorld(const ScriptSourceCode& source,
372                                       DOMWrapperWorld* world)
373{
374    Vector<ScriptSourceCode> sources;
375    sources.append(source);
376    // FIXME: Get an ID from the world param.
377    evaluateInIsolatedWorld(0, sources);
378}
379
380static NPObject* createNoScriptObject()
381{
382    notImplemented();
383    return 0;
384}
385
386static NPObject* createScriptObject(Frame* frame)
387{
388    v8::HandleScope handleScope;
389    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
390    if (v8Context.IsEmpty())
391        return createNoScriptObject();
392
393    v8::Context::Scope scope(v8Context);
394    DOMWindow* window = frame->domWindow();
395    v8::Handle<v8::Value> global = toV8(window);
396    ASSERT(global->IsObject());
397    return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(global), window);
398}
399
400NPObject* ScriptController::windowScriptNPObject()
401{
402    if (m_windowScriptNPObject)
403        return m_windowScriptNPObject;
404
405    if (canExecuteScripts()) {
406        // JavaScript is enabled, so there is a JavaScript window object.
407        // Return an NPObject bound to the window object.
408        m_windowScriptNPObject = createScriptObject(m_frame);
409        _NPN_RegisterObject(m_windowScriptNPObject, 0);
410    } else {
411        // JavaScript is not enabled, so we cannot bind the NPObject to the
412        // JavaScript window object. Instead, we create an NPObject of a
413        // different class, one which is not bound to a JavaScript object.
414        m_windowScriptNPObject = createNoScriptObject();
415    }
416    return m_windowScriptNPObject;
417}
418
419NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
420{
421    // Can't create NPObjects when JavaScript is disabled.
422    if (!canExecuteScripts())
423        return createNoScriptObject();
424
425    v8::HandleScope handleScope;
426    v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_frame);
427    if (v8Context.IsEmpty())
428        return createNoScriptObject();
429    v8::Context::Scope scope(v8Context);
430
431    DOMWindow* window = m_frame->domWindow();
432    v8::Handle<v8::Value> v8plugin = toV8(static_cast<HTMLEmbedElement*>(plugin));
433    if (!v8plugin->IsObject())
434        return createNoScriptObject();
435
436    return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(v8plugin), window);
437}
438
439
440void ScriptController::clearWindowShell()
441{
442    // V8 binding expects ScriptController::clearWindowShell only be called
443    // when a frame is loading a new page. V8Proxy::clearForNavigation
444    // creates a new context for the new page.
445    m_proxy->clearForNavigation();
446}
447
448void ScriptController::attachDebugger(void*)
449{
450    notImplemented();
451}
452
453void ScriptController::updateDocument()
454{
455    m_proxy->windowShell()->updateDocument();
456}
457
458} // namespace WebCore
459