Commit 7e9f254c authored by yhirano@chromium.org's avatar yhirano@chromium.org

Introduce KeepAliveWhilePending to ScriptPromiseResolverWithContext.

Some modules, for example WebMIDI and WebCrypto, needs "Async Initializer".
The initializer should stay alive until the initialization succeeds (i.e. the
associated Promise is resolved), the initialization fails
(i.e. rejected), or the associated ExecutionContext is stopped.
This CL introduces the the constructor mode ScriptPromiseResolverWithContext.
If KeepAliveWhilePending is specified, the created resolver stays alive while
one of the above conditions meets.
Each initializer can stay alive by inheriting ScriptPromiseResolverWithContext.

This CL rewrites WebMIDI and WebCrypto async operations with it.

BUG=361041

Committed: https://src.chromium.org/viewvc/blink?view=rev&revision=176381

Review URL: https://codereview.chromium.org/311733004

git-svn-id: svn://svn.chromium.org/blink/trunk@176403 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 18d0c760
...@@ -13,9 +13,21 @@ ScriptPromiseResolverWithContext::ScriptPromiseResolverWithContext(ScriptState* ...@@ -13,9 +13,21 @@ ScriptPromiseResolverWithContext::ScriptPromiseResolverWithContext(ScriptState*
: ActiveDOMObject(scriptState->executionContext()) : ActiveDOMObject(scriptState->executionContext())
, m_state(Pending) , m_state(Pending)
, m_scriptState(scriptState) , m_scriptState(scriptState)
, m_mode(Default)
, m_timer(this, &ScriptPromiseResolverWithContext::onTimerFired) , m_timer(this, &ScriptPromiseResolverWithContext::onTimerFired)
, m_resolver(ScriptPromiseResolver::create(m_scriptState.get())) , m_resolver(ScriptPromiseResolver::create(m_scriptState.get()))
{ {
if (executionContext()->activeDOMObjectsAreStopped())
m_state = ResolvedOrRejected;
}
ScriptPromiseResolverWithContext::~ScriptPromiseResolverWithContext()
{
if (m_state != ResolvedOrRejected) {
ScriptState::Scope scope(m_scriptState.get());
reject(v8::Exception::Error(v8::String::NewFromUtf8(m_scriptState->isolate(),
"ScriptPromiseResolverWithContext is destructed without resolve / reject")));
}
} }
void ScriptPromiseResolverWithContext::suspend() void ScriptPromiseResolverWithContext::suspend()
...@@ -35,9 +47,19 @@ void ScriptPromiseResolverWithContext::stop() ...@@ -35,9 +47,19 @@ void ScriptPromiseResolverWithContext::stop()
clear(); clear();
} }
void ScriptPromiseResolverWithContext::keepAliveWhilePending()
{
if (m_state == ResolvedOrRejected || m_mode == KeepAliveWhilePending)
return;
// Keep |this| while the promise is Pending.
// deref() will be called in clear().
m_mode = KeepAliveWhilePending;
ref();
}
void ScriptPromiseResolverWithContext::onTimerFired(Timer<ScriptPromiseResolverWithContext>*) void ScriptPromiseResolverWithContext::onTimerFired(Timer<ScriptPromiseResolverWithContext>*)
{ {
RefPtr<ScriptPromiseResolverWithContext> protect(this);
ScriptState::Scope scope(m_scriptState.get()); ScriptState::Scope scope(m_scriptState.get());
resolveOrRejectImmediately(); resolveOrRejectImmediately();
} }
...@@ -63,15 +85,23 @@ void ScriptPromiseResolverWithContext::resolveOrRejectImmediately() ...@@ -63,15 +85,23 @@ void ScriptPromiseResolverWithContext::resolveOrRejectImmediately()
void ScriptPromiseResolverWithContext::clear() void ScriptPromiseResolverWithContext::clear()
{ {
if (m_state == ResolvedOrRejected)
return;
ResolutionState state = m_state; ResolutionState state = m_state;
m_state = ResolvedOrRejected; m_state = ResolvedOrRejected;
m_resolver.clear(); m_resolver.clear();
m_value.clear(); m_value.clear();
if (m_mode == KeepAliveWhilePending) {
// |ref| was called in the constructor.
deref();
}
// |this| may be deleted here, but it is safe to check |state| because
// it doesn't depend on |this|. When |this| is deleted, |state| can't be
// |Resolving| nor |Rejecting| and hence |this->deref()| can't be executed.
if (state == Resolving || state == Rejecting) { if (state == Resolving || state == Rejecting) {
// |ref| was called in |resolveOrReject|. // |ref| was called in |resolveOrReject|.
deref(); deref();
} }
// |this| may be deleted here.
} }
} // namespace WebCore } // namespace WebCore
...@@ -38,6 +38,8 @@ public: ...@@ -38,6 +38,8 @@ public:
return resolver.release(); return resolver.release();
} }
virtual ~ScriptPromiseResolverWithContext();
// Anything that can be passed to toV8Value can be passed to this function. // Anything that can be passed to toV8Value can be passed to this function.
template <typename T> template <typename T>
void resolve(T value) void resolve(T value)
...@@ -68,7 +70,13 @@ public: ...@@ -68,7 +70,13 @@ public:
virtual void resume() OVERRIDE; virtual void resume() OVERRIDE;
virtual void stop() OVERRIDE; virtual void stop() OVERRIDE;
// Once this function is called this resolver stays alive while the
// promise is pending and the associated ExecutionContext isn't stopped.
void keepAliveWhilePending();
protected: protected:
// You need to call suspendIfNeeded after the construction because
// this is an ActiveDOMObject.
explicit ScriptPromiseResolverWithContext(ScriptState*); explicit ScriptPromiseResolverWithContext(ScriptState*);
private: private:
...@@ -78,6 +86,10 @@ private: ...@@ -78,6 +86,10 @@ private:
Rejecting, Rejecting,
ResolvedOrRejected, ResolvedOrRejected,
}; };
enum LifetimeMode {
Default,
KeepAliveWhilePending,
};
template<typename T> template<typename T>
v8::Handle<v8::Value> toV8Value(const T& value) v8::Handle<v8::Value> toV8Value(const T& value)
...@@ -90,6 +102,7 @@ private: ...@@ -90,6 +102,7 @@ private:
{ {
if (m_state != Pending || !executionContext() || executionContext()->activeDOMObjectsAreStopped()) if (m_state != Pending || !executionContext() || executionContext()->activeDOMObjectsAreStopped())
return; return;
ASSERT(newState == Resolving || newState == Rejecting);
m_state = newState; m_state = newState;
// Retain this object until it is actually resolved or rejected. // Retain this object until it is actually resolved or rejected.
// |deref| will be called in |clear|. // |deref| will be called in |clear|.
...@@ -107,6 +120,7 @@ private: ...@@ -107,6 +120,7 @@ private:
ResolutionState m_state; ResolutionState m_state;
const RefPtr<ScriptState> m_scriptState; const RefPtr<ScriptState> m_scriptState;
LifetimeMode m_mode;
Timer<ScriptPromiseResolverWithContext> m_timer; Timer<ScriptPromiseResolverWithContext> m_timer;
RefPtr<ScriptPromiseResolver> m_resolver; RefPtr<ScriptPromiseResolver> m_resolver;
ScopedPersistent<v8::Value> m_value; ScopedPersistent<v8::Value> m_value;
......
...@@ -47,6 +47,27 @@ ...@@ -47,6 +47,27 @@
namespace WebCore { namespace WebCore {
namespace {
class WeakResolver : public ScriptPromiseResolverWithContext {
public:
static WeakPtr<ScriptPromiseResolverWithContext> create(ScriptState* scriptState)
{
RefPtr<WeakResolver> p = adoptRef(new WeakResolver(scriptState));
p->suspendIfNeeded();
p->keepAliveWhilePending();
return p->m_weakPtrFactory.createWeakPtr();
}
private:
explicit WeakResolver(ScriptState* scriptState)
: ScriptPromiseResolverWithContext(scriptState)
, m_weakPtrFactory(this) { }
WeakPtrFactory<ScriptPromiseResolverWithContext> m_weakPtrFactory;
};
} // namespace
ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType errorType) ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType errorType)
{ {
switch (errorType) { switch (errorType) {
...@@ -75,120 +96,6 @@ ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType errorType) ...@@ -75,120 +96,6 @@ ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType errorType)
return 0; return 0;
} }
// The PromiseState class contains all the state which is tied to an
// ExecutionContext. Whereas CryptoResultImpl can be deleted from any thread,
// PromiseState is not thread safe and must only be accessed and deleted from
// the blink thread.
//
// This is achieved by making CryptoResultImpl hold a WeakPtr to the PromiseState.
// The PromiseState deletes itself after being notified of completion.
// Additionally the PromiseState is deleted when the ExecutionContext is
// destroyed (necessary to avoid leaks when dealing with WebWorker threads,
// which may die before the operation is completed).
class CryptoResultImpl::PromiseState FINAL {
public:
static WeakPtr<PromiseState> create(ScriptState* scriptState)
{
PromiseState* promiseState = new PromiseState(scriptState);
return promiseState->m_weakFactory.createWeakPtr();
}
void contextDestroyed()
{
delete this;
}
ScriptPromise promise()
{
return m_promiseResolver->promise();
}
void completeWithError(blink::WebCryptoErrorType errorType, const blink::WebString& errorDetails)
{
m_promiseResolver->reject(DOMException::create(webCryptoErrorToExceptionCode(errorType), errorDetails));
delete this;
}
void completeWithBuffer(const blink::WebArrayBuffer& buffer)
{
m_promiseResolver->resolve(PassRefPtr<ArrayBuffer>(buffer));
delete this;
}
void completeWithJson(const char* utf8Data, unsigned length)
{
ScriptState* scriptState = m_promiseResolver->scriptState();
ScriptState::Scope scope(scriptState);
v8::Handle<v8::String> jsonString = v8::String::NewFromUtf8(scriptState->isolate(), utf8Data, v8::String::kInternalizedString, length);
v8::TryCatch exceptionCatcher;
v8::Handle<v8::Value> jsonDictionary = v8::JSON::Parse(jsonString);
if (exceptionCatcher.HasCaught() || jsonDictionary.IsEmpty()) {
ASSERT_NOT_REACHED();
m_promiseResolver->reject(DOMException::create(OperationError, "Failed inflating JWK JSON to object"));
} else {
m_promiseResolver->resolve(jsonDictionary);
}
delete this;
}
void completeWithBoolean(bool b)
{
m_promiseResolver->resolve(b);
delete this;
}
void completeWithKey(const blink::WebCryptoKey& key)
{
m_promiseResolver->resolve(Key::create(key));
delete this;
}
void completeWithKeyPair(const blink::WebCryptoKey& publicKey, const blink::WebCryptoKey& privateKey)
{
m_promiseResolver->resolve(KeyPair::create(publicKey, privateKey));
delete this;
}
private:
// This subclass of ScriptPromiseResolverWithContext is to be notified
// when the context was destroyed.
class PromiseResolver FINAL : public ScriptPromiseResolverWithContext {
public:
static PassRefPtr<PromiseResolver> create(ScriptState* scriptState, PromiseState* promiseState)
{
RefPtr<PromiseResolver> resolver = adoptRef(new PromiseResolver(scriptState, promiseState));
resolver->suspendIfNeeded();
return resolver.release();
}
virtual void contextDestroyed() OVERRIDE
{
ScriptPromiseResolverWithContext::contextDestroyed();
m_promiseState->contextDestroyed();
}
private:
explicit PromiseResolver(ScriptState* scriptState, PromiseState* promiseState)
: ScriptPromiseResolverWithContext(scriptState)
, m_promiseState(promiseState)
{
}
PromiseState* m_promiseState;
};
explicit PromiseState(ScriptState* scriptState)
: m_weakFactory(this)
, m_promiseResolver(PromiseResolver::create(scriptState, this))
{
}
WeakPtrFactory<PromiseState> m_weakFactory;
RefPtr<PromiseResolver> m_promiseResolver;
};
CryptoResultImpl::~CryptoResultImpl() CryptoResultImpl::~CryptoResultImpl()
{ {
} }
...@@ -200,48 +107,62 @@ PassRefPtr<CryptoResultImpl> CryptoResultImpl::create(ScriptState* scriptState) ...@@ -200,48 +107,62 @@ PassRefPtr<CryptoResultImpl> CryptoResultImpl::create(ScriptState* scriptState)
void CryptoResultImpl::completeWithError(blink::WebCryptoErrorType errorType, const blink::WebString& errorDetails) void CryptoResultImpl::completeWithError(blink::WebCryptoErrorType errorType, const blink::WebString& errorDetails)
{ {
if (m_promiseState) if (m_resolver)
m_promiseState->completeWithError(errorType, errorDetails); m_resolver->reject(DOMException::create(webCryptoErrorToExceptionCode(errorType), errorDetails));
} }
void CryptoResultImpl::completeWithBuffer(const blink::WebArrayBuffer& buffer) void CryptoResultImpl::completeWithBuffer(const blink::WebArrayBuffer& buffer)
{ {
if (m_promiseState) if (m_resolver)
m_promiseState->completeWithBuffer(buffer); m_resolver->resolve(PassRefPtr<ArrayBuffer>(buffer));
} }
void CryptoResultImpl::completeWithJson(const char* utf8Data, unsigned length) void CryptoResultImpl::completeWithJson(const char* utf8Data, unsigned length)
{ {
if (m_promiseState) if (m_resolver) {
m_promiseState->completeWithJson(utf8Data, length); ScriptPromiseResolverWithContext* resolver = m_resolver.get();
ScriptState* scriptState = resolver->scriptState();
ScriptState::Scope scope(scriptState);
v8::Handle<v8::String> jsonString = v8::String::NewFromUtf8(scriptState->isolate(), utf8Data, v8::String::kInternalizedString, length);
v8::TryCatch exceptionCatcher;
v8::Handle<v8::Value> jsonDictionary = v8::JSON::Parse(jsonString);
if (exceptionCatcher.HasCaught() || jsonDictionary.IsEmpty()) {
ASSERT_NOT_REACHED();
resolver->reject(DOMException::create(OperationError, "Failed inflating JWK JSON to object"));
} else {
resolver->resolve(jsonDictionary);
}
}
} }
void CryptoResultImpl::completeWithBoolean(bool b) void CryptoResultImpl::completeWithBoolean(bool b)
{ {
if (m_promiseState) if (m_resolver)
m_promiseState->completeWithBoolean(b); m_resolver->resolve(b);
} }
void CryptoResultImpl::completeWithKey(const blink::WebCryptoKey& key) void CryptoResultImpl::completeWithKey(const blink::WebCryptoKey& key)
{ {
if (m_promiseState) if (m_resolver)
m_promiseState->completeWithKey(key); m_resolver->resolve(Key::create(key));
} }
void CryptoResultImpl::completeWithKeyPair(const blink::WebCryptoKey& publicKey, const blink::WebCryptoKey& privateKey) void CryptoResultImpl::completeWithKeyPair(const blink::WebCryptoKey& publicKey, const blink::WebCryptoKey& privateKey)
{ {
if (m_promiseState) if (m_resolver)
m_promiseState->completeWithKeyPair(publicKey, privateKey); m_resolver->resolve(KeyPair::create(publicKey, privateKey));
} }
CryptoResultImpl::CryptoResultImpl(ScriptState* scriptState) CryptoResultImpl::CryptoResultImpl(ScriptState* scriptState)
: m_promiseState(PromiseState::create(scriptState)) : m_resolver(WeakResolver::create(scriptState))
{ {
} }
ScriptPromise CryptoResultImpl::promise() ScriptPromise CryptoResultImpl::promise()
{ {
return m_promiseState->promise(); return m_resolver->promise();
} }
} // namespace WebCore } // namespace WebCore
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
namespace WebCore { namespace WebCore {
class ScriptPromiseResolverWithContext;
ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType); ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType);
// Wrapper around a Promise to notify completion of the crypto operation. // Wrapper around a Promise to notify completion of the crypto operation.
...@@ -51,7 +52,7 @@ ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType); ...@@ -51,7 +52,7 @@ ExceptionCode webCryptoErrorToExceptionCode(blink::WebCryptoErrorType);
// * The CryptoResult interface must only be called from the origin thread. // * The CryptoResult interface must only be called from the origin thread.
// * addref() and deref() can be called from any thread. // * addref() and deref() can be called from any thread.
// * One of the completeWith***() functions must be called, or the // * One of the completeWith***() functions must be called, or the
// PromiseState will be leaked until the ExecutionContext is destroyed. // m_resolver will be leaked until the ExecutionContext is destroyed.
class CryptoResultImpl FINAL : public CryptoResult { class CryptoResultImpl FINAL : public CryptoResult {
public: public:
~CryptoResultImpl(); ~CryptoResultImpl();
...@@ -71,8 +72,7 @@ public: ...@@ -71,8 +72,7 @@ public:
private: private:
explicit CryptoResultImpl(ScriptState*); explicit CryptoResultImpl(ScriptState*);
class PromiseState; WeakPtr<ScriptPromiseResolverWithContext> m_resolver;
WeakPtr<PromiseState> m_promiseState;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -31,11 +31,6 @@ ...@@ -31,11 +31,6 @@
#include "config.h" #include "config.h"
#include "modules/webmidi/MIDIAccess.h" #include "modules/webmidi/MIDIAccess.h"
#include "bindings/v8/ScriptFunction.h"
#include "bindings/v8/ScriptPromise.h"
#include "bindings/v8/ScriptPromiseResolverWithContext.h"
#include "bindings/v8/V8Binding.h"
#include "core/dom/DOMError.h"
#include "core/dom/Document.h" #include "core/dom/Document.h"
#include "core/loader/DocumentLoadTiming.h" #include "core/loader/DocumentLoadTiming.h"
#include "core/loader/DocumentLoader.h" #include "core/loader/DocumentLoader.h"
...@@ -49,28 +44,27 @@ ...@@ -49,28 +44,27 @@
namespace WebCore { namespace WebCore {
ScriptPromise MIDIAccess::request(const MIDIOptions& options, ScriptState* scriptState) MIDIAccess::MIDIAccess(PassOwnPtr<MIDIAccessor> accessor, bool sysexEnabled, const Vector<MIDIAccessInitializer::PortDescriptor>& ports, ExecutionContext* executionContext)
: ActiveDOMObject(executionContext)
, m_accessor(accessor)
, m_sysexEnabled(sysexEnabled)
{ {
RefPtrWillBeRawPtr<MIDIAccess> midiAccess(adoptRefWillBeRefCountedGarbageCollected(new MIDIAccess(options, scriptState->executionContext()))); ScriptWrappable::init(this);
midiAccess->suspendIfNeeded(); m_accessor->setClient(this);
// Create a wrapper to expose this object to the V8 GC so that for (size_t i = 0; i < ports.size(); ++i) {
// hasPendingActivity takes effect. const MIDIAccessInitializer::PortDescriptor& port = ports[i];
toV8NoInline(midiAccess.get(), scriptState->context()->Global(), scriptState->isolate()); if (port.type == MIDIPort::MIDIPortTypeInput) {
// Now this object is retained because hasPending returns true. m_inputs.append(MIDIInput::create(this, port.id, port.manufacturer, port.name, port.version));
return midiAccess->m_initializer->initialize(scriptState); } else {
m_outputs.append(MIDIOutput::create(this, m_outputs.size(), port.id, port.manufacturer, port.name, port.version));
}
}
} }
MIDIAccess::~MIDIAccess() MIDIAccess::~MIDIAccess()
{ {
} }
MIDIAccess::MIDIAccess(const MIDIOptions& options, ExecutionContext* context)
: ActiveDOMObject(context)
, m_initializer(MIDIAccessInitializer::create(options, this))
{
ScriptWrappable::init(this);
}
void MIDIAccess::didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) void MIDIAccess::didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version)
{ {
ASSERT(isMainThread()); ASSERT(isMainThread());
...@@ -126,20 +120,6 @@ void MIDIAccess::sendMIDIData(unsigned portIndex, const unsigned char* data, siz ...@@ -126,20 +120,6 @@ void MIDIAccess::sendMIDIData(unsigned portIndex, const unsigned char* data, siz
void MIDIAccess::stop() void MIDIAccess::stop()
{ {
m_accessor.clear(); m_accessor.clear();
m_initializer->cancel();
}
bool MIDIAccess::hasPendingActivity() const
{
return m_initializer->hasPendingActivity();
}
void MIDIAccess::initialize(PassOwnPtr<MIDIAccessor> accessor, bool sysexEnabled)
{
ASSERT(accessor);
m_accessor = accessor;
m_accessor->setClient(this);
m_sysexEnabled = sysexEnabled;
} }
void MIDIAccess::trace(Visitor* visitor) void MIDIAccess::trace(Visitor* visitor)
......
...@@ -35,29 +35,32 @@ ...@@ -35,29 +35,32 @@
#include "bindings/v8/ScriptWrappable.h" #include "bindings/v8/ScriptWrappable.h"
#include "core/dom/ActiveDOMObject.h" #include "core/dom/ActiveDOMObject.h"
#include "modules/EventTargetModules.h" #include "modules/EventTargetModules.h"
#include "modules/webmidi/MIDIAccessInitializer.h"
#include "modules/webmidi/MIDIAccessor.h" #include "modules/webmidi/MIDIAccessor.h"
#include "modules/webmidi/MIDIAccessorClient.h" #include "modules/webmidi/MIDIAccessorClient.h"
#include "modules/webmidi/MIDIInput.h" #include "modules/webmidi/MIDIInput.h"
#include "modules/webmidi/MIDIOutput.h" #include "modules/webmidi/MIDIOutput.h"
#include "platform/AsyncMethodRunner.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "wtf/RefCounted.h" #include "wtf/RefCounted.h"
#include "wtf/RefPtr.h" #include "wtf/RefPtr.h"
#include "wtf/WeakPtr.h" #include "wtf/Vector.h"
namespace WebCore { namespace WebCore {
class ExecutionContext; class ExecutionContext;
class MIDIAccessInitializer;
struct MIDIOptions; struct MIDIOptions;
class MIDIAccess FINAL : public RefCountedWillBeRefCountedGarbageCollected<MIDIAccess>, public ScriptWrappable, public ActiveDOMObject, public EventTargetWithInlineData, public MIDIAccessorClient { class MIDIAccess FINAL : public RefCountedWillBeRefCountedGarbageCollected<MIDIAccess>, public ScriptWrappable, public ActiveDOMObject, public EventTargetWithInlineData, public MIDIAccessorClient {
REFCOUNTED_EVENT_TARGET(MIDIAccess); REFCOUNTED_EVENT_TARGET(MIDIAccess);
WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(MIDIAccess); WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(MIDIAccess);
public: public:
static PassRefPtrWillBeRawPtr<MIDIAccess> create(PassOwnPtr<MIDIAccessor> accessor, bool sysexEnabled, const Vector<MIDIAccessInitializer::PortDescriptor>& ports, ExecutionContext* executionContext)
{
RefPtrWillBeRawPtr<MIDIAccess> access = adoptRefWillBeRefCountedGarbageCollected(new MIDIAccess(accessor, sysexEnabled, ports, executionContext));
access->suspendIfNeeded();
return access;
}
virtual ~MIDIAccess(); virtual ~MIDIAccess();
// Returns a promise object that will be resolved with this MIDIAccess.
static ScriptPromise request(const MIDIOptions&, ScriptState*);
MIDIInputVector inputs() const { return m_inputs; } MIDIInputVector inputs() const { return m_inputs; }
MIDIOutputVector outputs() const { return m_outputs; } MIDIOutputVector outputs() const { return m_outputs; }
...@@ -73,7 +76,6 @@ public: ...@@ -73,7 +76,6 @@ public:
// ActiveDOMObject // ActiveDOMObject
virtual void stop() OVERRIDE; virtual void stop() OVERRIDE;
virtual bool hasPendingActivity() const OVERRIDE;
// MIDIAccessorClient // MIDIAccessorClient
virtual void didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) OVERRIDE; virtual void didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) OVERRIDE;
...@@ -89,21 +91,15 @@ public: ...@@ -89,21 +91,15 @@ public:
// |timeStampInMilliseconds| is in the same time coordinate system as performance.now(). // |timeStampInMilliseconds| is in the same time coordinate system as performance.now().
void sendMIDIData(unsigned portIndex, const unsigned char* data, size_t length, double timeStampInMilliseconds); void sendMIDIData(unsigned portIndex, const unsigned char* data, size_t length, double timeStampInMilliseconds);
// Initialize this object before exposing it to JavaScript.
void initialize(PassOwnPtr<MIDIAccessor>, bool sysexEnabled);
virtual void trace(Visitor*) OVERRIDE; virtual void trace(Visitor*) OVERRIDE;
private: private:
MIDIAccess(const MIDIOptions&, ExecutionContext*); MIDIAccess(PassOwnPtr<MIDIAccessor>, bool sysexEnabled, const Vector<MIDIAccessInitializer::PortDescriptor>&, ExecutionContext*);
MIDIInputVector m_inputs;
MIDIOutputVector m_outputs;
OwnPtr<MIDIAccessor> m_accessor; OwnPtr<MIDIAccessor> m_accessor;
bool m_sysexEnabled; bool m_sysexEnabled;
MIDIInputVector m_inputs;
// FIXME: Stop owning initializer in this class. MIDIOutputVector m_outputs;
OwnPtr<MIDIAccessInitializer> m_initializer;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -5,66 +5,75 @@ ...@@ -5,66 +5,75 @@
#include "config.h" #include "config.h"
#include "modules/webmidi/MIDIAccessInitializer.h" #include "modules/webmidi/MIDIAccessInitializer.h"
#include "bindings/v8/ScriptFunction.h"
#include "bindings/v8/ScriptPromise.h" #include "bindings/v8/ScriptPromise.h"
#include "bindings/v8/ScriptPromiseResolverWithContext.h" #include "bindings/v8/ScriptPromiseResolverWithContext.h"
#include "core/dom/DOMError.h" #include "core/dom/DOMError.h"
#include "core/dom/Document.h" #include "core/dom/Document.h"
#include "core/frame/Navigator.h"
#include "modules/webmidi/MIDIAccess.h" #include "modules/webmidi/MIDIAccess.h"
#include "modules/webmidi/MIDIController.h" #include "modules/webmidi/MIDIController.h"
#include "modules/webmidi/MIDIOptions.h" #include "modules/webmidi/MIDIOptions.h"
#include "modules/webmidi/MIDIPort.h"
namespace WebCore { namespace WebCore {
class MIDIAccessInitializer::PostAction : public ScriptFunction { MIDIAccessInitializer::MIDIAccessInitializer(ScriptState* scriptState, const MIDIOptions& options)
public: : ScriptPromiseResolverWithContext(scriptState)
static PassOwnPtr<MIDIAccessInitializer::PostAction> create(v8::Isolate* isolate, WeakPtr<MIDIAccessInitializer> owner, State state) { return adoptPtr(new PostAction(isolate, owner, state)); } , m_options(options)
, m_sysexEnabled(false)
private: {
PostAction(v8::Isolate* isolate, WeakPtr<MIDIAccessInitializer> owner, State state): ScriptFunction(isolate), m_owner(owner), m_state(state) { } }
virtual ScriptValue call(ScriptValue value) OVERRIDE
{
if (!m_owner.get())
return value;
m_owner->doPostAction(m_state);
return value;
}
WeakPtr<MIDIAccessInitializer> m_owner;
State m_state;
};
MIDIAccessInitializer::~MIDIAccessInitializer() MIDIAccessInitializer::~MIDIAccessInitializer()
{ {
ASSERT(m_state != Requesting); // It is safe to cancel a request which is already finished or canceld.
Document* document = toDocument(executionContext());
ASSERT(document);
MIDIController* controller = MIDIController::from(document->frame());
if (controller)
controller->cancelSysexPermissionRequest(this);
} }
MIDIAccessInitializer::MIDIAccessInitializer(const MIDIOptions& options, MIDIAccess* access) ScriptPromise MIDIAccessInitializer::start()
: m_state(Requesting)
, m_weakPtrFactory(this)
, m_options(options)
, m_sysexEnabled(false)
, m_access(access)
{ {
ScriptPromise promise = this->promise();
m_accessor = MIDIAccessor::create(this); m_accessor = MIDIAccessor::create(this);
if (!m_options.sysex) {
m_accessor->startSession();
return promise;
}
Document* document = toDocument(executionContext());
ASSERT(document);
MIDIController* controller = MIDIController::from(document->frame());
if (controller) {
controller->requestSysexPermission(this);
} else {
reject(DOMError::create("SecurityError"));
}
return promise;
} }
void MIDIAccessInitializer::didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) void MIDIAccessInitializer::didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version)
{ {
m_access->didAddInputPort(id, manufacturer, name, version); ASSERT(m_accessor);
m_portDescriptors.append(PortDescriptor(id, manufacturer, name, MIDIPort::MIDIPortTypeInput, version));
} }
void MIDIAccessInitializer::didAddOutputPort(const String& id, const String& manufacturer, const String& name, const String& version) void MIDIAccessInitializer::didAddOutputPort(const String& id, const String& manufacturer, const String& name, const String& version)
{ {
m_access->didAddOutputPort(id, manufacturer, name, version); ASSERT(m_accessor);
m_portDescriptors.append(PortDescriptor(id, manufacturer, name, MIDIPort::MIDIPortTypeOutput, version));
} }
void MIDIAccessInitializer::didStartSession(bool success, const String& error, const String& message) void MIDIAccessInitializer::didStartSession(bool success, const String& error, const String& message)
{ {
if (success) ASSERT(m_accessor);
m_resolver->resolve(m_access); if (success) {
else resolve(MIDIAccess::create(m_accessor.release(), m_sysexEnabled, m_portDescriptors, executionContext()));
m_resolver->reject(DOMError::create(error, message)); } else {
reject(DOMError::create(error, message));
}
} }
void MIDIAccessInitializer::setSysexEnabled(bool enable) void MIDIAccessInitializer::setSysexEnabled(bool enable)
...@@ -73,71 +82,17 @@ void MIDIAccessInitializer::setSysexEnabled(bool enable) ...@@ -73,71 +82,17 @@ void MIDIAccessInitializer::setSysexEnabled(bool enable)
if (enable) if (enable)
m_accessor->startSession(); m_accessor->startSession();
else else
m_resolver->reject(DOMError::create("SecurityError")); reject(DOMError::create("SecurityError"));
} }
SecurityOrigin* MIDIAccessInitializer::securityOrigin() const SecurityOrigin* MIDIAccessInitializer::securityOrigin() const
{ {
return m_access->executionContext()->securityOrigin(); return executionContext()->securityOrigin();
}
void MIDIAccessInitializer::cancel()
{
if (m_state != Requesting)
return;
m_accessor.clear();
m_weakPtrFactory.revokeAll();
Document* document = toDocument(executionContext());
ASSERT(document);
MIDIController* controller = MIDIController::from(document->frame());
ASSERT(controller);
controller->cancelSysexPermissionRequest(this);
m_state = Stopped;
} }
ExecutionContext* MIDIAccessInitializer::executionContext() const ExecutionContext* MIDIAccessInitializer::executionContext() const
{ {
return m_access->executionContext(); return scriptState()->executionContext();
}
void MIDIAccessInitializer::permissionDenied()
{
ASSERT(isMainThread());
m_resolver->reject(DOMError::create("SecurityError"));
}
ScriptPromise MIDIAccessInitializer::initialize(ScriptState* scriptState)
{
m_resolver = ScriptPromiseResolverWithContext::create(scriptState);
ScriptPromise promise = m_resolver->promise();
promise.then(PostAction::create(scriptState->isolate(), m_weakPtrFactory.createWeakPtr(), Resolved),
PostAction::create(scriptState->isolate(), m_weakPtrFactory.createWeakPtr(), Stopped));
if (!m_options.sysex) {
m_accessor->startSession();
return promise;
}
Document* document = toDocument(executionContext());
ASSERT(document);
MIDIController* controller = MIDIController::from(document->frame());
if (controller) {
controller->requestSysexPermission(this);
} else {
m_resolver->reject(DOMError::create("SecurityError"));
}
return promise;
}
void MIDIAccessInitializer::doPostAction(State state)
{
ASSERT(m_state == Requesting);
ASSERT(state == Resolved || state == Stopped);
if (state == Resolved) {
m_access->initialize(m_accessor.release(), m_sysexEnabled);
}
m_accessor.clear();
m_weakPtrFactory.revokeAll();
m_state = state;
} }
} // namespace WebCore } // namespace WebCore
...@@ -6,29 +6,47 @@ ...@@ -6,29 +6,47 @@
#define MIDIAccessInitializer_h #define MIDIAccessInitializer_h
#include "bindings/v8/ScriptPromise.h" #include "bindings/v8/ScriptPromise.h"
#include "bindings/v8/ScriptPromiseResolverWithContext.h"
#include "modules/webmidi/MIDIAccessor.h" #include "modules/webmidi/MIDIAccessor.h"
#include "modules/webmidi/MIDIAccessorClient.h" #include "modules/webmidi/MIDIAccessorClient.h"
#include "modules/webmidi/MIDIOptions.h" #include "modules/webmidi/MIDIOptions.h"
#include "modules/webmidi/MIDIPort.h"
#include "wtf/OwnPtr.h" #include "wtf/OwnPtr.h"
#include "wtf/RawPtr.h"
#include "wtf/Vector.h"
namespace WebCore { namespace WebCore {
class MIDIAccess; class MIDIAccess;
class Navigator;
class ScriptState; class ScriptState;
class ScriptPromiseResolverWithContext;
class MIDIAccessInitializer : public MIDIAccessorClient { class MIDIAccessInitializer : public ScriptPromiseResolverWithContext, public MIDIAccessorClient {
public: public:
static PassOwnPtr<MIDIAccessInitializer> create(const MIDIOptions& options, MIDIAccess* access) struct PortDescriptor {
String id;
String manufacturer;
String name;
MIDIPort::MIDIPortTypeCode type;
String version;
PortDescriptor(const String& id, const String& manufacturer, const String& name, MIDIPort::MIDIPortTypeCode type, const String& version)
: id(id)
, manufacturer(manufacturer)
, name(name)
, type(type)
, version(version) { }
};
static ScriptPromise start(ScriptState* scriptState, const MIDIOptions& options)
{ {
return adoptPtr(new MIDIAccessInitializer(options, access)); RefPtr<MIDIAccessInitializer> p = adoptRef(new MIDIAccessInitializer(scriptState, options));
p->keepAliveWhilePending();
p->suspendIfNeeded();
return p->start();
} }
virtual ~MIDIAccessInitializer();
ScriptPromise initialize(ScriptState*); virtual ~MIDIAccessInitializer();
void cancel();
bool hasPendingActivity() const { return m_state == Requesting; }
// MIDIAccessorClient // MIDIAccessorClient
virtual void didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) OVERRIDE; virtual void didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version) OVERRIDE;
...@@ -40,27 +58,16 @@ public: ...@@ -40,27 +58,16 @@ public:
SecurityOrigin* securityOrigin() const; SecurityOrigin* securityOrigin() const;
private: private:
class PostAction; ScriptPromise start();
enum State {
Requesting,
Resolved,
Stopped,
};
MIDIAccessInitializer(const MIDIOptions&, MIDIAccess*); MIDIAccessInitializer(ScriptState*, const MIDIOptions&);
ExecutionContext* executionContext() const; ExecutionContext* executionContext() const;
void permissionDenied();
void doPostAction(State);
State m_state;
WeakPtrFactory<MIDIAccessInitializer> m_weakPtrFactory;
RefPtr<ScriptPromiseResolverWithContext> m_resolver;
OwnPtr<MIDIAccessor> m_accessor; OwnPtr<MIDIAccessor> m_accessor;
MIDIOptions m_options; MIDIOptions m_options;
bool m_sysexEnabled; bool m_sysexEnabled;
// m_access has this object, so it's safe to have the raw pointer. Vector<PortDescriptor> m_portDescriptors;
MIDIAccess* m_access;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
#include "core/dom/Document.h" #include "core/dom/Document.h"
#include "core/frame/LocalFrame.h" #include "core/frame/LocalFrame.h"
#include "core/frame/Navigator.h" #include "core/frame/Navigator.h"
#include "modules/webmidi/MIDIAccess.h" #include "modules/webmidi/MIDIAccessInitializer.h"
#include "modules/webmidi/MIDIOptions.h" #include "modules/webmidi/MIDIOptions.h"
namespace WebCore { namespace WebCore {
...@@ -81,7 +81,7 @@ ScriptPromise NavigatorWebMIDI::requestMIDIAccess(ScriptState* scriptState, cons ...@@ -81,7 +81,7 @@ ScriptPromise NavigatorWebMIDI::requestMIDIAccess(ScriptState* scriptState, cons
return promise; return promise;
} }
return MIDIAccess::request(MIDIOptions(options), scriptState); return MIDIAccessInitializer::start(scriptState, MIDIOptions(options));
} }
} // namespace WebCore } // namespace WebCore
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment