Unreviewed. Restabilize non-unified build.
[WebKit-https.git] / Source / WebCore / workers / Worker.cpp
index bfac593..e38eee3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2010 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008-2017 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
- *
  */
 
 #include "config.h"
-
-#if ENABLE(WORKERS)
-
 #include "Worker.h"
 
-#include "DOMWindow.h"
-#include "CachedResourceLoader.h"
-#include "Document.h"
-#include "EventException.h"
-#include "EventListener.h"
+#include "ContentSecurityPolicy.h"
+#include "ErrorEvent.h"
+#include "Event.h"
 #include "EventNames.h"
-#include "ExceptionCode.h"
-#include "Frame.h"
-#include "FrameLoader.h"
 #include "InspectorInstrumentation.h"
-#include "MessageEvent.h"
-#include "TextEncoding.h"
-#include "WorkerContextProxy.h"
+#include "LoaderStrategy.h"
+#include "PlatformStrategies.h"
+#include "ResourceResponse.h"
+#include "SecurityOrigin.h"
+#include "WorkerGlobalScopeProxy.h"
 #include "WorkerScriptLoader.h"
 #include "WorkerThread.h"
+#include <JavaScriptCore/IdentifiersFactory.h>
+#include <JavaScriptCore/ScriptCallStack.h>
+#include <wtf/HashSet.h>
+#include <wtf/IsoMallocInlines.h>
 #include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/Scope.h>
 
 namespace WebCore {
 
-inline Worker::Worker(ScriptExecutionContext* context)
-    : AbstractWorker(context)
-    , m_contextProxy(WorkerContextProxy::create(this))
+WTF_MAKE_ISO_ALLOCATED_IMPL(Worker);
+
+static HashSet<Worker*>& allWorkers()
+{
+    static NeverDestroyed<HashSet<Worker*>> set;
+    return set;
+}
+
+void Worker::networkStateChanged(bool isOnLine)
 {
+    for (auto* worker : allWorkers())
+        worker->notifyNetworkStateChange(isOnLine);
 }
 
-PassRefPtr<Worker> Worker::create(const String& url, ScriptExecutionContext* context, ExceptionCode& ec)
+inline Worker::Worker(ScriptExecutionContext& context, JSC::RuntimeFlags runtimeFlags, const Options& options)
+    : ActiveDOMObject(&context)
+    , m_name(options.name)
+    , m_identifier("worker:" + Inspector::IdentifiersFactory::createIdentifier())
+    , m_contextProxy(WorkerGlobalScopeProxy::create(*this))
+    , m_runtimeFlags(runtimeFlags)
+    , m_eventQueue(GenericEventQueue::create(*this))
 {
-    RefPtr<Worker> worker = adoptRef(new Worker(context));
+    static bool addedListener;
+    if (!addedListener) {
+        platformStrategies()->loaderStrategy()->addOnlineStateChangeListener(&networkStateChanged);
+        addedListener = true;
+    }
+
+    auto addResult = allWorkers().add(this);
+    ASSERT_UNUSED(addResult, addResult.isNewEntry);
+}
 
-    KURL scriptURL = worker->resolveURL(url, ec);
-    if (scriptURL.isEmpty())
-        return 0;
+ExceptionOr<Ref<Worker>> Worker::create(ScriptExecutionContext& context, JSC::RuntimeFlags runtimeFlags, const String& url, const Options& options)
+{
+    ASSERT(isMainThread());
+
+    // We don't currently support nested workers, so workers can only be created from documents.
+    ASSERT_WITH_SECURITY_IMPLICATION(context.isDocument());
+
+    auto worker = adoptRef(*new Worker(context, runtimeFlags, options));
+
+    worker->suspendIfNeeded();
+
+    bool shouldBypassMainWorldContentSecurityPolicy = context.shouldBypassMainWorldContentSecurityPolicy();
+    auto scriptURL = worker->resolveURL(url, shouldBypassMainWorldContentSecurityPolicy);
+    if (scriptURL.hasException())
+        return scriptURL.releaseException();
+
+    worker->m_shouldBypassMainWorldContentSecurityPolicy = shouldBypassMainWorldContentSecurityPolicy;
 
     // The worker context does not exist while loading, so we must ensure that the worker object is not collected, nor are its event listeners.
     worker->setPendingActivity(worker.get());
 
-    worker->m_scriptLoader = WorkerScriptLoader::create(ResourceRequestBase::TargetIsWorker);
-    worker->m_scriptLoader->loadAsynchronously(context, scriptURL, DenyCrossOriginRequests, worker.get());
+    // https://html.spec.whatwg.org/multipage/workers.html#official-moment-of-creation
+    worker->m_workerCreationTime = MonotonicTime::now();
+
+    worker->m_scriptLoader = WorkerScriptLoader::create();
+    auto contentSecurityPolicyEnforcement = shouldBypassMainWorldContentSecurityPolicy ? ContentSecurityPolicyEnforcement::DoNotEnforce : ContentSecurityPolicyEnforcement::EnforceChildSrcDirective;
 
-    InspectorInstrumentation::didCreateWorker(context, worker->asID(), scriptURL.string(), false);
+    ResourceRequest request { scriptURL.releaseReturnValue() };
+    request.setInitiatorIdentifier(worker->m_identifier);
 
-    return worker.release();
+    FetchOptions fetchOptions;
+    fetchOptions.mode = FetchOptions::Mode::SameOrigin;
+    fetchOptions.cache = FetchOptions::Cache::Default;
+    fetchOptions.redirect = FetchOptions::Redirect::Follow;
+    fetchOptions.destination = FetchOptions::Destination::Worker;
+    worker->m_scriptLoader->loadAsynchronously(context, WTFMove(request), WTFMove(fetchOptions), contentSecurityPolicyEnforcement, ServiceWorkersMode::All, worker);
+    return worker;
 }
 
 Worker::~Worker()
 {
     ASSERT(isMainThread());
     ASSERT(scriptExecutionContext()); // The context is protected by worker context proxy, so it cannot be destroyed while a Worker exists.
-    m_contextProxy->workerObjectDestroyed();
+    allWorkers().remove(this);
+    m_contextProxy.workerObjectDestroyed();
 }
 
-// FIXME: remove this when we update the ObjC bindings (bug #28774).
-void Worker::postMessage(PassRefPtr<SerializedScriptValue> message, MessagePort* port, ExceptionCode& ec)
+ExceptionOr<void> Worker::postMessage(JSC::ExecState& state, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer)
 {
-    MessagePortArray ports;
-    if (port)
-        ports.append(port);
-    postMessage(message, &ports, ec);
-}
+    Vector<RefPtr<MessagePort>> ports;
+    auto message = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports, SerializationContext::WorkerPostMessage);
+    if (message.hasException())
+        return message.releaseException();
 
