1/*
2 * Copyright (C) 2013 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#include "config.h"
31#include "modules/serviceworkers/ServiceWorkerContainer.h"
32
33#include "bindings/core/v8/CallbackPromiseAdapter.h"
34#include "bindings/core/v8/ScriptPromise.h"
35#include "bindings/core/v8/ScriptPromiseResolver.h"
36#include "bindings/core/v8/ScriptState.h"
37#include "bindings/core/v8/SerializedScriptValue.h"
38#include "core/dom/DOMException.h"
39#include "core/dom/ExceptionCode.h"
40#include "core/dom/ExecutionContext.h"
41#include "core/dom/MessagePort.h"
42#include "core/events/MessageEvent.h"
43#include "modules/serviceworkers/ServiceWorker.h"
44#include "modules/serviceworkers/ServiceWorkerContainerClient.h"
45#include "modules/serviceworkers/ServiceWorkerError.h"
46#include "modules/serviceworkers/ServiceWorkerRegistration.h"
47#include "platform/RuntimeEnabledFeatures.h"
48#include "public/platform/WebServiceWorker.h"
49#include "public/platform/WebServiceWorkerProvider.h"
50#include "public/platform/WebServiceWorkerRegistration.h"
51#include "public/platform/WebString.h"
52#include "public/platform/WebURL.h"
53
54namespace blink {
55
56// This wraps CallbackPromiseAdapter and resolves the promise with undefined
57// when nullptr is given to onSuccess.
58class GetRegistrationCallback : public WebServiceWorkerProvider::WebServiceWorkerGetRegistrationCallbacks {
59public:
60    explicit GetRegistrationCallback(PassRefPtr<ScriptPromiseResolver> resolver)
61        : m_resolver(resolver)
62        , m_adapter(m_resolver) { }
63    virtual ~GetRegistrationCallback() { }
64    virtual void onSuccess(WebServiceWorkerRegistration* registration) OVERRIDE
65    {
66        if (registration)
67            m_adapter.onSuccess(registration);
68        else if (m_resolver->executionContext() && !m_resolver->executionContext()->activeDOMObjectsAreStopped())
69            m_resolver->resolve();
70    }
71    virtual void onError(WebServiceWorkerError* error) OVERRIDE { m_adapter.onError(error); }
72private:
73    RefPtr<ScriptPromiseResolver> m_resolver;
74    CallbackPromiseAdapter<ServiceWorkerRegistration, ServiceWorkerError> m_adapter;
75    WTF_MAKE_NONCOPYABLE(GetRegistrationCallback);
76};
77
78ServiceWorkerContainer* ServiceWorkerContainer::create(ExecutionContext* executionContext)
79{
80    return new ServiceWorkerContainer(executionContext);
81}
82
83ServiceWorkerContainer::~ServiceWorkerContainer()
84{
85    ASSERT(!m_provider);
86}
87
88void ServiceWorkerContainer::willBeDetachedFromFrame()
89{
90    if (m_provider) {
91        m_provider->setClient(0);
92        m_provider = nullptr;
93    }
94}
95
96void ServiceWorkerContainer::trace(Visitor* visitor)
97{
98    visitor->trace(m_controller);
99    visitor->trace(m_readyRegistration);
100    visitor->trace(m_ready);
101}
102
103ScriptPromise ServiceWorkerContainer::registerServiceWorker(ScriptState* scriptState, const String& url, const RegistrationOptionList& options)
104{
105    ASSERT(RuntimeEnabledFeatures::serviceWorkerEnabled());
106    RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
107    ScriptPromise promise = resolver->promise();
108
109    if (!m_provider) {
110        resolver->reject(DOMException::create(InvalidStateError, "No associated provider is available"));
111        return promise;
112    }
113
114    // FIXME: This should use the container's execution context, not
115    // the callers.
116    ExecutionContext* executionContext = scriptState->executionContext();
117    RefPtr<SecurityOrigin> documentOrigin = executionContext->securityOrigin();
118    String errorMessage;
119    if (!documentOrigin->canAccessFeatureRequiringSecureOrigin(errorMessage)) {
120        resolver->reject(DOMException::create(NotSupportedError, errorMessage));
121        return promise;
122    }
123
124    KURL patternURL = executionContext->completeURL(options.scope());
125    patternURL.removeFragmentIdentifier();
126    if (!documentOrigin->canRequest(patternURL)) {
127        resolver->reject(DOMException::create(SecurityError, "The scope must match the current origin."));
128        return promise;
129    }
130
131    KURL scriptURL = executionContext->completeURL(url);
132    scriptURL.removeFragmentIdentifier();
133    if (!documentOrigin->canRequest(scriptURL)) {
134        resolver->reject(DOMException::create(SecurityError, "The origin of the script must match the current origin."));
135        return promise;
136    }
137
138    m_provider->registerServiceWorker(patternURL, scriptURL, new CallbackPromiseAdapter<ServiceWorkerRegistration, ServiceWorkerError>(resolver));
139
140    return promise;
141}
142
143class BooleanValue {
144public:
145    typedef bool WebType;
146    static bool take(ScriptPromiseResolver* resolver, WebType* boolean)
147    {
148        return *boolean;
149    }
150    static void dispose(WebType* boolean) { }
151
152private:
153    BooleanValue();
154};
155
156ScriptPromise ServiceWorkerContainer::getRegistration(ScriptState* scriptState, const String& documentURL)
157{
158    ASSERT(RuntimeEnabledFeatures::serviceWorkerEnabled());
159    RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
160    ScriptPromise promise = resolver->promise();
161
162    // FIXME: This should use the container's execution context, not
163    // the callers.
164    ExecutionContext* executionContext = scriptState->executionContext();
165    RefPtr<SecurityOrigin> documentOrigin = executionContext->securityOrigin();
166    String errorMessage;
167    if (!documentOrigin->canAccessFeatureRequiringSecureOrigin(errorMessage)) {
168        resolver->reject(DOMException::create(NotSupportedError, errorMessage));
169        return promise;
170    }
171
172    KURL completedURL = executionContext->completeURL(documentURL);
173    if (!documentOrigin->canRequest(completedURL)) {
174        resolver->reject(DOMException::create(SecurityError, "The documentURL must match the current origin."));
175        return promise;
176    }
177    m_provider->getRegistration(completedURL, new GetRegistrationCallback(resolver));
178
179    return promise;
180}
181
182ServiceWorkerContainer::ReadyProperty* ServiceWorkerContainer::createReadyProperty()
183{
184    return new ReadyProperty(executionContext(), this, ReadyProperty::Ready);
185}
186
187ScriptPromise ServiceWorkerContainer::ready(ScriptState* callerState)
188{
189    if (!executionContext())
190        return ScriptPromise();
191
192    if (!callerState->world().isMainWorld()) {
193        // FIXME: Support .ready from isolated worlds when
194        // ScriptPromiseProperty can vend Promises in isolated worlds.
195        return ScriptPromise::rejectWithDOMException(callerState, DOMException::create(NotSupportedError, "'ready' is only supported in pages."));
196    }
197
198    return m_ready->promise(callerState->world());
199}
200
201// If the WebServiceWorker is up for adoption (does not have a
202// WebServiceWorkerProxy owner), rejects the adoption by deleting the
203// WebServiceWorker.
204static void deleteIfNoExistingOwner(WebServiceWorker* serviceWorker)
205{
206    if (serviceWorker && !serviceWorker->proxy())
207        delete serviceWorker;
208}
209
210static void deleteIfNoExistingOwner(WebServiceWorkerRegistration* registration)
211{
212    if (registration && !registration->proxy())
213        delete registration;
214}
215
216void ServiceWorkerContainer::setController(WebServiceWorker* serviceWorker)
217{
218    if (!executionContext()) {
219        deleteIfNoExistingOwner(serviceWorker);
220        return;
221    }
222    m_controller = ServiceWorker::from(executionContext(), serviceWorker);
223}
224
225void ServiceWorkerContainer::setReadyRegistration(WebServiceWorkerRegistration* registration)
226{
227    if (!executionContext()) {
228        deleteIfNoExistingOwner(registration);
229        return;
230    }
231
232    ServiceWorkerRegistration* readyRegistration = ServiceWorkerRegistration::from(executionContext(), registration);
233    ASSERT(readyRegistration->active());
234
235    if (m_readyRegistration) {
236        ASSERT(m_readyRegistration == readyRegistration);
237        ASSERT(m_ready->state() == ReadyProperty::Resolved);
238        return;
239    }
240
241    m_readyRegistration = readyRegistration;
242    m_ready->resolve(readyRegistration);
243}
244
245void ServiceWorkerContainer::dispatchMessageEvent(const WebString& message, const WebMessagePortChannelArray& webChannels)
246{
247    if (!executionContext() || !executionContext()->executingWindow())
248        return;
249
250    OwnPtrWillBeRawPtr<MessagePortArray> ports = MessagePort::toMessagePortArray(executionContext(), webChannels);
251    RefPtr<SerializedScriptValue> value = SerializedScriptValue::createFromWire(message);
252    executionContext()->executingWindow()->dispatchEvent(MessageEvent::create(ports.release(), value));
253}
254
255ServiceWorkerContainer::ServiceWorkerContainer(ExecutionContext* executionContext)
256    : ContextLifecycleObserver(executionContext)
257    , m_provider(0)
258{
259
260    if (!executionContext)
261        return;
262
263    m_ready = createReadyProperty();
264
265    if (ServiceWorkerContainerClient* client = ServiceWorkerContainerClient::from(executionContext)) {
266        m_provider = client->provider();
267        if (m_provider)
268            m_provider->setClient(this);
269    }
270}
271
272} // namespace blink
273