OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 13 matching lines...) Expand all Loading... |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | 30 |
31 #include "config.h" | 31 #include "config.h" |
32 #include "modules/webmidi/MIDIAccess.h" | 32 #include "modules/webmidi/MIDIAccess.h" |
33 | 33 |
| 34 #include "bindings/v8/MIDIAccessResolver.h" |
| 35 #include "bindings/v8/ScriptPromise.h" |
| 36 #include "bindings/v8/ScriptPromiseResolver.h" |
| 37 #include "bindings/v8/V8Binding.h" |
34 #include "core/dom/DOMError.h" | 38 #include "core/dom/DOMError.h" |
35 #include "core/dom/Document.h" | 39 #include "core/dom/Document.h" |
36 #include "core/loader/DocumentLoadTiming.h" | 40 #include "core/loader/DocumentLoadTiming.h" |
37 #include "core/loader/DocumentLoader.h" | 41 #include "core/loader/DocumentLoader.h" |
38 #include "modules/webmidi/MIDIAccessPromise.h" | |
39 #include "modules/webmidi/MIDIConnectionEvent.h" | 42 #include "modules/webmidi/MIDIConnectionEvent.h" |
40 #include "modules/webmidi/MIDIController.h" | 43 #include "modules/webmidi/MIDIController.h" |
| 44 #include "modules/webmidi/MIDIOptions.h" |
41 #include "modules/webmidi/MIDIPort.h" | 45 #include "modules/webmidi/MIDIPort.h" |
| 46 #include "platform/AsyncMethodRunner.h" |
42 | 47 |
43 namespace WebCore { | 48 namespace WebCore { |
44 | 49 |
45 PassRefPtrWillBeRawPtr<MIDIAccess> MIDIAccess::create(ExecutionContext* context,
MIDIAccessPromise* promise) | 50 class MIDIAccess::PostAction : public ScriptFunction { |
| 51 public: |
| 52 static PassOwnPtr<MIDIAccess::PostAction> create(v8::Isolate* isolate, WeakP
tr<MIDIAccess> owner, State state) { return adoptPtr(new PostAction(isolate, own
er, state)); } |
| 53 |
| 54 private: |
| 55 PostAction(v8::Isolate* isolate, WeakPtr<MIDIAccess> owner, State state): Sc
riptFunction(isolate), m_owner(owner), m_state(state) { } |
| 56 virtual ScriptValue call(ScriptValue value) OVERRIDE |
| 57 { |
| 58 if (!m_owner.get()) |
| 59 return value; |
| 60 m_owner->doPostAction(m_state); |
| 61 return value; |
| 62 } |
| 63 |
| 64 WeakPtr<MIDIAccess> m_owner; |
| 65 State m_state; |
| 66 }; |
| 67 |
| 68 ScriptPromise MIDIAccess::request(const MIDIOptions& options, ExecutionContext*
context) |
46 { | 69 { |
47 RefPtrWillBeRawPtr<MIDIAccess> midiAccess(adoptRefWillBeRefCountedGarbageCol
lected(new MIDIAccess(context, promise))); | 70 RefPtrWillBeRawPtr<MIDIAccess> midiAccess(adoptRefWillBeRefCountedGarbageCol
lected(new MIDIAccess(options, context))); |
48 midiAccess->suspendIfNeeded(); | 71 midiAccess->suspendIfNeeded(); |
49 midiAccess->startRequest(); | 72 // Create a wrapper to expose this object to the V8 GC so that |
50 return midiAccess.release(); | 73 // hasPendingActivity takes effect. |
| 74 toV8NoInline(midiAccess.get(), context); |
| 75 // Now this object is retained because m_state equals to Requesting. |
| 76 return midiAccess->startRequest(); |
51 } | 77 } |
52 | 78 |
53 MIDIAccess::~MIDIAccess() | 79 MIDIAccess::~MIDIAccess() |
54 { | 80 { |
55 stop(); | |
56 } | 81 } |
57 | 82 |
58 MIDIAccess::MIDIAccess(ExecutionContext* context, MIDIAccessPromise* promise) | 83 MIDIAccess::MIDIAccess(const MIDIOptions& options, ExecutionContext* context) |
59 : ActiveDOMObject(context) | 84 : ActiveDOMObject(context) |
60 , m_promise(promise) | 85 , m_state(Requesting) |
61 , m_hasAccess(false) | 86 , m_weakPtrFactory(this) |
| 87 , m_options(options) |
62 , m_sysExEnabled(false) | 88 , m_sysExEnabled(false) |
63 , m_requesting(false) | 89 , m_asyncResolveRunner(this, &MIDIAccess::resolveNow) |
| 90 , m_asyncRejectRunner(this, &MIDIAccess::rejectNow) |
64 { | 91 { |
65 ScriptWrappable::init(this); | 92 ScriptWrappable::init(this); |
66 m_accessor = MIDIAccessor::create(this); | 93 m_accessor = MIDIAccessor::create(this); |
67 } | 94 } |
68 | 95 |
69 void MIDIAccess::setSysExEnabled(bool enable) | 96 void MIDIAccess::setSysExEnabled(bool enable) |
70 { | 97 { |
71 m_requesting = false; | |
72 m_sysExEnabled = enable; | 98 m_sysExEnabled = enable; |
73 if (enable) | 99 if (enable) { |
74 m_accessor->startSession(); | 100 m_accessor->startSession(); |
75 else | 101 } else { |
76 permissionDenied(); | 102 reject(DOMError::create("SecurityError")); |
| 103 } |
77 } | 104 } |
78 | 105 |
79 void MIDIAccess::didAddInputPort(const String& id, const String& manufacturer, c
onst String& name, const String& version) | 106 void MIDIAccess::didAddInputPort(const String& id, const String& manufacturer, c
onst String& name, const String& version) |
80 { | 107 { |
81 ASSERT(isMainThread()); | 108 ASSERT(isMainThread()); |
82 | 109 |
83 m_inputs.append(MIDIInput::create(this, id, manufacturer, name, version)); | 110 m_inputs.append(MIDIInput::create(this, id, manufacturer, name, version)); |
84 } | 111 } |
85 | 112 |
86 void MIDIAccess::didAddOutputPort(const String& id, const String& manufacturer,
const String& name, const String& version) | 113 void MIDIAccess::didAddOutputPort(const String& id, const String& manufacturer,
const String& name, const String& version) |
87 { | 114 { |
88 ASSERT(isMainThread()); | 115 ASSERT(isMainThread()); |
89 | 116 |
90 unsigned portIndex = m_outputs.size(); | 117 unsigned portIndex = m_outputs.size(); |
91 m_outputs.append(MIDIOutput::create(this, portIndex, id, manufacturer, name,
version)); | 118 m_outputs.append(MIDIOutput::create(this, portIndex, id, manufacturer, name,
version)); |
92 } | 119 } |
93 | 120 |
94 void MIDIAccess::didStartSession(bool success) | 121 void MIDIAccess::didStartSession(bool success) |
95 { | 122 { |
96 ASSERT(isMainThread()); | 123 ASSERT(isMainThread()); |
97 | |
98 m_hasAccess = success; | |
99 if (success) | 124 if (success) |
100 m_promise->fulfill(); | 125 resolve(); |
101 else | 126 else |
102 m_promise->reject(DOMError::create("InvalidStateError")); | 127 reject(DOMError::create("InvalidStateError")); |
103 } | 128 } |
104 | 129 |
105 void MIDIAccess::didReceiveMIDIData(unsigned portIndex, const unsigned char* dat
a, size_t length, double timeStamp) | 130 void MIDIAccess::didReceiveMIDIData(unsigned portIndex, const unsigned char* dat
a, size_t length, double timeStamp) |
106 { | 131 { |
107 ASSERT(isMainThread()); | 132 ASSERT(isMainThread()); |
108 | 133 |
109 if (m_hasAccess && portIndex < m_inputs.size()) { | 134 if (m_state == Resolved && portIndex < m_inputs.size()) { |
110 // Convert from time in seconds which is based on the time coordinate sy
stem of monotonicallyIncreasingTime() | 135 // Convert from time in seconds which is based on the time coordinate sy
stem of monotonicallyIncreasingTime() |
111 // into time in milliseconds (a DOMHighResTimeStamp) according to the sa
me time coordinate system as performance.now(). | 136 // into time in milliseconds (a DOMHighResTimeStamp) according to the sa
me time coordinate system as performance.now(). |
112 // This is how timestamps are defined in the Web MIDI spec. | 137 // This is how timestamps are defined in the Web MIDI spec. |
113 Document* document = toDocument(executionContext()); | 138 Document* document = toDocument(executionContext()); |
114 ASSERT(document); | 139 ASSERT(document); |
115 | 140 |
116 double timeStampInMilliseconds = 1000 * document->loader()->timing()->mo
notonicTimeToZeroBasedDocumentTime(timeStamp); | 141 double timeStampInMilliseconds = 1000 * document->loader()->timing()->mo
notonicTimeToZeroBasedDocumentTime(timeStamp); |
117 | 142 |
118 m_inputs[portIndex]->didReceiveMIDIData(portIndex, data, length, timeSta
mpInMilliseconds); | 143 m_inputs[portIndex]->didReceiveMIDIData(portIndex, data, length, timeSta
mpInMilliseconds); |
119 } | 144 } |
120 } | 145 } |
121 | 146 |
122 void MIDIAccess::sendMIDIData(unsigned portIndex, const unsigned char* data, siz
e_t length, double timeStampInMilliseconds) | 147 void MIDIAccess::sendMIDIData(unsigned portIndex, const unsigned char* data, siz
e_t length, double timeStampInMilliseconds) |
123 { | 148 { |
124 if (m_hasAccess && portIndex < m_outputs.size() && data && length > 0) { | 149 if (m_state == Resolved && portIndex < m_outputs.size() && data && length >
0) { |
125 // Convert from a time in milliseconds (a DOMHighResTimeStamp) according
to the same time coordinate system as performance.now() | 150 // Convert from a time in milliseconds (a DOMHighResTimeStamp) according
to the same time coordinate system as performance.now() |
126 // into a time in seconds which is based on the time coordinate system o
f monotonicallyIncreasingTime(). | 151 // into a time in seconds which is based on the time coordinate system o
f monotonicallyIncreasingTime(). |
127 double timeStamp; | 152 double timeStamp; |
128 | 153 |
129 if (!timeStampInMilliseconds) { | 154 if (!timeStampInMilliseconds) { |
130 // We treat a value of 0 (which is the default value) as special, me
aning "now". | 155 // We treat a value of 0 (which is the default value) as special, me
aning "now". |
131 // We need to translate it exactly to 0 seconds. | 156 // We need to translate it exactly to 0 seconds. |
132 timeStamp = 0; | 157 timeStamp = 0; |
133 } else { | 158 } else { |
134 Document* document = toDocument(executionContext()); | 159 Document* document = toDocument(executionContext()); |
135 ASSERT(document); | 160 ASSERT(document); |
136 double documentStartTime = document->loader()->timing()->referenceMo
notonicTime(); | 161 double documentStartTime = document->loader()->timing()->referenceMo
notonicTime(); |
137 timeStamp = documentStartTime + 0.001 * timeStampInMilliseconds; | 162 timeStamp = documentStartTime + 0.001 * timeStampInMilliseconds; |
138 } | 163 } |
139 | 164 |
140 m_accessor->sendMIDIData(portIndex, data, length, timeStamp); | 165 m_accessor->sendMIDIData(portIndex, data, length, timeStamp); |
141 } | 166 } |
142 } | 167 } |
143 | 168 |
| 169 void MIDIAccess::suspend() |
| 170 { |
| 171 m_asyncResolveRunner.suspend(); |
| 172 m_asyncRejectRunner.suspend(); |
| 173 } |
| 174 |
| 175 void MIDIAccess::resume() |
| 176 { |
| 177 m_asyncResolveRunner.resume(); |
| 178 m_asyncRejectRunner.resume(); |
| 179 } |
| 180 |
144 void MIDIAccess::stop() | 181 void MIDIAccess::stop() |
145 { | 182 { |
146 m_hasAccess = false; | 183 if (m_state == Stopped) |
147 if (!m_requesting) | |
148 return; | 184 return; |
149 m_requesting = false; | 185 m_error.clear(); |
150 Document* document = toDocument(executionContext()); | 186 m_accessor.clear(); |
151 ASSERT(document); | 187 m_asyncResolveRunner.stop(); |
152 MIDIController* controller = MIDIController::from(document->page()); | 188 m_asyncRejectRunner.stop(); |
153 ASSERT(controller); | 189 m_weakPtrFactory.revokeAll(); |
154 controller->cancelSysExPermissionRequest(this); | 190 if (m_state == Requesting) { |
| 191 Document* document = toDocument(executionContext()); |
| 192 ASSERT(document); |
| 193 MIDIController* controller = MIDIController::from(document->page()); |
| 194 ASSERT(controller); |
| 195 controller->cancelSysExPermissionRequest(this); |
| 196 } |
| 197 m_state = Stopped; |
155 } | 198 } |
156 | 199 |
157 void MIDIAccess::startRequest() | 200 bool MIDIAccess::hasPendingActivity() const |
158 { | 201 { |
159 if (!m_promise->options()->sysex) { | 202 return m_state == Requesting; |
| 203 } |
| 204 |
| 205 void MIDIAccess::permissionDenied() |
| 206 { |
| 207 ASSERT(isMainThread()); |
| 208 reject(DOMError::create("SecurityError")); |
| 209 } |
| 210 |
| 211 ScriptPromise MIDIAccess::startRequest() |
| 212 { |
| 213 m_resolver = MIDIAccessResolver::create(ScriptPromiseResolver::create(execut
ionContext()), toIsolate(executionContext())); |
| 214 ScriptPromise promise = m_resolver->promise(); |
| 215 promise.then(PostAction::create(toIsolate(executionContext()), m_weakPtrFact
ory.createWeakPtr(), Resolved), |
| 216 PostAction::create(toIsolate(executionContext()), m_weakPtrFactory.creat
eWeakPtr(), Stopped)); |
| 217 |
| 218 if (!m_options.sysex) { |
160 m_accessor->startSession(); | 219 m_accessor->startSession(); |
161 return; | 220 return promise; |
162 } | 221 } |
163 Document* document = toDocument(executionContext()); | 222 Document* document = toDocument(executionContext()); |
164 ASSERT(document); | 223 ASSERT(document); |
165 MIDIController* controller = MIDIController::from(document->page()); | 224 MIDIController* controller = MIDIController::from(document->page()); |
166 if (controller) { | 225 if (controller) { |
167 m_requesting = true; | |
168 controller->requestSysExPermission(this); | 226 controller->requestSysExPermission(this); |
169 } else { | 227 } else { |
170 permissionDenied(); | 228 reject(DOMError::create("SecurityError")); |
171 } | 229 } |
| 230 return promise; |
172 } | 231 } |
173 | 232 |
174 void MIDIAccess::permissionDenied() | 233 void MIDIAccess::resolve() |
175 { | 234 { |
176 ASSERT(isMainThread()); | 235 m_asyncResolveRunner.runAsync(); |
| 236 } |
177 | 237 |
178 m_hasAccess = false; | 238 void MIDIAccess::reject(PassRefPtr<DOMError> error) |
179 m_promise->reject(DOMError::create("SecurityError")); | 239 { |
| 240 m_error = error; |
| 241 m_asyncRejectRunner.runAsync(); |
| 242 } |
| 243 |
| 244 void MIDIAccess::resolveNow() |
| 245 { |
| 246 m_resolver->resolve(this, executionContext()); |
| 247 } |
| 248 |
| 249 void MIDIAccess::rejectNow() |
| 250 { |
| 251 m_resolver->reject(m_error.release().get(), executionContext()); |
| 252 } |
| 253 |
| 254 void MIDIAccess::doPostAction(State state) |
| 255 { |
| 256 ASSERT(m_state == Requesting); |
| 257 ASSERT(state == Resolved || state == Stopped); |
| 258 m_error.clear(); |
| 259 if (state == Stopped) { |
| 260 m_accessor.clear(); |
| 261 } |
| 262 m_weakPtrFactory.revokeAll(); |
| 263 m_state = state; |
180 } | 264 } |
181 | 265 |
182 void MIDIAccess::trace(Visitor* visitor) | 266 void MIDIAccess::trace(Visitor* visitor) |
183 { | 267 { |
184 visitor->trace(m_inputs); | 268 visitor->trace(m_inputs); |
185 visitor->trace(m_outputs); | 269 visitor->trace(m_outputs); |
186 visitor->trace(m_promise); | |
187 } | 270 } |
188 | 271 |
189 } // namespace WebCore | 272 } // namespace WebCore |
OLD | NEW |