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