62eb5339efb27c2b7707b80b8e095af19f4dc9de
[WebKit.git] / WebCore / bindings / v8 / ScriptController.cpp
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 "Document.h"
37 #include "DOMWindow.h"
38 #include "Event.h"
39 #include "EventListener.h"
40 #include "EventNames.h"
41 #include "Frame.h"
42 #include "FrameLoaderClient.h"
43 #include "Node.h"
44 #include "NotImplemented.h"
45 #include "npruntime_impl.h"
46 #include "npruntime_priv.h"
47 #include "NPV8Object.h"
48 #include "ScriptSourceCode.h"
49 #include "Settings.h"
50 #include "UserGestureIndicator.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 #include <wtf/text/CString.h>
63
64 namespace WebCore {
65
66 void ScriptController::initializeThreading()
67 {
68     static bool initializedThreading = false;
69     if (!initializedThreading) {
70         WTF::initializeThreading();
71         WTF::initializeMainThread();
72         initializedThreading = true;
73     }
74 }
75
76 void ScriptController::setFlags(const char* string, int length)
77 {
78     v8::V8::SetFlagsFromString(string, length);
79 }
80
81 Frame* ScriptController::retrieveFrameForEnteredContext()
82 {
83     return V8Proxy::retrieveFrameForEnteredContext();
84 }
85
86 Frame* ScriptController::retrieveFrameForCurrentContext()
87 {
88     return V8Proxy::retrieveFrameForCurrentContext();
89 }
90
91 bool ScriptController::isSafeScript(Frame* target)
92 {
93     return V8BindingSecurity::canAccessFrame(V8BindingState::Only(), target, true);
94 }
95
96 void ScriptController::gcProtectJSWrapper(void* domObject)
97 {
98     V8GCController::gcProtect(domObject);
99 }
100
101 void ScriptController::gcUnprotectJSWrapper(void* domObject)
102 {
103     V8GCController::gcUnprotect(domObject);
104 }
105
106 ScriptController::ScriptController(Frame* frame)
107     : m_frame(frame)
108     , m_sourceURL(0)
109     , m_inExecuteScript(false)
110     , m_processingTimerCallback(false)
111     , m_paused(false)
112     , m_proxy(new V8Proxy(frame))
113 #if ENABLE(NETSCAPE_PLUGIN_API)
114     , m_windowScriptNPObject(0)
115 #endif
116     , m_XSSAuditor(new XSSAuditor(frame))
117 {
118 }
119
120 ScriptController::~ScriptController()
121 {
122     m_proxy->disconnectFrame();
123 }
124
125 void ScriptController::clearScriptObjects()
126 {
127     PluginObjectMap::iterator it = m_pluginObjects.begin();
128     for (; it != m_pluginObjects.end(); ++it) {
129         _NPN_UnregisterObject(it->second);
130         _NPN_ReleaseObject(it->second);
131     }
132     m_pluginObjects.clear();
133
134 #if ENABLE(NETSCAPE_PLUGIN_API)
135     if (m_windowScriptNPObject) {
136         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
137         // script object properly.
138         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
139         _NPN_DeallocateObject(m_windowScriptNPObject);
140         m_windowScriptNPObject = 0;
141     }
142 #endif
143 }
144
145 void ScriptController::updateSecurityOrigin()
146 {
147     m_proxy->windowShell()->updateSecurityOrigin();
148 }
149
150 void ScriptController::updatePlatformScriptObjects()
151 {
152     notImplemented();
153 }
154
155 bool ScriptController::processingUserGesture(DOMWrapperWorld*) const
156 {
157     Frame* activeFrame = V8Proxy::retrieveFrameForEnteredContext();
158     // No script is running, so it is user-initiated unless the gesture stack
159     // explicitly says it is not.
160     if (!activeFrame)
161         return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture;
162
163     V8Proxy* activeProxy = activeFrame->script()->proxy();
164
165     v8::HandleScope handleScope;
166     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(activeFrame);
167     // FIXME: find all cases context can be empty:
168     //  1) JS is disabled;
169     //  2) page is NULL;
170     if (v8Context.IsEmpty())
171         return true;
172
173     v8::Context::Scope scope(v8Context);
174
175     v8::Handle<v8::Object> global = v8Context->Global();
176     v8::Handle<v8::Value> jsEvent = global->Get(v8::String::NewSymbol("event"));
177     Event* event = V8DOMWrapper::isValidDOMObject(jsEvent) ? V8Event::toNative(v8::Handle<v8::Object>::Cast(jsEvent)) : 0;
178
179     // Based on code from kjs_bindings.cpp.
180     // Note: This is more liberal than Firefox's implementation.
181     if (event) {
182         if (!UserGestureIndicator::processingUserGesture())
183             return false;
184
185         const AtomicString& type = event->type();
186         bool eventOk =
187             // mouse events
188             type == eventNames().clickEvent || type == eventNames().mousedownEvent || type == eventNames().mouseupEvent || type == eventNames().dblclickEvent
189             // keyboard events
190             || type == eventNames().keydownEvent || type == eventNames().keypressEvent || type == eventNames().keyupEvent
191             // other accepted events
192             || type == eventNames().selectEvent || type == eventNames().changeEvent || type == eventNames().focusEvent || type == eventNames().blurEvent || type == eventNames().submitEvent;
193
194         if (eventOk)
195             return true;
196     } else if (m_sourceURL && m_sourceURL->isNull() && !activeProxy->timerCallback()) {
197         // This is the <a href="javascript:window.open('...')> case -> we let it through.
198         return true;
199     }
200
201     // This is the <script>window.open(...)</script> case or a timer callback -> block it.
202     return false;
203 }
204
205 bool ScriptController::anyPageIsProcessingUserGesture() const
206 {
207     // FIXME: is this right?
208     return processingUserGesture();
209 }
210
211 void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources)
212 {
213     m_proxy->evaluateInIsolatedWorld(worldID, sources, 0);
214 }
215
216 void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources, int extensionGroup)
217 {
218     m_proxy->evaluateInIsolatedWorld(worldID, sources, extensionGroup);
219 }
220
221 // Evaluate a script file in the environment of this proxy.
222 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
223 {
224     String sourceURL = sourceCode.url();
225     const String* savedSourceURL = m_sourceURL;
226     m_sourceURL = &sourceURL;
227
228     if (!m_XSSAuditor->canEvaluate(sourceCode.source())) {
229         // This script is not safe to be evaluated.
230         return ScriptValue();
231     }
232
233     v8::HandleScope handleScope;
234     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame());
235     if (v8Context.IsEmpty())
236         return ScriptValue();
237
238     v8::Context::Scope scope(v8Context);
239
240     RefPtr<Frame> protect(m_frame);
241
242     v8::Local<v8::Value> object = m_proxy->evaluate(sourceCode, 0);
243
244     // Evaluating the JavaScript could cause the frame to be deallocated
245     // so we start the keep alive timer here.
246     m_frame->keepAlive();
247
248     m_sourceURL = savedSourceURL;
249
250     if (object.IsEmpty() || object->IsUndefined())
251         return ScriptValue();
252
253     return ScriptValue(object);
254 }
255
256 void ScriptController::setEventHandlerLineNumber(int lineNumber)
257 {
258     m_proxy->setEventHandlerLineNumber(lineNumber);
259 }
260
261 void ScriptController::finishedWithEvent(Event* event)
262 {
263     m_proxy->finishedWithEvent(event);
264 }
265
266 // Create a V8 object with an interceptor of NPObjectPropertyGetter.
267 void ScriptController::bindToWindowObject(Frame* frame, const String& key, NPObject* object)
268 {
269     v8::HandleScope handleScope;
270
271     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
272     if (v8Context.IsEmpty())
273         return;
274
275     v8::Context::Scope scope(v8Context);
276
277     v8::Handle<v8::Object> value = createV8ObjectForNPObject(object, 0);
278
279     // Attach to the global object.
280     v8::Handle<v8::Object> global = v8Context->Global();
281     global->Set(v8String(key), value);
282 }
283
284 void ScriptController::collectGarbage()
285 {
286     v8::HandleScope handleScope;
287     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame());
288     if (v8Context.IsEmpty())
289         return;
290
291     v8::Context::Scope scope(v8Context);
292
293     m_proxy->evaluate(ScriptSourceCode("if (window.gc) void(gc());"), 0);
294 }
295
296 void ScriptController::lowMemoryNotification()
297 {
298     v8::V8::LowMemoryNotification();
299 }
300
301 bool ScriptController::haveInterpreter() const
302 {
303     return m_proxy->windowShell()->isContextInitialized();
304 }
305
306 PassScriptInstance ScriptController::createScriptInstanceForWidget(Widget* widget)
307 {
308     ASSERT(widget);
309
310     if (widget->isFrameView())
311         return 0;
312
313     NPObject* npObject = PlatformBridge::pluginScriptableObject(widget);
314
315     if (!npObject)
316         return 0;
317
318     // Frame Memory Management for NPObjects
319     // -------------------------------------
320     // NPObjects are treated differently than other objects wrapped by JS.
321     // NPObjects can be created either by the browser (e.g. the main
322     // window object) or by the plugin (the main plugin object
323     // for a HTMLEmbedElement). Further, unlike most DOM Objects, the frame
324     // is especially careful to ensure NPObjects terminate at frame teardown because
325     // if a plugin leaks a reference, it could leak its objects (or the browser's objects).
326     //
327     // The Frame maintains a list of plugin objects (m_pluginObjects)
328     // which it can use to quickly find the wrapped embed object.
329     //
330     // Inside the NPRuntime, we've added a few methods for registering
331     // wrapped NPObjects. The purpose of the registration is because
332     // javascript garbage collection is non-deterministic, yet we need to
333     // be able to tear down the plugin objects immediately. When an object
334     // is registered, javascript can use it. When the object is destroyed,
335     // or when the object's "owning" object is destroyed, the object will
336     // be un-registered, and the javascript engine must not use it.
337     //
338     // Inside the javascript engine, the engine can keep a reference to the
339     // NPObject as part of its wrapper. However, before accessing the object
340     // it must consult the _NPN_Registry.
341
342     v8::Local<v8::Object> wrapper = createV8ObjectForNPObject(npObject, 0);
343
344     // Track the plugin object. We've been given a reference to the object.
345     m_pluginObjects.set(widget, npObject);
346
347     return V8ScriptInstance::create(wrapper);
348 }
349
350 void ScriptController::cleanupScriptObjectsForPlugin(Widget* nativeHandle)
351 {
352     PluginObjectMap::iterator it = m_pluginObjects.find(nativeHandle);
353     if (it == m_pluginObjects.end())
354         return;
355     _NPN_UnregisterObject(it->second);
356     _NPN_ReleaseObject(it->second);
357     m_pluginObjects.remove(it);
358 }
359
360 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
361 {
362     worlds.append(mainThreadNormalWorld());
363 }
364
365 void ScriptController::evaluateInWorld(const ScriptSourceCode& source,
366                                        DOMWrapperWorld* world)
367 {
368     Vector<ScriptSourceCode> sources;
369     sources.append(source);
370     // FIXME: Get an ID from the world param.
371     evaluateInIsolatedWorld(0, sources);
372 }
373
374 static NPObject* createNoScriptObject()
375 {
376     notImplemented();
377     return 0;
378 }
379
380 static NPObject* createScriptObject(Frame* frame)
381 {
382     v8::HandleScope handleScope;
383     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
384     if (v8Context.IsEmpty())
385         return createNoScriptObject();
386
387     v8::Context::Scope scope(v8Context);
388     DOMWindow* window = frame->domWindow();
389     v8::Handle<v8::Value> global = toV8(window);
390     ASSERT(global->IsObject());
391     return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(global), window);
392 }
393
394 NPObject* ScriptController::windowScriptNPObject()
395 {
396     if (m_windowScriptNPObject)
397         return m_windowScriptNPObject;
398
399     if (canExecuteScripts(NotAboutToExecuteScript)) {
400         // JavaScript is enabled, so there is a JavaScript window object.
401         // Return an NPObject bound to the window object.
402         m_windowScriptNPObject = createScriptObject(m_frame);
403         _NPN_RegisterObject(m_windowScriptNPObject, 0);
404     } else {
405         // JavaScript is not enabled, so we cannot bind the NPObject to the
406         // JavaScript window object. Instead, we create an NPObject of a
407         // different class, one which is not bound to a JavaScript object.
408         m_windowScriptNPObject = createNoScriptObject();
409     }
410     return m_windowScriptNPObject;
411 }
412
413 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
414 {
415     // Can't create NPObjects when JavaScript is disabled.
416     if (!canExecuteScripts(NotAboutToExecuteScript))
417         return createNoScriptObject();
418
419     v8::HandleScope handleScope;
420     v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_frame);
421     if (v8Context.IsEmpty())
422         return createNoScriptObject();
423     v8::Context::Scope scope(v8Context);
424
425     DOMWindow* window = m_frame->domWindow();
426     v8::Handle<v8::Value> v8plugin = toV8(static_cast<HTMLEmbedElement*>(plugin));
427     if (!v8plugin->IsObject())
428         return createNoScriptObject();
429
430     return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(v8plugin), window);
431 }
432
433
434 void ScriptController::clearWindowShell()
435 {
436     // V8 binding expects ScriptController::clearWindowShell only be called
437     // when a frame is loading a new page. V8Proxy::clearForNavigation
438     // creates a new context for the new page.
439     m_proxy->clearForNavigation();
440 }
441
442 void ScriptController::attachDebugger(void*)
443 {
444     notImplemented();
445 }
446
447 void ScriptController::updateDocument()
448 {
449     m_proxy->windowShell()->updateDocument();
450 }
451
452 } // namespace WebCore