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 "DOMTimer.h"
33#include "ErrorEvent.h"
34#include "MessagePort.h"
35#include "PublicURLManager.h"
36#include "Settings.h"
37#include "WorkerGlobalScope.h"
38#include "WorkerThread.h"
39#include <inspector/ScriptCallStack.h>
40#include <wtf/MainThread.h>
41#include <wtf/Ref.h>
42
43// FIXME: This is a layering violation.
44#include "JSDOMWindow.h"
45
46#if PLATFORM(IOS)
47#include "Document.h"
48#endif
49
50#if ENABLE(SQL_DATABASE)
51#include "DatabaseContext.h"
52#endif
53
54using namespace Inspector;
55
56namespace WebCore {
57
58class ScriptExecutionContext::PendingException {
59    WTF_MAKE_NONCOPYABLE(PendingException);
60public:
61    PendingException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack)
62        : m_errorMessage(errorMessage)
63        , m_lineNumber(lineNumber)
64        , m_columnNumber(columnNumber)
65        , m_sourceURL(sourceURL)
66        , m_callStack(callStack)
67    {
68    }
69    String m_errorMessage;
70    int m_lineNumber;
71    int m_columnNumber;
72    String m_sourceURL;
73    RefPtr<ScriptCallStack> m_callStack;
74};
75
76ScriptExecutionContext::ScriptExecutionContext()
77    : m_circularSequentialID(0)
78    , m_inDispatchErrorEvent(false)
79    , m_activeDOMObjectsAreSuspended(false)
80    , m_reasonForSuspendingActiveDOMObjects(static_cast<ActiveDOMObject::ReasonForSuspension>(-1))
81    , m_activeDOMObjectsAreStopped(false)
82    , m_activeDOMObjectAdditionForbidden(false)
83#if !ASSERT_DISABLED
84    , m_inScriptExecutionContextDestructor(false)
85    , m_activeDOMObjectRemovalForbidden(false)
86#endif
87{
88}
89
90#if ASSERT_DISABLED
91
92inline void ScriptExecutionContext::checkConsistency() const
93{
94}
95
96#else
97
98void ScriptExecutionContext::checkConsistency() const
99{
100    for (auto* messagePort : m_messagePorts)
101        ASSERT(messagePort->scriptExecutionContext() == this);
102
103    for (auto* destructionObserver : m_destructionObservers)
104        ASSERT(destructionObserver->scriptExecutionContext() == this);
105
106    for (auto* activeDOMObject : m_activeDOMObjects) {
107        ASSERT(activeDOMObject->scriptExecutionContext() == this);
108        activeDOMObject->assertSuspendIfNeededWasCalled();
109    }
110}
111
112#endif
113
114ScriptExecutionContext::~ScriptExecutionContext()
115{
116    checkConsistency();
117
118#if !ASSERT_DISABLED
119    m_inScriptExecutionContextDestructor = true;
120#endif
121
122    while (auto* destructionObserver = m_destructionObservers.takeAny())
123        destructionObserver->contextDestroyed();
124
125    for (auto* messagePort : m_messagePorts)
126        messagePort->contextDestroyed();
127
128#if !ASSERT_DISABLED
129    m_inScriptExecutionContextDestructor = false;
130#endif
131}
132
133void ScriptExecutionContext::processMessagePortMessagesSoon()
134{
135    postTask([] (ScriptExecutionContext& context) {
136        context.dispatchMessagePortEvents();
137    });
138}
139
140void ScriptExecutionContext::dispatchMessagePortEvents()
141{
142    checkConsistency();
143
144    Ref<ScriptExecutionContext> protect(*this);
145
146    // Make a frozen copy of the ports so we can iterate while new ones might be added or destroyed.
147    Vector<MessagePort*> possibleMessagePorts;
148    copyToVector(m_messagePorts, possibleMessagePorts);
149    for (auto* messagePort : possibleMessagePorts) {
150        // The port may be destroyed, and another one created at the same address,
151        // but this is harmless. The worst that can happen as a result is that
152        // dispatchMessages() will be called needlessly.
153        if (m_messagePorts.contains(messagePort) && messagePort->started())
154            messagePort->dispatchMessages();
155    }
156}
157
158void ScriptExecutionContext::createdMessagePort(MessagePort& messagePort)
159{
160    ASSERT((isDocument() && isMainThread())
161        || (isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(this)->thread().threadID()));
162
163    m_messagePorts.add(&messagePort);
164}
165
166void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort)
167{
168    ASSERT((isDocument() && isMainThread())
169        || (isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(this)->thread().threadID()));
170
171    m_messagePorts.remove(&messagePort);
172}
173
174bool ScriptExecutionContext::canSuspendActiveDOMObjects()
175{
176    checkConsistency();
177
178    bool canSuspend = true;
179
180    m_activeDOMObjectAdditionForbidden = true;
181#if !ASSERT_DISABLED
182    m_activeDOMObjectRemovalForbidden = true;
183#endif
184
185    // We assume that m_activeDOMObjects will not change during iteration: canSuspend
186    // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
187    // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code
188    // canSuspend functions so it will not happen!
189    for (auto* activeDOMObject : m_activeDOMObjects) {
190        if (!activeDOMObject->canSuspend()) {
191            canSuspend = false;
192            break;
193        }
194    }
195
196    m_activeDOMObjectAdditionForbidden = false;
197#if !ASSERT_DISABLED
198    m_activeDOMObjectRemovalForbidden = false;
199#endif
200
201    return canSuspend;
202}
203
204void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)
205{
206    checkConsistency();
207
208#if PLATFORM(IOS)
209    if (m_activeDOMObjectsAreSuspended) {
210        ASSERT(m_reasonForSuspendingActiveDOMObjects == ActiveDOMObject::DocumentWillBePaused);
211        return;
212    }
213#endif
214
215    m_activeDOMObjectAdditionForbidden = true;
216#if !ASSERT_DISABLED
217    m_activeDOMObjectRemovalForbidden = true;
218#endif
219
220    // We assume that m_activeDOMObjects will not change during iteration: suspend
221    // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
222    // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code
223    // suspend functions so it will not happen!
224    for (auto* activeDOMObject : m_activeDOMObjects)
225        activeDOMObject->suspend(why);
226
227    m_activeDOMObjectAdditionForbidden = false;
228#if !ASSERT_DISABLED
229    m_activeDOMObjectRemovalForbidden = false;
230#endif
231
232    m_activeDOMObjectsAreSuspended = true;
233    m_reasonForSuspendingActiveDOMObjects = why;
234}
235
236void ScriptExecutionContext::resumeActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)
237{
238    checkConsistency();
239
240    if (m_reasonForSuspendingActiveDOMObjects != why)
241        return;
242    m_activeDOMObjectsAreSuspended = false;
243
244    m_activeDOMObjectAdditionForbidden = true;
245#if !ASSERT_DISABLED
246    m_activeDOMObjectRemovalForbidden = true;
247#endif
248
249    // We assume that m_activeDOMObjects will not change during iteration: resume
250    // functions should not add new active DOM objects, nor execute arbitrary JavaScript.
251    // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code
252    // resume functions so it will not happen!
253    for (auto* activeDOMObject : m_activeDOMObjects)
254        activeDOMObject->resume();
255
256    m_activeDOMObjectAdditionForbidden = false;
257#if !ASSERT_DISABLED
258    m_activeDOMObjectRemovalForbidden = false;
259#endif
260}
261
262void ScriptExecutionContext::stopActiveDOMObjects()
263{
264    checkConsistency();
265
266    if (m_activeDOMObjectsAreStopped)
267        return;
268    m_activeDOMObjectsAreStopped = true;
269
270    // Make a frozen copy of the objects so we can iterate while new ones might be destroyed.
271    Vector<ActiveDOMObject*> possibleActiveDOMObjects;
272    copyToVector(m_activeDOMObjects, possibleActiveDOMObjects);
273
274    m_activeDOMObjectAdditionForbidden = true;
275
276    // We assume that new objects will not be added to m_activeDOMObjects during iteration:
277    // stop functions should not add new active DOM objects, nor execute arbitrary JavaScript.
278    // A RELEASE_ASSERT will fire if this happens, but it's important to code stop functions
279    // so it will not happen!
280    for (auto* activeDOMObject : possibleActiveDOMObjects) {
281        // Check if this object was deleted already. If so, just skip it.
282        // Calling contains on a possibly-already-deleted object is OK because we guarantee
283        // no new object can be added, so even if a new object ends up allocated with the
284        // same address, that will be *after* this function exits.
285        if (!m_activeDOMObjects.contains(activeDOMObject))
286            continue;
287        activeDOMObject->stop();
288    }
289
290    m_activeDOMObjectAdditionForbidden = false;
291
292    // FIXME: Make message ports be active DOM objects and let them implement stop instead
293    // of having this separate mechanism just for them.
294    for (auto* messagePort : m_messagePorts)
295        messagePort->close();
296}
297
298void ScriptExecutionContext::suspendActiveDOMObjectIfNeeded(ActiveDOMObject& activeDOMObject)
299{
300    ASSERT(m_activeDOMObjects.contains(&activeDOMObject));
301    if (m_activeDOMObjectsAreSuspended)
302        activeDOMObject.suspend(m_reasonForSuspendingActiveDOMObjects);
303    if (m_activeDOMObjectsAreStopped)
304        activeDOMObject.stop();
305}
306
307void ScriptExecutionContext::didCreateActiveDOMObject(ActiveDOMObject& activeDOMObject)
308{
309    // The m_activeDOMObjectAdditionForbidden check is a RELEASE_ASSERT because of the
310    // consequences of having an ActiveDOMObject that is not correctly reflected in the set.
311    // If we do have one of those, it can possibly be a security vulnerability. So we'd
312    // rather have a crash than continue running with the set possibly compromised.
313    ASSERT(!m_inScriptExecutionContextDestructor);
314    RELEASE_ASSERT(!m_activeDOMObjectAdditionForbidden);
315    m_activeDOMObjects.add(&activeDOMObject);
316}
317
318void ScriptExecutionContext::willDestroyActiveDOMObject(ActiveDOMObject& activeDOMObject)
319{
320    ASSERT(!m_activeDOMObjectRemovalForbidden);
321    m_activeDOMObjects.remove(&activeDOMObject);
322}
323
324void ScriptExecutionContext::didCreateDestructionObserver(ContextDestructionObserver& observer)
325{
326    ASSERT(!m_inScriptExecutionContextDestructor);
327    m_destructionObservers.add(&observer);
328}
329
330void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionObserver& observer)
331{
332    m_destructionObservers.remove(&observer);
333}
334
335bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, int& columnNumber, String& sourceURL, CachedScript* cachedScript)
336{
337    URL targetURL = completeURL(sourceURL);
338    if (securityOrigin()->canRequest(targetURL) || (cachedScript && cachedScript->passesAccessControlCheck(securityOrigin())))
339        return false;
340    errorMessage = "Script error.";
341    sourceURL = String();
342    lineNumber = 0;
343    columnNumber = 0;
344    return true;
345}
346
347void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack, CachedScript* cachedScript)
348{
349    if (m_inDispatchErrorEvent) {
350        if (!m_pendingExceptions)
351            m_pendingExceptions = std::make_unique<Vector<std::unique_ptr<PendingException>>>();
352        m_pendingExceptions->append(std::make_unique<PendingException>(errorMessage, lineNumber, columnNumber, sourceURL, callStack));
353        return;
354    }
355
356    // First report the original exception and only then all the nested ones.
357    if (!dispatchErrorEvent(errorMessage, lineNumber, columnNumber, sourceURL, cachedScript))
358        logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, callStack);
359
360    if (!m_pendingExceptions)
361        return;
362
363    std::unique_ptr<Vector<std::unique_ptr<PendingException>>> pendingExceptions = WTF::move(m_pendingExceptions);
364    for (auto& exception : *pendingExceptions)
365        logExceptionToConsole(exception->m_errorMessage, exception->m_sourceURL, exception->m_lineNumber, exception->m_columnNumber, exception->m_callStack);
366}
367
368void ScriptExecutionContext::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier)
369{
370    addMessage(source, level, message, sourceURL, lineNumber, columnNumber, 0, state, requestIdentifier);
371}
372
373bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, CachedScript* cachedScript)
374{
375    EventTarget* target = errorEventTarget();
376    if (!target)
377        return false;
378
379#if PLATFORM(IOS)
380    if (target == target->toDOMWindow() && isDocument()) {
381        Settings* settings = static_cast<Document*>(this)->settings();
382        if (settings && !settings->shouldDispatchJavaScriptWindowOnErrorEvents())
383            return false;
384    }
385#endif
386
387    String message = errorMessage;
388    int line = lineNumber;
389    int column = columnNumber;
390    String sourceName = sourceURL;
391    sanitizeScriptError(message, line, column, sourceName, cachedScript);
392
393    ASSERT(!m_inDispatchErrorEvent);
394    m_inDispatchErrorEvent = true;
395    RefPtr<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line, column);
396    target->dispatchEvent(errorEvent);
397    m_inDispatchErrorEvent = false;
398    return errorEvent->defaultPrevented();
399}
400
401int ScriptExecutionContext::circularSequentialID()
402{
403    ++m_circularSequentialID;
404    if (m_circularSequentialID <= 0)
405        m_circularSequentialID = 1;
406    return m_circularSequentialID;
407}
408
409PublicURLManager& ScriptExecutionContext::publicURLManager()
410{
411    if (!m_publicURLManager)
412        m_publicURLManager = PublicURLManager::create(this);
413    return *m_publicURLManager;
414}
415
416void ScriptExecutionContext::adjustMinimumTimerInterval(double oldMinimumTimerInterval)
417{
418    if (minimumTimerInterval() != oldMinimumTimerInterval) {
419        for (auto* timer : m_timeouts.values())
420            timer->adjustMinimumTimerInterval(oldMinimumTimerInterval);
421    }
422}
423
424double ScriptExecutionContext::minimumTimerInterval() const
425{
426    // The default implementation returns the DOMTimer's default
427    // minimum timer interval. FIXME: to make it work with dedicated
428    // workers, we will have to override it in the appropriate
429    // subclass, and provide a way to enumerate a Document's dedicated
430    // workers so we can update them all.
431    return Settings::defaultMinDOMTimerInterval();
432}
433
434void ScriptExecutionContext::didChangeTimerAlignmentInterval()
435{
436    for (auto* timer : m_timeouts.values())
437        timer->didChangeAlignmentInterval();
438}
439
440double ScriptExecutionContext::timerAlignmentInterval() const
441{
442    return Settings::defaultDOMTimerAlignmentInterval();
443}
444
445JSC::VM& ScriptExecutionContext::vm()
446{
447     if (isDocument())
448        return JSDOMWindow::commonVM();
449
450    return toWorkerGlobalScope(*this).script()->vm();
451}
452
453#if ENABLE(SQL_DATABASE)
454void ScriptExecutionContext::setDatabaseContext(DatabaseContext* databaseContext)
455{
456    ASSERT(!m_databaseContext);
457    m_databaseContext = databaseContext;
458}
459#endif
460
461bool ScriptExecutionContext::hasPendingActivity() const
462{
463    checkConsistency();
464
465    for (auto* activeDOMObject : m_activeDOMObjects) {
466        if (activeDOMObject->hasPendingActivity())
467            return true;
468    }
469
470    for (auto* messagePort : m_messagePorts) {
471        if (messagePort->hasPendingActivity())
472            return true;
473    }
474
475    return false;
476}
477
478} // namespace WebCore
479