Make DeferredPromise behave nicely with regards to the back/forward cache
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 22:10:58 +0000 (22:10 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 22:10:58 +0000 (22:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203976

Reviewed by Ryosuke Niwa.

Source/JavaScriptCore:

Add template parameter to JSC::Strong to indicate that the destructor should grab the JS lock.
Normally, the callers are in charge of grabbing the lock but this is not always feasible.
In particular, in this patch, I capture a JSC::Strong in a lambda. If the document gets destroyed
before the lambda has run, the lambda will get destroyed and it will destroy the captured JSC::Strong
as a result.

* heap/Handle.h:
* heap/Strong.h:
(JSC::Strong::clear):
* heap/StrongInlines.h:
(JSC::shouldStrongDestructorGrabLock>::Strong):
(JSC::shouldStrongDestructorGrabLock>::set):

Source/WebCore:

Previously, DeferredPromise would ignore requests to fulfill the promise if
reject/resolve/settle got called while the promise's script execution context
is suspended in the back/forward cache. This is really never the right thing to
do though because this means the promise will never be fulfilled if the user
ever goes back to this page.

To address the issue, DeferredPromise now schedules a task on the event loop if
it gets fulfilled while its script execution context is suspended. As a result,
the promise will properly get fulfilled if the document ever comes out of the
back/forward cache and developers using DeferredPromise do not have to worry
about suspension.

Now that DeferredPromise properly deals with suspended documents, this patch also
reverts changes I made recently in code using DeferredPromise. I had made them
queue a task on the event loop before fulfilling the promise, which did not match
their specification and is no longer needed.

Note that capturing the Strong<> in the lambda is correct here because we want
the resolution value to survive until either:
1. The task is processed by the event loop and the promise is resolved.
2. The back/forward cache entry containing this document is destroyed

Note that we do not leak here because when the back/forward cache's CachedFrame
gets destroyed, it will call Document::prepareForDestruction(), which will call
WindowEventLoop::stop() and destroys all pending tasks associated with this
document. This will therefore destroy the Strong captured in the task.

No new tests, covered by existing back/forward cache tests.

* Modules/cache/DOMCacheStorage.cpp:
(WebCore::DOMCacheStorage::doSequentialMatch):
(WebCore::DOMCacheStorage::match):
(WebCore::DOMCacheStorage::has):
(WebCore::DOMCacheStorage::open):
(WebCore::DOMCacheStorage::doOpen):
(WebCore::DOMCacheStorage::remove):
(WebCore::DOMCacheStorage::doRemove):
(WebCore::DOMCacheStorage::keys):
* Modules/cache/DOMCacheStorage.h:
* Modules/fetch/FetchBodyOwner.cpp:
(WebCore::FetchBodyOwner::blobLoadingSucceeded):
(WebCore::FetchBodyOwner::blobLoadingFailed):
(WebCore::FetchBodyOwner::blobChunk):
* Modules/fetch/FetchBodyOwner.h:
* Modules/mediastream/UserMediaRequest.cpp:
(WebCore::UserMediaRequest::allow):
(WebCore::UserMediaRequest::deny):
* WebCore.xcodeproj/project.pbxproj:
* bindings/IDLTypes.h:
* bindings/js/JSDOMGuardedObject.h:
* bindings/js/JSDOMPromiseDeferred.cpp:
(WebCore::DeferredPromise::callFunction):
(WebCore::DeferredPromise::whenSettled):
(WebCore::DeferredPromise::reject):
* bindings/js/JSDOMPromiseDeferred.h:
(WebCore::DeferredPromise::resolve):
(WebCore::DeferredPromise::resolveWithNewlyCreated):
(WebCore::DeferredPromise::resolveCallbackValueWithNewlyCreated):
(WebCore::DeferredPromise::reject):
(WebCore::DeferredPromise::resolveWithCallback):
(WebCore::DeferredPromise::rejectWithCallback):
(WebCore::DeferredPromise::shouldIgnoreRequestToFulfill const):
* css/FontFaceSet.cpp:
(WebCore::FontFaceSet::didFirstLayout):
(WebCore::FontFaceSet::completedLoading):
(WebCore::FontFaceSet::faceFinished):
* dom/ActiveDOMCallback.cpp:
(WebCore::ActiveDOMCallback::activeDOMObjectsAreSuspended const):
(WebCore::ActiveDOMCallback::activeDOMObjectAreStopped const):
* dom/ActiveDOMCallback.h:
* dom/Element.h:
* dom/ScriptExecutionContext.h:
* page/DOMWindow.h:
* page/RemoteDOMWindow.h:

LayoutTests:

Rebaseline test where ordering has changed slightly due to not queueing a task anymore before
resolving the promise. This restores pre-r251746 behavior.

* fast/mediastream/MediaDevices-getUserMedia-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@252263 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/mediastream/MediaDevices-getUserMedia-expected.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/heap/Handle.h
Source/JavaScriptCore/heap/Strong.h
Source/JavaScriptCore/heap/StrongInlines.h
Source/WebCore/ChangeLog
Source/WebCore/Modules/cache/DOMCacheStorage.cpp
Source/WebCore/Modules/cache/DOMCacheStorage.h
Source/WebCore/Modules/fetch/FetchBodyOwner.cpp
Source/WebCore/Modules/fetch/FetchBodyOwner.h
Source/WebCore/Modules/mediastream/UserMediaRequest.cpp
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/IDLTypes.h
Source/WebCore/bindings/js/JSDOMGuardedObject.h
Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp
Source/WebCore/bindings/js/JSDOMPromiseDeferred.h
Source/WebCore/css/FontFaceSet.cpp
Source/WebCore/dom/ActiveDOMCallback.cpp
Source/WebCore/dom/ActiveDOMCallback.h
Source/WebCore/dom/Element.h
Source/WebCore/dom/ScriptExecutionContext.h
Source/WebCore/page/DOMWindow.h
Source/WebCore/page/RemoteDOMWindow.h

index 6053d8a..1838510 100644 (file)
@@ -1,3 +1,15 @@
+2019-11-08  Chris Dumez  <cdumez@apple.com>
+
+        Make DeferredPromise behave nicely with regards to the back/forward cache
+        https://bugs.webkit.org/show_bug.cgi?id=203976
+
+        Reviewed by Ryosuke Niwa.
+
+        Rebaseline test where ordering has changed slightly due to not queueing a task anymore before
+        resolving the promise. This restores pre-r251746 behavior.
+
+        * fast/mediastream/MediaDevices-getUserMedia-expected.txt:
+
 2019-11-08  Truitt Savell  <tsavell@apple.com>
 
         Update expectations for 3 fast/events/ios/key-events-comprehensive tests
index 034738a..0ec3251 100644 (file)
@@ -6,9 +6,9 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 PASS typeof navigator.mediaDevices.webkitGetUserMedia is 'undefined'
 
 PASS navigator.mediaDevices.getUserMedia({audio:true}).then(gotStream1); did not throw exception.
-PASS navigator.mediaDevices.getUserMedia.apply(undefined) rejected with error: TypeError: Can only call MediaDevices.getUserMedia on instances of MediaDevices
 PASS navigator.mediaDevices.getUserMedia() rejected with error: TypeError: Type error
 PASS navigator.mediaDevices.getUserMedia({}) rejected with error: TypeError: Type error
+PASS navigator.mediaDevices.getUserMedia.apply(undefined) rejected with error: TypeError: Can only call MediaDevices.getUserMedia on instances of MediaDevices
 PASS Stream 1 generated.
 PASS stream.getAudioTracks().length is 1
 PASS stream.getVideoTracks().length is 0
index 2b798f8..718c28e 100644 (file)
@@ -1,3 +1,23 @@
+2019-11-08  Chris Dumez  <cdumez@apple.com>
+
+        Make DeferredPromise behave nicely with regards to the back/forward cache
+        https://bugs.webkit.org/show_bug.cgi?id=203976
+
+        Reviewed by Ryosuke Niwa.
+
+        Add template parameter to JSC::Strong to indicate that the destructor should grab the JS lock.
+        Normally, the callers are in charge of grabbing the lock but this is not always feasible.
+        In particular, in this patch, I capture a JSC::Strong in a lambda. If the document gets destroyed
+        before the lambda has run, the lambda will get destroyed and it will destroy the captured JSC::Strong
+        as a result.
+
+        * heap/Handle.h:
+        * heap/Strong.h:
+        (JSC::Strong::clear):
+        * heap/StrongInlines.h:
+        (JSC::shouldStrongDestructorGrabLock>::Strong):
+        (JSC::shouldStrongDestructorGrabLock>::set):
+
 2019-11-08  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Use LinkTimeConstants and make some properties lazy
index aba6511..866a076 100644 (file)
@@ -37,6 +37,8 @@ namespace JSC {
     lifetime is guaranteed by something else.
 */
 
+enum class ShouldStrongDestructorGrabLock : bool { No, Yes };
+
 template <class T> class Handle;
 
 // Creating a JSValue Handle is invalid
@@ -44,7 +46,7 @@ template <> class Handle<JSValue>;
 
 class HandleBase {
     template <typename T> friend class Weak;
-    template <typename T> friend class Strong;
+    template <typename T, ShouldStrongDestructorGrabLock shouldStrongDestructorGrabLock> friend class Strong;
     friend class HandleSet;
     friend struct JSCallbackObjectData;
 
index bf4dd39..c37481a 100644 (file)
 #include <wtf/Assertions.h>
 #include "Handle.h"
 #include "HandleSet.h"
+#include "JSLock.h"
 
 namespace JSC {
 
 class VM;
 
 // A strongly referenced handle that prevents the object it points to from being garbage collected.
-template <typename T> class Strong : public Handle<T> {
+template <typename T, ShouldStrongDestructorGrabLock shouldStrongDestructorGrabLock = ShouldStrongDestructorGrabLock::No> class Strong : public Handle<T> {
     using Handle<T>::slot;
     using Handle<T>::setSlot;
-    template <typename U> friend class Strong;
+    template <typename U, ShouldStrongDestructorGrabLock> friend class Strong;
 
 public:
     typedef typename Handle<T>::ExternalType ExternalType;
@@ -120,8 +121,16 @@ public:
     {
         if (!slot())
             return;
-        HandleSet::heapFor(slot())->deallocate(slot());
-        setSlot(0);
+
+        auto* heap = HandleSet::heapFor(slot());
+        if (shouldStrongDestructorGrabLock == ShouldStrongDestructorGrabLock::Yes) {
+            JSLockHolder holder(heap->vm());
+            heap->deallocate(slot());
+            setSlot(0);
+        } else {
+            heap->deallocate(slot());
+            setSlot(0);
+        }
     }
 
 private:
index 205fb04..2855ef8 100644 (file)
 
 namespace JSC {
 
-template <typename T>
-inline Strong<T>::Strong(VM& vm, ExternalType value)
+template <typename T, ShouldStrongDestructorGrabLock shouldStrongDestructorGrabLock>
+inline Strong<T, shouldStrongDestructorGrabLock>::Strong(VM& vm, ExternalType value)
     : Handle<T>(vm.heap.handleSet()->allocate())
 {
     set(value);
 }
 
-template <typename T>
-inline Strong<T>::Strong(VM& vm, Handle<T> handle)
+template <typename T, ShouldStrongDestructorGrabLock shouldStrongDestructorGrabLock>
+inline Strong<T, shouldStrongDestructorGrabLock>::Strong(VM& vm, Handle<T> handle)
     : Handle<T>(vm.heap.handleSet()->allocate())
 {
     set(handle.get());
 }
 
-template <typename T>
-inline void Strong<T>::set(VM& vm, ExternalType value)
+template <typename T, ShouldStrongDestructorGrabLock shouldStrongDestructorGrabLock>
+inline void Strong<T, shouldStrongDestructorGrabLock>::set(VM& vm, ExternalType value)
 {
     if (!slot())
         setSlot(vm.heap.handleSet()->allocate());
index 7697acc..1a4252f 100644 (file)
@@ -1,3 +1,85 @@
+2019-11-08  Chris Dumez  <cdumez@apple.com>
+
+        Make DeferredPromise behave nicely with regards to the back/forward cache
+        https://bugs.webkit.org/show_bug.cgi?id=203976
+
+        Reviewed by Ryosuke Niwa.
+
+        Previously, DeferredPromise would ignore requests to fulfill the promise if
+        reject/resolve/settle got called while the promise's script execution context
+        is suspended in the back/forward cache. This is really never the right thing to
+        do though because this means the promise will never be fulfilled if the user
+        ever goes back to this page.
+
+        To address the issue, DeferredPromise now schedules a task on the event loop if
+        it gets fulfilled while its script execution context is suspended. As a result,
+        the promise will properly get fulfilled if the document ever comes out of the
+        back/forward cache and developers using DeferredPromise do not have to worry
+        about suspension.
+
+        Now that DeferredPromise properly deals with suspended documents, this patch also
+        reverts changes I made recently in code using DeferredPromise. I had made them
+        queue a task on the event loop before fulfilling the promise, which did not match
+        their specification and is no longer needed.
+
+        Note that capturing the Strong<> in the lambda is correct here because we want
+        the resolution value to survive until either:
+        1. The task is processed by the event loop and the promise is resolved.
+        2. The back/forward cache entry containing this document is destroyed
+
+        Note that we do not leak here because when the back/forward cache's CachedFrame
+        gets destroyed, it will call Document::prepareForDestruction(), which will call
+        WindowEventLoop::stop() and destroys all pending tasks associated with this
+        document. This will therefore destroy the Strong captured in the task.
+
+        No new tests, covered by existing back/forward cache tests.
+
+        * Modules/cache/DOMCacheStorage.cpp:
+        (WebCore::DOMCacheStorage::doSequentialMatch):
+        (WebCore::DOMCacheStorage::match):
+        (WebCore::DOMCacheStorage::has):
+        (WebCore::DOMCacheStorage::open):
+        (WebCore::DOMCacheStorage::doOpen):
+        (WebCore::DOMCacheStorage::remove):
+        (WebCore::DOMCacheStorage::doRemove):
+        (WebCore::DOMCacheStorage::keys):
+        * Modules/cache/DOMCacheStorage.h:
+        * Modules/fetch/FetchBodyOwner.cpp:
+        (WebCore::FetchBodyOwner::blobLoadingSucceeded):
+        (WebCore::FetchBodyOwner::blobLoadingFailed):
+        (WebCore::FetchBodyOwner::blobChunk):
+        * Modules/fetch/FetchBodyOwner.h:
+        * Modules/mediastream/UserMediaRequest.cpp:
+        (WebCore::UserMediaRequest::allow):
+        (WebCore::UserMediaRequest::deny):
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/IDLTypes.h:
+        * bindings/js/JSDOMGuardedObject.h:
+        * bindings/js/JSDOMPromiseDeferred.cpp:
+        (WebCore::DeferredPromise::callFunction):
+        (WebCore::DeferredPromise::whenSettled):
+        (WebCore::DeferredPromise::reject):
+        * bindings/js/JSDOMPromiseDeferred.h:
+        (WebCore::DeferredPromise::resolve):
+        (WebCore::DeferredPromise::resolveWithNewlyCreated):
+        (WebCore::DeferredPromise::resolveCallbackValueWithNewlyCreated):
+        (WebCore::DeferredPromise::reject):
+        (WebCore::DeferredPromise::resolveWithCallback):
+        (WebCore::DeferredPromise::rejectWithCallback):
+        (WebCore::DeferredPromise::shouldIgnoreRequestToFulfill const):
+        * css/FontFaceSet.cpp:
+        (WebCore::FontFaceSet::didFirstLayout):
+        (WebCore::FontFaceSet::completedLoading):
+        (WebCore::FontFaceSet::faceFinished):
+        * dom/ActiveDOMCallback.cpp:
+        (WebCore::ActiveDOMCallback::activeDOMObjectsAreSuspended const):
+        (WebCore::ActiveDOMCallback::activeDOMObjectAreStopped const):
+        * dom/ActiveDOMCallback.h:
+        * dom/Element.h:
+        * dom/ScriptExecutionContext.h:
+        * page/DOMWindow.h:
+        * page/RemoteDOMWindow.h:
+
 2019-11-08  Antti Koivisto  <antti@apple.com>
 
         StyleResolver state should store user agent appearance style as RenderStyle
index 4500c5a..603e93f 100644 (file)
@@ -87,18 +87,16 @@ static inline Ref<DOMCache> copyCache(const Ref<DOMCache>& cache)
 
 void DOMCacheStorage::doSequentialMatch(DOMCache::RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
 {
-    startSequentialMatch(WTF::map(m_caches, copyCache), WTFMove(info), WTFMove(options), [this, pendingActivity = makePendingActivity(*this), promise = WTFMove(promise)](auto&& result) mutable {
-        enqueueTask([promise = WTFMove(promise), result = WTFMove(result)]() mutable {
-            if (result.hasException()) {
-                promise->reject(result.releaseException());
-                return;
-            }
-            if (!result.returnValue()) {
-                promise->resolve();
-                return;
-            }
-            promise->resolve<IDLInterface<FetchResponse>>(*result.returnValue());
-        });
+    startSequentialMatch(WTF::map(m_caches, copyCache), WTFMove(info), WTFMove(options), [pendingActivity = makePendingActivity(*this), promise = WTFMove(promise)](auto&& result) mutable {
+        if (result.hasException()) {
+            promise->reject(result.releaseException());
+            return;
+        }
+        if (!result.returnValue()) {
+            promise->resolve();
+            return;
+        }
+        promise->resolve<IDLInterface<FetchResponse>>(*result.returnValue());
     });
 }
 
@@ -106,9 +104,7 @@ void DOMCacheStorage::match(DOMCache::RequestInfo&& info, CacheQueryOptions&& op
 {
     retrieveCaches([this, info = WTFMove(info), options = WTFMove(options), promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
         if (exception) {
-            enqueueTask([promise = WTFMove(promise), exception = WTFMove(exception.value())]() mutable {
-                promise->reject(WTFMove(exception));
-            });
+            promise->reject(WTFMove(*exception));
             return;
         }
 
@@ -118,9 +114,7 @@ void DOMCacheStorage::match(DOMCache::RequestInfo&& info, CacheQueryOptions&& op
                 m_caches[position]->match(WTFMove(info), WTFMove(options), WTFMove(promise));
                 return;
             }
-            enqueueTask([promise = WTFMove(promise)]() mutable {
-                promise->resolve();
-            });
+            promise->resolve();
             return;
         }
 
@@ -131,13 +125,11 @@ void DOMCacheStorage::match(DOMCache::RequestInfo&& info, CacheQueryOptions&& op
 void DOMCacheStorage::has(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
 {
     retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
-        enqueueTask([this, name, promise = WTFMove(promise), exception = WTFMove(exception)]() mutable {
-            if (exception) {
-                promise.reject(WTFMove(exception.value()));
-                return;
-            }
-            promise.resolve(m_caches.findMatching([&](auto& item) { return item->name() == name; }) != notFound);
-        });
+        if (exception) {
+            promise.reject(WTFMove(exception.value()));
+            return;
+        }
+        promise.resolve(m_caches.findMatching([&](auto& item) { return item->name() == name; }) != notFound);
     });
 }
 
@@ -188,9 +180,7 @@ void DOMCacheStorage::open(const String& name, DOMPromiseDeferred<IDLInterface<D
 {
     retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
         if (exception) {
-            enqueueTask([promise = WTFMove(promise), exception = WTFMove(exception.value())]() mutable {
-                promise.reject(WTFMove(exception));
-            });
+            promise.reject(WTFMove(*exception));
             return;
         }
         doOpen(name, WTFMove(promise));
@@ -201,26 +191,20 @@ void DOMCacheStorage::doOpen(const String& name, DOMPromiseDeferred<IDLInterface
 {
     auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
     if (position != notFound) {
-        enqueueTask([this, promise = WTFMove(promise), cache = m_caches[position].copyRef()]() mutable {
-            promise.resolve(DOMCache::create(*scriptExecutionContext(), String { cache->name() }, cache->identifier(), m_connection.copyRef()));
-        });
+        promise.resolve(DOMCache::create(*scriptExecutionContext(), String { m_caches[position]->name() }, m_caches[position]->identifier(), m_connection.copyRef()));
         return;
     }
 
     m_connection->open(*origin(), name, [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
-        if (!result.has_value()) {
-            enqueueTask([this, promise = WTFMove(promise), error = result.error()]() mutable {
-                promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), error));
-            });
-        } else {
+        if (!result.has_value())
+            promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
+        else {
             if (result.value().hadStorageError)
                 logConsolePersistencyError(scriptExecutionContext(), name);
 
-            enqueueTask([this, name, promise = WTFMove(promise), identifier = result.value().identifier]() mutable {
-                auto cache = DOMCache::create(*scriptExecutionContext(), String { name }, identifier, m_connection.copyRef());
-                promise.resolve(cache);
-                m_caches.append(WTFMove(cache));
-            });
+            auto cache = DOMCache::create(*scriptExecutionContext(), String { name }, result.value().identifier, m_connection.copyRef());
+            promise.resolve(cache);
+            m_caches.append(WTFMove(cache));
         }
     });
 }
@@ -229,9 +213,7 @@ void DOMCacheStorage::remove(const String& name, DOMPromiseDeferred<IDLBoolean>&
 {
     retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
         if (exception) {
-            enqueueTask([promise = WTFMove(promise), exception = WTFMove(exception.value())]() mutable {
-                promise.reject(WTFMove(exception));
-            });
+            promise.reject(WTFMove(*exception));
             return;
         }
         doRemove(name, WTFMove(promise));
@@ -242,38 +224,32 @@ void DOMCacheStorage::doRemove(const String& name, DOMPromiseDeferred<IDLBoolean
 {
     auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
     if (position == notFound) {
-        enqueueTask([promise = WTFMove(promise)]() mutable {
-            promise.resolve(false);
-        });
+        promise.resolve(false);
         return;
     }
 
     m_connection->remove(m_caches[position]->identifier(), [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
-        enqueueTask([this, name, promise = WTFMove(promise), result]() mutable {
-            if (!result.has_value())
-                promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
-            else {
-                if (result.value().hadStorageError)
-                    logConsolePersistencyError(scriptExecutionContext(), name);
-                promise.resolve(!!result.value().identifier);
-            }
-        });
+        if (!result.has_value())
+            promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
+        else {
+            if (result.value().hadStorageError)
+                logConsolePersistencyError(scriptExecutionContext(), name);
+            promise.resolve(!!result.value().identifier);
+        }
     });
 }
 
 void DOMCacheStorage::keys(KeysPromise&& promise)
 {
     retrieveCaches([this, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
-        enqueueTask([this, promise = WTFMove(promise), exception = WTFMove(exception)]() mutable {
-            if (exception) {
-                promise.reject(WTFMove(exception.value()));
-                return;
-            }
+        if (exception) {
+            promise.reject(WTFMove(exception.value()));
+            return;
+        }
 
-            promise.resolve(WTF::map(m_caches, [] (const auto& cache) {
-                return cache->name();
-            }));
-        });
+        promise.resolve(WTF::map(m_caches, [] (const auto& cache) {
+            return cache->name();
+        }));
     });
 }
 
@@ -287,14 +263,4 @@ const char* DOMCacheStorage::activeDOMObjectName() const
     return "CacheStorage";
 }
 
-void DOMCacheStorage::enqueueTask(Function<void()>&& task)
-{
-    auto* context = scriptExecutionContext();
-    if (!context)
-        return;
-    context->eventLoop().queueTask(TaskSource::DOMManipulation, *context, [protectedThis = makeRef(*this), pendingActivity = makePendingActivity(*this), task = WTFMove(task)] {
-        task();
-    });
-}
-
 } // namespace WebCore
index 010539f..686f0a2 100644 (file)
@@ -59,8 +59,6 @@ private:
     Ref<DOMCache> findCacheOrCreate(DOMCacheEngine::CacheInfo&&);
     Optional<ClientOrigin> origin() const;
 
-    void enqueueTask(Function<void()>&&);
-
     Vector<Ref<DOMCache>> m_caches;
     uint64_t m_updateCounter { 0 };
     Ref<CacheStorageConnection> m_connection;
index 000d79e..25ecbe9 100644 (file)
 
 namespace WebCore {
 
-void FetchBodyOwner::runNetworkTaskWhenPossible(Function<void()>&& task)
-{
-    auto* context = scriptExecutionContext();
-    if (!context || !scriptExecutionContext()->activeDOMObjectsAreSuspended())
-        return task();
-
-    context->eventLoop().queueTask(TaskSource::Networking, *context, [pendingActivity = makePendingActivity(*this), task = WTFMove(task)] {
-        task();
-    });
-}
-
 FetchBodyOwner::FetchBodyOwner(ScriptExecutionContext& context, Optional<FetchBody>&& body, Ref<FetchHeaders>&& headers)
     : ActiveDOMObject(&context)
     , m_body(WTFMove(body))
@@ -281,31 +270,27 @@ void FetchBodyOwner::finishBlobLoading()
 void FetchBodyOwner::blobLoadingSucceeded()
 {
     ASSERT(!isBodyNull());
-    runNetworkTaskWhenPossible([this]() mutable {
 #if ENABLE(STREAMS_API)
-        if (m_readableStreamSource) {
-            m_readableStreamSource->close();
-            m_readableStreamSource = nullptr;
-        }
+    if (m_readableStreamSource) {
+        m_readableStreamSource->close();
+        m_readableStreamSource = nullptr;
+    }
 #endif
-        m_body->loadingSucceeded();
-    });
+    m_body->loadingSucceeded();
     finishBlobLoading();
 }
 
 void FetchBodyOwner::blobLoadingFailed()
 {
     ASSERT(!isBodyNull());
-    runNetworkTaskWhenPossible([this]() mutable {
 #if ENABLE(STREAMS_API)
-        if (m_readableStreamSource) {
-            if (!m_readableStreamSource->isCancelling())
-                m_readableStreamSource->error(Exception { TypeError, "Blob loading failed"_s});
-            m_readableStreamSource = nullptr;
-        } else
+    if (m_readableStreamSource) {
+        if (!m_readableStreamSource->isCancelling())
+            m_readableStreamSource->error(Exception { TypeError, "Blob loading failed"_s});
+        m_readableStreamSource = nullptr;
+    } else
 #endif
-            m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
-    });
+        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
     finishBlobLoading();
 }
 
@@ -314,10 +299,8 @@ void FetchBodyOwner::blobChunk(const char* data, size_t size)
     ASSERT(data);
 #if ENABLE(STREAMS_API)
     ASSERT(m_readableStreamSource);
-    runNetworkTaskWhenPossible([this, arrayBuffer = ArrayBuffer::tryCreate(data, size)]() mutable {
-        if (!m_readableStreamSource->enqueue(WTFMove(arrayBuffer)))
-            stop();
-    });
+    if (!m_readableStreamSource->enqueue(ArrayBuffer::tryCreate(data, size)))
+        stop();
 #else
     UNUSED_PARAM(data);
     UNUSED_PARAM(size);
index 3b389a8..dd21e55 100644 (file)
@@ -125,8 +125,6 @@ protected:
     Ref<FetchHeaders> m_headers;
 
 private:
-    void runNetworkTaskWhenPossible(Function<void()>&&);
-
     Optional<BlobLoader> m_blobLoader;
     bool m_isBodyOpaque { false };
 
index 209d9af..0e038f3 100644 (file)
@@ -230,53 +230,48 @@ static inline bool isMediaStreamCorrectlyStarted(const MediaStream& stream)
 void UserMediaRequest::allow(CaptureDevice&& audioDevice, CaptureDevice&& videoDevice, String&& deviceIdentifierHashSalt, CompletionHandler<void()>&& completionHandler)
 {
     RELEASE_LOG(MediaStream, "UserMediaRequest::allow %s %s", audioDevice ? audioDevice.persistentId().utf8().data() : "", videoDevice ? videoDevice.persistentId().utf8().data() : "");
-    auto* document = this->document();
-    if (!document)
-        return completionHandler();
-
-    document->eventLoop().queueTask(TaskSource::UserInteraction, *document, [this, protectedThis = makeRef(*this), audioDevice = WTFMove(audioDevice), videoDevice = WTFMove(videoDevice), deviceIdentifierHashSalt = WTFMove(deviceIdentifierHashSalt), completionHandler = WTFMove(completionHandler)]() mutable {
-        auto callback = [this, protector = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
-            auto scopeExit = makeScopeExit([completionHandler = WTFMove(completionHandler)]() mutable {
-                completionHandler();
-            });
-            if (isContextStopped())
-                return;
-
-            if (!privateStream) {
-                RELEASE_LOG(MediaStream, "UserMediaRequest::allow failed to create media stream!");
-                deny(MediaAccessDenialReason::HardwareError);
-                return;
-            }
-
-            auto& document = downcast<Document>(*m_scriptExecutionContext);
-            privateStream->monitorOrientation(document.orientationNotifier());
-
-            auto stream = MediaStream::create(document, privateStream.releaseNonNull());
-            stream->startProducingData();
-
-            if (!isMediaStreamCorrectlyStarted(stream)) {
-                deny(MediaAccessDenialReason::HardwareError);
-                return;
-            }
-
-            ASSERT(document.isCapturing());
-            stream->document()->setHasCaptureMediaStreamTrack();
-            m_promise->resolve(WTFMove(stream));
-        };
-
-        auto& document = downcast<Document>(*scriptExecutionContext());
-        document.setDeviceIDHashSalt(deviceIdentifierHashSalt);
-
-        RealtimeMediaSourceCenter::singleton().createMediaStream(document.logger(), WTFMove(callback), WTFMove(deviceIdentifierHashSalt), WTFMove(audioDevice), WTFMove(videoDevice), m_request);
-
-        if (!m_scriptExecutionContext)
+
+    auto callback = [this, protector = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
+        auto scopeExit = makeScopeExit([completionHandler = WTFMove(completionHandler)]() mutable {
+            completionHandler();
+        });
+        if (isContextStopped())
+            return;
+
+        if (!privateStream) {
+            RELEASE_LOG(MediaStream, "UserMediaRequest::allow failed to create media stream!");
+            deny(MediaAccessDenialReason::HardwareError);
             return;
+        }
+
+        auto& document = downcast<Document>(*m_scriptExecutionContext);
+        privateStream->monitorOrientation(document.orientationNotifier());
+
+        auto stream = MediaStream::create(document, privateStream.releaseNonNull());
+        stream->startProducingData();
+
+        if (!isMediaStreamCorrectlyStarted(stream)) {
+            deny(MediaAccessDenialReason::HardwareError);
+            return;
+        }
+
+        ASSERT(document.isCapturing());
+        stream->document()->setHasCaptureMediaStreamTrack();
+        m_promise->resolve(WTFMove(stream));
+    };
+
+    auto& document = downcast<Document>(*scriptExecutionContext());
+    document.setDeviceIDHashSalt(deviceIdentifierHashSalt);
+
+    RealtimeMediaSourceCenter::singleton().createMediaStream(document.logger(), WTFMove(callback), WTFMove(deviceIdentifierHashSalt), WTFMove(audioDevice), WTFMove(videoDevice), m_request);
+
+    if (!m_scriptExecutionContext)
+        return;
 
 #if ENABLE(WEB_RTC)
-        if (auto* page = document.page())
-            page->rtcController().disableICECandidateFilteringForDocument(document);
+    if (auto* page = document.page())
+        page->rtcController().disableICECandidateFilteringForDocument(document);
 #endif
-    });
 }
 
 void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& message)
@@ -324,12 +319,10 @@ void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& messag
         break;
     }
 
-    document()->eventLoop().queueTask(TaskSource::UserInteraction, *document(), [this, protectedThis = makeRef(*this), code, message]() mutable {
-        if (!message.isEmpty())
-            m_promise->reject(code, message);
-        else
-            m_promise->reject(code);
-    });
+    if (!message.isEmpty())
+        m_promise->reject(code, message);
+    else
+        m_promise->reject(code);
 }
 
 void UserMediaRequest::stop()
index c401b46..2ce5a97 100644 (file)
                46EF142C1F97B7D800C2A524 /* ServiceWorkerClients.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EF14221F97B7BA00C2A524 /* ServiceWorkerClients.h */; };
                46EF142D1F97B7D800C2A524 /* ServiceWorkerClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EF14241F97B7BA00C2A524 /* ServiceWorkerClient.h */; };
                46EFAF121E5FB9F100E7F34B /* LowPowerModeNotifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EFAF101E5FB9E100E7F34B /* LowPowerModeNotifier.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               46F02A1A23737F8300106A64 /* AbstractEventLoop.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B0ABCA123679AB300B45085 /* AbstractEventLoop.h */; settings = {ATTRIBUTES = (Private, ); }; };
                46FCB6181A70820E00C5A21E /* DiagnosticLoggingKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = CD37B37515C1A7E1006DC898 /* DiagnosticLoggingKeys.h */; settings = {ATTRIBUTES = (Private, ); }; };
                490707E71219C04300D90E51 /* ANGLEWebKitBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 490707E51219C04300D90E51 /* ANGLEWebKitBridge.h */; settings = {ATTRIBUTES = (Private, ); }; };
                49291E4B134172C800E753DE /* ImageRenderingMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 49291E4A134172C800E753DE /* ImageRenderingMode.h */; };
                                7CD0E2B81F80A4820016A4CE /* AbortController.h in Headers */,
                                7CD0E2BF1F80A56E0016A4CE /* AbortSignal.h in Headers */,
                                46B95195207D633400A7D2DD /* AbstractDOMWindow.h in Headers */,
+                               46F02A1A23737F8300106A64 /* AbstractEventLoop.h in Headers */,
                                46B95196207D633A00A7D2DD /* AbstractFrame.h in Headers */,
                                F48223131E386E240066FC79 /* AbstractPasteboard.h in Headers */,
                                41E1B1D10FF5986900576B3B /* AbstractWorker.h in Headers */,
index 750ea8d..305bcf5 100644 (file)
@@ -43,7 +43,6 @@ class ArrayBufferView;
 class DataView;
 class JSValue;
 class JSObject;
-template<typename> class Strong;
 }
 
 namespace WebCore {
index 63c1772..a04b83f 100644 (file)
@@ -51,7 +51,7 @@ protected:
     DOMGuardedObject(JSDOMGlobalObject&, JSC::JSCell&);
 
     void contextDestroyed() override;
-    bool isEmpty() { return !m_guarded; }
+    bool isEmpty() const { return !m_guarded; }
 
     JSC::Weak<JSC::JSCell> m_guarded;
     JSC::Weak<JSDOMGlobalObject> m_globalObject;
index 550bcc3..0bc5a89 100644 (file)
@@ -33,6 +33,7 @@
 #include <JavaScriptCore/Exception.h>
 #include <JavaScriptCore/JSONObject.h>
 #include <JavaScriptCore/JSPromiseConstructor.h>
+#include <JavaScriptCore/Strong.h>
 
 namespace WebCore {
 using namespace JSC;
@@ -45,9 +46,18 @@ JSC::JSValue DeferredPromise::promise() const
 
 void DeferredPromise::callFunction(JSGlobalObject& lexicalGlobalObject, ResolveMode mode, JSValue resolution)
 {
-    if (!canInvokeCallback())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
+    if (activeDOMObjectsAreSuspended()) {
+        JSC::Strong<JSC::Unknown, ShouldStrongDestructorGrabLock::Yes> strongResolution(lexicalGlobalObject.vm(), resolution);
+        scriptExecutionContext()->eventLoop().queueTask(TaskSource::Networking, *scriptExecutionContext(), [this, protectedThis = makeRef(*this), mode, strongResolution = WTFMove(strongResolution)]() mutable {
+            if (!shouldIgnoreRequestToFulfill())
+                callFunction(*globalObject(), mode, strongResolution.get());
+        });
+        return;
+    }
+
     // FIXME: We could have error since any JS call can throw stack-overflow errors.
     // https://bugs.webkit.org/show_bug.cgi?id=203402
     switch (mode) {
@@ -65,15 +75,22 @@ void DeferredPromise::callFunction(JSGlobalObject& lexicalGlobalObject, ResolveM
 
 void DeferredPromise::whenSettled(Function<void()>&& callback)
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
+    if (activeDOMObjectsAreSuspended()) {
+        scriptExecutionContext()->eventLoop().queueTask(TaskSource::Networking, *scriptExecutionContext(), [this, protectedThis = makeRef(*this), callback = WTFMove(callback)]() mutable {
+            whenSettled(WTFMove(callback));
+        });
+        return;
+    }
+
     DOMPromise::whenPromiseIsSettled(globalObject(), deferred(), WTFMove(callback));
 }
 
 void DeferredPromise::reject()
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
     ASSERT(deferred());
@@ -85,7 +102,7 @@ void DeferredPromise::reject()
 
 void DeferredPromise::reject(std::nullptr_t)
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
     ASSERT(deferred());
@@ -97,7 +114,7 @@ void DeferredPromise::reject(std::nullptr_t)
 
 void DeferredPromise::reject(Exception exception)
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
     ASSERT(deferred());
@@ -129,7 +146,7 @@ void DeferredPromise::reject(Exception exception)
 
 void DeferredPromise::reject(ExceptionCode ec, const String& message)
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
     ASSERT(deferred());
@@ -162,7 +179,7 @@ void DeferredPromise::reject(ExceptionCode ec, const String& message)
 
 void DeferredPromise::reject(const JSC::PrivateName& privateName)
 {
-    if (isSuspended())
+    if (shouldIgnoreRequestToFulfill())
         return;
 
     ASSERT(deferred());
index f0a1e7e..c563a26 100644 (file)
 
 #pragma once
 
+#include "AbstractEventLoop.h"
 #include "ExceptionOr.h"
 #include "JSDOMConvert.h"
 #include "JSDOMGuardedObject.h"
+#include "ScriptExecutionContext.h"
 #include <JavaScriptCore/CatchScope.h>
 #include <JavaScriptCore/JSPromise.h>
 
@@ -58,8 +60,9 @@ public:
     template<class IDLType>
     void resolve(typename IDLType::ParameterType value)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -69,8 +72,9 @@ public:
 
     void resolve()
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -81,8 +85,9 @@ public:
     template<class IDLType>
     void resolveWithNewlyCreated(typename IDLType::ParameterType value)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -93,8 +98,9 @@ public:
     template<class IDLType>
     void resolveCallbackValueWithNewlyCreated(const Function<typename IDLType::InnerParameterType(ScriptExecutionContext&)>& createValue)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         auto* lexicalGlobalObject = globalObject();
@@ -105,8 +111,9 @@ public:
     template<class IDLType>
     void reject(typename IDLType::ParameterType value)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -123,8 +130,9 @@ public:
     template<typename Callback>
     void resolveWithCallback(Callback callback)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -135,8 +143,9 @@ public:
     template<typename Callback>
     void rejectWithCallback(Callback callback)
     {
-        if (isSuspended())
+        if (shouldIgnoreRequestToFulfill())
             return;
+
         ASSERT(deferred());
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
@@ -155,6 +164,8 @@ private:
     {
     }
 
+    bool shouldIgnoreRequestToFulfill() const { return isEmpty() || activeDOMObjectAreStopped(); }
+
     JSC::JSPromise* deferred() const { return guarded(); }
 
     enum class ResolveMode { Resolve, Reject };
index 43cba89..27ff175 100644 (file)
@@ -199,22 +199,14 @@ void FontFaceSet::startedLoading()
 void FontFaceSet::didFirstLayout()
 {
     m_isFirstLayoutDone = true;
-    if (!m_backing->hasActiveFontFaces() && !m_readyPromise->isFulfilled()) {
-        queueTaskKeepingObjectAlive(*this, TaskSource::FontLoading, [this] {
-            if (!m_readyPromise->isFulfilled())
-                m_readyPromise->resolve(*this);
-        });
-    }
+    if (!m_backing->hasActiveFontFaces() && !m_readyPromise->isFulfilled())
+        m_readyPromise->resolve(*this);
 }
 
 void FontFaceSet::completedLoading()
 {
-    if (m_isFirstLayoutDone && !m_readyPromise->isFulfilled()) {
-        queueTaskKeepingObjectAlive(*this, TaskSource::FontLoading, [this] {
-            if (!m_readyPromise->isFulfilled())
-                m_readyPromise->resolve(*this);
-        });
-    }
+    if (m_isFirstLayoutDone && !m_readyPromise->isFulfilled())
+        m_readyPromise->resolve(*this);
 }
 
 void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
@@ -226,22 +218,20 @@ void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
     if (pendingPromises.isEmpty())
         return;
 
-    queueTaskKeepingObjectAlive(*this, TaskSource::FontLoading, [pendingPromises = WTFMove(pendingPromises), newStatus] {
-        for (auto& pendingPromise : pendingPromises) {
-            if (pendingPromise->hasReachedTerminalState)
-                continue;
-            if (newStatus == CSSFontFace::Status::Success) {
-                if (pendingPromise->hasOneRef()) {
-                    pendingPromise->promise->resolve(pendingPromise->faces);
-                    pendingPromise->hasReachedTerminalState = true;
-                }
-            } else {
-                ASSERT(newStatus == CSSFontFace::Status::Failure);
-                pendingPromise->promise->reject(NetworkError);
+    for (auto& pendingPromise : pendingPromises) {
+        if (pendingPromise->hasReachedTerminalState)
+            continue;
+        if (newStatus == CSSFontFace::Status::Success) {
+            if (pendingPromise->hasOneRef()) {
+                pendingPromise->promise->resolve(pendingPromise->faces);
                 pendingPromise->hasReachedTerminalState = true;
             }
+        } else {
+            ASSERT(newStatus == CSSFontFace::Status::Failure);
+            pendingPromise->promise->reject(NetworkError);
+            pendingPromise->hasReachedTerminalState = true;
         }
-    });
+    }
 }
 
 FontFaceSet& FontFaceSet::readyPromiseResolve()
index ae7a4a1..2f7936a 100644 (file)
@@ -48,4 +48,16 @@ bool ActiveDOMCallback::canInvokeCallback() const
     return context && !context->activeDOMObjectsAreSuspended() && !context->activeDOMObjectsAreStopped();
 }
 
+bool ActiveDOMCallback::activeDOMObjectsAreSuspended() const
+{
+    auto* context = scriptExecutionContext();
+    return context && context->activeDOMObjectsAreSuspended();
+}
+
+bool ActiveDOMCallback::activeDOMObjectAreStopped() const
+{
+    auto* context = scriptExecutionContext();
+    return !context || context->activeDOMObjectsAreStopped();
+}
+
 } // namespace WebCore
index 2b35b12..7b11ce0 100644 (file)
@@ -51,6 +51,9 @@ public:
     virtual ~ActiveDOMCallback();
 
     WEBCORE_EXPORT bool canInvokeCallback() const;
+
+    WEBCORE_EXPORT bool activeDOMObjectsAreSuspended() const;
+    WEBCORE_EXPORT bool activeDOMObjectAreStopped() const;
     
     virtual void visitJSFunction(JSC::SlotVisitor&) { }
 };
index 42a7f22..366c036 100644 (file)
@@ -34,6 +34,7 @@
 #include "ShadowRootMode.h"
 #include "SimulatedClickOptions.h"
 #include "StyleChange.h"
+#include <JavaScriptCore/Strong.h>
 
 namespace WebCore {
 
index a63347b..0f172a1 100644 (file)
@@ -44,7 +44,6 @@ class CallFrame;
 class Exception;
 class JSPromise;
 class VM;
-template<typename> class Strong;
 }
 
 namespace Inspector {
index 587292d..4bb1c9f 100644 (file)
@@ -37,6 +37,7 @@
 #include "ScrollTypes.h"
 #include "Supplementable.h"
 #include <JavaScriptCore/HandleTypes.h>
+#include <JavaScriptCore/Strong.h>
 #include <wtf/Function.h>
 #include <wtf/HashSet.h>
 #include <wtf/WeakPtr.h>
@@ -45,7 +46,6 @@ namespace JSC {
 class CallFrame;
 class JSObject;
 class JSValue;
-template<typename> class Strong;
 }
 
 namespace WebCore {
index 4050df5..6e05c3b 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "AbstractDOMWindow.h"
 #include "RemoteFrame.h"
+#include <JavaScriptCore/Strong.h>
 #include <wtf/IsoMalloc.h>
 #include <wtf/TypeCasts.h>
 
@@ -35,7 +36,6 @@ class CallFrame;
 class JSGlobalObject;
 class JSObject;
 class JSValue;
-template<typename> class Strong;
 }
 
 namespace WebCore {