Fetching a Worker with url that isn't allowed from a file based test causes DRT to...
[WebKit-https.git] / Source / WebCore / workers / WorkerContext.cpp
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 "ScheduledAction.h"
51 #include "ScriptCallStack.h"
52 #include "ScriptSourceCode.h"
53 #include "ScriptValue.h"
54 #include "SecurityOrigin.h"
55 #include "WorkerInspectorController.h"
56 #include "WorkerLocation.h"
57 #include "WorkerNavigator.h"
58 #include "WorkerObjectProxy.h"
59 #include "WorkerScriptLoader.h"
60 #include "WorkerThread.h"
61 #include "WorkerThreadableLoader.h"
62 #include "XMLHttpRequestException.h"
63 #include <wtf/RefPtr.h>
64 #include <wtf/UnusedParam.h>
65
66 #if ENABLE(NOTIFICATIONS)
67 #include "NotificationCenter.h"
68 #endif
69
70 #if ENABLE(FILE_SYSTEM)
71 #include "AsyncFileSystem.h"
72 #include "DirectoryEntrySync.h"
73 #include "DOMFileSystem.h"
74 #include "DOMFileSystemBase.h"
75 #include "DOMFileSystemSync.h"
76 #include "ErrorCallback.h"
77 #include "FileEntrySync.h"
78 #include "FileError.h"
79 #include "FileException.h"
80 #include "FileSystemCallback.h"
81 #include "FileSystemCallbacks.h"
82 #include "LocalFileSystem.h"
83 #include "SyncCallbackHelper.h"
84 #endif
85
86 namespace WebCore {
87
88 class CloseWorkerContextTask : public ScriptExecutionContext::Task {
89 public:
90     static PassOwnPtr<CloseWorkerContextTask> create()
91     {
92         return adoptPtr(new CloseWorkerContextTask);
93     }
94
95     virtual void performTask(ScriptExecutionContext *context)
96     {
97         ASSERT(context->isWorkerContext());
98         WorkerContext* workerContext = static_cast<WorkerContext*>(context);
99         // Notify parent that this context is closed. Parent is responsible for calling WorkerThread::stop().
100         workerContext->thread()->workerReportingProxy().workerContextClosed();
101     }
102
103     virtual bool isCleanupTask() const { return true; }
104 };
105
106 WorkerContext::WorkerContext(const KURL& url, const String& userAgent, WorkerThread* thread)
107     : m_url(url)
108     , m_userAgent(userAgent)
109     , m_script(adoptPtr(new WorkerScriptController(this)))
110     , m_thread(thread)
111 #if ENABLE(INSPECTOR)
112     , m_workerInspectorController(adoptPtr(new WorkerInspectorController(this)))
113 #endif
114     , m_closing(false)
115 {
116     setSecurityOrigin(SecurityOrigin::create(url));
117 }
118
119 WorkerContext::~WorkerContext()
120 {
121     ASSERT(currentThread() == thread()->threadID());
122 #if ENABLE(NOTIFICATIONS)
123     m_notifications.clear();
124 #endif
125
126     // Make sure we have no observers.
127     notifyObserversOfStop();
128
129     // Notify proxy that we are going away. This can free the WorkerThread object, so do not access it after this.
130     thread()->workerReportingProxy().workerContextDestroyed();
131 }
132
133 ScriptExecutionContext* WorkerContext::scriptExecutionContext() const
134 {
135     return const_cast<WorkerContext*>(this);
136 }
137
138 const KURL& WorkerContext::virtualURL() const
139 {
140     return m_url;
141 }
142
143 KURL WorkerContext::virtualCompleteURL(const String& url) const
144 {
145     return completeURL(url);
146 }
147
148 KURL WorkerContext::completeURL(const String& url) const
149 {
150     // Always return a null URL when passed a null string.
151     // FIXME: Should we change the KURL constructor to have this behavior?
152     if (url.isNull())
153         return KURL();
154     // Always use UTF-8 in Workers.
155     return KURL(m_url, url);
156 }
157
158 String WorkerContext::userAgent(const KURL&) const
159 {
160     return m_userAgent;
161 }
162
163 WorkerLocation* WorkerContext::location() const
164 {
165     if (!m_location)
166         m_location = WorkerLocation::create(m_url);
167     return m_location.get();
168 }
169
170 void WorkerContext::close()
171 {
172     if (m_closing)
173         return;
174
175     // Let current script run to completion but prevent future script evaluations.
176     // After m_closing is set, all the tasks in the queue continue to be fetched but only
177     // tasks with isCleanupTask()==true will be executed.
178     m_closing = true;
179     postTask(CloseWorkerContextTask::create());
180 }
181
182 WorkerNavigator* WorkerContext::navigator() const
183 {
184     if (!m_navigator)
185         m_navigator = WorkerNavigator::create(m_userAgent);
186     return m_navigator.get();
187 }
188
189 bool WorkerContext::hasPendingActivity() const
190 {
191     ActiveDOMObjectsMap& activeObjects = activeDOMObjects();
192     ActiveDOMObjectsMap::const_iterator activeObjectsEnd = activeObjects.end();
193     for (ActiveDOMObjectsMap::const_iterator iter = activeObjects.begin(); iter != activeObjectsEnd; ++iter) {
194         if (iter->first->hasPendingActivity())
195             return true;
196     }
197
198     HashSet<MessagePort*>::const_iterator messagePortsEnd = messagePorts().end();
199     for (HashSet<MessagePort*>::const_iterator iter = messagePorts().begin(); iter != messagePortsEnd; ++iter) {
200         if ((*iter)->hasPendingActivity())
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         RefPtr<WorkerScriptLoader> scriptLoader(WorkerScriptLoader::create(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)