88ef918b9a8482053db049bcda7673a7865d9358
[WebKit-https.git] / Source / WebCore / dom / ScriptExecutionContext.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2012 Google Inc. All Rights Reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27
28 #include "config.h"
29 #include "ScriptExecutionContext.h"
30
31 #include "CachedScript.h"
32 #include "CommonVM.h"
33 #include "DOMTimer.h"
34 #include "DatabaseContext.h"
35 #include "Document.h"
36 #include "ErrorEvent.h"
37 #include "JSDOMExceptionHandling.h"
38 #include "JSDOMWindow.h"
39 #include "MessagePort.h"
40 #include "NoEventDispatchAssertion.h"
41 #include "PublicURLManager.h"
42 #include "RejectedPromiseTracker.h"
43 #include "ResourceRequest.h"
44 #include "ScriptState.h"
45 #include "Settings.h"
46 #include "WorkerGlobalScope.h"
47 #include "WorkerThread.h"
48 #include <heap/StrongInlines.h>
49 #include <inspector/ScriptCallStack.h>
50 #include <runtime/Exception.h>
51 #include <runtime/JSPromise.h>
52 #include <wtf/MainThread.h>
53 #include <wtf/Ref.h>
54
55 using namespace Inspector;
56
57 namespace WebCore {
58
59 struct ScriptExecutionContext::PendingException {
60     WTF_MAKE_FAST_ALLOCATED;
61 public:
62     PendingException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, RefPtr<ScriptCallStack>&& callStack)
63         : m_errorMessage(errorMessage)
64         , m_lineNumber(lineNumber)
65         , m_columnNumber(columnNumber)
66         , m_sourceURL(sourceURL)
67         , m_callStack(WTFMove(callStack))
68     {
69     }
70     String m_errorMessage;
71     int m_lineNumber;
72     int m_columnNumber;
73     String m_sourceURL;
74     RefPtr<ScriptCallStack> m_callStack;
75 };
76
77 ScriptExecutionContext::ScriptExecutionContext()
78 {
79 }
80
81 #if ASSERT_DISABLED
82
83 inline void ScriptExecutionContext::checkConsistency() const
84 {
85 }
86
87 #else
88
89 void ScriptExecutionContext::checkConsistency() const
90 {
91     for (auto* messagePort : m_messagePorts)
92         ASSERT(messagePort->scriptExecutionContext() == this);
93
94     for (auto* destructionObserver : m_destructionObservers)
95         ASSERT(destructionObserver->scriptExecutionContext() == this);
96
97     for (auto* activeDOMObject : m_activeDOMObjects) {
98         ASSERT(activeDOMObject->scriptExecutionContext() == this);
99         activeDOMObject->assertSuspendIfNeededWasCalled();
100     }
101 }
102
103 #endif
104
105 ScriptExecutionContext::~ScriptExecutionContext()
106 {
107     checkConsistency();
108
109 #if !ASSERT_DISABLED
110     m_inScriptExecutionContextDestructor = true;
111 #endif
112
113     while (auto* destructionObserver = m_destructionObservers.takeAny())
114         destructionObserver->contextDestroyed();
115
116     for (auto* messagePort : m_messagePorts)
117         messagePort->contextDestroyed();
118
119 #if !ASSERT_DISABLED
120     m_inScriptExecutionContextDestructor = false;
121 #endif
122 }
123
124 void ScriptExecutionContext::processMessagePortMessagesSoon()
125 {
126     if (m_willProcessMessagePortMessagesSoon)
127         return;
128
129     m_willProcessMessagePortMessagesSoon = true;
130     postTask([] (ScriptExecutionContext& context) {
131         context.dispatchMessagePortEvents();
132     });
133 }
134
135 void ScriptExecutionContext::dispatchMessagePortEvents()
136 {
137     checkConsistency();
138
139     Ref<ScriptExecutionContext> protectedThis(*this);
140     ASSERT(m_willProcessMessagePortMessagesSoon);
141     m_willProcessMessagePortMessagesSoon = false;
142
143     // Make a frozen copy of the ports so we can iterate while new ones might be added or destroyed.
144     Vector<MessagePort*> possibleMessagePorts;
145     copyToVector(m_messagePorts, possibleMessagePorts);
146     for (auto* messagePort : possibleMessagePorts) {
147         // The port may be destroyed, and another one created at the same address,
148         // but this is harmless. The worst that can happen as a result is that
149         // dispatchMessages() will be called needlessly.
150         if (m_messagePorts.contains(messagePort) && messagePort->started())
151             messagePort->dispatchMessages();
152     }
153 }
154
155 void ScriptExecutionContext::createdMessagePort(MessagePort& messagePort)
156 {
157     ASSERT((is<Document>(*this) && isMainThread())
158         || (is<WorkerGlobalScope>(*this) && currentThread() == downcast<WorkerGlobalScope>(*this).thread().threadID()));
159
160     m_messagePorts.add(&messagePort);
161 }
162
163 void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort)
164 {
165     ASSERT((is<Document>(*this) && isMainThread())
166         || (is<WorkerGlobalScope>(*this) && currentThread() == downcast<WorkerGlobalScope>(*this).thread().threadID()));
167
168     m_messagePorts.remove(&messagePort);
169 }
170
171 void ScriptExecutionContext::didLoadResourceSynchronously()
172 {
173 }
174
175 bool ScriptExecutionContext::canSuspendActiveDOMObjectsForDocumentSuspension(Vector<ActiveDOMObject*>* unsuspendableObjects)
176 {
177     checkConsistency();
178
179     bool canSuspend = true;
180
181     m_activeDOMObjectAdditionForbidden = true;
182 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
183     m_activeDOMObjectRemovalForbidden = true;
184 #endif
185
186     // We assume that m_activeDOMObjects will not change during iteration: canSuspend
187     // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
188     // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code
189     // canSuspend functions so it will not happen!
190     NoEventDispatchAssertion assertNoEventDispatch;
191     for (auto* activeDOMObject : m_activeDOMObjects) {
192         if (!activeDOMObject->canSuspendForDocumentSuspension()) {
193             canSuspend = false;
194             if (unsuspendableObjects)
195                 unsuspendableObjects->append(activeDOMObject);
196             else
197                 break;
198         }
199     }
200
201     m_activeDOMObjectAdditionForbidden = false;
202 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
203     m_activeDOMObjectRemovalForbidden = false;
204 #endif
205
206     return canSuspend;
207 }
208
209 void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)
210 {
211     checkConsistency();
212
213     if (m_activeDOMObjectsAreSuspended) {
214         // A page may subsequently suspend DOM objects, say as part of entering the page cache, after the embedding
215         // client requested the page be suspended. We ignore such requests so long as the embedding client requested
216         // the suspension first. See <rdar://problem/13754896> for more details.
217         ASSERT(m_reasonForSuspendingActiveDOMObjects == ActiveDOMObject::PageWillBeSuspended);
218         return;
219     }
220
221     m_activeDOMObjectsAreSuspended = true;
222
223     m_activeDOMObjectAdditionForbidden = true;
224 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
225     m_activeDOMObjectRemovalForbidden = true;
226 #endif
227
228     // We assume that m_activeDOMObjects will not change during iteration: suspend
229     // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
230     // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code
231     // suspend functions so it will not happen!
232     NoEventDispatchAssertion assertNoEventDispatch;
233     for (auto* activeDOMObject : m_activeDOMObjects)
234         activeDOMObject->suspend(why);
235
236     m_activeDOMObjectAdditionForbidden = false;
237 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
238     m_activeDOMObjectRemovalForbidden = false;
239 #endif
240
241     m_reasonForSuspendingActiveDOMObjects = why;
242 }
243
244 void ScriptExecutionContext::resumeActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)
245 {
246     checkConsistency();
247
248     if (m_reasonForSuspendingActiveDOMObjects != why)
249         return;
250     m_activeDOMObjectsAreSuspended = false;
251
252     m_activeDOMObjectAdditionForbidden = true;
253 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
254     m_activeDOMObjectRemovalForbidden = true;
255 #endif
256
257     // We assume that m_activeDOMObjects will not change during iteration: resume
258     // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
259     // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code
260     // resume functions so it will not happen!
261     NoEventDispatchAssertion assertNoEventDispatch;
262     for (auto* activeDOMObject : m_activeDOMObjects)
263         activeDOMObject->resume();
264
265     m_activeDOMObjectAdditionForbidden = false;
266 #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS)
267     m_activeDOMObjectRemovalForbidden = false;
268 #endif
269 }
270
271 void ScriptExecutionContext::stopActiveDOMObjects()
272 {
273     checkConsistency();
274
275     if (m_activeDOMObjectsAreStopped)
276         return;
277     m_activeDOMObjectsAreStopped = true;
278
279     // Make a frozen copy of the objects so we can iterate while new ones might be destroyed.
280     Vector<ActiveDOMObject*> possibleActiveDOMObjects;
281     copyToVector(m_activeDOMObjects, possibleActiveDOMObjects);
282
283     m_activeDOMObjectAdditionForbidden = true;
284
285     // We assume that new objects will not be added to m_activeDOMObjects during iteration:
286     // stop functions should not add new active DOM objects, nor execute arbitrary JavaScript.
287     // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code stop functions
288     // so it will not happen!
289     NoEventDispatchAssertion assertNoEventDispatch;
290     for (auto* activeDOMObject : possibleActiveDOMObjects) {
291         // Check if this object was deleted already. If so, just skip it.
292         // Calling contains on a possibly-already-deleted object is OK because we guarantee
293         // no new object can be added, so even if a new object ends up allocated with the
294         // same address, that will be *after* this function exits.
295         if (!m_activeDOMObjects.contains(activeDOMObject))
296             continue;
297         activeDOMObject->stop();
298     }
299
300     m_activeDOMObjectAdditionForbidden = false;
301
302     // FIXME: Make message ports be active DOM objects and let them implement stop instead
303     // of having this separate mechanism just for them.
304     for (auto* messagePort : m_messagePorts)
305         messagePort->close();
306 }
307
308 void ScriptExecutionContext::suspendActiveDOMObjectIfNeeded(ActiveDOMObject& activeDOMObject)
309 {
310     ASSERT(m_activeDOMObjects.contains(&activeDOMObject));
311     if (m_activeDOMObjectsAreSuspended)
312         activeDOMObject.suspend(m_reasonForSuspendingActiveDOMObjects);
313     if (m_activeDOMObjectsAreStopped)
314         activeDOMObject.stop();
315 }
316
317 void ScriptExecutionContext::didCreateActiveDOMObject(ActiveDOMObject& activeDOMObject)
318 {
319     // The m_activeDOMObjectAdditionForbidden check is a RELEASE_ASSERT because of the
320     // consequences of having an ActiveDOMObject that is not correctly reflected in the set.
321     // If we do have one of those, it can possibly be a security vulnerability. So we'd
322     // rather have a crash than continue running with the set possibly compromised.
323     ASSERT(!m_inScriptExecutionContextDestructor);
324     RELEASE_ASSERT(!m_activeDOMObjectAdditionForbidden);
325     m_activeDOMObjects.add(&activeDOMObject);
326 }
327
328 void ScriptExecutionContext::willDestroyActiveDOMObject(ActiveDOMObject& activeDOMObject)
329 {
330     ASSERT_WITH_SECURITY_IMPLICATION(!m_activeDOMObjectRemovalForbidden);
331     m_activeDOMObjects.remove(&activeDOMObject);
332 }
333
334 void ScriptExecutionContext::didCreateDestructionObserver(ContextDestructionObserver& observer)
335 {
336     ASSERT(!m_inScriptExecutionContextDestructor);
337     m_destructionObservers.add(&observer);
338 }
339
340 void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionObserver& observer)
341 {
342     m_destructionObservers.remove(&observer);
343 }
344
345 bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, int& columnNumber, String& sourceURL, JSC::Strong<JSC::Unknown>& error, CachedScript* cachedScript)
346 {
347     ASSERT(securityOrigin());
348     if (cachedScript) {
349         ASSERT(cachedScript->origin());
350         ASSERT(securityOrigin()->toString() == cachedScript->origin()->toString());
351         if (cachedScript->isCORSSameOrigin())
352             return false;
353     } else if (securityOrigin()->canRequest(completeURL(sourceURL)))
354         return false;
355
356     errorMessage = ASCIILiteral { "Script error." };
357     sourceURL = { };
358     lineNumber = 0;
359     columnNumber = 0;
360     error = { };
361     return true;
362 }
363
364 void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, RefPtr<ScriptCallStack>&& callStack, CachedScript* cachedScript)
365 {
366     if (m_inDispatchErrorEvent) {
367         if (!m_pendingExceptions)
368             m_pendingExceptions = std::make_unique<Vector<std::unique_ptr<PendingException>>>();
369         m_pendingExceptions->append(std::make_unique<PendingException>(errorMessage, lineNumber, columnNumber, sourceURL, WTFMove(callStack)));
370         return;
371     }
372
373     // First report the original exception and only then all the nested ones.
374     if (!dispatchErrorEvent(errorMessage, lineNumber, columnNumber, sourceURL, exception, cachedScript))
375         logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, callStack.copyRef());
376
377     if (!m_pendingExceptions)
378         return;
379
380     auto pendingExceptions = WTFMove(m_pendingExceptions);
381     for (auto& exception : *pendingExceptions)
382         logExceptionToConsole(exception->m_errorMessage, exception->m_sourceURL, exception->m_lineNumber, exception->m_columnNumber, WTFMove(exception->m_callStack));
383 }
384
385 void ScriptExecutionContext::reportUnhandledPromiseRejection(JSC::ExecState& state, JSC::JSPromise& promise, RefPtr<Inspector::ScriptCallStack>&& callStack)
386 {
387     JSC::VM& vm = state.vm();
388     auto scope = DECLARE_CATCH_SCOPE(vm);
389
390     int lineNumber = 0;
391     int columnNumber = 0;
392     String sourceURL;
393
394     JSC::JSValue result = promise.result(vm);
395     String resultMessage = retrieveErrorMessage(state, vm, result, scope);
396     String errorMessage = makeString("Unhandled Promise Rejection: ", resultMessage);
397     if (callStack) {
398         if (const ScriptCallFrame* callFrame = callStack->firstNonNativeCallFrame()) {
399             lineNumber = callFrame->lineNumber();
400             columnNumber = callFrame->columnNumber();
401             sourceURL = callFrame->sourceURL();
402         }
403     }
404
405     logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, WTFMove(callStack));
406 }
407
408 void ScriptExecutionContext::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier)
409 {
410     addMessage(source, level, message, sourceURL, lineNumber, columnNumber, 0, state, requestIdentifier);
411 }
412
413 bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, CachedScript* cachedScript)
414 {
415     EventTarget* target = errorEventTarget();
416     if (!target)
417         return false;
418
419 #if PLATFORM(IOS)
420     if (target->toDOMWindow() && is<Document>(*this)) {
421         if (!downcast<Document>(*this).settings().shouldDispatchJavaScriptWindowOnErrorEvents())
422             return false;
423     }
424 #endif
425
426     String message = errorMessage;
427     int line = lineNumber;
428     int column = columnNumber;
429     String sourceName = sourceURL;
430     JSC::Strong<JSC::Unknown> error = exception && exception->value() ? JSC::Strong<JSC::Unknown>(vm(), exception->value()) : JSC::Strong<JSC::Unknown>();
431     sanitizeScriptError(message, line, column, sourceName, error, cachedScript);
432
433     ASSERT(!m_inDispatchErrorEvent);
434     m_inDispatchErrorEvent = true;
435     Ref<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line, column, error);
436     target->dispatchEvent(errorEvent);
437     m_inDispatchErrorEvent = false;
438     return errorEvent->defaultPrevented();
439 }
440
441 int ScriptExecutionContext::circularSequentialID()
442 {
443     ++m_circularSequentialID;
444     if (m_circularSequentialID <= 0)
445         m_circularSequentialID = 1;
446     return m_circularSequentialID;
447 }
448
449 PublicURLManager& ScriptExecutionContext::publicURLManager()
450 {
451     if (!m_publicURLManager)
452         m_publicURLManager = PublicURLManager::create(this);
453     return *m_publicURLManager;
454 }
455
456 void ScriptExecutionContext::adjustMinimumDOMTimerInterval(Seconds oldMinimumTimerInterval)
457 {
458     if (minimumDOMTimerInterval() != oldMinimumTimerInterval) {
459         for (auto& timer : m_timeouts.values())
460             timer->updateTimerIntervalIfNecessary();
461     }
462 }
463
464 Seconds ScriptExecutionContext::minimumDOMTimerInterval() const
465 {
466     // The default implementation returns the DOMTimer's default
467     // minimum timer interval. FIXME: to make it work with dedicated
468     // workers, we will have to override it in the appropriate
469     // subclass, and provide a way to enumerate a Document's dedicated
470     // workers so we can update them all.
471     return DOMTimer::defaultMinimumInterval();
472 }
473
474 void ScriptExecutionContext::didChangeTimerAlignmentInterval()
475 {
476     for (auto& timer : m_timeouts.values())
477         timer->didChangeAlignmentInterval();
478 }
479
480 Seconds ScriptExecutionContext::domTimerAlignmentInterval(bool) const
481 {
482     return DOMTimer::defaultAlignmentInterval();
483 }
484
485 JSC::VM& ScriptExecutionContext::vm()
486 {
487     if (is<Document>(*this))
488         return commonVM();
489
490     return downcast<WorkerGlobalScope>(*this).script()->vm();
491 }
492
493 RejectedPromiseTracker& ScriptExecutionContext::ensureRejectedPromiseTrackerSlow()
494 {
495     // ScriptExecutionContext::vm() in Worker is only available after WorkerGlobalScope initialization is done.
496     // When initializing ScriptExecutionContext, vm() is not ready.
497
498     ASSERT(!m_rejectedPromiseTracker);
499     m_rejectedPromiseTracker = std::make_unique<RejectedPromiseTracker>(*this, vm());
500     return *m_rejectedPromiseTracker.get();
501 }
502
503 void ScriptExecutionContext::setDatabaseContext(DatabaseContext* databaseContext)
504 {
505     m_databaseContext = databaseContext;
506 }
507
508 bool ScriptExecutionContext::hasPendingActivity() const
509 {
510     checkConsistency();
511
512     for (auto* activeDOMObject : m_activeDOMObjects) {
513         if (activeDOMObject->hasPendingActivity())
514             return true;
515     }
516
517     for (auto* messagePort : m_messagePorts) {
518         if (messagePort->hasPendingActivity())
519             return true;
520     }
521
522     return false;
523 }
524
525 JSC::ExecState* ScriptExecutionContext::execState()
526 {
527     if (is<Document>(*this)) {
528         Document& document = downcast<Document>(*this);
529         return execStateFromPage(mainThreadNormalWorld(), document.page());
530     }
531
532     WorkerGlobalScope* workerGlobalScope = static_cast<WorkerGlobalScope*>(this);
533     return execStateFromWorkerGlobalScope(workerGlobalScope);
534 }
535
536 } // namespace WebCore