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 "CString.h"
25 #include "Event.h"
26 #include "EventNames.h"
27 #include "Frame.h"
28 #include "FrameLoaderClient.h"
29 #include "GCController.h"
30 #include "HTMLPlugInElement.h"
31 #include "InspectorTimelineAgent.h"
32 #include "JSDocument.h"
33 #include "NP_jsobject.h"
34 #include "Page.h"
35 #include "PageGroup.h"
36 #include "ScriptSourceCode.h"
37 #include "ScriptValue.h"
38 #include "Settings.h"
39 #include "StorageNamespace.h"
40 #include "XSSAuditor.h"
41 #include "npruntime_impl.h"
42 #include "runtime_root.h"
43 #include <debugger/Debugger.h>
44 #include <runtime/InitializeThreading.h>
45 #include <runtime/JSLock.h>
46
47 using namespace JSC;
48 using namespace std;
49
50 namespace WebCore {
51
initializeThreading()52 void ScriptController::initializeThreading()
53 {
54 JSC::initializeThreading();
55 }
56
ScriptController(Frame * frame)57 ScriptController::ScriptController(Frame* frame)
58 : m_frame(frame)
59 , m_handlerLineNumber(0)
60 , m_sourceURL(0)
61 , m_inExecuteScript(false)
62 , m_processingTimerCallback(false)
63 , m_paused(false)
64 , m_allowPopupsFromPlugin(false)
65 #if ENABLE(NETSCAPE_PLUGIN_API)
66 , m_windowScriptNPObject(0)
67 #endif
68 #if PLATFORM(MAC)
69 , m_windowScriptObject(0)
70 #endif
71 , m_XSSAuditor(new XSSAuditor(frame))
72 {
73 #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE)
74 static bool initializedJavaJSBindings;
75 if (!initializedJavaJSBindings) {
76 initializedJavaJSBindings = true;
77 initJavaJSBindings();
78 }
79 #endif
80 }
81
~ScriptController()82 ScriptController::~ScriptController()
83 {
84 if (!m_windowShells.isEmpty()) {
85 m_windowShells.clear();
86
87 // It's likely that releasing the global object has created a lot of garbage.
88 gcController().garbageCollectSoon();
89 }
90
91 disconnectPlatformScriptObjects();
92 }
93
evaluateInWorld(const ScriptSourceCode & sourceCode,DOMWrapperWorld * world)94 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
95 {
96 const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
97 String sourceURL = jsSourceCode.provider()->url();
98
99 if (!m_XSSAuditor->canEvaluate(sourceCode.source())) {
100 // This script is not safe to be evaluated.
101 return JSValue();
102 }
103
104 // evaluate code. Returns the JS return value or 0
105 // if there was none, an error occurred or the type couldn't be converted.
106
107 // inlineCode is true for <a href="javascript:doSomething()">
108 // and false for <script>doSomething()</script>. Check if it has the
109 // expected value in all cases.
110 // See smart window.open policy for where this is used.
111 JSDOMWindowShell* shell = windowShell(world);
112 ExecState* exec = shell->window()->globalExec();
113 const String* savedSourceURL = m_sourceURL;
114 m_sourceURL = &sourceURL;
115
116 JSLock lock(SilenceAssertionsOnly);
117
118 RefPtr<Frame> protect = m_frame;
119
120 #if ENABLE(INSPECTOR)
121 if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0)
122 timelineAgent->willEvaluateScript(sourceURL, sourceCode.startLine());
123 #endif
124
125 exec->globalData().timeoutChecker.start();
126 Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
127 exec->globalData().timeoutChecker.stop();
128
129 #if ENABLE(INSPECTOR)
130 if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0)
131 timelineAgent->didEvaluateScript();
132 #endif
133
134 // Evaluating the JavaScript could cause the frame to be deallocated
135 // so we start the keep alive timer here.
136 m_frame->keepAlive();
137
138 if (comp.complType() == Normal || comp.complType() == ReturnValue) {
139 m_sourceURL = savedSourceURL;
140 return comp.value();
141 }
142
143 if (comp.complType() == Throw || comp.complType() == Interrupted)
144 reportException(exec, comp.value());
145
146 m_sourceURL = savedSourceURL;
147 return JSValue();
148 }
149
evaluate(const ScriptSourceCode & sourceCode)150 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
151 {
152 return evaluateInWorld(sourceCode, mainThreadNormalWorld());
153 }
154
155 // An DOMWrapperWorld other than the thread's normal world.
156 class IsolatedWorld : public DOMWrapperWorld {
157 public:
create(JSGlobalData * globalData)158 static PassRefPtr<IsolatedWorld> create(JSGlobalData* globalData) { return adoptRef(new IsolatedWorld(globalData)); }
159
160 protected:
IsolatedWorld(JSGlobalData * globalData)161 IsolatedWorld(JSGlobalData* globalData)
162 : DOMWrapperWorld(globalData, false)
163 {
164 JSGlobalData::ClientData* clientData = globalData->clientData;
165 ASSERT(clientData);
166 static_cast<WebCoreJSClientData*>(clientData)->rememberWorld(this);
167 }
168 };
169
createWorld()170 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
171 {
172 return IsolatedWorld::create(JSDOMWindow::commonJSGlobalData());
173 }
174
getAllWorlds(Vector<DOMWrapperWorld * > & worlds)175 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
176 {
177 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
178 }
179
clearWindowShell()180 void ScriptController::clearWindowShell()
181 {
182 if (m_windowShells.isEmpty())
183 return;
184
185 JSLock lock(SilenceAssertionsOnly);
186
187 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
188 JSDOMWindowShell* windowShell = iter->second;
189
190 // Clear the debugger from the current window before setting the new window.
191 attachDebugger(windowShell, 0);
192
193 windowShell->window()->willRemoveFromWindowShell();
194 windowShell->setWindow(m_frame->domWindow());
195
196 if (Page* page = m_frame->page()) {
197 attachDebugger(windowShell, page->debugger());
198 windowShell->window()->setProfileGroup(page->group().identifier());
199 }
200 }
201
202 // There is likely to be a lot of garbage now.
203 gcController().garbageCollectSoon();
204 }
205
initScript(DOMWrapperWorld * world)206 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
207 {
208 ASSERT(!m_windowShells.contains(world));
209
210 JSLock lock(SilenceAssertionsOnly);
211
212 JSDOMWindowShell* windowShell = new JSDOMWindowShell(m_frame->domWindow(), world);
213 m_windowShells.add(world, windowShell);
214 windowShell->window()->updateDocument();
215
216 if (Page* page = m_frame->page()) {
217 attachDebugger(windowShell, page->debugger());
218 windowShell->window()->setProfileGroup(page->group().identifier());
219 }
220
221 m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
222
223 return windowShell;
224 }
225
processingUserGesture(DOMWrapperWorld * world) const226 bool ScriptController::processingUserGesture(DOMWrapperWorld* world) const
227 {
228 return m_allowPopupsFromPlugin || processingUserGestureEvent(world) || isJavaScriptAnchorNavigation();
229 }
230
processingUserGestureEvent(DOMWrapperWorld * world) const231 bool ScriptController::processingUserGestureEvent(DOMWrapperWorld* world) const
232 {
233 JSDOMWindowShell* shell = existingWindowShell(world);
234 if (!shell)
235 return false;
236
237 if (Event* event = shell->window()->currentEvent())
238 return event->fromUserGesture();
239
240 return false;
241 }
242
243 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
isJavaScriptAnchorNavigation() const244 bool ScriptController::isJavaScriptAnchorNavigation() const
245 {
246 // This is the <a href="javascript:window.open('...')> case -> we let it through
247 if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
248 return true;
249
250 // This is the <script>window.open(...)</script> case or a timer callback -> block it
251 return false;
252 }
253
anyPageIsProcessingUserGesture() const254 bool ScriptController::anyPageIsProcessingUserGesture() const
255 {
256 Page* page = m_frame->page();
257 if (!page)
258 return false;
259
260 const HashSet<Page*>& pages = page->group().pages();
261 HashSet<Page*>::const_iterator end = pages.end();
262 for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
263 for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
264 ScriptController* script = frame->script();
265
266 if (script->m_allowPopupsFromPlugin)
267 return true;
268
269 const ShellMap::const_iterator iterEnd = m_windowShells.end();
270 for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) {
271 JSDOMWindowShell* shell = iter->second.get();
272 Event* event = shell->window()->currentEvent();
273 if (event && event->fromUserGesture())
274 return true;
275 }
276
277 if (isJavaScriptAnchorNavigation())
278 return true;
279 }
280 }
281
282 return false;
283 }
284
attachDebugger(JSC::Debugger * debugger)285 void ScriptController::attachDebugger(JSC::Debugger* debugger)
286 {
287 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
288 attachDebugger(iter->second, debugger);
289 }
290
attachDebugger(JSDOMWindowShell * shell,JSC::Debugger * debugger)291 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
292 {
293 if (!shell)
294 return;
295
296 JSDOMWindow* globalObject = shell->window();
297 if (debugger)
298 debugger->attach(globalObject);
299 else if (JSC::Debugger* currentDebugger = globalObject->debugger())
300 currentDebugger->detach(globalObject);
301 }
302
updateDocument()303 void ScriptController::updateDocument()
304 {
305 if (!m_frame->document())
306 return;
307
308 JSLock lock(SilenceAssertionsOnly);
309 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
310 iter->second->window()->updateDocument();
311 }
312
updateSecurityOrigin()313 void ScriptController::updateSecurityOrigin()
314 {
315 // Our bindings do not do anything in this case.
316 }
317
bindingRootObject()318 Bindings::RootObject* ScriptController::bindingRootObject()
319 {
320 if (!canExecuteScripts())
321 return 0;
322
323 if (!m_bindingRootObject) {
324 JSLock lock(SilenceAssertionsOnly);
325 m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
326 }
327 return m_bindingRootObject.get();
328 }
329
createRootObject(void * nativeHandle)330 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
331 {
332 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
333 if (it != m_rootObjects.end())
334 return it->second;
335
336 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
337
338 m_rootObjects.set(nativeHandle, rootObject);
339 return rootObject.release();
340 }
341
342 #if ENABLE(NETSCAPE_PLUGIN_API)
343
windowScriptNPObject()344 NPObject* ScriptController::windowScriptNPObject()
345 {
346 if (!m_windowScriptNPObject) {
347 if (canExecuteScripts()) {
348 // JavaScript is enabled, so there is a JavaScript window object.
349 // Return an NPObject bound to the window object.
350 JSC::JSLock lock(SilenceAssertionsOnly);
351 JSObject* win = windowShell(pluginWorld())->window();
352 ASSERT(win);
353 Bindings::RootObject* root = bindingRootObject();
354 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
355 } else {
356 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
357 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
358 m_windowScriptNPObject = _NPN_CreateNoScriptObject();
359 }
360 }
361
362 return m_windowScriptNPObject;
363 }
364
createScriptObjectForPluginElement(HTMLPlugInElement * plugin)365 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
366 {
367 JSObject* object = jsObjectForPluginElement(plugin);
368 if (!object)
369 return _NPN_CreateNoScriptObject();
370
371 // Wrap the JSObject in an NPObject
372 return _NPN_CreateScriptObject(0, object, bindingRootObject());
373 }
374
375 #endif
376
jsObjectForPluginElement(HTMLPlugInElement * plugin)377 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
378 {
379 // Can't create JSObjects when JavaScript is disabled
380 if (!canExecuteScripts())
381 return 0;
382
383 // Create a JSObject bound to this element
384 JSLock lock(SilenceAssertionsOnly);
385 JSDOMWindow* globalObj = globalObject(pluginWorld());
386 // FIXME: is normal okay? - used for NP plugins?
387 JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
388 if (!jsElementValue || !jsElementValue.isObject())
389 return 0;
390
391 return jsElementValue.getObject();
392 }
393
394 #if !PLATFORM(MAC)
395
updatePlatformScriptObjects()396 void ScriptController::updatePlatformScriptObjects()
397 {
398 }
399
disconnectPlatformScriptObjects()400 void ScriptController::disconnectPlatformScriptObjects()
401 {
402 }
403
404 #endif
405
cleanupScriptObjectsForPlugin(void * nativeHandle)406 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
407 {
408 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
409
410 if (it == m_rootObjects.end())
411 return;
412
413 it->second->invalidate();
414 m_rootObjects.remove(it);
415 }
416
clearScriptObjects()417 void ScriptController::clearScriptObjects()
418 {
419 JSLock lock(SilenceAssertionsOnly);
420
421 RootObjectMap::const_iterator end = m_rootObjects.end();
422 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
423 it->second->invalidate();
424
425 m_rootObjects.clear();
426
427 if (m_bindingRootObject) {
428 m_bindingRootObject->invalidate();
429 m_bindingRootObject = 0;
430 }
431
432 #if ENABLE(NETSCAPE_PLUGIN_API)
433 if (m_windowScriptNPObject) {
434 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
435 // script object properly.
436 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
437 _NPN_DeallocateObject(m_windowScriptNPObject);
438 m_windowScriptNPObject = 0;
439 }
440 #endif
441 }
442
executeScriptInWorld(DOMWrapperWorld * world,const String & script,bool forceUserGesture)443 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture)
444 {
445 ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->loader()->url());
446
447 if (!canExecuteScripts() || isPaused())
448 return ScriptValue();
449
450 bool wasInExecuteScript = m_inExecuteScript;
451 m_inExecuteScript = true;
452
453 ScriptValue result = evaluateInWorld(sourceCode, world);
454
455 if (!wasInExecuteScript) {
456 m_inExecuteScript = false;
457 Document::updateStyleForAllDocuments();
458 }
459
460 return result;
461 }
462
463 } // namespace WebCore
464