Home | History | Annotate | Download | only in webmidi
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     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.
     29  */
     30 
     31 #include "config.h"
     32 #include "modules/webmidi/MIDIAccess.h"
     33 
     34 #include "core/dom/DOMError.h"
     35 #include "core/dom/Document.h"
     36 #include "core/loader/DocumentLoadTiming.h"
     37 #include "core/loader/DocumentLoader.h"
     38 #include "modules/webmidi/MIDIAccessPromise.h"
     39 #include "modules/webmidi/MIDIConnectionEvent.h"
     40 #include "modules/webmidi/MIDIController.h"
     41 #include "modules/webmidi/MIDIPort.h"
     42 
     43 namespace WebCore {
     44 
     45 PassRefPtr<MIDIAccess> MIDIAccess::create(ExecutionContext* context, MIDIAccessPromise* promise)
     46 {
     47     RefPtr<MIDIAccess> midiAccess(adoptRef(new MIDIAccess(context, promise)));
     48     midiAccess->suspendIfNeeded();
     49     midiAccess->startRequest();
     50     return midiAccess.release();
     51 }
     52 
     53 MIDIAccess::~MIDIAccess()
     54 {
     55     stop();
     56 }
     57 
     58 MIDIAccess::MIDIAccess(ExecutionContext* context, MIDIAccessPromise* promise)
     59     : ActiveDOMObject(context)
     60     , m_promise(promise)
     61     , m_hasAccess(false)
     62     , m_sysExEnabled(false)
     63     , m_requesting(false)
     64 {
     65     ScriptWrappable::init(this);
     66     m_accessor = MIDIAccessor::create(this);
     67 }
     68 
     69 void MIDIAccess::setSysExEnabled(bool enable)
     70 {
     71     m_requesting = false;
     72     m_sysExEnabled = enable;
     73     if (enable)
     74         m_accessor->startSession();
     75     else
     76         permissionDenied();
     77 }
     78 
     79 void MIDIAccess::didAddInputPort(const String& id, const String& manufacturer, const String& name, const String& version)
     80 {
     81     ASSERT(isMainThread());
     82 
     83     m_inputs.append(MIDIInput::create(this, executionContext(), id, manufacturer, name, version));
     84 }
     85 
     86 void MIDIAccess::didAddOutputPort(const String& id, const String& manufacturer, const String& name, const String& version)
     87 {
     88     ASSERT(isMainThread());
     89 
     90     unsigned portIndex = m_outputs.size();
     91     m_outputs.append(MIDIOutput::create(this, portIndex, executionContext(), id, manufacturer, name, version));
     92 }
     93 
     94 void MIDIAccess::didStartSession(bool success)
     95 {
     96     ASSERT(isMainThread());
     97 
     98     m_hasAccess = success;
     99     if (success)
    100         m_promise->fulfill();
    101     else
    102         m_promise->reject(DOMError::create("InvalidStateError"));
    103 }
    104 
    105 void MIDIAccess::didReceiveMIDIData(unsigned portIndex, const unsigned char* data, size_t length, double timeStamp)
    106 {
    107     ASSERT(isMainThread());
    108 
    109     if (m_hasAccess && portIndex < m_inputs.size()) {
    110         // Convert from time in seconds which is based on the time coordinate system of monotonicallyIncreasingTime()
    111         // into time in milliseconds (a DOMHighResTimeStamp) according to the same time coordinate system as performance.now().
    112         // This is how timestamps are defined in the Web MIDI spec.
    113         Document* document = toDocument(executionContext());
    114         ASSERT(document);
    115 
    116         double timeStampInMilliseconds = 1000 * document->loader()->timing()->monotonicTimeToZeroBasedDocumentTime(timeStamp);
    117 
    118         m_inputs[portIndex]->didReceiveMIDIData(portIndex, data, length, timeStampInMilliseconds);
    119     }
    120 }
    121 
    122 void MIDIAccess::sendMIDIData(unsigned portIndex, const unsigned char* data, size_t length, double timeStampInMilliseconds)
    123 {
    124     if (m_hasAccess && portIndex < m_outputs.size() && data && length > 1) {
    125         // 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 of monotonicallyIncreasingTime().
    127         double timeStamp;
    128 
    129         if (!timeStampInMilliseconds) {
    130             // We treat a value of 0 (which is the default value) as special, meaning "now".
    131             // We need to translate it exactly to 0 seconds.
    132             timeStamp = 0;
    133         } else {
    134             Document* document = toDocument(executionContext());
    135             ASSERT(document);
    136             double documentStartTime = document->loader()->timing()->referenceMonotonicTime();
    137             timeStamp = documentStartTime + 0.001 * timeStampInMilliseconds;
    138         }
    139 
    140         m_accessor->sendMIDIData(portIndex, data, length, timeStamp);
    141     }
    142 }
    143 
    144 void MIDIAccess::stop()
    145 {
    146     m_hasAccess = false;
    147     if (!m_requesting)
    148         return;
    149     m_requesting = false;
    150     Document* document = toDocument(executionContext());
    151     ASSERT(document);
    152     MIDIController* controller = MIDIController::from(document->page());
    153     ASSERT(controller);
    154     controller->cancelSysExPermissionRequest(this);
    155 
    156     m_accessor.clear();
    157 }
    158 
    159 void MIDIAccess::startRequest()
    160 {
    161     if (!m_promise->options()->sysex) {
    162         m_accessor->startSession();
    163         return;
    164     }
    165     Document* document = toDocument(executionContext());
    166     ASSERT(document);
    167     MIDIController* controller = MIDIController::from(document->page());
    168     if (controller) {
    169         m_requesting = true;
    170         controller->requestSysExPermission(this);
    171     } else {
    172         permissionDenied();
    173     }
    174 }
    175 
    176 void MIDIAccess::permissionDenied()
    177 {
    178     ASSERT(isMainThread());
    179 
    180     m_hasAccess = false;
    181     m_promise->reject(DOMError::create("SecurityError"));
    182 }
    183 
    184 } // namespace WebCore
    185