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
31#include "config.h"
32#include "web/WebEmbeddedWorkerImpl.h"
33
34#include "core/dom/CrossThreadTask.h"
35#include "core/dom/Document.h"
36#include "core/inspector/InspectorInstrumentation.h"
37#include "core/inspector/WorkerDebuggerAgent.h"
38#include "core/inspector/WorkerInspectorController.h"
39#include "core/loader/FrameLoadRequest.h"
40#include "core/loader/SubstituteData.h"
41#include "core/workers/WorkerClients.h"
42#include "core/workers/WorkerGlobalScope.h"
43#include "core/workers/WorkerInspectorProxy.h"
44#include "core/workers/WorkerLoaderProxy.h"
45#include "core/workers/WorkerScriptLoader.h"
46#include "core/workers/WorkerScriptLoaderClient.h"
47#include "core/workers/WorkerThreadStartupData.h"
48#include "modules/serviceworkers/ServiceWorkerThread.h"
49#include "platform/NotImplemented.h"
50#include "platform/SharedBuffer.h"
51#include "platform/heap/Handle.h"
52#include "platform/network/ContentSecurityPolicyParsers.h"
53#include "public/platform/Platform.h"
54#include "public/platform/WebURLRequest.h"
55#include "public/web/WebDevToolsAgent.h"
56#include "public/web/WebServiceWorkerContextClient.h"
57#include "public/web/WebServiceWorkerNetworkProvider.h"
58#include "public/web/WebSettings.h"
59#include "public/web/WebView.h"
60#include "public/web/WebWorkerPermissionClientProxy.h"
61#include "web/ServiceWorkerGlobalScopeClientImpl.h"
62#include "web/ServiceWorkerGlobalScopeProxy.h"
63#include "web/WebDataSourceImpl.h"
64#include "web/WebLocalFrameImpl.h"
65#include "web/WorkerPermissionClient.h"
66#include "wtf/Functional.h"
67
68namespace blink {
69
70// A thin wrapper for one-off script loading.
71class WebEmbeddedWorkerImpl::Loader : public WorkerScriptLoaderClient {
72public:
73    static PassOwnPtr<Loader> create()
74    {
75        return adoptPtr(new Loader());
76    }
77
78    virtual ~Loader()
79    {
80        m_scriptLoader->setClient(0);
81    }
82
83    void load(ExecutionContext* loadingContext, const KURL& scriptURL, const Closure& callback)
84    {
85        ASSERT(loadingContext);
86        m_callback = callback;
87        m_scriptLoader->setRequestContext(WebURLRequest::RequestContextServiceWorker);
88        m_scriptLoader->loadAsynchronously(
89            *loadingContext, scriptURL, DenyCrossOriginRequests, this);
90    }
91
92    virtual void notifyFinished() OVERRIDE
93    {
94        m_callback();
95    }
96
97    void cancel()
98    {
99        m_scriptLoader->cancel();
100    }
101
102    bool failed() const { return m_scriptLoader->failed(); }
103    const KURL& url() const { return m_scriptLoader->responseURL(); }
104    String script() const { return m_scriptLoader->script(); }
105
106private:
107    Loader() : m_scriptLoader(WorkerScriptLoader::create())
108    {
109    }
110
111    RefPtr<WorkerScriptLoader> m_scriptLoader;
112    Closure m_callback;
113};
114
115class WebEmbeddedWorkerImpl::LoaderProxy : public WorkerLoaderProxy {
116public:
117    static PassOwnPtr<LoaderProxy> create(WebEmbeddedWorkerImpl& embeddedWorker)
118    {
119        return adoptPtr(new LoaderProxy(embeddedWorker));
120    }
121
122    virtual void postTaskToLoader(PassOwnPtr<ExecutionContextTask> task) OVERRIDE
123    {
124        toWebLocalFrameImpl(m_embeddedWorker.m_mainFrame)->frame()->document()->postTask(task);
125    }
126
127    virtual bool postTaskToWorkerGlobalScope(PassOwnPtr<ExecutionContextTask> task) OVERRIDE
128    {
129        if (m_embeddedWorker.m_askedToTerminate || !m_embeddedWorker.m_workerThread)
130            return false;
131        m_embeddedWorker.m_workerThread->postTask(task);
132        return !m_embeddedWorker.m_workerThread->terminated();
133    }
134
135private:
136    explicit LoaderProxy(WebEmbeddedWorkerImpl& embeddedWorker)
137        : m_embeddedWorker(embeddedWorker)
138    {
139    }
140
141    // Not owned, embedded worker owns this.
142    WebEmbeddedWorkerImpl& m_embeddedWorker;
143};
144
145WebEmbeddedWorker* WebEmbeddedWorker::create(
146    WebServiceWorkerContextClient* client,
147    WebWorkerPermissionClientProxy* permissionClient)
148{
149    return new WebEmbeddedWorkerImpl(adoptPtr(client), adoptPtr(permissionClient));
150}
151
152static HashSet<WebEmbeddedWorkerImpl*>& runningWorkerInstances()
153{
154    DEFINE_STATIC_LOCAL(HashSet<WebEmbeddedWorkerImpl*>, set, ());
155    return set;
156}
157
158WebEmbeddedWorkerImpl::WebEmbeddedWorkerImpl(
159    PassOwnPtr<WebServiceWorkerContextClient> client,
160    PassOwnPtr<WebWorkerPermissionClientProxy> permissionClient)
161    : m_workerContextClient(client)
162    , m_permissionClient(permissionClient)
163    , m_workerInspectorProxy(WorkerInspectorProxy::create())
164    , m_webView(0)
165    , m_mainFrame(0)
166    , m_askedToTerminate(false)
167    , m_pauseAfterDownloadState(DontPauseAfterDownload)
168    , m_waitingForDebuggerState(NotWaitingForDebugger)
169{
170    runningWorkerInstances().add(this);
171}
172
173WebEmbeddedWorkerImpl::~WebEmbeddedWorkerImpl()
174{
175    ASSERT(runningWorkerInstances().contains(this));
176    runningWorkerInstances().remove(this);
177    ASSERT(m_webView);
178
179    // Detach the client before closing the view to avoid getting called back.
180    toWebLocalFrameImpl(m_mainFrame)->setClient(0);
181
182    m_webView->close();
183    m_mainFrame->close();
184}
185
186void WebEmbeddedWorkerImpl::terminateAll()
187{
188    HashSet<WebEmbeddedWorkerImpl*> instances = runningWorkerInstances();
189    for (HashSet<WebEmbeddedWorkerImpl*>::iterator it = instances.begin(), itEnd = instances.end(); it != itEnd; ++it) {
190        (*it)->terminateWorkerContext();
191    }
192}
193
194void WebEmbeddedWorkerImpl::startWorkerContext(
195    const WebEmbeddedWorkerStartData& data)
196{
197    ASSERT(!m_askedToTerminate);
198    ASSERT(!m_mainScriptLoader);
199    ASSERT(m_pauseAfterDownloadState == DontPauseAfterDownload);
200    m_workerStartData = data;
201    if (data.pauseAfterDownloadMode == WebEmbeddedWorkerStartData::PauseAfterDownload)
202        m_pauseAfterDownloadState = DoPauseAfterDownload;
203    prepareShadowPageForLoader();
204}
205
206void WebEmbeddedWorkerImpl::terminateWorkerContext()
207{
208    if (m_askedToTerminate)
209        return;
210    m_askedToTerminate = true;
211    if (m_mainScriptLoader) {
212        m_mainScriptLoader->cancel();
213        m_mainScriptLoader.clear();
214        // This may delete 'this'.
215        m_workerContextClient->workerContextFailedToStart();
216        return;
217    }
218    if (m_pauseAfterDownloadState == IsPausedAfterDownload) {
219        // This may delete 'this'.
220        m_workerContextClient->workerContextFailedToStart();
221        return;
222    }
223    if (m_workerThread)
224        m_workerThread->stop();
225    m_workerInspectorProxy->workerThreadTerminated();
226}
227
228void WebEmbeddedWorkerImpl::resumeAfterDownload()
229{
230    ASSERT(!m_askedToTerminate);
231    bool wasPaused = (m_pauseAfterDownloadState == IsPausedAfterDownload);
232    m_pauseAfterDownloadState = DontPauseAfterDownload;
233
234    // If we were asked to wait for debugger while updating service worker version then it is good time now.
235    m_workerContextClient->workerReadyForInspection();
236    if (m_workerStartData.waitForDebuggerMode == WebEmbeddedWorkerStartData::WaitForDebugger)
237        m_waitingForDebuggerState = WaitingForDebuggerAfterScriptLoaded;
238    else if (wasPaused)
239        startWorkerThread();
240}
241
242void WebEmbeddedWorkerImpl::resumeWorkerContext()
243{
244}
245
246void WebEmbeddedWorkerImpl::attachDevTools(const WebString& hostId)
247{
248    WebDevToolsAgent* devtoolsAgent = m_webView->devToolsAgent();
249    if (devtoolsAgent)
250        devtoolsAgent->attach(hostId);
251}
252
253void WebEmbeddedWorkerImpl::reattachDevTools(const WebString& hostId, const WebString& savedState)
254{
255    WebDevToolsAgent* devtoolsAgent = m_webView->devToolsAgent();
256    if (devtoolsAgent)
257        devtoolsAgent->reattach(hostId, savedState);
258    resumeStartup();
259}
260
261void WebEmbeddedWorkerImpl::detachDevTools()
262{
263    WebDevToolsAgent* devtoolsAgent = m_webView->devToolsAgent();
264    if (devtoolsAgent)
265        devtoolsAgent->detach();
266}
267
268void WebEmbeddedWorkerImpl::dispatchDevToolsMessage(const WebString& message)
269{
270    if (m_askedToTerminate)
271        return;
272    WebDevToolsAgent* devtoolsAgent = m_webView->devToolsAgent();
273    if (devtoolsAgent)
274        devtoolsAgent->dispatchOnInspectorBackend(message);
275}
276
277void WebEmbeddedWorkerImpl::postMessageToPageInspector(const String& message)
278{
279    WorkerInspectorProxy::PageInspector* pageInspector = m_workerInspectorProxy->pageInspector();
280    if (!pageInspector)
281        return;
282    pageInspector->dispatchMessageFromWorker(message);
283}
284
285void WebEmbeddedWorkerImpl::prepareShadowPageForLoader()
286{
287    // Create 'shadow page', which is never displayed and is used mainly to
288    // provide a context for loading on the main thread.
289    //
290    // FIXME: This does mostly same as WebSharedWorkerImpl::initializeLoader.
291    // This code, and probably most of the code in this class should be shared
292    // with SharedWorker.
293    ASSERT(!m_webView);
294    m_webView = WebView::create(0);
295    // FIXME: http://crbug.com/363843. This needs to find a better way to
296    // not create graphics layers.
297    m_webView->settings()->setAcceleratedCompositingEnabled(false);
298    m_mainFrame = WebLocalFrame::create(this);
299    m_webView->setMainFrame(m_mainFrame);
300    m_webView->setDevToolsAgentClient(this);
301
302    WebLocalFrameImpl* webFrame = toWebLocalFrameImpl(m_webView->mainFrame());
303
304    // Construct substitute data source for the 'shadow page'. We only need it
305    // to have same origin as the worker so the loading checks work correctly.
306    CString content("");
307    int length = static_cast<int>(content.length());
308    RefPtr<SharedBuffer> buffer(SharedBuffer::create(content.data(), length));
309    webFrame->frame()->loader().load(FrameLoadRequest(0, ResourceRequest(m_workerStartData.scriptURL), SubstituteData(buffer, "text/html", "UTF-8", KURL())));
310}
311
312void WebEmbeddedWorkerImpl::willSendRequest(
313    WebLocalFrame* frame, unsigned, WebURLRequest& request,
314    const WebURLResponse& redirectResponse)
315{
316    if (m_networkProvider)
317        m_networkProvider->willSendRequest(frame->dataSource(), request);
318}
319
320void WebEmbeddedWorkerImpl::didFinishDocumentLoad(WebLocalFrame* frame)
321{
322    // If we were asked to wait for debugger then it is the good time to do that.
323    // However if we are updating service worker version (m_pauseAfterDownloadState is set)
324    // Then we need to load the worker script to check the version, so in this case we wait for debugger
325    // later in ::resumeAfterDownload().
326    if (m_pauseAfterDownloadState != DoPauseAfterDownload) {
327        m_workerContextClient->workerReadyForInspection();
328        if (m_workerStartData.waitForDebuggerMode == WebEmbeddedWorkerStartData::WaitForDebugger) {
329            m_waitingForDebuggerState = WaitingForDebuggerBeforeLoadingScript;
330            return;
331        }
332    }
333    startScriptLoader(frame);
334}
335
336void WebEmbeddedWorkerImpl::sendMessageToInspectorFrontend(const WebString& message)
337{
338    m_workerContextClient->dispatchDevToolsMessage(message);
339}
340
341void WebEmbeddedWorkerImpl::resumeStartup()
342{
343    WaitingForDebuggerState waitingForDebuggerState = m_waitingForDebuggerState;
344    m_waitingForDebuggerState = NotWaitingForDebugger;
345    if (waitingForDebuggerState == WaitingForDebuggerBeforeLoadingScript)
346        startScriptLoader(toWebLocalFrameImpl(m_mainFrame));
347    else if (waitingForDebuggerState == WaitingForDebuggerAfterScriptLoaded)
348        startWorkerThread();
349}
350
351void WebEmbeddedWorkerImpl::saveAgentRuntimeState(const WebString& inspectorState)
352{
353    m_workerContextClient->saveDevToolsAgentState(inspectorState);
354}
355
356void WebEmbeddedWorkerImpl::startScriptLoader(WebLocalFrame* frame)
357{
358    ASSERT(!m_mainScriptLoader);
359    ASSERT(!m_networkProvider);
360    ASSERT(m_mainFrame);
361    ASSERT(m_workerContextClient);
362    m_networkProvider = adoptPtr(m_workerContextClient->createServiceWorkerNetworkProvider(frame->dataSource()));
363    m_mainScriptLoader = Loader::create();
364    m_mainScriptLoader->load(
365        toWebLocalFrameImpl(m_mainFrame)->frame()->document(),
366        m_workerStartData.scriptURL,
367        bind(&WebEmbeddedWorkerImpl::onScriptLoaderFinished, this));
368}
369
370void WebEmbeddedWorkerImpl::onScriptLoaderFinished()
371{
372    ASSERT(m_mainScriptLoader);
373
374    if (m_askedToTerminate)
375        return;
376
377    if (m_mainScriptLoader->failed()) {
378        m_mainScriptLoader.clear();
379        // This may delete 'this'.
380        m_workerContextClient->workerContextFailedToStart();
381        return;
382    }
383
384    Platform::current()->histogramCustomCounts("ServiceWorker.ScriptSize", m_mainScriptLoader->script().length(), 1000, 5000000, 50);
385
386    if (m_pauseAfterDownloadState == DoPauseAfterDownload) {
387        m_pauseAfterDownloadState = IsPausedAfterDownload;
388        m_workerContextClient->didPauseAfterDownload();
389        return;
390    }
391    startWorkerThread();
392}
393
394void WebEmbeddedWorkerImpl::startWorkerThread()
395{
396    ASSERT(m_pauseAfterDownloadState == DontPauseAfterDownload);
397    ASSERT(!m_askedToTerminate);
398
399    Document* document = toWebLocalFrameImpl(m_mainFrame)->frame()->document();
400
401    WorkerThreadStartMode startMode = DontPauseWorkerGlobalScopeOnStart;
402    if (InspectorInstrumentation::shouldPauseDedicatedWorkerOnStart(document))
403        startMode = PauseWorkerGlobalScopeOnStart;
404
405    OwnPtrWillBeRawPtr<WorkerClients> workerClients = WorkerClients::create();
406    providePermissionClientToWorker(workerClients.get(), m_permissionClient.release());
407    provideServiceWorkerGlobalScopeClientToWorker(workerClients.get(), ServiceWorkerGlobalScopeClientImpl::create(*m_workerContextClient));
408
409    KURL scriptURL = m_mainScriptLoader->url();
410    OwnPtrWillBeRawPtr<WorkerThreadStartupData> startupData =
411        WorkerThreadStartupData::create(
412            scriptURL,
413            m_workerStartData.userAgent,
414            m_mainScriptLoader->script(),
415            startMode,
416            // FIXME: fill appropriate CSP info and policy type.
417            String(),
418            ContentSecurityPolicyHeaderTypeEnforce,
419            workerClients.release());
420
421    m_mainScriptLoader.clear();
422
423    m_workerGlobalScopeProxy = ServiceWorkerGlobalScopeProxy::create(*this, *document, *m_workerContextClient);
424    m_loaderProxy = LoaderProxy::create(*this);
425    m_workerThread = ServiceWorkerThread::create(*m_loaderProxy, *m_workerGlobalScopeProxy, startupData.release());
426    m_workerThread->start();
427    m_workerInspectorProxy->workerThreadCreated(document, m_workerThread.get(), scriptURL);
428}
429
430} // namespace blink
431