-void Worker::postMessage(PassRefPtr<SerializedScriptValue> message, ExceptionCode& ec)
-{
-    postMessage(message, static_cast<MessagePortArray*>(0), ec);
+    // Disentangle the port in preparation for sending it to the remote context.
+    auto channels = MessagePort::disentanglePorts(WTFMove(ports));
+    if (channels.hasException())
+        return channels.releaseException();
+
+    m_contextProxy.postMessageToWorkerGlobalScope({ message.releaseReturnValue(), channels.releaseReturnValue() });
+    return { };
 }
 
-void Worker::postMessage(PassRefPtr<SerializedScriptValue> message, const MessagePortArray* ports, ExceptionCode& ec)
+void Worker::terminate()
 {
-    // Disentangle the port in preparation for sending it to the remote context.
-    OwnPtr<MessagePortChannelArray> channels = MessagePort::disentanglePorts(ports, ec);
-    if (ec)
-        return;
-    m_contextProxy->postMessageToWorkerContext(message, channels.release());
+    m_contextProxy.terminateWorkerGlobalScope();
+    m_eventQueue->cancelAllEvents();
 }
 
-void Worker::terminate()
+bool Worker::canSuspendForDocumentSuspension() const
 {
-    m_contextProxy->terminateWorkerContext();
+    return true;
 }
 
