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 "BridgeJSC.h"
25#include "ContentSecurityPolicy.h"
26#include "Event.h"
27#include "EventNames.h"
28#include "Frame.h"
29#include "FrameLoaderClient.h"
30#include "GCController.h"
31#include "HTMLPlugInElement.h"
32#include "InspectorInstrumentation.h"
33#include "JSDOMWindow.h"
34#include "JSDocument.h"
35#include "JSMainThreadExecState.h"
36#include "NP_jsobject.h"
37#include "Page.h"
38#include "PageGroup.h"
39#include "PluginView.h"
40#include "ScriptCallStack.h"
41#include "ScriptSourceCode.h"
42#include "ScriptValue.h"
43#include "ScriptableDocumentParser.h"
44#include "Settings.h"
45#include "StorageNamespace.h"
46#include "UserGestureIndicator.h"
47#include "WebCoreJSClientData.h"
48#include "npruntime_impl.h"
49#include "runtime_root.h"
50#include <debugger/Debugger.h>
51#include <heap/StrongInlines.h>
52#include <runtime/InitializeThreading.h>
53#include <runtime/JSLock.h>
54#include <wtf/text/TextPosition.h>
55#include <wtf/Threading.h>
56
57using namespace JSC;
58using namespace std;
59
60namespace WebCore {
61
62void ScriptController::initializeThreading()
63{
64    JSC::initializeThreading();
65    WTF::initializeMainThread();
66}
67
68ScriptController::ScriptController(Frame* frame)
69    : m_frame(frame)
70    , m_sourceURL(0)
71    , m_paused(false)
72#if ENABLE(NETSCAPE_PLUGIN_API)
73    , m_windowScriptNPObject(0)
74#endif
75#if PLATFORM(MAC)
76    , m_windowScriptObject(0)
77#endif
78{
79}
80
81ScriptController::~ScriptController()
82{
83    disconnectPlatformScriptObjects();
84
85    if (m_cacheableBindingRootObject) {
86        JSLockHolder lock(JSDOMWindowBase::commonVM());
87        m_cacheableBindingRootObject->invalidate();
88        m_cacheableBindingRootObject = 0;
89    }
90
91    // It's likely that destroying m_windowShells will create a lot of garbage.
92    if (!m_windowShells.isEmpty()) {
93        while (!m_windowShells.isEmpty())
94            destroyWindowShell(m_windowShells.begin()->key.get());
95        gcController().garbageCollectSoon();
96    }
97}
98
99void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
100{
101    ASSERT(m_windowShells.contains(world));
102    m_windowShells.remove(world);
103    world->didDestroyWindowShell(this);
104}
105
106JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world)
107{
108    ASSERT(!m_windowShells.contains(world));
109    Structure* structure = JSDOMWindowShell::createStructure(*world->vm(), jsNull());
110    Strong<JSDOMWindowShell> windowShell(*world->vm(), JSDOMWindowShell::create(m_frame->document()->domWindow(), structure, world));
111    Strong<JSDOMWindowShell> windowShell2(windowShell);
112    m_windowShells.add(world, windowShell);
113    world->didCreateWindowShell(this);
114    return windowShell.get();
115}
116
117ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
118{
119    const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
120    String sourceURL = jsSourceCode.provider()->url();
121
122    // evaluate code. Returns the JS return value or 0
123    // if there was none, an error occurred or the type couldn't be converted.
124
125    // inlineCode is true for <a href="javascript:doSomething()">
126    // and false for <script>doSomething()</script>. Check if it has the
127    // expected value in all cases.
128    // See smart window.open policy for where this is used.
129    JSDOMWindowShell* shell = windowShell(world);
130    ExecState* exec = shell->window()->globalExec();
131    const String* savedSourceURL = m_sourceURL;
132    m_sourceURL = &sourceURL;
133
134    JSLockHolder lock(exec);
135
136    RefPtr<Frame> protect = m_frame;
137
138    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
139
140    JSValue evaluationException;
141
142    JSValue returnValue = JSMainThreadExecState::evaluate(exec, jsSourceCode, shell, &evaluationException);
143
144    InspectorInstrumentation::didEvaluateScript(cookie);
145
146    if (evaluationException) {
147        reportException(exec, evaluationException, sourceCode.cachedScript());
148        m_sourceURL = savedSourceURL;
149        return ScriptValue();
150    }
151
152    m_sourceURL = savedSourceURL;
153    return ScriptValue(exec->vm(), returnValue);
154}
155
156ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
157{
158    return evaluateInWorld(sourceCode, mainThreadNormalWorld());
159}
160
161PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
162{
163    return DOMWrapperWorld::create(JSDOMWindow::commonVM());
164}
165
166void ScriptController::getAllWorlds(Vector<RefPtr<DOMWrapperWorld> >& worlds)
167{
168    static_cast<WebCoreJSClientData*>(JSDOMWindow::commonVM()->clientData)->getAllWorlds(worlds);
169}
170
171void ScriptController::clearWindowShell(DOMWindow* newDOMWindow, bool goingIntoPageCache)
172{
173    if (m_windowShells.isEmpty())
174        return;
175
176    JSLockHolder lock(JSDOMWindowBase::commonVM());
177
178    for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
179        JSDOMWindowShell* windowShell = iter->value.get();
180
181        if (windowShell->window()->impl() == newDOMWindow)
182            continue;
183
184        // Clear the debugger from the current window before setting the new window.
185        attachDebugger(windowShell, 0);
186
187        windowShell->window()->willRemoveFromWindowShell();
188        windowShell->setWindow(newDOMWindow);
189
190        // An m_cacheableBindingRootObject persists between page navigations
191        // so needs to know about the new JSDOMWindow.
192        if (m_cacheableBindingRootObject)
193            m_cacheableBindingRootObject->updateGlobalObject(windowShell->window());
194
195        if (Page* page = m_frame->page()) {
196            attachDebugger(windowShell, page->debugger());
197            windowShell->window()->setProfileGroup(page->group().identifier());
198        }
199    }
200
201    // It's likely that resetting our windows created a lot of garbage, unless
202    // it went in a back/forward cache.
203    if (!goingIntoPageCache)
204        gcController().garbageCollectSoon();
205}
206
207JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
208{
209    ASSERT(!m_windowShells.contains(world));
210
211    JSLockHolder lock(world->vm());
212
213    JSDOMWindowShell* windowShell = createWindowShell(world);
214
215    windowShell->window()->updateDocument();
216
217    if (m_frame->document())
218        windowShell->window()->setEvalEnabled(m_frame->document()->contentSecurityPolicy()->allowEval(0, ContentSecurityPolicy::SuppressReport), m_frame->document()->contentSecurityPolicy()->evalDisabledErrorMessage());
219
220    if (Page* page = m_frame->page()) {
221        attachDebugger(windowShell, page->debugger());
222        windowShell->window()->setProfileGroup(page->group().identifier());
223    }
224
225    m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
226
227    return windowShell;
228}
229
230TextPosition ScriptController::eventHandlerPosition() const
231{
232    ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
233    if (parser)
234        return parser->textPosition();
235    return TextPosition::minimumPosition();
236}
237
238void ScriptController::enableEval()
239{
240    JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld());
241    if (!windowShell)
242        return;
243    windowShell->window()->setEvalEnabled(true);
244}
245
246void ScriptController::disableEval(const String& errorMessage)
247{
248    JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld());
249    if (!windowShell)
250        return;
251    windowShell->window()->setEvalEnabled(false, errorMessage);
252}
253
254bool ScriptController::processingUserGesture()
255{
256    return UserGestureIndicator::processingUserGesture();
257}
258
259bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
260{
261    ExecState* exec = JSMainThreadExecState::currentState();
262    if (exec)
263        return shouldAllowAccessToFrame(exec, frame);
264    // If the current state is 0 we're in a call path where the DOM security
265    // check doesn't apply (eg. parser).
266    return true;
267}
268
269void ScriptController::attachDebugger(JSC::Debugger* debugger)
270{
271    for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
272        attachDebugger(iter->value.get(), debugger);
273}
274
275void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
276{
277    if (!shell)
278        return;
279
280    JSDOMWindow* globalObject = shell->window();
281    if (debugger)
282        debugger->attach(globalObject);
283    else if (JSC::Debugger* currentDebugger = globalObject->debugger())
284        currentDebugger->detach(globalObject);
285}
286
287void ScriptController::updateDocument()
288{
289    for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
290        JSLockHolder lock(iter->key->vm());
291        iter->value->window()->updateDocument();
292    }
293}
294
295Bindings::RootObject* ScriptController::cacheableBindingRootObject()
296{
297    if (!canExecuteScripts(NotAboutToExecuteScript))
298        return 0;
299
300    if (!m_cacheableBindingRootObject) {
301        JSLockHolder lock(JSDOMWindowBase::commonVM());
302        m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
303    }
304    return m_cacheableBindingRootObject.get();
305}
306
307Bindings::RootObject* ScriptController::bindingRootObject()
308{
309    if (!canExecuteScripts(NotAboutToExecuteScript))
310        return 0;
311
312    if (!m_bindingRootObject) {
313        JSLockHolder lock(JSDOMWindowBase::commonVM());
314        m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
315    }
316    return m_bindingRootObject.get();
317}
318
319PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
320{
321    RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
322    if (it != m_rootObjects.end())
323        return it->value;
324
325    RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
326
327    m_rootObjects.set(nativeHandle, rootObject);
328    return rootObject.release();
329}
330
331#if ENABLE(INSPECTOR)
332void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
333{
334}
335
336void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*> >& result)
337{
338    for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
339        JSC::ExecState* exec = iter->value->window()->globalExec();
340        SecurityOrigin* origin = iter->value->window()->impl()->document()->securityOrigin();
341        result.append(std::pair<ScriptState*, SecurityOrigin*>(exec, origin));
342    }
343}
344
345#endif
346
347#if ENABLE(NETSCAPE_PLUGIN_API)
348
349NPObject* ScriptController::windowScriptNPObject()
350{
351    if (!m_windowScriptNPObject) {
352        if (canExecuteScripts(NotAboutToExecuteScript)) {
353            // JavaScript is enabled, so there is a JavaScript window object.
354            // Return an NPObject bound to the window object.
355            JSDOMWindow* win = windowShell(pluginWorld())->window();
356            ASSERT(win);
357            JSC::JSLockHolder lock(win->globalExec());
358            Bindings::RootObject* root = bindingRootObject();
359            m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
360        } else {
361            // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
362            // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
363            m_windowScriptNPObject = _NPN_CreateNoScriptObject();
364        }
365    }
366
367    return m_windowScriptNPObject;
368}
369
370NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
371{
372    JSObject* object = jsObjectForPluginElement(plugin);
373    if (!object)
374        return _NPN_CreateNoScriptObject();
375
376    // Wrap the JSObject in an NPObject
377    return _NPN_CreateScriptObject(0, object, bindingRootObject());
378}
379
380#endif
381
382#if !PLATFORM(MAC) && !PLATFORM(QT)
383PassRefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget)
384{
385    if (!widget->isPluginView())
386        return 0;
387
388    return toPluginView(widget)->bindingInstance();
389}
390#endif
391
392JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
393{
394    // Can't create JSObjects when JavaScript is disabled
395    if (!canExecuteScripts(NotAboutToExecuteScript))
396        return 0;
397
398    // Create a JSObject bound to this element
399    JSDOMWindow* globalObj = globalObject(pluginWorld());
400    JSLockHolder lock(globalObj->globalExec());
401    // FIXME: is normal okay? - used for NP plugins?
402    JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
403    if (!jsElementValue || !jsElementValue.isObject())
404        return 0;
405
406    return jsElementValue.getObject();
407}
408
409#if !PLATFORM(MAC)
410
411void ScriptController::updatePlatformScriptObjects()
412{
413}
414
415void ScriptController::disconnectPlatformScriptObjects()
416{
417}
418
419#endif
420
421void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
422{
423    RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
424
425    if (it == m_rootObjects.end())
426        return;
427
428    it->value->invalidate();
429    m_rootObjects.remove(it);
430}
431
432void ScriptController::clearScriptObjects()
433{
434    JSLockHolder lock(JSDOMWindowBase::commonVM());
435
436    RootObjectMap::const_iterator end = m_rootObjects.end();
437    for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
438        it->value->invalidate();
439
440    m_rootObjects.clear();
441
442    if (m_bindingRootObject) {
443        m_bindingRootObject->invalidate();
444        m_bindingRootObject = 0;
445    }
446
447#if ENABLE(NETSCAPE_PLUGIN_API)
448    if (m_windowScriptNPObject) {
449        // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
450        // script object properly.
451        // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
452        _NPN_DeallocateObject(m_windowScriptNPObject);
453        m_windowScriptNPObject = 0;
454    }
455#endif
456}
457
458ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture)
459{
460    UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingNewUserGesture : PossiblyProcessingUserGesture);
461    ScriptSourceCode sourceCode(script, m_frame->document()->url());
462
463    if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
464        return ScriptValue();
465
466    return evaluateInWorld(sourceCode, world);
467}
468
469bool ScriptController::shouldBypassMainWorldContentSecurityPolicy()
470{
471    CallFrame* callFrame = JSDOMWindow::commonVM()->topCallFrame;
472    if (!callFrame || callFrame == CallFrame::noCaller())
473        return false;
474    DOMWrapperWorld* domWrapperWorld = currentWorld(callFrame);
475    if (domWrapperWorld->isNormal())
476        return false;
477    return true;
478}
479
480} // namespace WebCore
481