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