-bool Worker::canSuspend() const
+const char* Worker::activeDOMObjectName() const
 {
-    // FIXME: It is not currently possible to suspend a worker, so pages with workers can not go into page cache.
-    return false;
+    return "Worker";
 }
 
 void Worker::stop()
@@ -123,24 +167,58 @@ void Worker::stop()
 
 bool Worker::hasPendingActivity() const
 {
-    return m_contextProxy->hasPendingActivity() || ActiveDOMObject::hasPendingActivity();
+    return m_contextProxy.hasPendingActivity() || ActiveDOMObject::hasPendingActivity() || m_eventQueue->hasPendingEvents();
+}
+
+void Worker::notifyNetworkStateChange(bool isOnLine)
+{
+    m_contextProxy.notifyNetworkStateChange(isOnLine);
+}
+
+void Worker::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
+{
+    const URL& responseURL = response.url();
+    if (!responseURL.protocolIsBlob() && !responseURL.protocolIs("file") && !SecurityOrigin::create(responseURL)->isUnique())
+        m_contentSecurityPolicyResponseHeaders = ContentSecurityPolicyResponseHeaders(response);
+    InspectorInstrumentation::didReceiveScriptResponse(scriptExecutionContext(), identifier);
 }
 
 void Worker::notifyFinished()
 {
-    if (m_scriptLoader->failed())
-        dispatchEvent(Event::create(eventNames().errorEvent, false, true));
-    else {
-        bool shouldStartPaused = InspectorInstrumentation::willStartWorkerContext(scriptExecutionContext(), m_contextProxy);
-        m_contextProxy->startWorkerContext(m_scriptLoader->url(), scriptExecutionContext()->userAgent(m_scriptLoader->url()), m_scriptLoader->script());
-        InspectorInstrumentation::didStartWorkerContext(scriptExecutionContext(), m_contextProxy, shouldStartPaused, m_scriptLoader->url());
-        InspectorInstrumentation::scriptImported(scriptExecutionContext(), m_scriptLoader->identifier(), m_scriptLoader->script());
+    auto clearLoader = makeScopeExit([this] {
+        m_scriptLoader = nullptr;
+        unsetPendingActivity(*this);
+    });
+
+    auto* context = scriptExecutionContext();
+    if (!context)
+        return;
+
+    if (m_scriptLoader->failed()) {
+        enqueueEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::Yes));
+        return;
     }
-    m_scriptLoader = nullptr;
 
-    unsetPendingActivity(this);
+    bool isOnline = platformStrategies()->loaderStrategy()->isOnLine();
+    const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders = m_contentSecurityPolicyResponseHeaders ? m_contentSecurityPolicyResponseHeaders.value() : context->contentSecurityPolicy()->responseHeaders();
+    m_contextProxy.startWorkerGlobalScope(m_scriptLoader->url(), m_name, context->userAgent(m_scriptLoader->url()), isOnline, m_scriptLoader->script(), contentSecurityPolicyResponseHeaders, m_shouldBypassMainWorldContentSecurityPolicy, m_workerCreationTime, m_runtimeFlags);
+    InspectorInstrumentation::scriptImported(*context, m_scriptLoader->identifier(), m_scriptLoader->script());
 }
 
-} // namespace WebCore
+void Worker::enqueueEvent(Ref<Event>&& event)
+{
+    m_eventQueue->enqueueEvent(WTFMove(event));
+}
 
-#endif // ENABLE(WORKERS)
+void Worker::dispatchEvent(Event& event)
+{
+    RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!m_eventQueue->isSuspended());
+
+    AbstractWorker::dispatchEvent(event);
+    if (is<ErrorEvent>(event) && !event.defaultPrevented() && event.isTrusted() && scriptExecutionContext()) {
+        auto& errorEvent = downcast<ErrorEvent>(event);
+        scriptExecutionContext()->reportException(errorEvent.message(), errorEvent.lineno(), errorEvent.colno(), errorEvent.filename(), nullptr, nullptr);
+    }
+}
+
+} // namespace WebCore