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:
create()61 static PassOwnPtr<ProcessMessagesSoonTask> create()
62 {
63 return new ProcessMessagesSoonTask;
64 }
65
performTask(ScriptExecutionContext * context)66 virtual void performTask(ScriptExecutionContext* context)
67 {
68 context->dispatchMessagePortEvents();
69 }
70 };
71
72 class ScriptExecutionContext::PendingException {
73 WTF_MAKE_NONCOPYABLE(PendingException);
74 public:
PendingException(const String & errorMessage,int lineNumber,const String & sourceURL,PassRefPtr<ScriptCallStack> callStack)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
ScriptExecutionContext()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
~ScriptExecutionContext()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
databaseThread()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
stopDatabases(DatabaseTaskSynchronizer * cleanupSync)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
processMessagePortMessagesSoon()165 void ScriptExecutionContext::processMessagePortMessagesSoon()
166 {
167 postTask(ProcessMessagesSoonTask::create());
168 }
169
dispatchMessagePortEvents()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
createdMessagePort(MessagePort * port)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
destroyedMessagePort(MessagePort * port)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)
createdDomUrl(DOMURL * url)211 void ScriptExecutionContext::createdDomUrl(DOMURL* url)
212 {
213 ASSERT(url);
214 m_domUrls.add(url);
215 }
216
destroyedDomUrl(DOMURL * url)217 void ScriptExecutionContext::destroyedDomUrl(DOMURL* url)
218 {
219 ASSERT(url);
220 m_domUrls.remove(url);
221 }
222 #endif
223
canSuspendActiveDOMObjects()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
suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why)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
resumeActiveDOMObjects()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
stopActiveDOMObjects()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
createdActiveDOMObject(ActiveDOMObject * object,void * upcastPointer)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
destroyedActiveDOMObject(ActiveDOMObject * object)289 void ScriptExecutionContext::destroyedActiveDOMObject(ActiveDOMObject* object)
290 {
291 ASSERT(object);
292 if (m_iteratingActiveDOMObjects)
293 CRASH();
294 m_activeDOMObjects.remove(object);
295 }
296
closeMessagePorts()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
setSecurityOrigin(PassRefPtr<SecurityOrigin> securityOrigin)305 void ScriptExecutionContext::setSecurityOrigin(PassRefPtr<SecurityOrigin> securityOrigin)
306 {
307 m_securityOrigin = securityOrigin;
308 }
309
sanitizeScriptError(String & errorMessage,int & lineNumber,String & sourceURL)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
reportException(const String & errorMessage,int lineNumber,const String & sourceURL,PassRefPtr<ScriptCallStack> callStack)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
dispatchErrorEvent(const String & errorMessage,int lineNumber,const String & sourceURL)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
addTimeout(int timeoutId,DOMTimer * timer)363 void ScriptExecutionContext::addTimeout(int timeoutId, DOMTimer* timer)
364 {
365 ASSERT(!m_timeouts.contains(timeoutId));
366 m_timeouts.set(timeoutId, timer);
367 }
368
removeTimeout(int timeoutId)369 void ScriptExecutionContext::removeTimeout(int timeoutId)
370 {
371 m_timeouts.remove(timeoutId);
372 }
373
findTimeout(int timeoutId)374 DOMTimer* ScriptExecutionContext::findTimeout(int timeoutId)
375 {
376 return m_timeouts.get(timeoutId);
377 }
378
379 #if ENABLE(BLOB)
createPublicBlobURL(Blob * 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
revokePublicBlobURL(const KURL & url)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)
fileThread()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
adjustMinimumTimerInterval(double oldMinimumTimerInterval)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
minimumTimerInterval() const423 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
~Task()433 ScriptExecutionContext::Task::~Task()
434 {
435 }
436
437 #if USE(JSC)
globalData()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