1 /*
2 * Copyright (C) 2009 Google 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 are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32
33 #if ENABLE(SHARED_WORKERS)
34
35 #include "DefaultSharedWorkerRepository.h"
36
37 #include "ActiveDOMObject.h"
38 #include "MessagePort.h"
39 #include "NotImplemented.h"
40 #include "PlatformString.h"
41 #include "SecurityOrigin.h"
42 #include "SecurityOriginHash.h"
43 #include "SharedWorker.h"
44 #include "SharedWorkerContext.h"
45 #include "SharedWorkerThread.h"
46 #include "WorkerLoaderProxy.h"
47 #include "WorkerScriptLoader.h"
48 #include "WorkerScriptLoaderClient.h"
49
50 #include <wtf/Threading.h>
51
52 namespace WebCore {
53
54 class SharedWorkerProxy : public ThreadSafeShared<SharedWorkerProxy>, public WorkerLoaderProxy {
55 public:
create(const String & name,const KURL & url)56 static PassRefPtr<SharedWorkerProxy> create(const String& name, const KURL& url) { return adoptRef(new SharedWorkerProxy(name, url)); }
57
setThread(PassRefPtr<SharedWorkerThread> thread)58 void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; }
thread()59 SharedWorkerThread* thread() { return m_thread.get(); }
closing() const60 bool closing() const { return m_closing; }
url() const61 KURL url() const { return m_url.copy(); }
name() const62 String name() const { return m_name.copy(); }
63
64 // WorkerLoaderProxy
65 // FIXME: Implement WorkerLoaderProxy APIs by proxying to an active document.
postTaskToLoader(PassRefPtr<ScriptExecutionContext::Task>)66 virtual void postTaskToLoader(PassRefPtr<ScriptExecutionContext::Task>) { notImplemented(); }
postTaskForModeToWorkerContext(PassRefPtr<ScriptExecutionContext::Task>,const String &)67 virtual void postTaskForModeToWorkerContext(PassRefPtr<ScriptExecutionContext::Task>, const String&) { notImplemented(); }
68
69 // Updates the list of the worker's documents, per section 4.5 of the WebWorkers spec.
70 void addToWorkerDocuments(ScriptExecutionContext*);
71 private:
72 SharedWorkerProxy(const String& name, const KURL&);
73 bool m_closing;
74 String m_name;
75 KURL m_url;
76 RefPtr<SharedWorkerThread> m_thread;
77 };
78
SharedWorkerProxy(const String & name,const KURL & url)79 SharedWorkerProxy::SharedWorkerProxy(const String& name, const KURL& url)
80 : m_closing(false)
81 , m_name(name.copy())
82 , m_url(url.copy())
83 {
84 }
85
addToWorkerDocuments(ScriptExecutionContext * context)86 void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context)
87 {
88 // Nested workers are not yet supported, so passed-in context should always be a Document.
89 ASSERT(context->isDocument());
90 // FIXME: track referring documents so we can shutdown the thread when the last one exits and remove the proxy from the cache.
91 }
92
93 class SharedWorkerConnectTask : public ScriptExecutionContext::Task {
94 public:
create(PassOwnPtr<MessagePortChannel> channel)95 static PassRefPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel)
96 {
97 return adoptRef(new SharedWorkerConnectTask(channel));
98 }
99
100 private:
SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)101 SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)
102 : m_channel(channel)
103 {
104 }
105
performTask(ScriptExecutionContext * scriptContext)106 virtual void performTask(ScriptExecutionContext* scriptContext)
107 {
108 RefPtr<MessagePort> port = MessagePort::create(*scriptContext);
109 port->entangle(m_channel.release());
110 ASSERT(scriptContext->isWorkerContext());
111 WorkerContext* workerContext = static_cast<WorkerContext*>(scriptContext);
112 ASSERT(workerContext->isSharedWorkerContext());
113 workerContext->toSharedWorkerContext()->dispatchConnect(port);
114 }
115
116 OwnPtr<MessagePortChannel> m_channel;
117 };
118
119 // Loads the script on behalf of a worker.
120 class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, public ActiveDOMObject, private WorkerScriptLoaderClient {
121 public:
122 SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, PassOwnPtr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>);
123 void load(const KURL&);
124
125 private:
126 // WorkerScriptLoaderClient callback
127 virtual void notifyFinished();
128
129 RefPtr<SharedWorker> m_worker;
130 OwnPtr<MessagePortChannel> m_port;
131 RefPtr<SharedWorkerProxy> m_proxy;
132 OwnPtr<WorkerScriptLoader> m_scriptLoader;
133 };
134
SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,PassRefPtr<SharedWorkerProxy> proxy)135 SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy)
136 : ActiveDOMObject(worker->scriptExecutionContext(), this)
137 , m_worker(worker)
138 , m_port(port)
139 , m_proxy(proxy)
140 {
141 }
142
load(const KURL & url)143 void SharedWorkerScriptLoader::load(const KURL& url)
144 {
145 // Mark this object as active for the duration of the load.
146 ASSERT(!hasPendingActivity());
147 m_scriptLoader = new WorkerScriptLoader();
148 m_scriptLoader->loadAsynchronously(scriptExecutionContext(), url, DenyCrossOriginRedirect, this);
149
150 // Stay alive until the load finishes.
151 setPendingActivity(this);
152 }
153
notifyFinished()154 void SharedWorkerScriptLoader::notifyFinished()
155 {
156 // Hand off the just-loaded code to the repository to start up the worker thread.
157 if (m_scriptLoader->failed())
158 m_worker->dispatchLoadErrorEvent();
159 else
160 DefaultSharedWorkerRepository::instance().workerScriptLoaded(*m_proxy, scriptExecutionContext()->userAgent(m_scriptLoader->url()), m_scriptLoader->script(), m_port.release());
161
162 // This frees this object - must be the last action in this function.
163 unsetPendingActivity(this);
164 }
165
instance()166 DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
167 {
168 AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository);
169 return *instance;
170 }
171
workerScriptLoaded(SharedWorkerProxy & proxy,const String & userAgent,const String & workerScript,PassOwnPtr<MessagePortChannel> port)172 void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, PassOwnPtr<MessagePortChannel> port)
173 {
174 MutexLocker lock(m_lock);
175 if (proxy.closing())
176 return;
177
178 // Another loader may have already started up a thread for this proxy - if so, just send a connect to the pre-existing thread.
179 if (!proxy.thread()) {
180 RefPtr<SharedWorkerThread> thread = SharedWorkerThread::create(proxy.name(), proxy.url(), userAgent, workerScript, proxy);
181 proxy.setThread(thread);
182 thread->start();
183 }
184 proxy.thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
185 }
186
connect(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,const KURL & url,const String & name,ExceptionCode & ec)187 void SharedWorkerRepository::connect(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
188 {
189 DefaultSharedWorkerRepository::instance().connectToWorker(worker, port, url, name, ec);
190 }
191
connectToWorker(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,const KURL & url,const String & name,ExceptionCode & ec)192 void DefaultSharedWorkerRepository::connectToWorker(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
193 {
194 MutexLocker lock(m_lock);
195 ASSERT(worker->scriptExecutionContext()->securityOrigin()->canAccess(SecurityOrigin::create(url).get()));
196 // Fetch a proxy corresponding to this SharedWorker.
197 RefPtr<SharedWorkerProxy> proxy = getProxy(name, url);
198 proxy->addToWorkerDocuments(worker->scriptExecutionContext());
199 if (proxy->url() != url) {
200 // Proxy already existed under alternate URL - return an error.
201 ec = URL_MISMATCH_ERR;
202 return;
203 }
204 // If proxy is already running, just connect to it - otherwise, kick off a loader to load the script.
205 if (proxy->thread())
206 proxy->thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
207 else {
208 RefPtr<SharedWorkerScriptLoader> loader = adoptRef(new SharedWorkerScriptLoader(worker, port.release(), proxy.release()));
209 loader->load(url);
210 }
211 }
212
213 // Creates a new SharedWorkerProxy or returns an existing one from the repository. Must only be called while the repository mutex is held.
getProxy(const String & name,const KURL & url)214 PassRefPtr<SharedWorkerProxy> DefaultSharedWorkerRepository::getProxy(const String& name, const KURL& url)
215 {
216 // Look for an existing worker, and create one if it doesn't exist.
217 // Items in the cache are freed on another thread, so copy the URL before creating the origin, to make sure no references to external strings linger.
218 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url.copy());
219 SharedWorkerNameMap* nameMap = m_cache.get(origin);
220 if (!nameMap) {
221 nameMap = new SharedWorkerNameMap();
222 m_cache.set(origin, nameMap);
223 }
224
225 RefPtr<SharedWorkerProxy> proxy = nameMap->get(name);
226 if (!proxy.get()) {
227 proxy = SharedWorkerProxy::create(name, url);
228 nameMap->set(proxy->name(), proxy);
229 }
230 return proxy;
231 }
232
DefaultSharedWorkerRepository()233 DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
234 {
235 }
236
~DefaultSharedWorkerRepository()237 DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
238 {
239 }
240
241 } // namespace WebCore
242
243 #endif // ENABLE(SHARED_WORKERS)
244