1/*
2 * Copyright (C) 2008-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 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 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 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#include "config.h"
28#include "Worker.h"
29
30#include "ContentSecurityPolicy.h"
31#include "Event.h"
32#include "EventNames.h"
33#include "InspectorInstrumentation.h"
34#include "LoaderStrategy.h"
35#include "PlatformStrategies.h"
36#include "ResourceResponse.h"
37#include "SecurityOrigin.h"
38#include "WorkerGlobalScopeProxy.h"
39#include "WorkerScriptLoader.h"
40#include "WorkerThread.h"
41#include <JavaScriptCore/IdentifiersFactory.h>
42#include <wtf/HashSet.h>
43#include <wtf/IsoMallocInlines.h>
44#include <wtf/MainThread.h>
45#include <wtf/NeverDestroyed.h>
46
47namespace WebCore {
48
49WTF_MAKE_ISO_ALLOCATED_IMPL(Worker);
50
51static HashSet<Worker*>& allWorkers()
52{
53 static NeverDestroyed<HashSet<Worker*>> set;
54 return set;
55}
56
57void Worker::networkStateChanged(bool isOnLine)
58{
59 for (auto* worker : allWorkers())
60 worker->notifyNetworkStateChange(isOnLine);
61}
62
63inline Worker::Worker(ScriptExecutionContext& context, JSC::RuntimeFlags runtimeFlags, const Options& options)
64 : ActiveDOMObject(&context)
65 , m_name(options.name)
66 , m_identifier("worker:" + Inspector::IdentifiersFactory::createIdentifier())
67 , m_contextProxy(WorkerGlobalScopeProxy::create(*this))
68 , m_runtimeFlags(runtimeFlags)
69{
70 static bool addedListener;
71 if (!addedListener) {
72 platformStrategies()->loaderStrategy()->addOnlineStateChangeListener(&networkStateChanged);
73 addedListener = true;
74 }
75
76 auto addResult = allWorkers().add(this);
77 ASSERT_UNUSED(addResult, addResult.isNewEntry);
78}
79
80ExceptionOr<Ref<Worker>> Worker::create(ScriptExecutionContext& context, JSC::RuntimeFlags runtimeFlags, const String& url, const Options& options)
81{
82 ASSERT(isMainThread());
83
84 // We don't currently support nested workers, so workers can only be created from documents.
85 ASSERT_WITH_SECURITY_IMPLICATION(context.isDocument());
86
87 auto worker = adoptRef(*new Worker(context, runtimeFlags, options));
88
89 worker->suspendIfNeeded();
90
91 bool shouldBypassMainWorldContentSecurityPolicy = context.shouldBypassMainWorldContentSecurityPolicy();
92 auto scriptURL = worker->resolveURL(url, shouldBypassMainWorldContentSecurityPolicy);
93 if (scriptURL.hasException())
94 return scriptURL.releaseException();
95
96 worker->m_shouldBypassMainWorldContentSecurityPolicy = shouldBypassMainWorldContentSecurityPolicy;
97
98 // The worker context does not exist while loading, so we must ensure that the worker object is not collected, nor are its event listeners.
99 worker->setPendingActivity(worker.get());
100
101 // https://html.spec.whatwg.org/multipage/workers.html#official-moment-of-creation
102 worker->m_workerCreationTime = MonotonicTime::now();
103
104 worker->m_scriptLoader = WorkerScriptLoader::create();
105 auto contentSecurityPolicyEnforcement = shouldBypassMainWorldContentSecurityPolicy ? ContentSecurityPolicyEnforcement::DoNotEnforce : ContentSecurityPolicyEnforcement::EnforceChildSrcDirective;
106
107 ResourceRequest request { scriptURL.releaseReturnValue() };
108 request.setInitiatorIdentifier(worker->m_identifier);
109
110 FetchOptions fetchOptions;
111 fetchOptions.mode = FetchOptions::Mode::SameOrigin;
112 fetchOptions.cache = FetchOptions::Cache::Default;
113 fetchOptions.redirect = FetchOptions::Redirect::Follow;
114 fetchOptions.destination = FetchOptions::Destination::Worker;
115 worker->m_scriptLoader->loadAsynchronously(context, WTFMove(request), WTFMove(fetchOptions), contentSecurityPolicyEnforcement, ServiceWorkersMode::All, worker);
116 return worker;
117}
118
119Worker::~Worker()
120{
121 ASSERT(isMainThread());
122 ASSERT(scriptExecutionContext()); // The context is protected by worker context proxy, so it cannot be destroyed while a Worker exists.
123 allWorkers().remove(this);
124 m_contextProxy.workerObjectDestroyed();
125}
126
127ExceptionOr<void> Worker::postMessage(JSC::ExecState& state, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer)
128{
129 Vector<RefPtr<MessagePort>> ports;
130 auto message = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports, SerializationContext::WorkerPostMessage);
131 if (message.hasException())
132 return message.releaseException();
133
134 // Disentangle the port in preparation for sending it to the remote context.
135 auto channels = MessagePort::disentanglePorts(WTFMove(ports));
136 if (channels.hasException())
137 return channels.releaseException();
138
139 m_contextProxy.postMessageToWorkerGlobalScope({ message.releaseReturnValue(), channels.releaseReturnValue() });
140 return { };
141}
142
143void Worker::terminate()
144{
145 m_contextProxy.terminateWorkerGlobalScope();
146}
147
148bool Worker::canSuspendForDocumentSuspension() const
149{
150 // FIXME: It is not currently possible to suspend a worker, so pages with workers can not go into page cache.
151 return false;
152}
153
154const char* Worker::activeDOMObjectName() const
155{
156 return "Worker";
157}
158
159void Worker::stop()
160{
161 terminate();
162}
163
164bool Worker::hasPendingActivity() const
165{
166 return m_contextProxy.hasPendingActivity() || ActiveDOMObject::hasPendingActivity();
167}
168
169void Worker::notifyNetworkStateChange(bool isOnLine)
170{
171 m_contextProxy.notifyNetworkStateChange(isOnLine);
172}
173
174void Worker::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
175{
176 const URL& responseURL = response.url();
177 if (!responseURL.protocolIsBlob() && !responseURL.protocolIs("file") && !SecurityOrigin::create(responseURL)->isUnique())
178 m_contentSecurityPolicyResponseHeaders = ContentSecurityPolicyResponseHeaders(response);
179 InspectorInstrumentation::didReceiveScriptResponse(scriptExecutionContext(), identifier);
180}
181
182void Worker::notifyFinished()
183{
184 auto* context = scriptExecutionContext();
185 PAL::SessionID sessionID = context ? context->sessionID() : PAL::SessionID();
186
187 if (m_scriptLoader->failed() || !sessionID.isValid())
188 dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::Yes));
189 else {
190 bool isOnline = platformStrategies()->loaderStrategy()->isOnLine();
191 const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders = m_contentSecurityPolicyResponseHeaders ? m_contentSecurityPolicyResponseHeaders.value() : scriptExecutionContext()->contentSecurityPolicy()->responseHeaders();
192 m_contextProxy.startWorkerGlobalScope(m_scriptLoader->url(), m_name, scriptExecutionContext()->userAgent(m_scriptLoader->url()), isOnline, m_scriptLoader->script(), contentSecurityPolicyResponseHeaders, m_shouldBypassMainWorldContentSecurityPolicy, m_workerCreationTime, m_runtimeFlags, sessionID);
193 InspectorInstrumentation::scriptImported(*scriptExecutionContext(), m_scriptLoader->identifier(), m_scriptLoader->script());
194 }
195 m_scriptLoader = nullptr;
196
197 unsetPendingActivity(*this);
198}
199
200} // namespace WebCore
201