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