Home | History | Annotate | Download | only in workers
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  * Copyright (C) 2009, 2011 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 COMPUTER, 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 COMPUTER, 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 
     30 #if ENABLE(WORKERS)
     31 
     32 #include "WorkerContext.h"
     33 
     34 #include "AbstractDatabase.h"
     35 #include "ActiveDOMObject.h"
     36 #include "Database.h"
     37 #include "DatabaseCallback.h"
     38 #include "DatabaseSync.h"
     39 #include "DatabaseTracker.h"
     40 #include "DOMTimer.h"
     41 #include "DOMURL.h"
     42 #include "DOMWindow.h"
     43 #include "ErrorEvent.h"
     44 #include "Event.h"
     45 #include "EventException.h"
     46 #include "InspectorInstrumentation.h"
     47 #include "KURL.h"
     48 #include "MessagePort.h"
     49 #include "NotImplemented.h"
     50 #include "ScriptCallStack.h"
     51 #include "ScriptSourceCode.h"
     52 #include "ScriptValue.h"
     53 #include "SecurityOrigin.h"
     54 #include "WorkerInspectorController.h"
     55 #include "WorkerLocation.h"
     56 #include "WorkerNavigator.h"
     57 #include "WorkerObjectProxy.h"
     58 #include "WorkerScriptLoader.h"
     59 #include "WorkerThread.h"
     60 #include "WorkerThreadableLoader.h"
     61 #include "XMLHttpRequestException.h"
     62 #include <wtf/RefPtr.h>
     63 #include <wtf/UnusedParam.h>
     64 
     65 #if ENABLE(NOTIFICATIONS)
     66 #include "NotificationCenter.h"
     67 #endif
     68 
     69 #if ENABLE(FILE_SYSTEM)
     70 #include "AsyncFileSystem.h"
     71 #include "DirectoryEntrySync.h"
     72 #include "DOMFileSystem.h"
     73 #include "DOMFileSystemBase.h"
     74 #include "DOMFileSystemSync.h"
     75 #include "ErrorCallback.h"
     76 #include "FileEntrySync.h"
     77 #include "FileError.h"
     78 #include "FileException.h"
     79 #include "FileSystemCallback.h"
     80 #include "FileSystemCallbacks.h"
     81 #include "LocalFileSystem.h"
     82 #include "SyncCallbackHelper.h"
     83 #endif
     84 
     85 namespace WebCore {
     86 
     87 class CloseWorkerContextTask : public ScriptExecutionContext::Task {
     88 public:
     89     static PassOwnPtr<CloseWorkerContextTask> create()
     90     {
     91         return new CloseWorkerContextTask;
     92     }
     93 
     94     virtual void performTask(ScriptExecutionContext *context)
     95     {
     96         ASSERT(context->isWorkerContext());
     97         WorkerContext* workerContext = static_cast<WorkerContext*>(context);
     98         // Notify parent that this context is closed. Parent is responsible for calling WorkerThread::stop().
     99         workerContext->thread()->workerReportingProxy().workerContextClosed();
    100     }
    101 
    102     virtual bool isCleanupTask() const { return true; }
    103 };
    104 
    105 WorkerContext::WorkerContext(const KURL& url, const String& userAgent, WorkerThread* thread)
    106     : m_url(url)
    107     , m_userAgent(userAgent)
    108     , m_script(new WorkerScriptController(this))
    109     , m_thread(thread)
    110 #if ENABLE(INSPECTOR)
    111     , m_workerInspectorController(new WorkerInspectorController(this))
    112 #endif
    113     , m_closing(false)
    114 {
    115     setSecurityOrigin(SecurityOrigin::create(url));
    116 }
    117 
    118 WorkerContext::~WorkerContext()
    119 {
    120     ASSERT(currentThread() == thread()->threadID());
    121 #if ENABLE(NOTIFICATIONS)
    122     m_notifications.clear();
    123 #endif
    124 
    125     // Make sure we have no observers.
    126     notifyObserversOfStop();
    127 
    128     // Notify proxy that we are going away. This can free the WorkerThread object, so do not access it after this.
    129     thread()->workerReportingProxy().workerContextDestroyed();
    130 }
    131 
    132 ScriptExecutionContext* WorkerContext::scriptExecutionContext() const
    133 {
    134     return const_cast<WorkerContext*>(this);
    135 }
    136 
    137 const KURL& WorkerContext::virtualURL() const
    138 {
    139     return m_url;
    140 }
    141 
    142 KURL WorkerContext::virtualCompleteURL(const String& url) const
    143 {
    144     return completeURL(url);
    145 }
    146 
    147 KURL WorkerContext::completeURL(const String& url) const
    148 {
    149     // Always return a null URL when passed a null string.
    150     // FIXME: Should we change the KURL constructor to have this behavior?
    151     if (url.isNull())
    152         return KURL();
    153     // Always use UTF-8 in Workers.
    154     return KURL(m_url, url);
    155 }
    156 
    157 String WorkerContext::userAgent(const KURL&) const
    158 {
    159     return m_userAgent;
    160 }
    161 
    162 WorkerLocation* WorkerContext::location() const
    163 {
    164     if (!m_location)
    165         m_location = WorkerLocation::create(m_url);
    166     return m_location.get();
    167 }
    168 
    169 void WorkerContext::close()
    170 {
    171     if (m_closing)
    172         return;
    173 
    174     // Let current script run to completion but prevent future script evaluations.
    175     // After m_closing is set, all the tasks in the queue continue to be fetched but only
    176     // tasks with isCleanupTask()==true will be executed.
    177     m_closing = true;
    178     postTask(CloseWorkerContextTask::create());
    179 }
    180 
    181 WorkerNavigator* WorkerContext::navigator() const
    182 {
    183     if (!m_navigator)
    184         m_navigator = WorkerNavigator::create(m_userAgent);
    185     return m_navigator.get();
    186 }
    187 
    188 bool WorkerContext::hasPendingActivity() const
    189 {
    190     ActiveDOMObjectsMap& activeObjects = activeDOMObjects();
    191     ActiveDOMObjectsMap::const_iterator activeObjectsEnd = activeObjects.end();
    192     for (ActiveDOMObjectsMap::const_iterator iter = activeObjects.begin(); iter != activeObjectsEnd; ++iter) {
    193         if (iter->first->hasPendingActivity())
    194             return true;
    195     }
    196 
    197     // Keep the worker active as long as there is a MessagePort with pending activity or that is remotely entangled.
    198     HashSet<MessagePort*>::const_iterator messagePortsEnd = messagePorts().end();
    199     for (HashSet<MessagePort*>::const_iterator iter = messagePorts().begin(); iter != messagePortsEnd; ++iter) {
    200         if ((*iter)->hasPendingActivity() || ((*iter)->isEntangled() && !(*iter)->locallyEntangledPort()))
    201             return true;
    202     }
    203 
    204     return false;
    205 }
    206 
    207 void WorkerContext::postTask(PassOwnPtr<Task> task)
    208 {
    209     thread()->runLoop().postTask(task);
    210 }
    211 
    212 int WorkerContext::setTimeout(PassOwnPtr<ScheduledAction> action, int timeout)
    213 {
    214     return DOMTimer::install(scriptExecutionContext(), action, timeout, true);
    215 }
    216 
    217 void WorkerContext::clearTimeout(int timeoutId)
    218 {
    219     DOMTimer::removeById(scriptExecutionContext(), timeoutId);
    220 }
    221 
    222 int WorkerContext::setInterval(PassOwnPtr<ScheduledAction> action, int timeout)
    223 {
    224     return DOMTimer::install(scriptExecutionContext(), action, timeout, false);
    225 }
    226 
    227 void WorkerContext::clearInterval(int timeoutId)
    228 {
    229     DOMTimer::removeById(scriptExecutionContext(), timeoutId);
    230 }
    231 
    232 void WorkerContext::importScripts(const Vector<String>& urls, ExceptionCode& ec)
    233 {
    234     ec = 0;
    235     Vector<String>::const_iterator urlsEnd = urls.end();
    236     Vector<KURL> completedURLs;
    237     for (Vector<String>::const_iterator it = urls.begin(); it != urlsEnd; ++it) {
    238         const KURL& url = scriptExecutionContext()->completeURL(*it);
    239         if (!url.isValid()) {
    240             ec = SYNTAX_ERR;
    241             return;
    242         }
    243         completedURLs.append(url);
    244     }
    245     Vector<KURL>::const_iterator end = completedURLs.end();
    246 
    247     for (Vector<KURL>::const_iterator it = completedURLs.begin(); it != end; ++it) {
    248         WorkerScriptLoader scriptLoader(ResourceRequestBase::TargetIsScript);
    249         scriptLoader.loadSynchronously(scriptExecutionContext(), *it, AllowCrossOriginRequests);
    250 
    251         // If the fetching attempt failed, throw a NETWORK_ERR exception and abort all these steps.
    252         if (scriptLoader.failed()) {
    253             ec = XMLHttpRequestException::NETWORK_ERR;
    254             return;
    255         }
    256 
    257        InspectorInstrumentation::scriptImported(scriptExecutionContext(), scriptLoader.identifier(), scriptLoader.script());
    258 
    259         ScriptValue exception;
    260         m_script->evaluate(ScriptSourceCode(scriptLoader.script(), scriptLoader.responseURL()), &exception);
    261         if (!exception.hasNoValue()) {
    262             m_script->setException(exception);
    263             return;
    264         }
    265     }
    266 }
    267 
    268 EventTarget* WorkerContext::errorEventTarget()
    269 {
    270     return this;
    271 }
    272 
    273 void WorkerContext::logExceptionToConsole(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>)
    274 {
    275     thread()->workerReportingProxy().postExceptionToWorkerObject(errorMessage, lineNumber, sourceURL);
    276 }
    277 
    278 void WorkerContext::addMessage(MessageSource source, MessageType type, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>)
    279 {
    280     thread()->workerReportingProxy().postConsoleMessageToWorkerObject(source, type, level, message, lineNumber, sourceURL);
    281 }
    282 
    283 #if ENABLE(NOTIFICATIONS)
    284 NotificationCenter* WorkerContext::webkitNotifications() const
    285 {
    286     if (!m_notifications)
    287         m_notifications = NotificationCenter::create(scriptExecutionContext(), m_thread->getNotificationPresenter());
    288     return m_notifications.get();
    289 }
    290 #endif
    291 
    292 #if ENABLE(DATABASE)
    293 PassRefPtr<Database> WorkerContext::openDatabase(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, ExceptionCode& ec)
    294 {
    295     if (!securityOrigin()->canAccessDatabase() || !AbstractDatabase::isAvailable()) {
    296         ec = SECURITY_ERR;
    297         return 0;
    298     }
    299 
    300     return Database::openDatabase(this, name, version, displayName, estimatedSize, creationCallback, ec);
    301 }
    302 
    303 void WorkerContext::databaseExceededQuota(const String&)
    304 {
    305 #if !PLATFORM(CHROMIUM)
    306     // FIXME: This needs a real implementation; this is a temporary solution for testing.
    307     const unsigned long long defaultQuota = 5 * 1024 * 1024;
    308     DatabaseTracker::tracker().setQuota(securityOrigin(), defaultQuota);
    309 #endif
    310 }
    311 
    312 PassRefPtr<DatabaseSync> WorkerContext::openDatabaseSync(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, ExceptionCode& ec)
    313 {
    314     if (!securityOrigin()->canAccessDatabase() || !AbstractDatabase::isAvailable()) {
    315         ec = SECURITY_ERR;
    316         return 0;
    317     }
    318 
    319     return DatabaseSync::openDatabaseSync(this, name, version, displayName, estimatedSize, creationCallback, ec);
    320 }
    321 #endif
    322 
    323 bool WorkerContext::isContextThread() const
    324 {
    325     return currentThread() == thread()->threadID();
    326 }
    327 
    328 bool WorkerContext::isJSExecutionForbidden() const
    329 {
    330     return m_script->isExecutionForbidden();
    331 }
    332 
    333 EventTargetData* WorkerContext::eventTargetData()
    334 {
    335     return &m_eventTargetData;
    336 }
    337 
    338 EventTargetData* WorkerContext::ensureEventTargetData()
    339 {
    340     return &m_eventTargetData;
    341 }
    342 
    343 #if ENABLE(BLOB)
    344 DOMURL* WorkerContext::webkitURL() const
    345 {
    346     if (!m_domURL)
    347         m_domURL = DOMURL::create(this->scriptExecutionContext());
    348     return m_domURL.get();
    349 }
    350 #endif
    351 
    352 #if ENABLE(FILE_SYSTEM)
    353 void WorkerContext::webkitRequestFileSystem(int type, long long size, PassRefPtr<FileSystemCallback> successCallback, PassRefPtr<ErrorCallback> errorCallback)
    354 {
    355     if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem()) {
    356         DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::SECURITY_ERR));
    357         return;
    358     }
    359 
    360     AsyncFileSystem::Type fileSystemType = static_cast<AsyncFileSystem::Type>(type);
    361     if (fileSystemType != AsyncFileSystem::Temporary && fileSystemType != AsyncFileSystem::Persistent && fileSystemType != AsyncFileSystem::External) {
    362         DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::INVALID_MODIFICATION_ERR));
    363         return;
    364     }
    365 
    366     LocalFileSystem::localFileSystem().requestFileSystem(this, fileSystemType, size, FileSystemCallbacks::create(successCallback, errorCallback, this), false);
    367 }
    368 
    369 PassRefPtr<DOMFileSystemSync> WorkerContext::webkitRequestFileSystemSync(int type, long long size, ExceptionCode& ec)
    370 {
    371     ec = 0;
    372     if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem()) {
    373         ec = FileException::SECURITY_ERR;
    374         return 0;
    375     }
    376 
    377     AsyncFileSystem::Type fileSystemType = static_cast<AsyncFileSystem::Type>(type);
    378     if (fileSystemType != AsyncFileSystem::Temporary && fileSystemType != AsyncFileSystem::Persistent && fileSystemType != AsyncFileSystem::External) {
    379         ec = FileException::INVALID_MODIFICATION_ERR;
    380         return 0;
    381     }
    382 
    383     FileSystemSyncCallbackHelper helper;
    384     LocalFileSystem::localFileSystem().requestFileSystem(this, fileSystemType, size, FileSystemCallbacks::create(helper.successCallback(), helper.errorCallback(), this), true);
    385     return helper.getResult(ec);
    386 }
    387 
    388 void WorkerContext::webkitResolveLocalFileSystemURL(const String& url, PassRefPtr<EntryCallback> successCallback, PassRefPtr<ErrorCallback> errorCallback)
    389 {
    390     KURL completedURL = completeURL(url);
    391     if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem() || !securityOrigin()->canRequest(completedURL)) {
    392         DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::SECURITY_ERR));
    393         return;
    394     }
    395 
    396     AsyncFileSystem::Type type;
    397     String filePath;
    398     if (!completedURL.isValid() || !DOMFileSystemBase::crackFileSystemURL(completedURL, type, filePath)) {
    399         DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::ENCODING_ERR));
    400         return;
    401     }
    402 
    403     LocalFileSystem::localFileSystem().readFileSystem(this, type, ResolveURICallbacks::create(successCallback, errorCallback, this, filePath));
    404 }
    405 
    406 PassRefPtr<EntrySync> WorkerContext::webkitResolveLocalFileSystemSyncURL(const String& url, ExceptionCode& ec)
    407 {
    408     ec = 0;
    409     KURL completedURL = completeURL(url);
    410     if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem() || !securityOrigin()->canRequest(completedURL)) {
    411         ec = FileException::SECURITY_ERR;
    412         return 0;
    413     }
    414 
    415     AsyncFileSystem::Type type;
    416     String filePath;
    417     if (!completedURL.isValid() || !DOMFileSystemBase::crackFileSystemURL(completedURL, type, filePath)) {
    418         ec = FileException::ENCODING_ERR;
    419         return 0;
    420     }
    421 
    422     FileSystemSyncCallbackHelper readFileSystemHelper;
    423     LocalFileSystem::localFileSystem().readFileSystem(this, type, FileSystemCallbacks::create(readFileSystemHelper.successCallback(), readFileSystemHelper.errorCallback(), this), true);
    424     RefPtr<DOMFileSystemSync> fileSystem = readFileSystemHelper.getResult(ec);
    425     if (!fileSystem)
    426         return 0;
    427 
    428     RefPtr<EntrySync> entry = fileSystem->root()->getDirectory(filePath, 0, ec);
    429     if (ec == FileException::TYPE_MISMATCH_ERR)
    430         return fileSystem->root()->getFile(filePath, 0, ec);
    431 
    432     return entry.release();
    433 }
    434 
    435 COMPILE_ASSERT(static_cast<int>(WorkerContext::TEMPORARY) == static_cast<int>(AsyncFileSystem::Temporary), enum_mismatch);
    436 COMPILE_ASSERT(static_cast<int>(WorkerContext::PERSISTENT) == static_cast<int>(AsyncFileSystem::Persistent), enum_mismatch);
    437 COMPILE_ASSERT(static_cast<int>(WorkerContext::EXTERNAL) == static_cast<int>(AsyncFileSystem::External), enum_mismatch);
    438 #endif
    439 
    440 WorkerContext::Observer::Observer(WorkerContext* context)
    441     : m_context(context)
    442 {
    443     ASSERT(m_context && m_context->isContextThread());
    444     m_context->registerObserver(this);
    445 }
    446 
    447 WorkerContext::Observer::~Observer()
    448 {
    449     if (!m_context)
    450         return;
    451     ASSERT(m_context->isContextThread());
    452     m_context->unregisterObserver(this);
    453 }
    454 
    455 void WorkerContext::Observer::stopObserving()
    456 {
    457     if (!m_context)
    458         return;
    459     ASSERT(m_context->isContextThread());
    460     m_context->unregisterObserver(this);
    461     m_context = 0;
    462 }
    463 
    464 void WorkerContext::registerObserver(Observer* observer)
    465 {
    466     ASSERT(observer);
    467     m_workerObservers.add(observer);
    468 }
    469 
    470 void WorkerContext::unregisterObserver(Observer* observer)
    471 {
    472     ASSERT(observer);
    473     m_workerObservers.remove(observer);
    474 }
    475 
    476 void WorkerContext::notifyObserversOfStop()
    477 {
    478     HashSet<Observer*>::iterator iter = m_workerObservers.begin();
    479     while (iter != m_workerObservers.end()) {
    480         WorkerContext::Observer* observer = *iter;
    481         observer->stopObserving();
    482         observer->notifyStop();
    483         iter = m_workerObservers.begin();
    484     }
    485 }
    486 
    487 } // namespace WebCore
    488 
    489 #endif // ENABLE(WORKERS)
    490