• 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 "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