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
50using namespace JSC;
51using namespace std;
52
53namespace WebCore {
54
55void ScriptController::initializeThreading()
56{
57    JSC::initializeThreading();
58    WTF::initializeMainThread();
59}
60
61ScriptController::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
84ScriptController::~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
101void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
102{
103    ASSERT(m_windowShells.contains(world));
104    m_windowShells.remove(world);
105    world->didDestroyWindowShell(this);
106}
107
108JSDOMWindowShell* 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
118ScriptValue 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
163ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
164{
165    return evaluateInWorld(sourceCode, mainThreadNormalWorld());
166}
167
168PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
169{
170    return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData());
171}
172
173void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
174{
175    static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
176}
177
178void 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
211JSDOMWindowShell* 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
231int 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
240bool 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.
267bool 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
277bool 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
308bool 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
318void 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
324void 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
336void 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
346void ScriptController::updateSecurityOrigin()
347{
348    // Our bindings do not do anything in this case.
349}
350
351Bindings::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
363Bindings::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
375PassRefPtr<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)
388void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
389{
390}
391#endif
392
393#if ENABLE(NETSCAPE_PLUGIN_API)
394
395NPObject* 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
416NPObject* 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
428JSObject* 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
447void ScriptController::updatePlatformScriptObjects()
448{
449}
450
451void ScriptController::disconnectPlatformScriptObjects()
452{
453}
454
455#endif
456
457void 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
468void 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
494ScriptValue 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