1/*
2 * Copyright (C) 2017 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 INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ServiceWorker.h"
28
29#if ENABLE(SERVICE_WORKER)
30
31#include "Document.h"
32#include "EventNames.h"
33#include "Logging.h"
34#include "MessagePort.h"
35#include "SWClientConnection.h"
36#include "ScriptExecutionContext.h"
37#include "SerializedScriptValue.h"
38#include "ServiceWorkerClientData.h"
39#include "ServiceWorkerContainer.h"
40#include "ServiceWorkerGlobalScope.h"
41#include "ServiceWorkerProvider.h"
42#include "ServiceWorkerThread.h"
43#include <JavaScriptCore/JSCJSValueInlines.h>
44#include <wtf/IsoMallocInlines.h>
45#include <wtf/NeverDestroyed.h>
46
47#define WORKER_RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorker::" fmt, this, ##__VA_ARGS__)
48#define WORKER_RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorker::" fmt, this, ##__VA_ARGS__)
49
50namespace WebCore {
51
52WTF_MAKE_ISO_ALLOCATED_IMPL(ServiceWorker);
53
54Ref<ServiceWorker> ServiceWorker::getOrCreate(ScriptExecutionContext& context, ServiceWorkerData&& data)
55{
56 if (auto existingServiceWorker = context.serviceWorker(data.identifier))
57 return *existingServiceWorker;
58 return adoptRef(*new ServiceWorker(context, WTFMove(data)));
59}
60
61ServiceWorker::ServiceWorker(ScriptExecutionContext& context, ServiceWorkerData&& data)
62 : ActiveDOMObject(&context)
63 , m_data(WTFMove(data))
64{
65 suspendIfNeeded();
66
67 context.registerServiceWorker(*this);
68
69 relaxAdoptionRequirement();
70 updatePendingActivityForEventDispatch();
71
72 WORKER_RELEASE_LOG_IF_ALLOWED("ServiceWorker: ID: %llu, state: %hhu", identifier().toUInt64(), m_data.state);
73}
74
75ServiceWorker::~ServiceWorker()
76{
77 if (auto* context = scriptExecutionContext())
78 context->unregisterServiceWorker(*this);
79}
80
81void ServiceWorker::updateState(State state)
82{
83 WORKER_RELEASE_LOG_IF_ALLOWED("updateState: Updating service worker %llu state from %hhu to %hhu. Registration ID: %llu", identifier().toUInt64(), m_data.state, state, registrationIdentifier().toUInt64());
84 m_data.state = state;
85 if (state != State::Installing && !m_isStopped) {
86 ASSERT(m_pendingActivityForEventDispatch);
87 dispatchEvent(Event::create(eventNames().statechangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
88 }
89
90 updatePendingActivityForEventDispatch();
91}
92
93ExceptionOr<void> ServiceWorker::postMessage(ScriptExecutionContext& context, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer)
94{
95 if (m_isStopped || !context.sessionID().isValid())
96 return Exception { InvalidStateError };
97
98 if (state() == State::Redundant)
99 return Exception { InvalidStateError, "Service Worker state is redundant"_s };
100
101 // FIXME: Invoke Run Service Worker algorithm with serviceWorker as the argument.
102
103 auto* execState = context.execState();
104 ASSERT(execState);
105
106 Vector<RefPtr<MessagePort>> ports;
107 auto messageData = SerializedScriptValue::create(*execState, messageValue, WTFMove(transfer), ports, SerializationContext::WorkerPostMessage);
108 if (messageData.hasException())
109 return messageData.releaseException();
110
111 // Disentangle the port in preparation for sending it to the remote context.
112 auto portsOrException = MessagePort::disentanglePorts(WTFMove(ports));
113 if (portsOrException.hasException())
114 return portsOrException.releaseException();
115
116 ServiceWorkerOrClientIdentifier sourceIdentifier;
117 if (is<ServiceWorkerGlobalScope>(context))
118 sourceIdentifier = downcast<ServiceWorkerGlobalScope>(context).thread().identifier();
119 else {
120 auto& connection = ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(context.sessionID());
121 sourceIdentifier = ServiceWorkerClientIdentifier { connection.serverConnectionIdentifier(), downcast<Document>(context).identifier() };
122 }
123
124 MessageWithMessagePorts message = { messageData.releaseReturnValue(), portsOrException.releaseReturnValue() };
125 callOnMainThread([sessionID = context.sessionID(), destinationIdentifier = identifier(), message = WTFMove(message), sourceIdentifier = WTFMove(sourceIdentifier)]() mutable {
126 auto& connection = ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(sessionID);
127 connection.postMessageToServiceWorker(destinationIdentifier, WTFMove(message), sourceIdentifier);
128 });
129 return { };
130}
131
132EventTargetInterface ServiceWorker::eventTargetInterface() const
133{
134 return ServiceWorkerEventTargetInterfaceType;
135}
136
137ScriptExecutionContext* ServiceWorker::scriptExecutionContext() const
138{
139 return ContextDestructionObserver::scriptExecutionContext();
140}
141
142const char* ServiceWorker::activeDOMObjectName() const
143{
144 return "ServiceWorker";
145}
146
147bool ServiceWorker::canSuspendForDocumentSuspension() const
148{
149 // FIXME: We should do better as this prevents the page from entering PageCache when there is a Service Worker.
150 return !hasPendingActivity();
151}
152
153void ServiceWorker::stop()
154{
155 m_isStopped = true;
156 removeAllEventListeners();
157 scriptExecutionContext()->unregisterServiceWorker(*this);
158 updatePendingActivityForEventDispatch();
159}
160
161void ServiceWorker::updatePendingActivityForEventDispatch()
162{
163 // ServiceWorkers can dispatch events until they become redundant or they are stopped.
164 if (m_isStopped || state() == State::Redundant) {
165 m_pendingActivityForEventDispatch = nullptr;
166 return;
167 }
168 if (m_pendingActivityForEventDispatch)
169 return;
170 m_pendingActivityForEventDispatch = makePendingActivity(*this);
171}
172
173bool ServiceWorker::isAlwaysOnLoggingAllowed() const
174{
175 auto* context = scriptExecutionContext();
176 if (!context)
177 return false;
178
179 auto* container = context->serviceWorkerContainer();
180 if (!container)
181 return false;
182
183 return container->isAlwaysOnLoggingAllowed();
184}
185
186} // namespace WebCore
187
188#endif // ENABLE(SERVICE_WORKER)
189