Unreviewed, rolling out r223744, r223750, and r223751.
[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-2017 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 "CachedScriptFetcher.h"
26 #include "CommonVM.h"
27 #include "ContentSecurityPolicy.h"
28 #include "DocumentLoader.h"
29 #include "Event.h"
30 #include "Frame.h"
31 #include "FrameLoader.h"
32 #include "FrameLoaderClient.h"
33 #include "GCController.h"
34 #include "HTMLPlugInElement.h"
35 #include "InspectorInstrumentation.h"
36 #include "JSDOMBindingSecurity.h"
37 #include "JSDOMExceptionHandling.h"
38 #include "JSDOMWindow.h"
39 #include "JSDocument.h"
40 #include "JSMainThreadExecState.h"
41 #include "LoadableModuleScript.h"
42 #include "MainFrame.h"
43 #include "ModuleFetchFailureKind.h"
44 #include "ModuleFetchParameters.h"
45 #include "NP_jsobject.h"
46 #include "Page.h"
47 #include "PageConsoleClient.h"
48 #include "PageGroup.h"
49 #include "PluginViewBase.h"
50 #include "ScriptSourceCode.h"
51 #include "ScriptableDocumentParser.h"
52 #include "Settings.h"
53 #include "UserGestureIndicator.h"
54 #include "WebCoreJSClientData.h"
55 #include "npruntime_impl.h"
56 #include "runtime_root.h"
57 #include <debugger/Debugger.h>
58 #include <heap/StrongInlines.h>
59 #include <inspector/ScriptCallStack.h>
60 #include <runtime/InitializeThreading.h>
61 #include <runtime/JSFunction.h>
62 #include <runtime/JSInternalPromise.h>
63 #include <runtime/JSLock.h>
64 #include <runtime/JSModuleRecord.h>
65 #include <runtime/JSNativeStdFunction.h>
66 #include <runtime/JSScriptFetchParameters.h>
67 #include <runtime/JSScriptFetcher.h>
68 #include <wtf/MemoryPressureHandler.h>
69 #include <wtf/SetForScope.h>
70 #include <wtf/Threading.h>
71 #include <wtf/text/TextPosition.h>
72
73
74 namespace WebCore {
75 using namespace JSC;
76
77 static void collectGarbageAfterWindowProxyDestruction()
78 {
79     // Make sure to GC Extra Soon(tm) during memory pressure conditions
80     // to soften high peaks of memory usage during navigation.
81     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
82         // NOTE: We do the collection on next runloop to ensure that there's no pointer
83         //       to the window object on the stack.
84         GCController::singleton().garbageCollectOnNextRunLoop();
85     } else
86         GCController::singleton().garbageCollectSoon();
87 }
88
89 void ScriptController::initializeThreading()
90 {
91 #if !PLATFORM(IOS)
92     JSC::initializeThreading();
93     WTF::initializeMainThread();
94 #endif
95 }
96
97 ScriptController::ScriptController(Frame& frame)
98     : m_frame(frame)
99     , m_sourceURL(0)
100     , m_paused(false)
101 #if ENABLE(NETSCAPE_PLUGIN_API)
102     , m_windowScriptNPObject(0)
103 #endif
104 #if PLATFORM(COCOA)
105     , m_windowScriptObject(0)
106 #endif
107 {
108 }
109
110 ScriptController::~ScriptController()
111 {
112     disconnectPlatformScriptObjects();
113
114     if (m_cacheableBindingRootObject) {
115         JSLockHolder lock(commonVM());
116         m_cacheableBindingRootObject->invalidate();
117         m_cacheableBindingRootObject = nullptr;
118     }
119
120     // It's likely that destroying m_windowProxies will create a lot of garbage.
121     if (!m_windowProxies.isEmpty()) {
122         while (!m_windowProxies.isEmpty()) {
123             auto iter = m_windowProxies.begin();
124             iter->value->window()->setConsoleClient(nullptr);
125             destroyWindowProxy(*iter->key);
126         }
127         collectGarbageAfterWindowProxyDestruction();
128     }
129 }
130
131 void ScriptController::destroyWindowProxy(DOMWrapperWorld& world)
132 {
133     ASSERT(m_windowProxies.contains(&world));
134     m_windowProxies.remove(&world);
135     world.didDestroyWindowProxy(this);
136 }
137
138 JSDOMWindowProxy& ScriptController::createWindowProxy(DOMWrapperWorld& world)
139 {
140     ASSERT(!m_windowProxies.contains(&world));
141
142     VM& vm = world.vm();
143
144     auto* structure = JSDOMWindowProxy::createStructure(vm, jsNull());
145     Strong<JSDOMWindowProxy> windowProxy(vm, JSDOMWindowProxy::create(vm, m_frame.document()->domWindow(), structure, world));
146     Strong<JSDOMWindowProxy> windowProxy2(windowProxy);
147     m_windowProxies.add(&world, windowProxy);
148     world.didCreateWindowProxy(this);
149     return *windowProxy.get();
150 }
151
152 JSValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld& world, ExceptionDetails* exceptionDetails)
153 {
154     JSLockHolder lock(world.vm());
155
156     const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
157     String sourceURL = jsSourceCode.provider()->url();
158
159     // evaluate code. Returns the JS return value or 0
160     // if there was none, an error occurred or the type couldn't be converted.
161
162     // inlineCode is true for <a href="javascript:doSomething()">
163     // and false for <script>doSomething()</script>. Check if it has the
164     // expected value in all cases.
165     // See smart window.open policy for where this is used.
166     auto& proxy = *windowProxy(world);
167     auto& exec = *proxy.window()->globalExec();
168     const String* savedSourceURL = m_sourceURL;
169     m_sourceURL = &sourceURL;
170
171     Ref<Frame> protector(m_frame);
172
173     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
174
175     NakedPtr<JSC::Exception> evaluationException;
176     JSValue returnValue = JSMainThreadExecState::profiledEvaluate(&exec, JSC::ProfilingReason::Other, jsSourceCode, &proxy, evaluationException);
177
178     InspectorInstrumentation::didEvaluateScript(cookie, m_frame);
179
180     if (evaluationException) {
181         reportException(&exec, evaluationException, sourceCode.cachedScript(), exceptionDetails);
182         m_sourceURL = savedSourceURL;
183         return { };
184     }
185
186     m_sourceURL = savedSourceURL;
187     return returnValue;
188 }
189
190 JSValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails)
191 {
192     return evaluateInWorld(sourceCode, mainThreadNormalWorld(), exceptionDetails);
193 }
194
195 void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters, DOMWrapperWorld& world)
196 {
197     JSLockHolder lock(world.vm());
198
199     auto& proxy = *windowProxy(world);
200     auto& state = *proxy.window()->globalExec();
201
202     auto& promise = JSMainThreadExecState::loadModule(state, moduleName, JSC::JSScriptFetchParameters::create(state.vm(), WTFMove(topLevelFetchParameters)), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript }));
203     setupModuleScriptHandlers(moduleScript, promise, world);
204 }
205
206 void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters)
207 {
208     loadModuleScriptInWorld(moduleScript, moduleName, WTFMove(topLevelFetchParameters), mainThreadNormalWorld());
209 }
210
211 void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode, DOMWrapperWorld& world)
212 {
213     JSLockHolder lock(world.vm());
214
215     auto& proxy = *windowProxy(world);
216     auto& state = *proxy.window()->globalExec();
217
218     auto& promise = JSMainThreadExecState::loadModule(state, sourceCode.jsSourceCode(), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript }));
219     setupModuleScriptHandlers(moduleScript, promise, world);
220 }
221
222 void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode)
223 {
224     loadModuleScriptInWorld(moduleScript, sourceCode, mainThreadNormalWorld());
225 }
226
227 JSC::JSValue ScriptController::linkAndEvaluateModuleScriptInWorld(LoadableModuleScript& moduleScript, DOMWrapperWorld& world)
228 {
229     JSLockHolder lock(world.vm());
230
231     auto& proxy = *windowProxy(world);
232     auto& state = *proxy.window()->globalExec();
233
234     // FIXME: Preventing Frame from being destroyed is essentially unnecessary.
235     // https://bugs.webkit.org/show_bug.cgi?id=164763
236     Ref<Frame> protector(m_frame);
237
238     NakedPtr<JSC::Exception> evaluationException;
239     auto returnValue = JSMainThreadExecState::linkAndEvaluateModule(state, Identifier::fromUid(&state.vm(), moduleScript.moduleKey()), jsUndefined(), evaluationException);
240     if (evaluationException) {
241         // FIXME: Give a chance to dump the stack trace if the "crossorigin" attribute allows.
242         // https://bugs.webkit.org/show_bug.cgi?id=164539
243         reportException(&state, evaluationException, nullptr);
244         return jsUndefined();
245     }
246     return returnValue;
247 }
248
249 JSC::JSValue ScriptController::linkAndEvaluateModuleScript(LoadableModuleScript& moduleScript)
250 {
251     return linkAndEvaluateModuleScriptInWorld(moduleScript, mainThreadNormalWorld());
252 }
253
254 JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord, DOMWrapperWorld& world)
255 {
256     JSLockHolder lock(world.vm());
257
258     const auto& jsSourceCode = moduleRecord.sourceCode();
259
260     auto& proxy = *windowProxy(world);
261     auto& state = *proxy.window()->globalExec();
262     SetForScope<const String*> sourceURLScope(m_sourceURL, &sourceURL.string());
263
264     Ref<Frame> protector(m_frame);
265
266     auto cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, jsSourceCode.firstLine().oneBasedInt());
267
268     auto returnValue = moduleRecord.evaluate(&state);
269     InspectorInstrumentation::didEvaluateScript(cookie, m_frame);
270
271     return returnValue;
272 }
273
274 JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord)
275 {
276     return evaluateModule(sourceURL, moduleRecord, mainThreadNormalWorld());
277 }
278
279 Ref<DOMWrapperWorld> ScriptController::createWorld()
280 {
281     return DOMWrapperWorld::create(commonVM());
282 }
283
284 Vector<JSC::Strong<JSDOMWindowProxy>> ScriptController::windowProxies()
285 {
286     return copyToVector(m_windowProxies.values());
287 }
288
289 void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds)
290 {
291     static_cast<JSVMClientData*>(commonVM().clientData)->getAllWorlds(worlds);
292 }
293
294 void ScriptController::clearWindowProxiesNotMatchingDOMWindow(DOMWindow* newDOMWindow, bool goingIntoPageCache)
295 {
296     if (m_windowProxies.isEmpty())
297         return;
298
299     JSLockHolder lock(commonVM());
300
301     for (auto& windowProxy : windowProxies()) {
302         if (&windowProxy->window()->wrapped() == newDOMWindow)
303             continue;
304
305         // Clear the debugger and console from the current window before setting the new window.
306         attachDebugger(windowProxy.get(), nullptr);
307         windowProxy->window()->setConsoleClient(nullptr);
308         windowProxy->window()->willRemoveFromWindowProxy();
309     }
310
311     // It's likely that resetting our windows created a lot of garbage, unless
312     // it went in a back/forward cache.
313     if (!goingIntoPageCache)
314         collectGarbageAfterWindowProxyDestruction();
315 }
316
317 void ScriptController::setDOMWindowForWindowProxy(DOMWindow* newDOMWindow)
318 {
319     if (m_windowProxies.isEmpty())
320         return;
321     
322     JSLockHolder lock(commonVM());
323     
324     for (auto& windowProxy : windowProxies()) {
325         if (&windowProxy->window()->wrapped() == newDOMWindow)
326             continue;
327         
328         windowProxy->setWindow(newDOMWindow);
329         
330         // An m_cacheableBindingRootObject persists between page navigations
331         // so needs to know about the new JSDOMWindow.
332         if (m_cacheableBindingRootObject)
333             m_cacheableBindingRootObject->updateGlobalObject(windowProxy->window());
334
335         if (Page* page = m_frame.page()) {
336             attachDebugger(windowProxy.get(), page->debugger());
337             windowProxy->window()->setProfileGroup(page->group().identifier());
338             windowProxy->window()->setConsoleClient(&page->console());
339         }
340     }
341 }
342
343 JSDOMWindowProxy* ScriptController::initScript(DOMWrapperWorld& world)
344 {
345     ASSERT(!m_windowProxies.contains(&world));
346
347     JSLockHolder lock(world.vm());
348
349     auto& windowProxy = createWindowProxy(world);
350
351     windowProxy.window()->updateDocument();
352
353     if (Document* document = m_frame.document())
354         document->contentSecurityPolicy()->didCreateWindowProxy(windowProxy);
355
356     if (Page* page = m_frame.page()) {
357         attachDebugger(&windowProxy, page->debugger());
358         windowProxy.window()->setProfileGroup(page->group().identifier());
359         windowProxy.window()->setConsoleClient(&page->console());
360     }
361
362     m_frame.loader().dispatchDidClearWindowObjectInWorld(world);
363
364     return &windowProxy;
365 }
366
367 static Identifier jsValueToModuleKey(ExecState* exec, JSValue value)
368 {
369     if (value.isSymbol())
370         return Identifier::fromUid(jsCast<Symbol*>(value)->privateName());
371     ASSERT(value.isString());
372     return asString(value)->toIdentifier(exec);
373 }
374
375 void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScriptRef, JSInternalPromise& promise, DOMWrapperWorld& world)
376 {
377     auto& proxy = *windowProxy(world);
378     auto& state = *proxy.window()->globalExec();
379
380     // It is not guaranteed that either fulfillHandler or rejectHandler is eventually called.
381     // For example, if the page load is canceled, the DeferredPromise used in the module loader pipeline will stop executing JS code.
382     // Thus the promise returned from this function could remain unresolved.
383
384     RefPtr<LoadableModuleScript> moduleScript(&moduleScriptRef);
385
386     auto& fulfillHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) {
387         Identifier moduleKey = jsValueToModuleKey(exec, exec->argument(0));
388         moduleScript->notifyLoadCompleted(*moduleKey.impl());
389         return JSValue::encode(jsUndefined());
390     });
391
392     auto& rejectHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) {
393         VM& vm = exec->vm();
394         JSValue errorValue = exec->argument(0);
395         if (errorValue.isObject()) {
396             auto* object = JSC::asObject(errorValue);
397             if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
398                 // This is host propagated error in the module loader pipeline.
399                 switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) {
400                 case ModuleFetchFailureKind::WasErrored:
401                     moduleScript->notifyLoadFailed(LoadableScript::Error {
402                         LoadableScript::ErrorType::CachedScript,
403                         std::nullopt
404                     });
405                     break;
406                 case ModuleFetchFailureKind::WasCanceled:
407                     moduleScript->notifyLoadWasCanceled();
408                     break;
409                 }
410                 return JSValue::encode(jsUndefined());
411             }
412         }
413
414         auto scope = DECLARE_CATCH_SCOPE(vm);
415         moduleScript->notifyLoadFailed(LoadableScript::Error {
416             LoadableScript::ErrorType::CachedScript,
417             LoadableScript::ConsoleMessage {
418                 MessageSource::JS,
419                 MessageLevel::Error,
420                 retrieveErrorMessage(*exec, vm, errorValue, scope),
421             }
422         });
423         return JSValue::encode(jsUndefined());
424     });
425
426     promise.then(&state, &fulfillHandler, &rejectHandler);
427 }
428
429 TextPosition ScriptController::eventHandlerPosition() const
430 {
431     // FIXME: If we are not currently parsing, we should use our current location
432     // in JavaScript, to cover cases like "element.setAttribute('click', ...)".
433
434     // FIXME: This location maps to the end of the HTML tag, and not to the
435     // exact column number belonging to the event handler attribute.
436     auto* parser = m_frame.document()->scriptableDocumentParser();
437     if (parser)
438         return parser->textPosition();
439     return TextPosition();
440 }
441
442 void ScriptController::enableEval()
443 {
444     auto* windowProxy = existingWindowProxy(mainThreadNormalWorld());
445     if (!windowProxy)
446         return;
447     windowProxy->window()->setEvalEnabled(true);
448 }
449
450 void ScriptController::enableWebAssembly()
451 {
452     auto* windowProxy = existingWindowProxy(mainThreadNormalWorld());
453     if (!windowProxy)
454         return;
455     windowProxy->window()->setWebAssemblyEnabled(true);
456 }
457
458 void ScriptController::disableEval(const String& errorMessage)
459 {
460     auto* windowProxy = existingWindowProxy(mainThreadNormalWorld());
461     if (!windowProxy)
462         return;
463     windowProxy->window()->setEvalEnabled(false, errorMessage);
464 }
465
466 void ScriptController::disableWebAssembly(const String& errorMessage)
467 {
468     auto* windowProxy = existingWindowProxy(mainThreadNormalWorld());
469     if (!windowProxy)
470         return;
471     windowProxy->window()->setWebAssemblyEnabled(false, errorMessage);
472 }
473
474 bool ScriptController::processingUserGesture()
475 {
476     return UserGestureIndicator::processingUserGesture();
477 }
478
479 bool ScriptController::processingUserGestureForMedia()
480 {
481     return UserGestureIndicator::processingUserGestureForMedia();
482 }
483
484 bool ScriptController::canAccessFromCurrentOrigin(Frame* frame)
485 {
486     auto* state = JSMainThreadExecState::currentState();
487
488     // If the current state is null we're in a call path where the DOM security check doesn't apply (eg. parser).
489     if (!state)
490         return true;
491
492     return BindingSecurity::shouldAllowAccessToFrame(state, frame);
493 }
494
495 void ScriptController::attachDebugger(JSC::Debugger* debugger)
496 {
497     for (auto& windowProxy : windowProxies())
498         attachDebugger(windowProxy.get(), debugger);
499 }
500
501 void ScriptController::attachDebugger(JSDOMWindowProxy* proxy, JSC::Debugger* debugger)
502 {
503     if (!proxy)
504         return;
505
506     auto* globalObject = proxy->window();
507     JSLockHolder lock(globalObject->vm());
508
509     if (debugger)
510         debugger->attach(globalObject);
511     else if (auto* currentDebugger = globalObject->debugger())
512         currentDebugger->detach(globalObject, JSC::Debugger::TerminatingDebuggingSession);
513 }
514
515 void ScriptController::updateDocument()
516 {
517     for (auto& windowProxy : windowProxies()) {
518         JSLockHolder lock(windowProxy->world().vm());
519         windowProxy->window()->updateDocument();
520     }
521 }
522
523 Bindings::RootObject* ScriptController::cacheableBindingRootObject()
524 {
525     if (!canExecuteScripts(NotAboutToExecuteScript))
526         return nullptr;
527
528     if (!m_cacheableBindingRootObject) {
529         JSLockHolder lock(commonVM());
530         m_cacheableBindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld()));
531     }
532     return m_cacheableBindingRootObject.get();
533 }
534
535 Bindings::RootObject* ScriptController::bindingRootObject()
536 {
537     if (!canExecuteScripts(NotAboutToExecuteScript))
538         return nullptr;
539
540     if (!m_bindingRootObject) {
541         JSLockHolder lock(commonVM());
542         m_bindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld()));
543     }
544     return m_bindingRootObject.get();
545 }
546
547 Ref<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
548 {
549     auto it = m_rootObjects.find(nativeHandle);
550     if (it != m_rootObjects.end())
551         return it->value.copyRef();
552
553     auto rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
554
555     m_rootObjects.set(nativeHandle, rootObject.copyRef());
556     return rootObject;
557 }
558
559 void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*>>& result)
560 {
561     for (auto& windowProxy : m_windowProxies.values()) {
562         auto* exec = windowProxy->window()->globalExec();
563         auto* origin = &windowProxy->window()->wrapped().document()->securityOrigin();
564         result.append(std::make_pair(exec, origin));
565     }
566 }
567
568 #if ENABLE(NETSCAPE_PLUGIN_API)
569 NPObject* ScriptController::windowScriptNPObject()
570 {
571     if (!m_windowScriptNPObject) {
572         JSLockHolder lock(commonVM());
573         if (canExecuteScripts(NotAboutToExecuteScript)) {
574             // JavaScript is enabled, so there is a JavaScript window object.
575             // Return an NPObject bound to the window object.
576             auto* window = windowProxy(pluginWorld())->window();
577             ASSERT(window);
578             Bindings::RootObject* root = bindingRootObject();
579             m_windowScriptNPObject = _NPN_CreateScriptObject(0, window, root);
580         } else {
581             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
582             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
583             m_windowScriptNPObject = _NPN_CreateNoScriptObject();
584         }
585     }
586
587     return m_windowScriptNPObject;
588 }
589 #endif
590
591 #if !PLATFORM(COCOA)
592 RefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget)
593 {
594     if (!is<PluginViewBase>(*widget))
595         return nullptr;
596
597     return downcast<PluginViewBase>(*widget).bindingInstance();
598 }
599 #endif
600
601 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
602 {
603     // Can't create JSObjects when JavaScript is disabled
604     if (!canExecuteScripts(NotAboutToExecuteScript))
605         return nullptr;
606
607     JSLockHolder lock(commonVM());
608
609     // Create a JSObject bound to this element
610     auto* globalObj = globalObject(pluginWorld());
611     // FIXME: is normal okay? - used for NP plugins?
612     JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
613     if (!jsElementValue || !jsElementValue.isObject())
614         return nullptr;
615     
616     return jsElementValue.getObject();
617 }
618
619 #if !PLATFORM(COCOA)
620
621 void ScriptController::updatePlatformScriptObjects()
622 {
623 }
624
625 void ScriptController::disconnectPlatformScriptObjects()
626 {
627 }
628
629 #endif
630
631 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
632 {
633     auto it = m_rootObjects.find(nativeHandle);
634     if (it == m_rootObjects.end())
635         return;
636
637     it->value->invalidate();
638     m_rootObjects.remove(it);
639 }
640
641 void ScriptController::clearScriptObjects()
642 {
643     JSLockHolder lock(commonVM());
644
645     for (auto& rootObject : m_rootObjects.values())
646         rootObject->invalidate();
647
648     m_rootObjects.clear();
649
650     if (m_bindingRootObject) {
651         m_bindingRootObject->invalidate();
652         m_bindingRootObject = nullptr;
653     }
654
655 #if ENABLE(NETSCAPE_PLUGIN_API)
656     if (m_windowScriptNPObject) {
657         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
658         // script object properly.
659         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
660         _NPN_DeallocateObject(m_windowScriptNPObject);
661         m_windowScriptNPObject = nullptr;
662     }
663 #endif
664 }
665
666 JSValue ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
667 {
668     UserGestureIndicator gestureIndicator(forceUserGesture ? std::optional<ProcessingUserGestureState>(ProcessingUserGesture) : std::nullopt);
669     ScriptSourceCode sourceCode(script, m_frame.document()->url(), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset()));
670
671     if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
672         return { };
673
674     return evaluateInWorld(sourceCode, world);
675 }
676
677 bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason)
678 {
679     if (m_frame.document() && m_frame.document()->isSandboxed(SandboxScripts)) {
680         // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
681         if (reason == AboutToExecuteScript)
682             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.");
683         return false;
684     }
685
686     if (!m_frame.page())
687         return false;
688
689     return m_frame.loader().client().allowScript(m_frame.settings().isScriptEnabled());
690 }
691
692 JSValue ScriptController::executeScript(const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails)
693 {
694     UserGestureIndicator gestureIndicator(forceUserGesture ? std::optional<ProcessingUserGestureState>(ProcessingUserGesture) : std::nullopt);
695     return executeScript(ScriptSourceCode(script, m_frame.document()->url(), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset())), exceptionDetails);
696 }
697
698 JSValue ScriptController::executeScript(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails)
699 {
700     if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
701         return { }; // FIXME: Would jsNull be better?
702
703     // FIXME: Preventing Frame from being destroyed is essentially unnecessary.
704     // https://bugs.webkit.org/show_bug.cgi?id=164763
705     Ref<Frame> protector(m_frame); // Script execution can destroy the frame, and thus the ScriptController.
706
707     return evaluate(sourceCode, exceptionDetails);
708 }
709
710 bool ScriptController::executeIfJavaScriptURL(const URL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL)
711 {
712     if (!protocolIsJavaScript(url))
713         return false;
714
715     if (!m_frame.page() || !m_frame.document()->contentSecurityPolicy()->allowJavaScriptURLs(m_frame.document()->url(), eventHandlerPosition().m_line))
716         return true;
717
718     // We need to hold onto the Frame here because executing script can
719     // destroy the frame.
720     Ref<Frame> protector(m_frame);
721     RefPtr<Document> ownerDocument(m_frame.document());
722
723     const int javascriptSchemeLength = sizeof("javascript:") - 1;
724
725     String decodedURL = decodeURLEscapeSequences(url.string());
726     auto result = executeScript(decodedURL.substring(javascriptSchemeLength));
727
728     // If executing script caused this frame to be removed from the page, we
729     // don't want to try to replace its document!
730     if (!m_frame.page())
731         return true;
732
733     String scriptResult;
734     if (!result || !result.getString(windowProxy(mainThreadNormalWorld())->window()->globalExec(), scriptResult))
735         return true;
736
737     // FIXME: We should always replace the document, but doing so
738     //        synchronously can cause crashes:
739     //        http://bugs.webkit.org/show_bug.cgi?id=16782
740     if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) {
741         // We're still in a frame, so there should be a DocumentLoader.
742         ASSERT(m_frame.document()->loader());
743         
744         // DocumentWriter::replaceDocument can cause the DocumentLoader to get deref'ed and possible destroyed,
745         // so protect it with a RefPtr.
746         if (RefPtr<DocumentLoader> loader = m_frame.document()->loader())
747             loader->writer().replaceDocument(scriptResult, ownerDocument.get());
748     }
749     return true;
750 }
751
752 } // namespace WebCore