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 "DocumentLoader.h"
27#include "Event.h"
28#include "EventNames.h"
29#include "Frame.h"
30#include "FrameLoaderClient.h"
31#include "GCController.h"
32#include "HTMLPlugInElement.h"
33#include "InspectorInstrumentation.h"
34#include "JSDOMWindow.h"
35#include "JSDocument.h"
36#include "JSMainThreadExecState.h"
37#include "MainFrame.h"
38#include "NP_jsobject.h"
39#include "Page.h"
40#include "PageConsole.h"
41#include "PageGroup.h"
42#include "PluginView.h"
43#include "ScriptSourceCode.h"
44#include "ScriptableDocumentParser.h"
45#include "Settings.h"
46#include "StorageNamespace.h"
47#include "UserGestureIndicator.h"
48#include "WebCoreJSClientData.h"
49#include "npruntime_impl.h"
50#include "runtime_root.h"
51#include <bindings/ScriptValue.h>
52#include <debugger/Debugger.h>
53#include <heap/StrongInlines.h>
54#include <inspector/ScriptCallStack.h>
55#include <runtime/InitializeThreading.h>
56#include <runtime/JSLock.h>
57#include <wtf/Threading.h>
58#include <wtf/text/TextPosition.h>
59
60using namespace JSC;
61
62namespace WebCore {
63
64void ScriptController::initializeThreading()
65{
66#if !PLATFORM(IOS)
67    JSC::initializeThreading();
68    WTF::initializeMainThread();
69#endif
70}
71
72ScriptController::ScriptController(Frame& frame)
73    : m_frame(frame)
74    , m_sourceURL(0)
75    , m_paused(false)
76#if ENABLE(NETSCAPE_PLUGIN_API)
77    , m_windowScriptNPObject(0)
78#endif
79#if PLATFORM(COCOA)
80    , m_windowScriptObject(0)
81#endif
82{
83}
84
85ScriptController::~ScriptController()
86{
87    disconnectPlatformScriptObjects();
88
89    if (m_cacheableBindingRootObject) {
90        JSLockHolder lock(JSDOMWindowBase::commonVM());
91        m_cacheableBindingRootObject->invalidate();
92        m_cacheableBindingRootObject = 0;
93    }
94
95    // It's likely that destroying m_windowShells will create a lot of garbage.
96    if (!m_windowShells.isEmpty()) {
97        while (!m_windowShells.isEmpty()) {
98            ShellMap::iterator iter = m_windowShells.begin();
99            iter->value->window()->setConsoleClient(nullptr);
100            destroyWindowShell(*iter->key);
101        }
102        gcController().garbageCollectSoon();
103    }
104}
105
106void ScriptController::destroyWindowShell(DOMWrapperWorld& world)
107{
108    ASSERT(m_windowShells.contains(&world));
109    m_windowShells.remove(&world);
110    world.didDestroyWindowShell(this);
111}
112
113JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld& world)
114{
115    ASSERT(!m_windowShells.contains(&world));
116
117    VM& vm = world.vm();
118
119    Structure* structure = JSDOMWindowShell::createStructure(vm, jsNull());
120    Strong<JSDOMWindowShell> windowShell(vm, JSDOMWindowShell::create(vm, m_frame.document()->domWindow(), structure, world));
121    Strong<JSDOMWindowShell> windowShell2(windowShell);
122    m_windowShells.add(&world, windowShell);
123    world.didCreateWindowShell(this);
124    return windowShell.get();
125}
126
127Deprecated::ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld& world)
128{
129    JSLockHolder lock(world.vm());
130
131    const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
132    String sourceURL = jsSourceCode.provider()->url();
133
134    // evaluate code. Returns the JS return value or 0
135    // if there was none, an error occurred or the type couldn't be converted.
136
137    // inlineCode is true for <a href="javascript:doSomething()">
138    // and false for <script>doSomething()</script>. Check if it has the
139    // expected value in all cases.
140    // See smart window.open policy for where this is used.
141    JSDOMWindowShell* shell = windowShell(world);
142    ExecState* exec = shell->window()->globalExec();
143    const String* savedSourceURL = m_sourceURL;
144    m_sourceURL = &sourceURL;
145
146    Ref<Frame> protect(m_frame);
147
148    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(&m_frame, sourceURL, sourceCode.startLine());
149
150    JSValue evaluationException;
151
152    JSValue returnValue = JSMainThreadExecState::evaluate(exec, jsSourceCode, shell, &evaluationException);
153
154    InspectorInstrumentation::didEvaluateScript(cookie, &m_frame);
155
156    if (evaluationException) {
157        reportException(exec, evaluationException, sourceCode.cachedScript());
158        m_sourceURL = savedSourceURL;
159        return Deprecated::ScriptValue();
160    }
161
162    m_sourceURL = savedSourceURL;
163    return Deprecated::ScriptValue(exec->vm(), returnValue);
164}
165
166Deprecated::ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
167{
168    return evaluateInWorld(sourceCode, mainThreadNormalWorld());
169}
170
171PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
172{
173    return DOMWrapperWorld::create(JSDOMWindow::commonVM());
174}
175
176Vector<JSC::Strong<JSDOMWindowShell>> ScriptController::windowShells()
177{
178    Vector<JSC::Strong<JSDOMWindowShell>> windowShells;
179    copyValuesToVector(m_windowShells, windowShells);
180    return windowShells;
181}
182
183void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds)
184{
185    static_cast<WebCoreJSClientData*>(JSDOMWindow::commonVM().clientData)->getAllWorlds(worlds);
186}
187
188void ScriptController::clearWindowShell(DOMWindow* newDOMWindow, bool goingIntoPageCache)
189{
190    if (m_windowShells.isEmpty())
191        return;
192
193    JSLockHolder lock(JSDOMWindowBase::commonVM());
194
195    Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells();
196    for (size_t i = 0; i < windowShells.size(); ++i) {
197        JSDOMWindowShell* windowShell = windowShells[i].get();
198
199        if (&windowShell->window()->impl() == newDOMWindow)
200            continue;
201
202        // Clear the debugger and console from the current window before setting the new window.
203        attachDebugger(windowShell, nullptr);
204        windowShell->window()->setConsoleClient(nullptr);
205
206        // FIXME: We should clear console profiles for each frame as soon as the frame is destroyed.
207        // Instead of clearing all of them when the main frame is destroyed.
208        if (m_frame.isMainFrame()) {
209            if (Page* page = m_frame.page())
210                page->console().clearProfiles();
211        }
212
213        windowShell->window()->willRemoveFromWindowShell();
214        windowShell->setWindow(newDOMWindow);
215
216        // An m_cacheableBindingRootObject persists between page navigations
217        // so needs to know about the new JSDOMWindow.
218        if (m_cacheableBindingRootObject)
219            m_cacheableBindingRootObject->updateGlobalObject(windowShell->window());
220
221        if (Page* page = m_frame.page()) {
222            attachDebugger(windowShell, page->debugger());
223            windowShell->window()->setProfileGroup(page->group().identifier());
224            windowShell->window()->setConsoleClient(&page->console());
225        }
226    }
227
228    // It's likely that resetting our windows created a lot of garbage, unless
229    // it went in a back/forward cache.
230    if (!goingIntoPageCache)
231        gcController().garbageCollectSoon();
232}
233
234JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld& world)
235{
236    ASSERT(!m_windowShells.contains(&world));
237
238    JSLockHolder lock(world.vm());
239
240    JSDOMWindowShell* windowShell = createWindowShell(world);
241
242    windowShell->window()->updateDocument();
243
244    if (m_frame.document())
245        windowShell->window()->setEvalEnabled(m_frame.document()->contentSecurityPolicy()->allowEval(0, ContentSecurityPolicy::SuppressReport), m_frame.document()->contentSecurityPolicy()->evalDisabledErrorMessage());
246
247    if (Page* page = m_frame.page()) {
248        attachDebugger(windowShell, page->debugger());
249        windowShell->window()->setProfileGroup(page->group().identifier());
250        windowShell->window()->setConsoleClient(&page->console());
251    }
252
253    m_frame.loader().dispatchDidClearWindowObjectInWorld(world);
254
255    return windowShell;
256}
257
258TextPosition ScriptController::eventHandlerPosition() const
259{
260    ScriptableDocumentParser* parser = m_frame.document()->scriptableDocumentParser();
261    if (parser)
262        return parser->textPosition();
263    return TextPosition::minimumPosition();
264}
265
266void ScriptController::enableEval()
267{
268    JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld());
269    if (!windowShell)
270        return;
271    windowShell->window()->setEvalEnabled(true);
272}
273
274void ScriptController::disableEval(const String& errorMessage)
275{
276    JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld());
277    if (!windowShell)
278        return;
279    windowShell->window()->setEvalEnabled(false, errorMessage);
280}
281
282bool ScriptController::processingUserGesture()
283{
284    return UserGestureIndicator::processingUserGesture();
285}
286
287bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
288{
289    ExecState* exec = JSMainThreadExecState::currentState();
290    if (exec)
291        return shouldAllowAccessToFrame(exec, frame);
292    // If the current state is 0 we're in a call path where the DOM security
293    // check doesn't apply (eg. parser).
294    return true;
295}
296
297void ScriptController::attachDebugger(JSC::Debugger* debugger)
298{
299    Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells();
300    for (size_t i = 0; i < windowShells.size(); ++i)
301        attachDebugger(windowShells[i].get(), debugger);
302}
303
304void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
305{
306    if (!shell)
307        return;
308
309    JSDOMWindow* globalObject = shell->window();
310    if (debugger)
311        debugger->attach(globalObject);
312    else if (JSC::Debugger* currentDebugger = globalObject->debugger())
313        currentDebugger->detach(globalObject, JSC::Debugger::TerminatingDebuggingSession);
314}
315
316void ScriptController::updateDocument()
317{
318    Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells();
319    for (size_t i = 0; i < windowShells.size(); ++i) {
320        JSDOMWindowShell* windowShell = windowShells[i].get();
321        JSLockHolder lock(windowShell->world().vm());
322        windowShell->window()->updateDocument();
323    }
324}
325
326Bindings::RootObject* ScriptController::cacheableBindingRootObject()
327{
328    if (!canExecuteScripts(NotAboutToExecuteScript))
329        return 0;
330
331    if (!m_cacheableBindingRootObject) {
332        JSLockHolder lock(JSDOMWindowBase::commonVM());
333        m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
334    }
335    return m_cacheableBindingRootObject.get();
336}
337
338Bindings::RootObject* ScriptController::bindingRootObject()
339{
340    if (!canExecuteScripts(NotAboutToExecuteScript))
341        return 0;
342
343    if (!m_bindingRootObject) {
344        JSLockHolder lock(JSDOMWindowBase::commonVM());
345        m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
346    }
347    return m_bindingRootObject.get();
348}
349
350PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
351{
352    RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
353    if (it != m_rootObjects.end())
354        return it->value;
355
356    RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
357
358    m_rootObjects.set(nativeHandle, rootObject);
359    return rootObject.release();
360}
361
362#if ENABLE(INSPECTOR)
363void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*>>& result)
364{
365    for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
366        JSC::ExecState* exec = iter->value->window()->globalExec();
367        SecurityOrigin* origin = iter->value->window()->impl().document()->securityOrigin();
368        result.append(std::pair<JSC::ExecState*, SecurityOrigin*>(exec, origin));
369    }
370}
371#endif
372
373#if ENABLE(NETSCAPE_PLUGIN_API)
374
375NPObject* ScriptController::windowScriptNPObject()
376{
377    if (!m_windowScriptNPObject) {
378        JSLockHolder lock(JSDOMWindowBase::commonVM());
379        if (canExecuteScripts(NotAboutToExecuteScript)) {
380            // JavaScript is enabled, so there is a JavaScript window object.
381            // Return an NPObject bound to the window object.
382            JSDOMWindow* win = windowShell(pluginWorld())->window();
383            ASSERT(win);
384            Bindings::RootObject* root = bindingRootObject();
385            m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
386        } else {
387            // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
388            // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
389            m_windowScriptNPObject = _NPN_CreateNoScriptObject();
390        }
391    }
392
393    return m_windowScriptNPObject;
394}
395
396NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
397{
398    JSObject* object = jsObjectForPluginElement(plugin);
399    if (!object)
400        return _NPN_CreateNoScriptObject();
401
402    // Wrap the JSObject in an NPObject
403    return _NPN_CreateScriptObject(0, object, bindingRootObject());
404}
405
406#endif
407
408#if !PLATFORM(COCOA)
409PassRefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget)
410{
411    if (!widget->isPluginView())
412        return 0;
413
414    return toPluginView(widget)->bindingInstance();
415}
416#endif
417
418JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
419{
420    // Can't create JSObjects when JavaScript is disabled
421    if (!canExecuteScripts(NotAboutToExecuteScript))
422        return 0;
423
424    JSLockHolder lock(JSDOMWindowBase::commonVM());
425
426    // Create a JSObject bound to this element
427    JSDOMWindow* globalObj = globalObject(pluginWorld());
428    // FIXME: is normal okay? - used for NP plugins?
429    JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
430    if (!jsElementValue || !jsElementValue.isObject())
431        return 0;
432
433    return jsElementValue.getObject();
434}
435
436#if !PLATFORM(COCOA)
437
438void ScriptController::updatePlatformScriptObjects()
439{
440}
441
442void ScriptController::disconnectPlatformScriptObjects()
443{
444}
445
446#endif
447
448void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
449{
450    RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
451
452    if (it == m_rootObjects.end())
453        return;
454
455    it->value->invalidate();
456    m_rootObjects.remove(it);
457}
458
459void ScriptController::clearScriptObjects()
460{
461    JSLockHolder lock(JSDOMWindowBase::commonVM());
462
463    RootObjectMap::const_iterator end = m_rootObjects.end();
464    for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
465        it->value->invalidate();
466
467    m_rootObjects.clear();
468
469    if (m_bindingRootObject) {
470        m_bindingRootObject->invalidate();
471        m_bindingRootObject = 0;
472    }
473
474#if ENABLE(NETSCAPE_PLUGIN_API)
475    if (m_windowScriptNPObject) {
476        // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
477        // script object properly.
478        // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
479        _NPN_DeallocateObject(m_windowScriptNPObject);
480        m_windowScriptNPObject = 0;
481    }
482#endif
483}
484
485Deprecated::ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
486{
487    UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
488    ScriptSourceCode sourceCode(script, m_frame.document()->url());
489
490    if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
491        return Deprecated::ScriptValue();
492
493    return evaluateInWorld(sourceCode, world);
494}
495
496bool ScriptController::shouldBypassMainWorldContentSecurityPolicy()
497{
498    CallFrame* callFrame = JSDOMWindow::commonVM().topCallFrame;
499    if (callFrame == CallFrame::noCaller())
500        return false;
501    DOMWrapperWorld& domWrapperWorld = currentWorld(callFrame);
502    if (domWrapperWorld.isNormal())
503        return false;
504    return true;
505}
506
507bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason)
508{
509    if (m_frame.document() && m_frame.document()->isSandboxed(SandboxScripts)) {
510        // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
511        if (reason == AboutToExecuteScript)
512            m_frame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked script execution in '" + m_frame.document()->url().stringCenterEllipsizedToLength() + "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.");
513        return false;
514    }
515
516    if (!m_frame.page())
517        return false;
518
519    return m_frame.loader().client().allowScript(m_frame.settings().isScriptEnabled());
520}
521
522Deprecated::ScriptValue ScriptController::executeScript(const String& script, bool forceUserGesture)
523{
524    UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
525    return executeScript(ScriptSourceCode(script, m_frame.document()->url()));
526}
527
528Deprecated::ScriptValue ScriptController::executeScript(const ScriptSourceCode& sourceCode)
529{
530    if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
531        return Deprecated::ScriptValue();
532
533    Ref<Frame> protect(m_frame); // Script execution can destroy the frame, and thus the ScriptController.
534
535    return evaluate(sourceCode);
536}
537
538bool ScriptController::executeIfJavaScriptURL(const URL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL)
539{
540    if (!protocolIsJavaScript(url))
541        return false;
542
543    if (!m_frame.page() || !m_frame.document()->contentSecurityPolicy()->allowJavaScriptURLs(m_frame.document()->url(), eventHandlerPosition().m_line))
544        return true;
545
546    // We need to hold onto the Frame here because executing script can
547    // destroy the frame.
548    Ref<Frame> protector(m_frame);
549    RefPtr<Document> ownerDocument(m_frame.document());
550
551    const int javascriptSchemeLength = sizeof("javascript:") - 1;
552
553    String decodedURL = decodeURLEscapeSequences(url.string());
554    Deprecated::ScriptValue result = executeScript(decodedURL.substring(javascriptSchemeLength));
555
556    // If executing script caused this frame to be removed from the page, we
557    // don't want to try to replace its document!
558    if (!m_frame.page())
559        return true;
560
561    String scriptResult;
562    JSDOMWindowShell* shell = windowShell(mainThreadNormalWorld());
563    JSC::ExecState* exec = shell->window()->globalExec();
564    if (!result.getString(exec, scriptResult))
565        return true;
566
567    // FIXME: We should always replace the document, but doing so
568    //        synchronously can cause crashes:
569    //        http://bugs.webkit.org/show_bug.cgi?id=16782
570    if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) {
571        // We're still in a frame, so there should be a DocumentLoader.
572        ASSERT(m_frame.document()->loader());
573
574        // DocumentWriter::replaceDocument can cause the DocumentLoader to get deref'ed and possible destroyed,
575        // so protect it with a RefPtr.
576        if (RefPtr<DocumentLoader> loader = m_frame.document()->loader())
577            loader->writer().replaceDocument(scriptResult, ownerDocument.get());
578    }
579    return true;
580}
581
582} // namespace WebCore
583