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