1/*
2 * Copyright (C) 2008 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
28#include "config.h"
29#include "WorkerMessagingProxy.h"
30
31#include "ContentSecurityPolicy.h"
32#include "DOMWindow.h"
33#include "DedicatedWorkerGlobalScope.h"
34#include "DedicatedWorkerThread.h"
35#include "Document.h"
36#include "ErrorEvent.h"
37#include "Event.h"
38#include "EventNames.h"
39#include "ExceptionCode.h"
40#include "InspectorInstrumentation.h"
41#include "MessageEvent.h"
42#include "PageGroup.h"
43#include "ScriptExecutionContext.h"
44#include "Worker.h"
45#include "WorkerDebuggerAgent.h"
46#include "WorkerInspectorController.h"
47#include <inspector/InspectorAgentBase.h>
48#include <inspector/ScriptCallStack.h>
49#include <runtime/ConsoleTypes.h>
50#include <wtf/MainThread.h>
51
52namespace WebCore {
53
54WorkerGlobalScopeProxy* WorkerGlobalScopeProxy::create(Worker* worker)
55{
56    return new WorkerMessagingProxy(worker);
57}
58
59WorkerMessagingProxy::WorkerMessagingProxy(Worker* workerObject)
60    : m_scriptExecutionContext(workerObject->scriptExecutionContext())
61    , m_workerObject(workerObject)
62    , m_mayBeDestroyed(false)
63    , m_unconfirmedMessageCount(0)
64    , m_workerThreadHadPendingActivity(false)
65    , m_askedToTerminate(false)
66#if ENABLE(INSPECTOR)
67    , m_pageInspector(0)
68#endif
69{
70    ASSERT(m_workerObject);
71    ASSERT((m_scriptExecutionContext->isDocument() && isMainThread())
72           || (m_scriptExecutionContext->isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(*m_scriptExecutionContext).thread().threadID()));
73}
74
75WorkerMessagingProxy::~WorkerMessagingProxy()
76{
77    ASSERT(!m_workerObject);
78    ASSERT((m_scriptExecutionContext->isDocument() && isMainThread())
79           || (m_scriptExecutionContext->isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(*m_scriptExecutionContext).thread().threadID()));
80}
81
82void WorkerMessagingProxy::startWorkerGlobalScope(const URL& scriptURL, const String& userAgent, const String& sourceCode, WorkerThreadStartMode startMode)
83{
84    // FIXME: This need to be revisited when we support nested worker one day
85    ASSERT_WITH_SECURITY_IMPLICATION(m_scriptExecutionContext->isDocument());
86    Document* document = static_cast<Document*>(m_scriptExecutionContext.get());
87    GroupSettings* settings = 0;
88    if (document->page())
89        settings = &document->page()->group().groupSettings();
90    RefPtr<DedicatedWorkerThread> thread = DedicatedWorkerThread::create(scriptURL, userAgent, settings, sourceCode, *this, *this, startMode, document->contentSecurityPolicy()->deprecatedHeader(), document->contentSecurityPolicy()->deprecatedHeaderType(), document->topOrigin());
91    workerThreadCreated(thread);
92    thread->start();
93    InspectorInstrumentation::didStartWorkerGlobalScope(m_scriptExecutionContext.get(), this, scriptURL);
94}
95
96void WorkerMessagingProxy::postMessageToWorkerObject(PassRefPtr<SerializedScriptValue> message, std::unique_ptr<MessagePortChannelArray> channels)
97{
98    MessagePortChannelArray* channelsPtr = channels.release();
99    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext& context) {
100        Worker* workerObject = this->workerObject();
101        if (!workerObject || askedToTerminate())
102            return;
103
104        std::unique_ptr<MessagePortArray> ports = MessagePort::entanglePorts(context, std::unique_ptr<MessagePortChannelArray>(channelsPtr));
105        workerObject->dispatchEvent(MessageEvent::create(WTF::move(ports), message));
106    });
107}
108
109void WorkerMessagingProxy::postMessageToWorkerGlobalScope(PassRefPtr<SerializedScriptValue> message, std::unique_ptr<MessagePortChannelArray> channels)
110{
111    if (m_askedToTerminate)
112        return;
113
114    MessagePortChannelArray* channelsPtr = channels.release();
115    ScriptExecutionContext::Task task([=] (ScriptExecutionContext& scriptContext) {
116        ASSERT_WITH_SECURITY_IMPLICATION(scriptContext.isWorkerGlobalScope());
117        DedicatedWorkerGlobalScope& context = static_cast<DedicatedWorkerGlobalScope&>(scriptContext);
118        std::unique_ptr<MessagePortArray> ports = MessagePort::entanglePorts(scriptContext, std::unique_ptr<MessagePortChannelArray>(channelsPtr));
119        context.dispatchEvent(MessageEvent::create(WTF::move(ports), message));
120        context.thread().workerObjectProxy().confirmMessageFromWorkerObject(context.hasPendingActivity());
121    });
122
123    if (m_workerThread) {
124        ++m_unconfirmedMessageCount;
125        m_workerThread->runLoop().postTask(WTF::move(task));
126    } else
127        m_queuedEarlyTasks.append(std::make_unique<ScriptExecutionContext::Task>(WTF::move(task)));
128}
129
130void WorkerMessagingProxy::postTaskToLoader(ScriptExecutionContext::Task task)
131{
132    // FIXME: In case of nested workers, this should go directly to the root Document context.
133    ASSERT(m_scriptExecutionContext->isDocument());
134    m_scriptExecutionContext->postTask(WTF::move(task));
135}
136
137bool WorkerMessagingProxy::postTaskForModeToWorkerGlobalScope(ScriptExecutionContext::Task task, const String& mode)
138{
139    if (m_askedToTerminate)
140        return false;
141
142    ASSERT(m_workerThread);
143    m_workerThread->runLoop().postTaskForMode(WTF::move(task), mode);
144    return true;
145}
146
147void WorkerMessagingProxy::postExceptionToWorkerObject(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL)
148{
149    String errorMessageCopy = errorMessage.isolatedCopy();
150    String sourceURLCopy = sourceURL.isolatedCopy();
151    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext& context) {
152        Worker* workerObject = this->workerObject();
153        if (!workerObject)
154            return;
155
156        // We don't bother checking the askedToTerminate() flag here, because exceptions should *always* be reported even if the thread is terminated.
157        // This is intentionally different than the behavior in MessageWorkerTask, because terminated workers no longer deliver messages (section 4.6 of the WebWorker spec), but they do report exceptions.
158
159        bool errorHandled = !workerObject->dispatchEvent(ErrorEvent::create(errorMessageCopy, sourceURLCopy, lineNumber, columnNumber));
160        if (!errorHandled)
161            context.reportException(errorMessageCopy, lineNumber, columnNumber, sourceURLCopy, 0);
162    });
163}
164
165void WorkerMessagingProxy::postConsoleMessageToWorkerObject(MessageSource source, MessageLevel level, const String& message, int lineNumber, int columnNumber, const String& sourceURL)
166{
167    String messageCopy = message.isolatedCopy();
168    String sourceURLCopy = sourceURL.isolatedCopy();
169    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext& context) {
170        if (askedToTerminate())
171            return;
172        context.addConsoleMessage(source, level, messageCopy, sourceURLCopy, lineNumber, columnNumber);
173    });
174}
175
176void WorkerMessagingProxy::workerThreadCreated(PassRefPtr<DedicatedWorkerThread> workerThread)
177{
178    m_workerThread = workerThread;
179
180    if (m_askedToTerminate) {
181        // Worker.terminate() could be called from JS before the thread was created.
182        m_workerThread->stop();
183    } else {
184        ASSERT(!m_unconfirmedMessageCount);
185        m_unconfirmedMessageCount = m_queuedEarlyTasks.size();
186        m_workerThreadHadPendingActivity = true; // Worker initialization means a pending activity.
187
188        auto queuedEarlyTasks = WTF::move(m_queuedEarlyTasks);
189        for (auto& task : queuedEarlyTasks)
190            m_workerThread->runLoop().postTask(WTF::move(*task));
191    }
192}
193
194void WorkerMessagingProxy::workerObjectDestroyed()
195{
196    m_workerObject = 0;
197    m_scriptExecutionContext->postTask([this] (ScriptExecutionContext&) {
198        m_mayBeDestroyed = true;
199        if (m_workerThread)
200            terminateWorkerGlobalScope();
201        else
202            workerGlobalScopeDestroyedInternal();
203    });
204}
205
206void WorkerMessagingProxy::notifyNetworkStateChange(bool isOnline)
207{
208    if (m_askedToTerminate)
209        return;
210
211    if (!m_workerThread)
212        return;
213
214    m_workerThread->runLoop().postTask([=] (ScriptExecutionContext& context) {
215        toWorkerGlobalScope(&context)->dispatchEvent(Event::create(isOnline ? eventNames().onlineEvent : eventNames().offlineEvent, false, false));
216    });
217}
218
219#if ENABLE(INSPECTOR)
220void WorkerMessagingProxy::connectToInspector(WorkerGlobalScopeProxy::PageInspector* pageInspector)
221{
222    if (m_askedToTerminate)
223        return;
224    ASSERT(!m_pageInspector);
225    m_pageInspector = pageInspector;
226    m_workerThread->runLoop().postTaskForMode([] (ScriptExecutionContext& context) {
227        toWorkerGlobalScope(&context)->workerInspectorController().connectFrontend();
228    }, WorkerDebuggerAgent::debuggerTaskMode);
229}
230
231void WorkerMessagingProxy::disconnectFromInspector()
232{
233    m_pageInspector = 0;
234    if (m_askedToTerminate)
235        return;
236    m_workerThread->runLoop().postTaskForMode([] (ScriptExecutionContext& context) {
237        toWorkerGlobalScope(&context)->workerInspectorController().disconnectFrontend(Inspector::InspectorDisconnectReason::InspectorDestroyed);
238    }, WorkerDebuggerAgent::debuggerTaskMode);
239}
240
241void WorkerMessagingProxy::sendMessageToInspector(const String& message)
242{
243    if (m_askedToTerminate)
244        return;
245    String messageCopy = message.isolatedCopy();
246    m_workerThread->runLoop().postTaskForMode([messageCopy] (ScriptExecutionContext& context) {
247        toWorkerGlobalScope(&context)->workerInspectorController().dispatchMessageFromFrontend(messageCopy);
248    }, WorkerDebuggerAgent::debuggerTaskMode);
249    WorkerDebuggerAgent::interruptAndDispatchInspectorCommands(m_workerThread.get());
250}
251#endif
252
253void WorkerMessagingProxy::workerGlobalScopeDestroyed()
254{
255    m_scriptExecutionContext->postTask([this] (ScriptExecutionContext&) {
256        workerGlobalScopeDestroyedInternal();
257    });
258    // Will execute workerGlobalScopeDestroyedInternal() on context's thread.
259}
260
261void WorkerMessagingProxy::workerGlobalScopeClosed()
262{
263    // Executes terminateWorkerGlobalScope() on parent context's thread.
264    m_scriptExecutionContext->postTask([this] (ScriptExecutionContext&) {
265        terminateWorkerGlobalScope();
266    });
267}
268
269void WorkerMessagingProxy::workerGlobalScopeDestroyedInternal()
270{
271    // WorkerGlobalScopeDestroyedTask is always the last to be performed, so the proxy is not needed for communication
272    // in either side any more. However, the Worker object may still exist, and it assumes that the proxy exists, too.
273    m_askedToTerminate = true;
274    m_workerThread = 0;
275
276    InspectorInstrumentation::workerGlobalScopeTerminated(m_scriptExecutionContext.get(), this);
277
278    if (m_mayBeDestroyed)
279        delete this;
280}
281
282void WorkerMessagingProxy::terminateWorkerGlobalScope()
283{
284    if (m_askedToTerminate)
285        return;
286    m_askedToTerminate = true;
287
288    if (m_workerThread)
289        m_workerThread->stop();
290
291    InspectorInstrumentation::workerGlobalScopeTerminated(m_scriptExecutionContext.get(), this);
292}
293
294#if ENABLE(INSPECTOR)
295void WorkerMessagingProxy::postMessageToPageInspector(const String& message)
296{
297    String messageCopy = message.isolatedCopy();
298    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext&) {
299        m_pageInspector->dispatchMessageFromWorker(messageCopy);
300    });
301}
302#endif
303
304void WorkerMessagingProxy::confirmMessageFromWorkerObject(bool hasPendingActivity)
305{
306    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext&) {
307        reportPendingActivityInternal(true, hasPendingActivity);
308    });
309    // Will execute reportPendingActivityInternal() on context's thread.
310}
311
312void WorkerMessagingProxy::reportPendingActivity(bool hasPendingActivity)
313{
314    m_scriptExecutionContext->postTask([=] (ScriptExecutionContext&) {
315        reportPendingActivityInternal(false, hasPendingActivity);
316    });
317    // Will execute reportPendingActivityInternal() on context's thread.
318}
319
320void WorkerMessagingProxy::reportPendingActivityInternal(bool confirmingMessage, bool hasPendingActivity)
321{
322    if (confirmingMessage && !m_askedToTerminate) {
323        ASSERT(m_unconfirmedMessageCount);
324        --m_unconfirmedMessageCount;
325    }
326
327    m_workerThreadHadPendingActivity = hasPendingActivity;
328}
329
330bool WorkerMessagingProxy::hasPendingActivity() const
331{
332    return (m_unconfirmedMessageCount || m_workerThreadHadPendingActivity) && !m_askedToTerminate;
333}
334
335} // namespace WebCore
336