Home | History | Annotate | Download | only in serviceworkers
      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 
     54 namespace blink {
     55 
     56 // This wraps CallbackPromiseAdapter and resolves the promise with undefined
     57 // when nullptr is given to onSuccess.
     58 class GetRegistrationCallback : public WebServiceWorkerProvider::WebServiceWorkerGetRegistrationCallbacks {
     59 public:
     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); }
     72 private:
     73     RefPtr<ScriptPromiseResolver> m_resolver;
     74     CallbackPromiseAdapter<ServiceWorkerRegistration, ServiceWorkerError> m_adapter;
     75     WTF_MAKE_NONCOPYABLE(GetRegistrationCallback);
     76 };
     77 
     78 ServiceWorkerContainer* ServiceWorkerContainer::create(ExecutionContext* executionContext)
     79 {
     80     return new ServiceWorkerContainer(executionContext);
     81 }
     82 
     83 ServiceWorkerContainer::~ServiceWorkerContainer()
     84 {
     85     ASSERT(!m_provider);
     86 }
     87 
     88 void ServiceWorkerContainer::willBeDetachedFromFrame()
     89 {
     90     if (m_provider) {
     91         m_provider->setClient(0);
     92         m_provider = nullptr;
     93     }
     94 }
     95 
     96 void ServiceWorkerContainer::trace(Visitor* visitor)
     97 {
     98     visitor->trace(m_controller);
     99     visitor->trace(m_readyRegistration);
    100     visitor->trace(m_ready);
    101 }
    102 
    103 ScriptPromise 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 
    143 class BooleanValue {
    144 public:
    145     typedef bool WebType;
    146     static bool take(ScriptPromiseResolver* resolver, WebType* boolean)
    147     {
    148         return *boolean;
    149     }
    150     static void dispose(WebType* boolean) { }
    151 
    152 private:
    153     BooleanValue();
    154 };
    155 
    156 ScriptPromise 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 
    182 ServiceWorkerContainer::ReadyProperty* ServiceWorkerContainer::createReadyProperty()
    183 {
    184     return new ReadyProperty(executionContext(), this, ReadyProperty::Ready);
    185 }
    186 
    187 ScriptPromise 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.
    204 static void deleteIfNoExistingOwner(WebServiceWorker* serviceWorker)
    205 {
    206     if (serviceWorker && !serviceWorker->proxy())
    207         delete serviceWorker;
    208 }
    209 
    210 static void deleteIfNoExistingOwner(WebServiceWorkerRegistration* registration)
    211 {
    212     if (registration && !registration->proxy())
    213         delete registration;
    214 }
    215 
    216 void ServiceWorkerContainer::setController(WebServiceWorker* serviceWorker)
    217 {
    218     if (!executionContext()) {
    219         deleteIfNoExistingOwner(serviceWorker);
    220         return;
    221     }
    222     m_controller = ServiceWorker::from(executionContext(), serviceWorker);
    223 }
    224 
    225 void 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 
    245 void 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 
    255 ServiceWorkerContainer::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