• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ScriptableDocumentParser.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 "InspectorInstrumentation.h"
32 #include "JSDocument.h"
33 #include "JSMainThreadExecState.h"
34 #include "NP_jsobject.h"
35 #include "Page.h"
36 #include "PageGroup.h"
37 #include "ScriptSourceCode.h"
38 #include "ScriptValue.h"
39 #include "Settings.h"
40 #include "StorageNamespace.h"
41 #include "UserGestureIndicator.h"
42 #include "WebCoreJSClientData.h"
43 #include "npruntime_impl.h"
44 #include "runtime_root.h"
45 #include <debugger/Debugger.h>
46 #include <runtime/InitializeThreading.h>
47 #include <runtime/JSLock.h>
48 #include <wtf/Threading.h>
49 
50 using namespace JSC;
51 using namespace std;
52 
53 namespace WebCore {
54 
initializeThreading()55 void ScriptController::initializeThreading()
56 {
57     JSC::initializeThreading();
58     WTF::initializeMainThread();
59 }
60 
ScriptController(Frame * frame)61 ScriptController::ScriptController(Frame* frame)
62     : m_frame(frame)
63     , m_sourceURL(0)
64     , m_inExecuteScript(false)
65     , m_processingTimerCallback(false)
66     , m_paused(false)
67     , m_allowPopupsFromPlugin(false)
68 #if ENABLE(NETSCAPE_PLUGIN_API)
69     , m_windowScriptNPObject(0)
70 #endif
71 #if PLATFORM(MAC)
72     , m_windowScriptObject(0)
73 #endif
74 {
75 #if PLATFORM(MAC) && ENABLE(JAVA_BRIDGE)
76     static bool initializedJavaJSBindings;
77     if (!initializedJavaJSBindings) {
78         initializedJavaJSBindings = true;
79         initJavaJSBindings();
80     }
81 #endif
82 }
83 
~ScriptController()84 ScriptController::~ScriptController()
85 {
86     disconnectPlatformScriptObjects();
87 
88     if (m_cacheableBindingRootObject) {
89         m_cacheableBindingRootObject->invalidate();
90         m_cacheableBindingRootObject = 0;
91     }
92 
93     // It's likely that destroying m_windowShells will create a lot of garbage.
94     if (!m_windowShells.isEmpty()) {
95         while (!m_windowShells.isEmpty())
96             destroyWindowShell(m_windowShells.begin()->first.get());
97         gcController().garbageCollectSoon();
98     }
99 }
100 
destroyWindowShell(DOMWrapperWorld * world)101 void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
102 {
103     ASSERT(m_windowShells.contains(world));
104     m_windowShells.remove(world);
105     world->didDestroyWindowShell(this);
106 }
107 
createWindowShell(DOMWrapperWorld * world)108 JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world)
109 {
110     ASSERT(!m_windowShells.contains(world));
111     Strong<JSDOMWindowShell> windowShell(*world->globalData(), new JSDOMWindowShell(m_frame->domWindow(), world));
112     Strong<JSDOMWindowShell> windowShell2(windowShell);
113     m_windowShells.add(world, windowShell);
114     world->didCreateWindowShell(this);
115     return windowShell.get();
116 }
117 
evaluateInWorld(const ScriptSourceCode & sourceCode,DOMWrapperWorld * world)118 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
119 {
120     const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
121     String sourceURL = ustringToString(jsSourceCode.provider()->url());
122 
123     // evaluate code. Returns the JS return value or 0
124     // if there was none, an error occurred or the type couldn't be converted.
125 
126     // inlineCode is true for <a href="javascript:doSomething()">
127     // and false for <script>doSomething()</script>. Check if it has the
128     // expected value in all cases.
129     // See smart window.open policy for where this is used.
130     JSDOMWindowShell* shell = windowShell(world);
131     ExecState* exec = shell->window()->globalExec();
132     const String* savedSourceURL = m_sourceURL;
133     m_sourceURL = &sourceURL;
134 
135     JSLock lock(SilenceAssertionsOnly);
136 
137     RefPtr<Frame> protect = m_frame;
138 
139     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
140 
141     exec->globalData().timeoutChecker.start();
142     Completion comp = JSMainThreadExecState::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
143     exec->globalData().timeoutChecker.stop();
144 
145     InspectorInstrumentation::didEvaluateScript(cookie);
146 
147     // Evaluating the JavaScript could cause the frame to be deallocated
148     // so we start the keep alive timer here.
149     m_frame->keepAlive();
150 
151     if (comp.complType() == Normal || comp.complType() == ReturnValue) {
152         m_sourceURL = savedSourceURL;
153         return ScriptValue(exec->globalData(), comp.value());
154     }
155 
156     if (comp.complType() == Throw || comp.complType() == Interrupted)
157         reportException(exec, comp.value());
158 
159     m_sourceURL = savedSourceURL;
160     return ScriptValue();
161 }
162 
evaluate(const ScriptSourceCode & sourceCode)163 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
164 {
165     return evaluateInWorld(sourceCode, mainThreadNormalWorld());
166 }
167 
createWorld()168 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
169 {
170     return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData());
171 }
172 
getAllWorlds(Vector<DOMWrapperWorld * > & worlds)173 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
174 {
175     static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
176 }
177 
clearWindowShell(bool goingIntoPageCache)178 void ScriptController::clearWindowShell(bool goingIntoPageCache)
179 {
180     if (m_windowShells.isEmpty())
181         return;
182 
183     JSLock lock(SilenceAssertionsOnly);
184 
185     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
186         JSDOMWindowShell* windowShell = iter->second.get();
187 
188         // Clear the debugger from the current window before setting the new window.
189         attachDebugger(windowShell, 0);
190 
191         windowShell->window()->willRemoveFromWindowShell();
192         windowShell->setWindow(m_frame->domWindow());
193 
194         // An m_cacheableBindingRootObject persists between page navigations
195         // so needs to know about the new JSDOMWindow.
196         if (m_cacheableBindingRootObject)
197             m_cacheableBindingRootObject->updateGlobalObject(windowShell->window());
198 
199         if (Page* page = m_frame->page()) {
200             attachDebugger(windowShell, page->debugger());
201             windowShell->window()->setProfileGroup(page->group().identifier());
202         }
203     }
204 
205     // It's likely that resetting our windows created a lot of garbage, unless
206     // it went in a back/forward cache.
207     if (!goingIntoPageCache)
208         gcController().garbageCollectSoon();
209 }
210 
initScript(DOMWrapperWorld * world)211 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
212 {
213     ASSERT(!m_windowShells.contains(world));
214 
215     JSLock lock(SilenceAssertionsOnly);
216 
217     JSDOMWindowShell* windowShell = createWindowShell(world);
218 
219     windowShell->window()->updateDocument();
220 
221     if (Page* page = m_frame->page()) {
222         attachDebugger(windowShell, page->debugger());
223         windowShell->window()->setProfileGroup(page->group().identifier());
224     }
225 
226     m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
227 
228     return windowShell;
229 }
230 
eventHandlerLineNumber() const231 int ScriptController::eventHandlerLineNumber() const
232 {
233     // JSC expects 1-based line numbers, so we must add one here to get it right.
234     ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
235     if (parser)
236         return parser->lineNumber() + 1;
237     return 0;
238 }
239 
processingUserGesture()240 bool ScriptController::processingUserGesture()
241 {
242     ExecState* exec = JSMainThreadExecState::currentState();
243     Frame* frame = exec ? toDynamicFrame(exec) : 0;
244     // No script is running, so it is user-initiated unless the gesture stack
245     // explicitly says it is not.
246     if (!frame)
247         return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture;
248 
249     // FIXME: We check the plugin popup flag and javascript anchor navigation
250     // from the dynamic frame becuase they should only be initiated on the
251     // dynamic frame in which execution began if they do happen.
252     ScriptController* scriptController = frame->script();
253     ASSERT(scriptController);
254     if (scriptController->allowPopupsFromPlugin() || scriptController->isJavaScriptAnchorNavigation())
255         return true;
256 
257     // If a DOM event is being processed, check that it was initiated by the user
258     // and that it is in the whitelist of event types allowed to generate pop-ups.
259     if (JSDOMWindowShell* shell = scriptController->existingWindowShell(currentWorld(exec)))
260         if (Event* event = shell->window()->currentEvent())
261             return event->fromUserGesture();
262 
263     return UserGestureIndicator::processingUserGesture();
264 }
265 
266 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
isJavaScriptAnchorNavigation() const267 bool ScriptController::isJavaScriptAnchorNavigation() const
268 {
269     // This is the <a href="javascript:window.open('...')> case -> we let it through
270     if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
271         return true;
272 
273     // This is the <script>window.open(...)</script> case or a timer callback -> block it
274     return false;
275 }
276 
anyPageIsProcessingUserGesture() const277 bool ScriptController::anyPageIsProcessingUserGesture() const
278 {
279     Page* page = m_frame->page();
280     if (!page)
281         return false;
282 
283     const HashSet<Page*>& pages = page->group().pages();
284     HashSet<Page*>::const_iterator end = pages.end();
285     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
286         for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
287             ScriptController* script = frame->script();
288 
289             if (script->m_allowPopupsFromPlugin)
290                 return true;
291 
292             const ShellMap::const_iterator iterEnd = m_windowShells.end();
293             for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) {
294                 JSDOMWindowShell* shell = iter->second.get();
295                 Event* event = shell->window()->currentEvent();
296                 if (event && event->fromUserGesture())
297                     return true;
298             }
299 
300             if (isJavaScriptAnchorNavigation())
301                 return true;
302         }
303     }
304 
305     return false;
306 }
307 
canAccessFromCurrentOrigin(Frame * frame)308 bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
309 {
310     ExecState* exec = JSMainThreadExecState::currentState();
311     if (exec)
312         return allowsAccessFromFrame(exec, frame);
313     // If the current state is 0 we're in a call path where the DOM security
314     // check doesn't apply (eg. parser).
315     return true;
316 }
317 
attachDebugger(JSC::Debugger * debugger)318 void ScriptController::attachDebugger(JSC::Debugger* debugger)
319 {
320     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
321         attachDebugger(iter->second.get(), debugger);
322 }
323 
attachDebugger(JSDOMWindowShell * shell,JSC::Debugger * debugger)324 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
325 {
326     if (!shell)
327         return;
328 
329     JSDOMWindow* globalObject = shell->window();
330     if (debugger)
331         debugger->attach(globalObject);
332     else if (JSC::Debugger* currentDebugger = globalObject->debugger())
333         currentDebugger->detach(globalObject);
334 }
335 
updateDocument()336 void ScriptController::updateDocument()
337 {
338     if (!m_frame->document())
339         return;
340 
341     JSLock lock(SilenceAssertionsOnly);
342     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
343         iter->second->window()->updateDocument();
344 }
345 
updateSecurityOrigin()346 void ScriptController::updateSecurityOrigin()
347 {
348     // Our bindings do not do anything in this case.
349 }
350 
cacheableBindingRootObject()351 Bindings::RootObject* ScriptController::cacheableBindingRootObject()
352 {
353     if (!canExecuteScripts(NotAboutToExecuteScript))
354         return 0;
355 
356     if (!m_cacheableBindingRootObject) {
357         JSLock lock(SilenceAssertionsOnly);
358         m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
359     }
360     return m_cacheableBindingRootObject.get();
361 }
362 
bindingRootObject()363 Bindings::RootObject* ScriptController::bindingRootObject()
364 {
365     if (!canExecuteScripts(NotAboutToExecuteScript))
366         return 0;
367 
368     if (!m_bindingRootObject) {
369         JSLock lock(SilenceAssertionsOnly);
370         m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
371     }
372     return m_bindingRootObject.get();
373 }
374 
createRootObject(void * nativeHandle)375 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
376 {
377     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
378     if (it != m_rootObjects.end())
379         return it->second;
380 
381     RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
382 
383     m_rootObjects.set(nativeHandle, rootObject);
384     return rootObject.release();
385 }
386 
387 #if ENABLE(INSPECTOR)
setCaptureCallStackForUncaughtExceptions(bool)388 void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
389 {
390 }
391 #endif
392 
393 #if ENABLE(NETSCAPE_PLUGIN_API)
394 
windowScriptNPObject()395 NPObject* ScriptController::windowScriptNPObject()
396 {
397     if (!m_windowScriptNPObject) {
398         if (canExecuteScripts(NotAboutToExecuteScript)) {
399             // JavaScript is enabled, so there is a JavaScript window object.
400             // Return an NPObject bound to the window object.
401             JSC::JSLock lock(SilenceAssertionsOnly);
402             JSObject* win = windowShell(pluginWorld())->window();
403             ASSERT(win);
404             Bindings::RootObject* root = bindingRootObject();
405             m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
406         } else {
407             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
408             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
409             m_windowScriptNPObject = _NPN_CreateNoScriptObject();
410         }
411     }
412 
413     return m_windowScriptNPObject;
414 }
415 
createScriptObjectForPluginElement(HTMLPlugInElement * plugin)416 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
417 {
418     JSObject* object = jsObjectForPluginElement(plugin);
419     if (!object)
420         return _NPN_CreateNoScriptObject();
421 
422     // Wrap the JSObject in an NPObject
423     return _NPN_CreateScriptObject(0, object, bindingRootObject());
424 }
425 
426 #endif
427 
jsObjectForPluginElement(HTMLPlugInElement * plugin)428 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
429 {
430     // Can't create JSObjects when JavaScript is disabled
431     if (!canExecuteScripts(NotAboutToExecuteScript))
432         return 0;
433 
434     // Create a JSObject bound to this element
435     JSLock lock(SilenceAssertionsOnly);
436     JSDOMWindow* globalObj = globalObject(pluginWorld());
437     // FIXME: is normal okay? - used for NP plugins?
438     JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
439     if (!jsElementValue || !jsElementValue.isObject())
440         return 0;
441 
442     return jsElementValue.getObject();
443 }
444 
445 #if !PLATFORM(MAC)
446 
updatePlatformScriptObjects()447 void ScriptController::updatePlatformScriptObjects()
448 {
449 }
450 
disconnectPlatformScriptObjects()451 void ScriptController::disconnectPlatformScriptObjects()
452 {
453 }
454 
455 #endif
456 
cleanupScriptObjectsForPlugin(void * nativeHandle)457 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
458 {
459     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
460 
461     if (it == m_rootObjects.end())
462         return;
463 
464     it->second->invalidate();
465     m_rootObjects.remove(it);
466 }
467 
clearScriptObjects()468 void ScriptController::clearScriptObjects()
469 {
470     JSLock lock(SilenceAssertionsOnly);
471 
472     RootObjectMap::const_iterator end = m_rootObjects.end();
473     for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
474         it->second->invalidate();
475 
476     m_rootObjects.clear();
477 
478     if (m_bindingRootObject) {
479         m_bindingRootObject->invalidate();
480         m_bindingRootObject = 0;
481     }
482 
483 #if ENABLE(NETSCAPE_PLUGIN_API)
484     if (m_windowScriptNPObject) {
485         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
486         // script object properly.
487         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
488         _NPN_DeallocateObject(m_windowScriptNPObject);
489         m_windowScriptNPObject = 0;
490     }
491 #endif
492 }
493 
executeScriptInWorld(DOMWrapperWorld * world,const String & script,bool forceUserGesture)494 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture)
495 {
496     ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->document()->url());
497 
498     if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
499         return ScriptValue();
500 
501     bool wasInExecuteScript = m_inExecuteScript;
502     m_inExecuteScript = true;
503 
504     ScriptValue result = evaluateInWorld(sourceCode, world);
505 
506     if (!wasInExecuteScript) {
507         m_inExecuteScript = false;
508         Document::updateStyleForAllDocuments();
509     }
510 
511     return result;
512 }
513 
514 } // namespace WebCore
515