Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  *
     25  */
     26 
     27 #include "config.h"
     28 #include "ScriptExecutionContext.h"
     29 
     30 #include "ActiveDOMObject.h"
     31 #include "Blob.h"
     32 #include "BlobURL.h"
     33 #include "DOMTimer.h"
     34 #include "DOMURL.h"
     35 #include "Database.h"
     36 #include "DatabaseTask.h"
     37 #include "DatabaseThread.h"
     38 #include "ErrorEvent.h"
     39 #include "EventListener.h"
     40 #include "EventTarget.h"
     41 #include "FileThread.h"
     42 #include "MessagePort.h"
     43 #include "ScriptCallStack.h"
     44 #include "SecurityOrigin.h"
     45 #include "Settings.h"
     46 #include "ThreadableBlobRegistry.h"
     47 #include "WorkerContext.h"
     48 #include "WorkerThread.h"
     49 #include <wtf/MainThread.h>
     50 #include <wtf/PassRefPtr.h>
     51 #include <wtf/Vector.h>
     52 
     53 #if USE(JSC)
     54 #include "JSDOMWindow.h"
     55 #endif
     56 
     57 namespace WebCore {
     58 
     59 class ProcessMessagesSoonTask : public ScriptExecutionContext::Task {
     60 public:
     61     static PassOwnPtr<ProcessMessagesSoonTask> create()
     62     {
     63         return new ProcessMessagesSoonTask;
     64     }
     65 
     66     virtual void performTask(ScriptExecutionContext* context)
     67     {
     68         context->dispatchMessagePortEvents();
     69     }
     70 };
     71 
     72 class ScriptExecutionContext::PendingException {
     73     WTF_MAKE_NONCOPYABLE(PendingException);
     74 public:
     75     PendingException(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack)
     76         : m_errorMessage(errorMessage)
     77         , m_lineNumber(lineNumber)
     78         , m_sourceURL(sourceURL)
     79         , m_callStack(callStack)
     80     {
     81     }
     82     String m_errorMessage;
     83     int m_lineNumber;
     84     String m_sourceURL;
     85     RefPtr<ScriptCallStack> m_callStack;
     86 };
     87 
     88 ScriptExecutionContext::ScriptExecutionContext()
     89     : m_iteratingActiveDOMObjects(false)
     90     , m_inDestructor(false)
     91     , m_inDispatchErrorEvent(false)
     92 #if ENABLE(DATABASE)
     93     , m_hasOpenDatabases(false)
     94 #endif
     95 {
     96 }
     97 
     98 ScriptExecutionContext::~ScriptExecutionContext()
     99 {
    100     m_inDestructor = true;
    101     for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != m_activeDOMObjects.end(); iter = m_activeDOMObjects.begin()) {
    102         ActiveDOMObject* object = iter->first;
    103         m_activeDOMObjects.remove(iter);
    104         ASSERT(object->scriptExecutionContext() == this);
    105         object->contextDestroyed();
    106     }
    107 
    108     HashSet<MessagePort*>::iterator messagePortsEnd = m_messagePorts.end();
    109     for (HashSet<MessagePort*>::iterator iter = m_messagePorts.begin(); iter != messagePortsEnd; ++iter) {
    110         ASSERT((*iter)->scriptExecutionContext() == this);
    111         (*iter)->contextDestroyed();
    112     }
    113 #if ENABLE(DATABASE)
    114     if (m_databaseThread) {
    115         ASSERT(m_databaseThread->terminationRequested());
    116         m_databaseThread = 0;
    117     }
    118 #endif
    119 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM)
    120     if (m_fileThread) {
    121         m_fileThread->stop();
    122         m_fileThread = 0;
    123     }
    124 #endif
    125 
    126 #if ENABLE(BLOB)
    127     HashSet<String>::iterator publicBlobURLsEnd = m_publicBlobURLs.end();
    128     for (HashSet<String>::iterator iter = m_publicBlobURLs.begin(); iter != publicBlobURLsEnd; ++iter)
    129         ThreadableBlobRegistry::unregisterBlobURL(KURL(ParsedURLString, *iter));
    130 
    131     HashSet<DOMURL*>::iterator domUrlsEnd = m_domUrls.end();
    132     for (HashSet<DOMURL*>::iterator iter = m_domUrls.begin(); iter != domUrlsEnd; ++iter) {
    133         ASSERT((*iter)->scriptExecutionContext() == this);
    134         (*iter)->contextDestroyed();
    135     }
    136 #endif
    137 }
    138 
    139 #if ENABLE(DATABASE)
    140 
    141 DatabaseThread* ScriptExecutionContext::databaseThread()
    142 {
    143     if (!m_databaseThread && !m_hasOpenDatabases) {
    144         // Create the database thread on first request - but not if at least one database was already opened,
    145         // because in that case we already had a database thread and terminated it and should not create another.
    146         m_databaseThread = DatabaseThread::create();
    147         if (!m_databaseThread->start())
    148             m_databaseThread = 0;
    149     }
    150 
    151     return m_databaseThread.get();
    152 }
    153 
    154 void ScriptExecutionContext::stopDatabases(DatabaseTaskSynchronizer* cleanupSync)
    155 {
    156     ASSERT(isContextThread());
    157     if (m_databaseThread)
    158         m_databaseThread->requestTermination(cleanupSync);
    159     else if (cleanupSync)
    160         cleanupSync->taskCompleted();
    161 }
    162 
    163 #endif
    164 
    165 void ScriptExecutionContext::processMessagePortMessagesSoon()
    166 {
    167     postTask(ProcessMessagesSoonTask::create());
    168 }
    169 
    170 void ScriptExecutionContext::dispatchMessagePortEvents()
    171 {
    172     RefPtr<ScriptExecutionContext> protect(this);
    173 
    174     // Make a frozen copy.
    175     Vector<MessagePort*> ports;
    176     copyToVector(m_messagePorts, ports);
    177 
    178     unsigned portCount = ports.size();
    179     for (unsigned i = 0; i < portCount; ++i) {
    180         MessagePort* port = ports[i];
    181         // The port may be destroyed, and another one created at the same address, but this is safe, as the worst that can happen
    182         // as a result is that dispatchMessages() will be called needlessly.
    183         if (m_messagePorts.contains(port) && port->started())
    184             port->dispatchMessages();
    185     }
    186 }
    187 
    188 void ScriptExecutionContext::createdMessagePort(MessagePort* port)
    189 {
    190     ASSERT(port);
    191 #if ENABLE(WORKERS)
    192     ASSERT((isDocument() && isMainThread())
    193         || (isWorkerContext() && currentThread() == static_cast<WorkerContext*>(this)->thread()->threadID()));
    194 #endif
    195 
    196     m_messagePorts.add(port);
    197 }
    198 
    199 void ScriptExecutionContext::destroyedMessagePort(MessagePort* port)
    200 {
    201     ASSERT(port);
    202 #if ENABLE(WORKERS)
    203     ASSERT((isDocument() && isMainThread())
    204         || (isWorkerContext() && currentThread() == static_cast<WorkerContext*>(this)->thread()->threadID()));
    205 #endif
    206 
    207     m_messagePorts.remove(port);
    208 }
    209 
    210 #if ENABLE(BLOB)
    211 void ScriptExecutionContext::createdDomUrl(DOMURL* url)
    212 {
    213     ASSERT(url);
    214     m_domUrls.add(url);
    215 }
    216 
    217 void ScriptExecutionContext::destroyedDomUrl(DOMURL* url)
    218 {
    219     ASSERT(url);
    220     m_domUrls.remove(url);
    221 }
    222 #endif
    223 
    224 bool ScriptExecutionContext::canSuspendActiveDOMObjects()
    225 {
    226     // No protection against m_activeDOMObjects changing during iteration: canSuspend() shouldn't execute arbitrary JS.
    227     m_iteratingActiveDOMObjects = true;
    228     HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end();
    229     for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) {
    230         ASSERT(iter->first->scriptExecutionContext() == this);
    231         if (!iter->first->canSuspend()) {
    232             m_iteratingActiveDOMObjects = false;
    233             return false;
    234         }
    235     }
    236     m_iteratingActiveDOMObjects = false;
    237     return true;
    238 }
    239 
    240 void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)
    241 {
    242     // No protection against m_activeDOMObjects changing during iteration: suspend() shouldn't execute arbitrary JS.
    243     m_iteratingActiveDOMObjects = true;
    244     HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end();
    245     for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) {
    246         ASSERT(iter->first->scriptExecutionContext() == this);
    247         iter->first->suspend(why);
    248     }
    249     m_iteratingActiveDOMObjects = false;
    250 }
    251 
    252 void ScriptExecutionContext::resumeActiveDOMObjects()
    253 {
    254     // No protection against m_activeDOMObjects changing during iteration: resume() shouldn't execute arbitrary JS.
    255     m_iteratingActiveDOMObjects = true;
    256     HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end();
    257     for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) {
    258         ASSERT(iter->first->scriptExecutionContext() == this);
    259         iter->first->resume();
    260     }
    261     m_iteratingActiveDOMObjects = false;
    262 }
    263 
    264 void ScriptExecutionContext::stopActiveDOMObjects()
    265 {
    266     // No protection against m_activeDOMObjects changing during iteration: stop() shouldn't execute arbitrary JS.
    267     m_iteratingActiveDOMObjects = true;
    268     HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end();
    269     for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) {
    270         ASSERT(iter->first->scriptExecutionContext() == this);
    271         iter->first->stop();
    272     }
    273     m_iteratingActiveDOMObjects = false;
    274 
    275     // Also close MessagePorts. If they were ActiveDOMObjects (they could be) then they could be stopped instead.
    276     closeMessagePorts();
    277 }
    278 
    279 void ScriptExecutionContext::createdActiveDOMObject(ActiveDOMObject* object, void* upcastPointer)
    280 {
    281     ASSERT(object);
    282     ASSERT(upcastPointer);
    283     ASSERT(!m_inDestructor);
    284     if (m_iteratingActiveDOMObjects)
    285         CRASH();
    286     m_activeDOMObjects.add(object, upcastPointer);
    287 }
    288 
    289 void ScriptExecutionContext::destroyedActiveDOMObject(ActiveDOMObject* object)
    290 {
    291     ASSERT(object);
    292     if (m_iteratingActiveDOMObjects)
    293         CRASH();
    294     m_activeDOMObjects.remove(object);
    295 }
    296 
    297 void ScriptExecutionContext::closeMessagePorts() {
    298     HashSet<MessagePort*>::iterator messagePortsEnd = m_messagePorts.end();
    299     for (HashSet<MessagePort*>::iterator iter = m_messagePorts.begin(); iter != messagePortsEnd; ++iter) {
    300         ASSERT((*iter)->scriptExecutionContext() == this);
    301         (*iter)->close();
    302     }
    303 }
    304 
    305 void ScriptExecutionContext::setSecurityOrigin(PassRefPtr<SecurityOrigin> securityOrigin)
    306 {
    307     m_securityOrigin = securityOrigin;
    308 }
    309 
    310 bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, String& sourceURL)
    311 {
    312     KURL targetURL = completeURL(sourceURL);
    313     if (securityOrigin()->canRequest(targetURL))
    314         return false;
    315     errorMessage = "Script error.";
    316     sourceURL = String();
    317     lineNumber = 0;
    318     return true;
    319 }
    320 
    321 void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack)
    322 {
    323     if (m_inDispatchErrorEvent) {
    324         if (!m_pendingExceptions)
    325             m_pendingExceptions = adoptPtr(new Vector<OwnPtr<PendingException> >());
    326         m_pendingExceptions->append(adoptPtr(new PendingException(errorMessage, lineNumber, sourceURL, callStack)));
    327         return;
    328     }
    329 
    330     // First report the original exception and only then all the nested ones.
    331     if (!dispatchErrorEvent(errorMessage, lineNumber, sourceURL))
    332         logExceptionToConsole(errorMessage, lineNumber, sourceURL, callStack);
    333 
    334     if (!m_pendingExceptions)
    335         return;
    336 
    337     for (size_t i = 0; i < m_pendingExceptions->size(); i++) {
    338         PendingException* e = m_pendingExceptions->at(i).get();
    339         logExceptionToConsole(e->m_errorMessage, e->m_lineNumber, e->m_sourceURL, e->m_callStack);
    340     }
    341     m_pendingExceptions.clear();
    342 }
    343 
    344 bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, const String& sourceURL)
    345 {
    346     EventTarget* target = errorEventTarget();
    347     if (!target)
    348         return false;
    349 
    350     String message = errorMessage;
    351     int line = lineNumber;
    352     String sourceName = sourceURL;
    353     sanitizeScriptError(message, line, sourceName);
    354 
    355     ASSERT(!m_inDispatchErrorEvent);
    356     m_inDispatchErrorEvent = true;
    357     RefPtr<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line);
    358     target->dispatchEvent(errorEvent);
    359     m_inDispatchErrorEvent = false;
    360     return errorEvent->defaultPrevented();
    361 }
    362 
    363 void ScriptExecutionContext::addTimeout(int timeoutId, DOMTimer* timer)
    364 {
    365     ASSERT(!m_timeouts.contains(timeoutId));
    366     m_timeouts.set(timeoutId, timer);
    367 }
    368 
    369 void ScriptExecutionContext::removeTimeout(int timeoutId)
    370 {
    371     m_timeouts.remove(timeoutId);
    372 }
    373 
    374 DOMTimer* ScriptExecutionContext::findTimeout(int timeoutId)
    375 {
    376     return m_timeouts.get(timeoutId);
    377 }
    378 
    379 #if ENABLE(BLOB)
    380 KURL ScriptExecutionContext::createPublicBlobURL(Blob* blob)
    381 {
    382     if (!blob)
    383         return KURL();
    384     KURL publicURL = BlobURL::createPublicURL(securityOrigin());
    385     if (publicURL.isEmpty())
    386         return KURL();
    387     ThreadableBlobRegistry::registerBlobURL(publicURL, blob->url());
    388     m_publicBlobURLs.add(publicURL.string());
    389     return publicURL;
    390 }
    391 
    392 void ScriptExecutionContext::revokePublicBlobURL(const KURL& url)
    393 {
    394     if (m_publicBlobURLs.contains(url.string())) {
    395         ThreadableBlobRegistry::unregisterBlobURL(url);
    396         m_publicBlobURLs.remove(url.string());
    397     }
    398 }
    399 #endif
    400 
    401 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM)
    402 FileThread* ScriptExecutionContext::fileThread()
    403 {
    404     if (!m_fileThread) {
    405         m_fileThread = FileThread::create();
    406         if (!m_fileThread->start())
    407             m_fileThread = 0;
    408     }
    409     return m_fileThread.get();
    410 }
    411 #endif
    412 
    413 void ScriptExecutionContext::adjustMinimumTimerInterval(double oldMinimumTimerInterval)
    414 {
    415     if (minimumTimerInterval() != oldMinimumTimerInterval) {
    416         for (TimeoutMap::iterator iter = m_timeouts.begin(); iter != m_timeouts.end(); ++iter) {
    417             DOMTimer* timer = iter->second;
    418             timer->adjustMinimumTimerInterval(oldMinimumTimerInterval);
    419         }
    420     }
    421 }
    422 
    423 double ScriptExecutionContext::minimumTimerInterval() const
    424 {
    425     // The default implementation returns the DOMTimer's default
    426     // minimum timer interval. FIXME: to make it work with dedicated
    427     // workers, we will have to override it in the appropriate
    428     // subclass, and provide a way to enumerate a Document's dedicated
    429     // workers so we can update them all.
    430     return Settings::defaultMinDOMTimerInterval();
    431 }
    432 
    433 ScriptExecutionContext::Task::~Task()
    434 {
    435 }
    436 
    437 #if USE(JSC)
    438 JSC::JSGlobalData* ScriptExecutionContext::globalData()
    439 {
    440      if (isDocument())
    441         return JSDOMWindow::commonJSGlobalData();
    442 
    443 #if ENABLE(WORKERS)
    444     if (isWorkerContext())
    445         return static_cast<WorkerContext*>(this)->script()->globalData();
    446 #endif
    447 
    448     ASSERT_NOT_REACHED();
    449     return 0;
    450 }
    451 #endif
    452 
    453 } // namespace WebCore
    454