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