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:
create()89 static PassOwnPtr<CloseWorkerContextTask> create()
90 {
91 return new CloseWorkerContextTask;
92 }
93
performTask(ScriptExecutionContext * context)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
isCleanupTask() const102 virtual bool isCleanupTask() const { return true; }
103 };
104
WorkerContext(const KURL & url,const String & userAgent,WorkerThread * thread)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
~WorkerContext()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
scriptExecutionContext() const132 ScriptExecutionContext* WorkerContext::scriptExecutionContext() const
133 {
134 return const_cast<WorkerContext*>(this);
135 }
136
virtualURL() const137 const KURL& WorkerContext::virtualURL() const
138 {
139 return m_url;
140 }
141
virtualCompleteURL(const String & url) const142 KURL WorkerContext::virtualCompleteURL(const String& url) const
143 {
144 return completeURL(url);
145 }
146
completeURL(const String & url) const147 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
userAgent(const KURL &) const157 String WorkerContext::userAgent(const KURL&) const
158 {
159 return m_userAgent;
160 }
161
location() const162 WorkerLocation* WorkerContext::location() const
163 {
164 if (!m_location)
165 m_location = WorkerLocation::create(m_url);
166 return m_location.get();
167 }
168
close()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
navigator() const181 WorkerNavigator* WorkerContext::navigator() const
182 {
183 if (!m_navigator)
184 m_navigator = WorkerNavigator::create(m_userAgent);
185 return m_navigator.get();
186 }
187
hasPendingActivity() const188 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
postTask(PassOwnPtr<Task> task)207 void WorkerContext::postTask(PassOwnPtr<Task> task)
208 {
209 thread()->runLoop().postTask(task);
210 }
211
setTimeout(PassOwnPtr<ScheduledAction> action,int timeout)212 int WorkerContext::setTimeout(PassOwnPtr<ScheduledAction> action, int timeout)
213 {
214 return DOMTimer::install(scriptExecutionContext(), action, timeout, true);
215 }
216
clearTimeout(int timeoutId)217 void WorkerContext::clearTimeout(int timeoutId)
218 {
219 DOMTimer::removeById(scriptExecutionContext(), timeoutId);
220 }
221
setInterval(PassOwnPtr<ScheduledAction> action,int timeout)222 int WorkerContext::setInterval(PassOwnPtr<ScheduledAction> action, int timeout)
223 {
224 return DOMTimer::install(scriptExecutionContext(), action, timeout, false);
225 }
226
clearInterval(int timeoutId)227 void WorkerContext::clearInterval(int timeoutId)
228 {
229 DOMTimer::removeById(scriptExecutionContext(), timeoutId);
230 }
231
importScripts(const Vector<String> & urls,ExceptionCode & ec)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
errorEventTarget()268 EventTarget* WorkerContext::errorEventTarget()
269 {
270 return this;
271 }
272
logExceptionToConsole(const String & errorMessage,int lineNumber,const String & sourceURL,PassRefPtr<ScriptCallStack>)273 void WorkerContext::logExceptionToConsole(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>)
274 {
275 thread()->workerReportingProxy().postExceptionToWorkerObject(errorMessage, lineNumber, sourceURL);
276 }
277
addMessage(MessageSource source,MessageType type,MessageLevel level,const String & message,unsigned lineNumber,const String & sourceURL,PassRefPtr<ScriptCallStack>)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)
webkitNotifications() const284 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)
openDatabase(const String & name,const String & version,const String & displayName,unsigned long estimatedSize,PassRefPtr<DatabaseCallback> creationCallback,ExceptionCode & ec)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
databaseExceededQuota(const String &)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
openDatabaseSync(const String & name,const String & version,const String & displayName,unsigned long estimatedSize,PassRefPtr<DatabaseCallback> creationCallback,ExceptionCode & ec)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
isContextThread() const323 bool WorkerContext::isContextThread() const
324 {
325 return currentThread() == thread()->threadID();
326 }
327
isJSExecutionForbidden() const328 bool WorkerContext::isJSExecutionForbidden() const
329 {
330 return m_script->isExecutionForbidden();
331 }
332
eventTargetData()333 EventTargetData* WorkerContext::eventTargetData()
334 {
335 return &m_eventTargetData;
336 }
337
ensureEventTargetData()338 EventTargetData* WorkerContext::ensureEventTargetData()
339 {
340 return &m_eventTargetData;
341 }
342
343 #if ENABLE(BLOB)
webkitURL() const344 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)
webkitRequestFileSystem(int type,long long size,PassRefPtr<FileSystemCallback> successCallback,PassRefPtr<ErrorCallback> errorCallback)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
webkitRequestFileSystemSync(int type,long long size,ExceptionCode & ec)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
webkitResolveLocalFileSystemURL(const String & url,PassRefPtr<EntryCallback> successCallback,PassRefPtr<ErrorCallback> errorCallback)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
webkitResolveLocalFileSystemSyncURL(const String & url,ExceptionCode & ec)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
Observer(WorkerContext * context)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
~Observer()447 WorkerContext::Observer::~Observer()
448 {
449 if (!m_context)
450 return;
451 ASSERT(m_context->isContextThread());
452 m_context->unregisterObserver(this);
453 }
454
stopObserving()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
registerObserver(Observer * observer)464 void WorkerContext::registerObserver(Observer* observer)
465 {
466 ASSERT(observer);
467 m_workerObservers.add(observer);
468 }
469
unregisterObserver(Observer * observer)470 void WorkerContext::unregisterObserver(Observer* observer)
471 {
472 ASSERT(observer);
473 m_workerObservers.remove(observer);
474 }
475
notifyObserversOfStop()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