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