1 /*
2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
4 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config.h"
22 #include "ScriptController.h"
23
24 #include "Event.h"
25 #include "EventNames.h"
26 #include "Frame.h"
27 #include "GCController.h"
28 #include "HTMLPlugInElement.h"
29 #include "JSDocument.h"
30 #include "NP_jsobject.h"
31 #include "Page.h"
32 #include "PageGroup.h"
33 #include "ScriptSourceCode.h"
34 #include "ScriptValue.h"
35 #include "Settings.h"
36 #include "XSSAuditor.h"
37 #include "npruntime_impl.h"
38 #include "runtime_root.h"
39 #include <debugger/Debugger.h>
40 #include <runtime/JSLock.h>
41
42 using namespace JSC;
43
44 namespace WebCore {
45
ScriptController(Frame * frame)46 ScriptController::ScriptController(Frame* frame)
47 : m_frame(frame)
48 , m_handlerLineNumber(0)
49 , m_sourceURL(0)
50 , m_processingTimerCallback(false)
51 , m_paused(false)
52 , m_allowPopupsFromPlugin(false)
53 #if ENABLE(NETSCAPE_PLUGIN_API)
54 , m_windowScriptNPObject(0)
55 #endif
56 #if PLATFORM(MAC)
57 , m_windowScriptObject(0)
58 #endif
59 , m_XSSAuditor(new XSSAuditor(frame))
60 {
61 #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE)
62 static bool initializedJavaJSBindings;
63 if (!initializedJavaJSBindings) {
64 initializedJavaJSBindings = true;
65 initJavaJSBindings();
66 }
67 #endif
68 }
69
~ScriptController()70 ScriptController::~ScriptController()
71 {
72 if (m_windowShell) {
73 m_windowShell = 0;
74
75 // It's likely that releasing the global object has created a lot of garbage.
76 gcController().garbageCollectSoon();
77 }
78
79 disconnectPlatformScriptObjects();
80 }
81
evaluate(const ScriptSourceCode & sourceCode)82 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
83 {
84 const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
85 String sourceURL = jsSourceCode.provider()->url();
86
87 if (sourceURL.isNull() && !m_XSSAuditor->canEvaluateJavaScriptURL(sourceCode.source())) {
88 // This JavaScript URL is not safe to be evaluated.
89 return JSValue();
90 }
91
92 if (!sourceURL.isNull() && !m_XSSAuditor->canEvaluate(sourceCode.source())) {
93 // This script is not safe to be evaluated.
94 return JSValue();
95 }
96
97 // evaluate code. Returns the JS return value or 0
98 // if there was none, an error occured or the type couldn't be converted.
99
100 initScriptIfNeeded();
101 // inlineCode is true for <a href="javascript:doSomething()">
102 // and false for <script>doSomething()</script>. Check if it has the
103 // expected value in all cases.
104 // See smart window.open policy for where this is used.
105 ExecState* exec = m_windowShell->window()->globalExec();
106 const String* savedSourceURL = m_sourceURL;
107 m_sourceURL = &sourceURL;
108
109 JSLock lock(SilenceAssertionsOnly);
110
111 RefPtr<Frame> protect = m_frame;
112
113 m_windowShell->window()->globalData()->timeoutChecker.start();
114 Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, m_windowShell);
115 m_windowShell->window()->globalData()->timeoutChecker.stop();
116
117 // Evaluating the JavaScript could cause the frame to be deallocated
118 // so we start the keep alive timer here.
119 m_frame->keepAlive();
120
121 if (comp.complType() == Normal || comp.complType() == ReturnValue) {
122 m_sourceURL = savedSourceURL;
123 return comp.value();
124 }
125
126 if (comp.complType() == Throw || comp.complType() == Interrupted)
127 reportException(exec, comp.value());
128
129 m_sourceURL = savedSourceURL;
130 return JSValue();
131 }
132
clearWindowShell()133 void ScriptController::clearWindowShell()
134 {
135 if (!m_windowShell)
136 return;
137
138 JSLock lock(SilenceAssertionsOnly);
139
140 // Clear the debugger from the current window before setting the new window.
141 attachDebugger(0);
142
143 m_windowShell->window()->willRemoveFromWindowShell();
144 m_windowShell->setWindow(m_frame->domWindow());
145
146 if (Page* page = m_frame->page()) {
147 attachDebugger(page->debugger());
148 m_windowShell->window()->setProfileGroup(page->group().identifier());
149 }
150
151 // There is likely to be a lot of garbage now.
152 gcController().garbageCollectSoon();
153 }
154
initScript()155 void ScriptController::initScript()
156 {
157 if (m_windowShell)
158 return;
159
160 JSLock lock(SilenceAssertionsOnly);
161
162 m_windowShell = new JSDOMWindowShell(m_frame->domWindow());
163 m_windowShell->window()->updateDocument();
164
165 if (Page* page = m_frame->page()) {
166 attachDebugger(page->debugger());
167 m_windowShell->window()->setProfileGroup(page->group().identifier());
168 }
169
170 m_frame->loader()->dispatchWindowObjectAvailable();
171 }
172
processingUserGesture() const173 bool ScriptController::processingUserGesture() const
174 {
175 return m_allowPopupsFromPlugin || processingUserGestureEvent() || isJavaScriptAnchorNavigation();
176 }
177
processingUserGestureEvent() const178 bool ScriptController::processingUserGestureEvent() const
179 {
180 if (!m_windowShell)
181 return false;
182
183 if (Event* event = m_windowShell->window()->currentEvent()) {
184 const AtomicString& type = event->type();
185 if ( // mouse events
186 type == eventNames().clickEvent || type == eventNames().mousedownEvent ||
187 type == eventNames().mouseupEvent || type == eventNames().dblclickEvent ||
188 // keyboard events
189 type == eventNames().keydownEvent || type == eventNames().keypressEvent ||
190 type == eventNames().keyupEvent ||
191 #if ENABLE(TOUCH_EVENTS) // Android
192 // touch events
193 type == eventNames().touchstartEvent || type == eventNames().touchmoveEvent ||
194 type == eventNames().touchendEvent || type == eventNames().touchcancelEvent ||
195 #endif
196 // other accepted events
197 type == eventNames().selectEvent || type == eventNames().changeEvent ||
198 type == eventNames().focusEvent || type == eventNames().blurEvent ||
199 type == eventNames().submitEvent)
200 return true;
201 }
202
203 return false;
204 }
205
206 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
isJavaScriptAnchorNavigation() const207 bool ScriptController::isJavaScriptAnchorNavigation() const
208 {
209 // This is the <a href="javascript:window.open('...')> case -> we let it through
210 if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
211 return true;
212
213 // This is the <script>window.open(...)</script> case or a timer callback -> block it
214 return false;
215 }
216
anyPageIsProcessingUserGesture() const217 bool ScriptController::anyPageIsProcessingUserGesture() const
218 {
219 Page* page = m_frame->page();
220 if (!page)
221 return false;
222
223 const HashSet<Page*>& pages = page->group().pages();
224 HashSet<Page*>::const_iterator end = pages.end();
225 for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
226 for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
227 if (frame->script()->processingUserGesture())
228 return true;
229 }
230 }
231
232 return false;
233 }
234
isEnabled()235 bool ScriptController::isEnabled()
236 {
237 Settings* settings = m_frame->settings();
238 return (settings && settings->isJavaScriptEnabled());
239 }
240
attachDebugger(JSC::Debugger * debugger)241 void ScriptController::attachDebugger(JSC::Debugger* debugger)
242 {
243 if (!m_windowShell)
244 return;
245
246 if (debugger)
247 debugger->attach(m_windowShell->window());
248 else if (JSC::Debugger* currentDebugger = m_windowShell->window()->debugger())
249 currentDebugger->detach(m_windowShell->window());
250 }
251
updateDocument()252 void ScriptController::updateDocument()
253 {
254 if (!m_frame->document())
255 return;
256
257 JSLock lock(SilenceAssertionsOnly);
258 if (m_windowShell)
259 m_windowShell->window()->updateDocument();
260 }
261
updateSecurityOrigin()262 void ScriptController::updateSecurityOrigin()
263 {
264 // Our bindings do not do anything in this case.
265 }
266
bindingRootObject()267 Bindings::RootObject* ScriptController::bindingRootObject()
268 {
269 if (!isEnabled())
270 return 0;
271
272 if (!m_bindingRootObject) {
273 JSLock lock(SilenceAssertionsOnly);
274 m_bindingRootObject = Bindings::RootObject::create(0, globalObject());
275 }
276 return m_bindingRootObject.get();
277 }
278
createRootObject(void * nativeHandle)279 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
280 {
281 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
282 if (it != m_rootObjects.end())
283 return it->second;
284
285 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject());
286
287 m_rootObjects.set(nativeHandle, rootObject);
288 return rootObject.release();
289 }
290
291 #if ENABLE(NETSCAPE_PLUGIN_API)
292
windowScriptNPObject()293 NPObject* ScriptController::windowScriptNPObject()
294 {
295 if (!m_windowScriptNPObject) {
296 if (isEnabled()) {
297 // JavaScript is enabled, so there is a JavaScript window object.
298 // Return an NPObject bound to the window object.
299 JSC::JSLock lock(SilenceAssertionsOnly);
300 JSObject* win = windowShell()->window();
301 ASSERT(win);
302 Bindings::RootObject* root = bindingRootObject();
303 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
304 } else {
305 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
306 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
307 m_windowScriptNPObject = _NPN_CreateNoScriptObject();
308 }
309 }
310
311 return m_windowScriptNPObject;
312 }
313
createScriptObjectForPluginElement(HTMLPlugInElement * plugin)314 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
315 {
316 JSObject* object = jsObjectForPluginElement(plugin);
317 if (!object)
318 return _NPN_CreateNoScriptObject();
319
320 // Wrap the JSObject in an NPObject
321 return _NPN_CreateScriptObject(0, object, bindingRootObject());
322 }
323
324 #endif
325
jsObjectForPluginElement(HTMLPlugInElement * plugin)326 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
327 {
328 // Can't create JSObjects when JavaScript is disabled
329 if (!isEnabled())
330 return 0;
331
332 // Create a JSObject bound to this element
333 JSLock lock(SilenceAssertionsOnly);
334 ExecState* exec = globalObject()->globalExec();
335 JSValue jsElementValue = toJS(exec, globalObject(), plugin);
336 if (!jsElementValue || !jsElementValue.isObject())
337 return 0;
338
339 return jsElementValue.getObject();
340 }
341
342 #if !PLATFORM(MAC)
343
updatePlatformScriptObjects()344 void ScriptController::updatePlatformScriptObjects()
345 {
346 }
347
disconnectPlatformScriptObjects()348 void ScriptController::disconnectPlatformScriptObjects()
349 {
350 }
351
352 #endif
353
cleanupScriptObjectsForPlugin(void * nativeHandle)354 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
355 {
356 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
357
358 if (it == m_rootObjects.end())
359 return;
360
361 it->second->invalidate();
362 m_rootObjects.remove(it);
363 }
364
clearScriptObjects()365 void ScriptController::clearScriptObjects()
366 {
367 JSLock lock(SilenceAssertionsOnly);
368
369 RootObjectMap::const_iterator end = m_rootObjects.end();
370 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
371 it->second->invalidate();
372
373 m_rootObjects.clear();
374
375 if (m_bindingRootObject) {
376 m_bindingRootObject->invalidate();
377 m_bindingRootObject = 0;
378 }
379
380 #if ENABLE(NETSCAPE_PLUGIN_API)
381 if (m_windowScriptNPObject) {
382 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
383 // script object properly.
384 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
385 _NPN_DeallocateObject(m_windowScriptNPObject);
386 m_windowScriptNPObject = 0;
387 }
388 #endif
389 }
390
391 } // namespace WebCore
392