Web Serial API ( 日本語訳 )

Draft Community Group Report 19 February 2021

この文書は、W3Cの文書 "Web Serial API , Draft Community Group Report 19 February 2021" の日本語訳です。

Web Serial API の正式な文書は英語版のみであり、日本語訳には翻訳に起因する誤りが含まれている場合があります。 英語版の正式な最新の文書は : https://wicg.github.io/serial/にあります。

この翻訳の元となる文書は現在、コミュニティグループのレポート (Draft Community Group Report)であり、今後も更新されて行きます。

日本語訳GitHub : https://github.com/g200kg/web-serial-api-ja/
日本語訳公開URL : https://g200kg.github.io/web-serial-api-ja/

また、とりあえず動かしたい場合に有用な手引書が付属文書として用意されています
Web Serial API 手引書 : https://g200kg.github.io/web-serial-api-ja/EXPLAINER.html

Tatsuya Shinyagaito @ g200kg
誤りその他があれば GitHub 経由などで連絡をお願いいたします。
https://www.g200kg.com/

2021年2月19日


Serial APIによって、ウェブサイトがスクリプトを介してシリアルデバイスに接続することができます。このようなAPIは、マイクロコントローラや3Dプリンタなどのシリアルデバイスとドキュメントが通信できるようにすることで、ウェブと物理的な世界の架け橋となるでしょう。 手引書 も用意されています。
これは作業中のドキュメントです。すべての コントリビューション を歓迎します。

{{Navigator}} インターフェースの拡張

    [Exposed=Window, SecureContext]
    partial interface Navigator {
      [SameObject] readonly attribute Serial serial;
    };
  

serial 属性

取得時、 {{Navigator/serial}} 属性は常に {{Serial}} オブジェクトの同じインスタンスを返します。

{{WorkerNavigator}} インターフェースの拡張

    [Exposed=DedicatedWorker, SecureContext]
    partial interface WorkerNavigator {
      [SameObject] readonly attribute Serial serial;
    };
  

serial 属性

取得時、 {{WorkerNavigator/serial}} 属性は常に {{Serial}} オブジェクトの同じインスタンスを返します。

{{Serial}} インターフェース

    [Exposed=(DedicatedWorker, Window), SecureContext]
    interface Serial : EventTarget {
      attribute EventHandler onconnect;
      attribute EventHandler ondisconnect;
      Promise<sequence<SerialPort>> getPorts();
      [Exposed=Window] Promise<SerialPort> requestPort(optional SerialPortRequestOptions options = {});
    };
  

requestPort() メソッド

{{Serial/requestPort()}} メソッドの手順は次のとおりです:
  1. |promise:Promise| を 新しい promise ([=a new promise=]) とします。
  2. [=this=] が 関連するグローバルオブジェクト([=relevant global object=]) の 関連付けられたドキュメント([=associated Document=]) が `"serial"` という名前の ポリシー制御機能([=policy-controlled feature=]) で 使用を許可([=allowed to use=]) されない場合、 "{{SecurityError}}" {{DOMException}} で |promise| を リジェクト([=reject=]) し、 |promise| を返します。
  3. [=this=] の 関連するグローバルオブジェクト([=relevant global object=]) が 一時的なアクティブ化([=transient activation=]) がされていない場合は、 |promise| を "{{SecurityError}}" {{DOMException}}を使用して リジェクト([=reject=]) し、 |promise| を返します。
  4. もし |options|["{{SerialPortRequestOptions/filters}}"] が存在していれば、|options|["{{SerialPortRequestOptions/filters}}"] 内のそれぞれの |filter:SerialPortFilter| に対して次のステップを実行します :
    1. もし |filter|["{{SerialPortFilter/usbVendorId}}"] が存在していない場合、 |promise| を {{TypeError}} で リジェクト([=reject=]) し、 |promise| を返します。
      このチェックは、 {{SerialPortFilter}} は空には出来ず、 {{SerialPortFilter/usbProductId}} が指定されている場合は {{SerialPortFilter/usbVendorId}} も指定しなくてはならない、という複合ルールを実装します。
  5. 以下の手順を 並列([=in parallel=]) に実行します:
    1. |options|["{{SerialPortRequestOptions/filters}}"] が存在する場合、そのフィルターの どれかに一致 ([=match any filter=]) する使用可能なポートのリストを提示し、存在しない場合はすべての使用可能なポートを提示して、サイトがシリアルポートにアクセスする許可を与えるようにユーザーに促します。
    2. ユーザーがポートを選択しなかった場合は、 [=this=] が 関連するグローバルオブジェクト ([=relevant global object=]) で、 [=シリアルポートタスクソース=] を使用して、グローバルタスクをキューに入れ ([=queue a global task=]) 、 {{"AbortError"}} {{DOMException}} で |promise| を リジェクト ([=reject=]) し、これらの手順を中止します。
    3. |port:SerialPort| を、ユーザが選択したポートを表す {{SerialPort}} とします。
    4. [=シリアルポートタスクソース=] を使用して、 [=this=] に 関連するグローバルオブジェクト ([=relevant global object=]) で グローバルタスクをキューに入れ ([=Queue a global task=]) 、 |port| で |promise| を リゾルブ ([=resolve=]) します。
  6. |promise| を返します。

SerialPortRequestOptions ディクショナリー

        dictionary SerialPortRequestOptions {
          sequence<SerialPortFilter> filters;
        };
      
filters メンバー
シリアルポートに対するフィルター

SerialPortFilter ディクショナリー

        dictionary SerialPortFilter {
          unsigned short usbVendorId;
          unsigned short usbProductId;
        };
      
usbVendorId メンバー
USB ベンダー ID
usbProductId メンバー
USB プロダクト ID

以下の手順が `true` を返す場合、シリアルポートは |filter:SerialPortFilter| と 一致 します:

  1. もし |filter|["{{SerialPortFilter/usbVendorId}}"] が存在しない場合は、 `true` を返します。
  2. シリアルポートが USB デバイスでない場合は、 `false` を返します。
  3. USB デバイスのベンダー ID が |filter|["{{SerialPortFilter/usbVendorId}}"] と等しくない場合は、 `false`を返します。
  4. もし |filter|["{{SerialPortFilter/usbProductId}}"] が存在しない場合は、 `true` を返します。
  5. もし USB デバイスのプロダクトID が |filter|["{{SerialPortFilter/usbProductId}}"] と等しくない場合は、 `false` を返します。
  6. それ以外の場合は `true` を返します。

以下の手順が `true` を返す場合、シリアルポートは {{SerialPortFilter}} のシーケンス内の どれかのフィルターと一致 します:

  1. シーケンス内の |filter| ごとに、次のサブステップを実行します:
    1. もしシリアルポートが |filter| と [=一致=] でない場合は、 false を返します。
  2. true を返します。

getPorts() メソッド

{{Serial/getPorts()}} メソッドの手順は:
  1. |promise:Promise| を 新しい promise ([=a new promise=]) とします。
  2. [=this=] が 関連するグローバルオブジェクト([=relevant global object=]) の 関連付けられたドキュメント([=associated Document=]) が `"serial"` という名前の ポリシー制御機能([=policy-controlled feature=]) で 使用を許可([=allowed to use=]) されない場合、 "{{SecurityError}}" {{DOMException}} で |promise| を リジェクト([=reject=]) し、 |promise| を返します。
  3. 以下の手順を 並列 ([=in parallel=]) に実行します:
    1. |availablePorts| を {{Serial/requestPort()}} を呼び出した結果である、ユーザーがサイトにアクセスを許可したシステム上の利用可能なシリアルポートのシーケンスとします。
    2. |ports| を、 |availablePorts| のポートを表す {{SerialPort}} のシーケンスとします。
    3. [=シリアルポートタスクソース=] を使用して、 [=this=] に 関連するグローバルオブジェクト ([=relevant global object=]) で グローバルタスクをキューに入れ ([=Queue a global task=])、 |ports| を持って |promise| を リゾルブ ([=resolve=]) します。
  4. |promise| を返す。

onconnect 属性

{{Serial/onconnect}} は、connect イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。

ondisconnect 属性

{{Serial/ondisconnect}} は、disconnect イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。

SerialPort インターフェース

    [Exposed=(DedicatedWorker,Window), SecureContext]
    interface SerialPort : EventTarget {
      attribute EventHandler onconnect;
      attribute EventHandler ondisconnect;
      readonly attribute ReadableStream readable;
      readonly attribute WritableStream writable;

      SerialPortInfo getInfo();

      Promise<undefined> open(SerialOptions options);
      Promise<undefined> setSignals(optional SerialOutputSignals signals = {});
      Promise<SerialInputSignals> getSignals();
      Promise<undefined> close();
    };
  
このインターフェイスのメソッドは通常、非同期で完了し、 シリアルポートタスクソース 上で作業をキューに入れます。

{{SerialPort}} の [=get the parent=] アルゴリズムは、 {{SerialPort}} の 関連するグローバルオブジェクト ([=relevant global object=]) の {{Navigator}} オブジェクトの {{Navigator/serial}} 取得によって返されるのと同じ {{Serial}} インスタンスを返します。

{{SerialPort}} のインスタンスは、次の表で説明されている内部スロットと共に作成されます。

内部スロット 初期値 説明 (非基準)
[[\state]] `"closed"` {{SerialPort}} のアクティブ状態を追跡します
[[\bufferSize]] undefined 送信および受信用にバッファリングするデータの量
[[\readable]] `null` ポートからデータを受信する {{ReadableStream}}
[[\readFatal]] `false` ポートで致命的な読み取りエラーが発生したことを示すフラグ
[[\writable]] `null` ポートにデータを送信する {{WritableStream}}
[[\writeFatal]] `false` ポートで致命的な書き込みエラーが発生したことを示すフラグ
[[\pendingClosePromise]] `null` {{SerialPort/readable}} と {{SerialPort/writable}} が close されるのを待つために使用される {{Promise}}

onconnect 属性

{{SerialPort/onconnect}} は、 {{connect}} イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。

ユーザーが以前に {{Serial/requestPort()}} を呼び出して、サイトがアクセスする事を許可したシステム上のシリアルポートが使用可能になったら、次の手順を実行します:

  1. |port:SerialPort| を、ポートを表す {{SerialPort}} とします。
  2. {{Event/bubbles}} 属性を `true` に初期化して、 |port| で {{connect}} という名前の イベントを発行 ([=Fire an event=]) します。

ondisconnect 属性

{{SerialPort/ondisconnect}} は、 {{disconnect}} イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。

ユーザーが以前に {{Serial/requestPort()}} を呼び出して、サイトがアクセスする事を許可したシステム上のシリアルポートが使用不能になったら、次の手順を実行します:

  1. |port:SerialPort| を、ポートを表す {{SerialPort}} とします。
  2. {{Event/bubbles}} 属性を `true` に初期化して、 |port| で {{disconnect}} という名前の イベントを発行 ([=Fire an event=]) します。

getInfo() メソッド

{{SerialPort/getInfo()}} メソッドの手順は次のとおりです:
  1. |info:SerialPortInfo| を 新しい([=new=]) {{SerialPortInfo}} ディクショナリーとします。
  2. もしそのポートが USB デバイスならば、次の手順を実行します:
    1. |info|["{{SerialPortInfo/usbVendorId}}"] をそのデバイスのベンダー ID に設定します。
    2. |info|["{{SerialPortInfo/usbProductId}}"] をそのデバイスのプロダクト ID に設定します。
  3. |info| を返します。

SerialPortInfo ディクショナリー

      dictionary SerialPortInfo {
        unsigned short usbVendorId;
        unsigned short usbProductId;
      };
    
usbVendorId メンバー
ポートが USB デバイスの一部である場合、このメンバーはそのデバイスの 16 ビットのベンダー ID になります。 それ以外の場合は `undefined` になります。
usbProductId メンバー
ポートが USB デバイスの一部である場合、このメンバーはそのデバイスの 16 ビットのプロダクト ID になります。 それ以外の場合は `undefined` になります。

open() メソッド

{{SerialPort/open()}} メソッドの手順は次のとおりです:
  1. |promise| を 新しい promise ([=a new promise=]) とします。
  2. もし [=this=].{{[[state]]}} が "closed" ではない場合、 "{{InvalidStateError}}" {{DOMException}} で |promise| をリジェクトし、 |promise| を返します。
  3. もし |options|["{{SerialOptions/dataBits}}"] が 7 または 8 ではない場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
  4. もし |options|["{{SerialOptions/stopBits}}"] が 1 または 2 ではない場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
  5. もし |options|["{{SerialOptions/bufferSize}}"] が 0 の場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
  6. 更に、もし |options|["{{SerialOptions/bufferSize}}"] が実装がサポートする範囲よりも大きい場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
  7. [=this=].{{[[state]]}} を `"opening"` に設定します。
  8. 以下の手順を 並列 ([=in parallel=]) に実行します。
    1. |options| で指定された接続パラメータ ( またはそれらのデフォルト値 ) を使用して、オペレーティングシステムを呼び出し、シリアルポートを開きます。
    2. これが何らかの理由で失敗した場合は、 [=シリアルポートタスクソース=] を使用して、 [=this=] が関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=queue a global task=]) 、"{{NetworkError}} {{DOMException}}" で |promise| を リジェクト ([=reject=]) して、これらの手順を中止します。
    3. [=this=].{{[[state]]}} を `"opened"` に設定します。
    4. [=this=].{{[[bufferSize]]}} を |options|["{{SerialOptions/bufferSize}}"] に設定します。
    5. |promise| を `undefined` でリゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れます ([=Queue a global task=])。
  9. |promise| を返します。

SerialOptions ディクショナリー

        dictionary SerialOptions {
          required [EnforceRange] unsigned long baudRate;
          [EnforceRange] octet dataBits = 8;
          [EnforceRange] octet stopBits = 1;
          ParityType parity = "none";
          [EnforceRange] unsigned long bufferSize = 255;
          FlowControlType flowControl = "none";
        };
      
baudRate メンバー
シリアル通信を確立するボーレートを示す 0 ではない正の値。
{{SerialOptions/baudRate}} は、このディクショナリーでの唯一の必須メンバーです。 他の接続パラメータには一般的なデフォルト値がありますが、開発者は、接続しようとするデバイスのドキュメントを検討および参照して、正しい値を決定することが重要です。 幾つかの一般的な値はありますが、ボーレートには標準となる値がありません。 この仕様で何らかのデフォルト値を決めてしまうよりもこのパラメータを要求するようにする事で混乱の可能性を下げる事ができます。
dataBits メンバー
フレームあたりのデータビット数です。 7 または 8 のいずれかになります。
stopBits メンバー
フレームの終わりのストップビットの数です。 1 または 2 のいずれかになります。
parity メンバー
パリティモードです。
bufferSize メンバー
作成する読み取りおよび書き込みバッファーのサイズを示す 0 ではない正の値です。
flowControl メンバー
フロー制御のモードです。
ParityType 列挙値
          enum ParityType {
            "none",
            "even",
            "odd"
          };
        
none
データワードごとにパリティビットは送信されません。
even
データワードとパリティビットの和は偶数パリティになります。
odd
データワードとパリティビットの和は奇数パリティになります。
FlowControlType 列挙値
          enum FlowControlType {
            "none",
            "hardware"
          };
        
none
フロー制御は使用しません。
hardware
RTS と CTS 信号によるハードウェアフロー制御を使用します。

readable 属性

{{SerialPort/readable}} を取得する際の手順は次のとおりです:
  1. もし [=this=].{{[[readable]]}} が `null` でない場合は [=this=].{{[[readable]]}} を返します。
  2. もし [=this=].{{[[state]]}} が `"opened"` でない場合は `null` を返します。
  3. もし [=this=].{{[[readFatal]]}} が `true` の場合は `null` を返します。
  4. |stream| を 新しい ([=new=]) {{ReadableStream}} とします。
  5. |pullAlgorithm| を次の手順とします:
    1. |desiredSize| を [=this=].{{[[readable]]}} の 内部キューdesired size とします。
    2. 次の手順を 並列 ([=in parallel=]) に実行します:
      1. オペレーティングシステムを呼び出して、ポートから最大で |desiredSize| バイトまで読み込み、その結果を バイト列 ([=byte sequence=]) |bytes| に格納します。
      2. [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れて ([=Queue a global task=])、次のステップを実行します:
        1. エラーが発生しなかった場合は、以下の手順を実行します:
          1. |buffer| を |bytes| から作成された 新しい ([=new=]) {{ArrayBuffer}} とします。
          2. |chunk| を |buffer| に対する、長さが |bytes| の 新しい ([=new=]) {{Uint8Array}} のビューとします。
          3. [=this=].{{[[readable]]}} に対して |chunk| を持って エンキュー ([=ReadableStream/enqueue=]) を呼び出します。
        2. もしバッファオーバーラン状態になった場合は、 [=this=].{{[[readable]]}} に対して "BufferOverrunError" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
        3. もしブレーク状態になった場合は、 [=this=].{{[[readable]]}} に対して "BreakError" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
        4. もしフレーミングエラー状態になった場合は、 [=this=].{{[[readable]]}} に対して "FramingError" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
        5. もしパリティエラー状態になった場合は、 [=this=].{{[[readable]]}} に対して "ParityError" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
        6. もしオペレーティングシステムのエラーが発生した場合は、 [=this=].{{[[readable]]}} に対して "{{UnknownError}}" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
        7. もしポートが切断された場合、次の手順を実行します:
          1. [=this=].{{[[readFatal]]}} を `true` に設定します。
          2. "{{NetworkError}}" {{DOMException}} を持って [=this=].{{[[readable]]}} 上で [=ReadableStream/error=] を呼び出します。
          3. [=readable ストリームのクローズ=] の手順を呼び出します。
    3. `undefined` を持って promise をリゾルブ ([=a promise resolved with=]) して返します。

    このアルゴリズムによって返される {{Promise}} は、ストリームのキャンセルをブロックしないように即座にリゾルブされます。 [[STREAMS]] は、チャンクがエンキューされるまでこのアルゴリズムが再び呼び出されないことを指定します。

  6. |cancelAlgorithm| を次の手順とします:
    1. |promise| を 新しい promise ([=a new promise=]) とします。
    2. 次の手順を 並列 ([=in parallel=]) に実行します。
      1. オペレーティングシステムを呼び出して、そのポートのすべてのソフトウェアおよびハードウェアの受信バッファの内容を破棄します。
      2. [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れて ([=Queue a global task=]) 次のステップを実行します。
        1. [=readable ストリームのクローズ=] 処理を行うためのステップを呼び出します。
        2. `undefined` を持って |promise| を リゾルブ ([=Resolve=]) します。
    3. |promise| を返します。
  7. pullAlgorithm に |pullAlgorithm| 、 cancelAlgorithm に |cancelAlgorithm| 、 highWaterMark に [=this=].{{[[bufferSize]]}} 、 sizeAlgorithm に バイトカウントサイズアルゴリズム を設定して |stream| をセットアップ ([=ReadableStream/Set up=])します。
  8. [=this=].{{[[readable]]}} を |stream| に設定します。
  9. |stream| を返します。
readable ストリームのクローズ 処理を行うには、以下の手順を実行します。 steps:
  1. [=this=].{{[[readable]]}} を `null` に設定します。
  2. もし [=this=].{{[[writable]]}} が `null` で、 [=this=].{{[[pendingClosePromise]]}} が `null` でない場合、 [=this=].{{[[pendingClosePromise]]}} を `undefined` でリゾルブ ([=resolve=]) します。

writable 属性

{{SerialPort/writable}} を取得する際の手順は次のとおりです:
  1. もし [=this=].{{[[writable]]}} が `null` でない場合は [=this=].{{[[writable]]}} を返します。
  2. もし [=this=].{{[[state]]}} が `"opened"` でない場合は `null` を返します。
  3. もし [=this=].{{[[writeFatal]]}} が `true` であれば `null` を返します。
  4. |stream:WritableStream| を 新しい ([=new=]) {{WritableStream}} とします。
  5. 与えられた |chunk| に対して |writeAlgorithm| は次の手順とします:
    1. |promise:Promise| を 新しい promise ([=a new promise=]) とします。
    2. もし |chunk| が {{BufferSource}} 型の IDL 値に変換 ([=converted to an IDL value=]) できなければ、 |promise| を {{TypeError}} でリジェクトして返します。 そうでなければ、変換の結果を |source:BufferSource| に保存します。
    3. |source| のコピーを取得 ([=Get a copy of the buffer source=]) し、|bytes| に保存します。
    4. 並列 ([=In parallel=]) に、次の手順を実行します:
      1. ポートに |bytes| を書き込むためにオペレーティングシステムを呼び出します。あるいは、後で結合するために chunk を保存します。
        オペレーティングシステムは |bytes| が送信された後ではなく、送信のためにキューに入れられた時点でこの操作から復帰することができます。
      2. [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) て、次の手順を実行します:
        1. chunk が正常に書き込まれたか、今後の結合のために保存された場合、 |promise| を `undefined` で リゾルブ ([=resolve=]) します。
          [[STREAMS]] は、このアルゴリズムの以前の呼び出しによって返された {{Promise}} がリゾルブされた後にのみ |writeAlgorithm| が呼び出されることを指定します。実装は効率化のために、この {{Promise}} を早期にリゾルブし、 {{WritableStream}} の内部キューで待機している複数のチャンクをオペレーティングシステムへの単一のリクエストとして結合しても構いません。
        2. オペレーティングシステムのエラーが発生した場合は "{{UnknownError}}" {{DOMException}} で |promise| をリジェクト ([=reject=]) します。
        3. もしポートが切断された場合、次の手順を実行します:
          1. Set [=this=].{{[[writeFatal]]}} to `true`.
          2. |promise| を "{{NetworkError}}" {{DOMException}} でリジェクト ([=Reject=]) します。
          3. [=writable ストリームのクローズ=] を呼び出します。
    5. |promise| を返します。
  6. |abortAlgorithm| を次の手順とします:
    1. |promise| を 新しい promise ([=a new promise=]) とします。
    2. 次の手順を 並列 ([=in parallel=]) に実行します。
      1. オペレーティングシステムを呼び出して、ポートのすべてのソフトウェアおよびハードウェアの送信バッファの内容を破棄します。
      2. [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=])、 [=シリアルポートタスクソース=]を使用して、以下のステップを実行します:
        1. [=writable ストリームのクローズ=] の手順を呼び出します。
        2. |promise| を `undefined` で リゾルブ ([=Resolve=]) します。
    3. |promise| を返します。
    [[STREAMS]] は、以前の |writeAlgorithm| の呼び出しによって返された {{Promise}} ( もしあれば ) がリゾルブした後にのみ |abortAlgorithm| が呼び出されることを指定します。これは、直近の書き込み操作の完了時までアボートをブロックします。これは |WriteAlgorithm| に {{AbortSignal}} を渡すことで修正できます。

    この拡張については whatwg/streams#1015 で追跡されています。

  7. |closeAlgorithm| を次の手順とします:
    1. |promise| を 新しい promise ([=a new promise=])とします。
    2. 次の手順を 並列 ([=in parallel=]) に実行します。
      1. オペレーティングシステムを呼び出して、ポートのすべてのソフトウェアとハードウェアの送信バッファの内容をフラッシュします。
      2. [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) 、以下の手順を実行します:
        1. [=writable ストリームのクローズ=] の手順を呼び出します。
        2. `undefined` で |promise| を リゾルブ ([=Resolve=])します。
    3. |promise| を返します。
  8. writeAlgorithm に |writeAlgorithm| 、 abortAlgorithm に |abortAlgorithm| 、closeAlgorithm に |closeAlgorithm| 、 highWaterMark に [=this=].{{[[bufferSize]]}} 、 sizeAlgorithm にバイトカウントサイズアルゴリズムを設定して |stream| をセットアップ ([=WritableStream/Set up=])します。
  9. [=this=].{{[[writable]]}} を |stream| にします。
  10. |stream| を返します。
writable ストリームのクローズ 処理は、以下の手順を実行します:
  1. [=this=].{{[[writable]]}} を `null` に設定します。
  2. もし [=this=].{{[[readable]]}} が `null` で、 [=this=].{{[[pendingClosePromise]]}} が `null` でない場合、 [=this=].{{[[pendingClosePromise]]}} を `undefined` で リゾルブ ([=resolve=]) します。

setSignals() メソッド

{{SerialPort/setSignals()}} メソッドの手順は以下の通りです:
  1. |promise| を 新しい promise ([=a new promise=]) とします。
  2. もし [=this=].{{[[state]]}} が `"opened"` でなければ |promise| は "{{InvalidStateError}}" {{DOMException}} でリジェクトし |promise| を返します。
  3. もし |signals| の指定されたメンバーが存在していない場合は |promise| を {{TypeError}} でリジェクトし、 |promise| を返します。
  4. 以下の手続きを 並列 ([=in parallel=]) に実行します:
    1. もし |signals|["{{SerialOutputSignals/dataTerminalReady}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポート上の "data terminal ready" または "DTR" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
    2. もし |signals|["{{SerialOutputSignals/requestToSend}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポート上の "request to send" または "RTS" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
    3. もし |signals|["{{SerialOutputSignals/break}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポートの "break" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
      "break" 信号は、典型的には送信ラインを "マーク" 電圧で保持することにより、インバンド信号として実装されますので、それがアサートされたままである間、データ伝送はできません。
    4. オペレーティングシステムが何らかの理由でこれらの信号の状態変更に失敗した場合、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で [=シリアルポートタスクソース=] を使用して グローバルタスクをキューに入れ ([=queue a global task=]) 、"{{NetworkError}}" {{DOMException}} で |promise| をリジェクトします。
    5. |promise| を `undefined` で リゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れます ([=Queue a global task=])。
  5. |promise| を返します。

SerialOutputSignals ディクショナリー

        dictionary SerialOutputSignals {
          boolean dataTerminalReady;
          boolean requestToSend;
          boolean break;
        };
      
dataTerminalReady
Data Terminal Ready (DTR)
requestToSend
Request To Send (RTS)
break
Break

getSignals() メソッド

{{SerialPort/getSignals()}} メソッドの手順は次の通りです:
  1. |promise:Promise| を 新しい promise ([=a new promise=]) とします。
  2. もし [=this=].{{[[state]]}} が `"opened"` でない場合 |promise| を "{{InvalidStateError}}" {{DOMException}} でリジェクトし、 |promise| を返します。
  3. 以下の手順を 並列 ([=in parallel=]) に実行します:
    1. シリアルポートに接続されたデバイスがアサートする可能性のある制御信号の状態をオペレーティングシステムに問い合わせます。
    2. もしオペレーティングシステムが何らかの理由でこれらの信号の状態を判断できない場合は、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で [=シリアルポートタスクソース=] を使用して グローバルタスクをキューに入れ ([=queue a global task=])、 |promise| を "{{NetworkError}}" {{DOMException}} でリジェクトして、これらの手順を中止します。
    3. |signals:SerialInputSignals| を 新しい ([=new=]) {{SerialInputSignals}} とします。
    4. もし "data carrier detect" または ""CTS" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/dataCarrierDetect}}"] を `true` に設定し、そうでなければ `false` に設定します。
    5. もし "clear to send" または "CTS" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/clearToSend}}"] を `true` に設定し、そうでなければ `false` に設定します。
    6. もし "ring indicator" または "RI" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/ringIndicator}}"] を `true` に設定し、そうでなければ `false` に設定します。
    7. もし "data set ready" または "DSR" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/dataSetReady}}"] を `true` に設定し、そうでなければ `false` に設定します。
    8. |signals| で |promise| を リゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上の グローバルタスクをキューに入れます ([=Queue a global task=]) 。
  4. |promise| を返します。

SerialInputSignals ディクショナリー

      dictionary SerialInputSignals {
        required boolean dataCarrierDetect;
        required boolean clearToSend;
        required boolean ringIndicator;
        required boolean dataSetReady;
      };
    
dataCarrierDetect メンバー
Data Carrier Detect (DCD)
clearToSend メンバー
Clear To Send (CTS)
ringIndicator メンバー
Ring Indicator (RI)
dataSetReady メンバー
Data Set Ready (DSR)

close() メソッド

{{SerialPort/close()}} メソッドの手順は次の通りです:
  1. |promise| を 新しい promise ([=a new promise=]) とします。
  2. |cancelPromise:Promise| を [=this=].{{[[readable]]}} に対して [=ReadableStream/cancel=] を呼び出した結果、または [=this=].{{[[readable]]}} が `null` であれば、 `undefined` で リゾルブされた promise ([=a promise resolved with=]) であるとします。
  3. |abortPromise:Promise| を [=this=].{{[[writable]]}} に対して [=WritableStream/abort=] を呼び出した結果、または [=this=].{{[[writable]]}} が `null` の場合は `undefined` でリゾルブされた promise ([=a promise resolved with=]) であるとします。
  4. |pendingClosePromise| を 新しい promise ([=a new promise=]) とします。
  5. もし [=this=].{{[[readable]]}} と [=this=].{{[[writable]]}} が `null` であれば [=resolve=] |pendingClosePromise| を `undefined` で リゾルブ ([=resolve=]) します。
  6. [=this=].{{[[pendingClosePromise]]}} を |pendingClosePromise| とします。
  7. «|cancelPromise|, |abortPromise|, |pendingClosePromise|» の 全てを待つ promise を取得 ([=getting a promise to wait for all=]) した結果を |combinedPromise| とします。
  8. [=this=].{{[[state]]}} を `"closing"` に設定します。
  9. |combinedPromise| に反応 ([=promise/React=]) します。
    • |combinedPromise| が満たされていた場合:
      1. 以下の手続きを 並列 ([=in parallel=]) に実行します:
        1. オペレーティング システムを呼び出してシリアル ポートを閉じ、関連するリソースを解放します。
        2. [=this=].{{[[state]]}} を `"closed"` に設定します。
        3. [=this=].{{[[readFatal]]}} と [=this=].{{[[writeFatal]]}} を `false` に設定します。
        4. [=this=].{{[[pendingClosePromise]]}} を `null` に設定します。
        5. [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) て、 [=シリアルポートタスクソース=] を使って |promise| を `undefined` でリゾルブ ([=resolve=]) します。
    • もし |combinedPromise| が理由 |r| でリジェクトされた場合:
      1. [=this=].{{[[pendingClosePromise]]}} を `null` に設定します。
      2. [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) 、[=シリアルポートタスクソース=] を使って |r| で |promise| を リジェクト ([=reject=]) します。
  10. |promise| を返します。

セキュリティに関する考察

このAPIは、 [[?WEB-BLUETOOTH]] および [[?WEBUSB]] と同様のセキュリティリスクをもたらすため、これらの教訓をここに適用できます。 主な脅威は次のとおりです。 これらすべての攻撃に対する主要な緩和策は、 {{Serial/requestPort()}} パターンであり、これはユーザーの操作を必要とし、一度に単一のデバイスへのアクセスを許可することしかサポートしていません。 これは、サイトが脆弱なデバイスが存在するかどうかを判断するために接続されているすべてのデバイスを列挙することができず、代わりにアクセスを希望することを能動的にユーザーが通知する必要があるため、ドライブバイ攻撃を防ぐことができます。 実装はまた、サイトが現在デバイスと通信中であることを視覚的に表示し、いつでもその許可を取り消すためのコントロールを提供する事もできます。

この仕様では、ネットワークベースの攻撃者によって悪意のあるコードが挿入されるのを防ぐために、 [=secure context=] からサイトにサービスを提供する必要があります。 これにより、権限の決定を行うときにユーザーに表示されるサイト ID が正確になります。 この仕様では、クロスオリジン iframe が API を使用できるようにする前に、 [[?PERMISSIONS-POLICY]] を介してオプトインするトップレベルのドキュメントも必要です。 [[?CSP3]] と組み合わせると、これらのメカニズムは悪意のあるコードインジェクション攻撃に対する保護を提供します。

残りの懸念は、フィッシング攻撃による接続デバイスの悪用であり、悪意のあるサイトにデバイスへのアクセスを許可するようにユーザーを誘導します。 これらの攻撃は、設計どおりにデバイスの機能を悪用する、またはデバイスに悪意のあるファームウェアをインストールしてホストコンピュータを攻撃するために使用される可能性があります。 ホストソフトウェアは、接続されたデバイスからの入力を不適切に検証するため、攻撃に対して脆弱である可能性があります。 この分野のセキュリティの研究により、ソフトウェアベンダーは、接続されたデバイスを信頼できないものとして扱うようになりました。

ページからデバイスに送信されるデータは不透明なバイトシーケンスであるため、このタイプの攻撃を完全に防止するメカニズムはありません。 特定のタイプのデータの送信をブロックする取り組みは、デバイスメーカーがこのタイプのデータをデバイスに送信したい場合の回避策によって満たされる可能性があります。

ユーザーエージェントは、デバイスへのアクセスを制御するための追加のメカニズムを実装できます:

[[?WEB-BLUETOOTH]] と [[?WEBUSB]] の実装はこれらの緩和策を実験してきましたが、それらの有効性には限界があります。 まず、デバイスが悪用可能かどうかを定義することは困難です。 たとえば、この API を使用すると、サイトでファームウェアをマイクロコントローラー開発ボードにアップロードできます。 これらのデバイスは教育および愛好家の市場で一般的であるため、これはこのAPIの主要な使用例です。 これらのボードはファームウェアの署名検証を実装していないため、悪意のあるデバイスに簡単に変換される可能性があります。 これらのボードは明らかに悪用可能ですが、ブロックしないでください。

さらに、脆弱なデバイスのリストを維持することは、 USB と Bluetooth でうまく機能します。これらのプロトコルは、デバイスのメタデータを収集するための帯域外メカニズムを定義しているためです。 したがって、このようなデバイスの製造元とモデルは、仮想シリアルポートとしてホストに提示されている場合でも簡単に識別できます。 ただし、一般的な USB または Bluetooth からシリアルへのアダプタと、 DB-25 、 DE-9 、または RJ-45 コネクタを使用する「実際の」シリアルポートを備えたシステムがあります。 これらの場合、ポートに接続されているデバイスのIDを判別するために読み取ることができるメタデータがないため、これらへのアクセスをブロックすることはできません。

プライバシーに関する考察

シリアルポートとシリアルデバイスには、 2 種類の機密情報が含まれています。 ポートが USB または Bluetooth デバイスの場合、 ( メーカーとモデルを識別する ) ベンダー ID やプロダクト ID などの識別子と、シリアル番号または MAC アドレスがあります。 シリアルデバイス自体にも、シリアルポート経由で送信されるコマンドを介して使用できる独自の識別子がある場合があります。 デバイスはまた、識別しているいないに関わらず、その他の個人情報を保存する場合もあります。

デバイスの使用許可を管理するために、実装では USB ベンダー ID、プロダクト ID、シリアル番号などのデバイス識別子をユーザー設定ファイルに保存し、ユーザーがサイトからのアクセスを許可したデバイスの安定した識別子として使用することが考えられます。これらはサイトと直接共有されることはなく、許可が取り消されたり、一般的にサイトのデータがクリアされた場合にはクリアされます。

ページがアクセスを許可された後、ページからデバイスに送る事ができるコマンドによって、デバイスに保存されている他の機密情報のいずれかにアクセスすることができるかもしれません。この情報へのアクセスをページが防ぐことは、 [[[#security]]] で述べた理由から、現実的ではなく、望ましくありません。

実装は、サイトからどのデバイスにアクセスを許可し、そして、ユーザーの操作なしにはデバイスへのアクセスを許可しないという完全な制御をユーザーに提供しなくてはなりません。 これが {{Serial/requestPort()}} メソッドの意図です。 これにより、サイトが黙って列挙し、接続されているすべてのデバイスからデータを収集することを防ぎます。 これはファイル選択の UI に似ています。サイトはファイルシステムの知識を持たず、ユーザーが選択したファイルやディレクトリのみを把握します。 実装では、サイトがこれらの許可を使用しているときに、タブやアドレスバーに表示されるインジケータアイコンでユーザーに通知することができます。

実装が、 "プライベート" または "インコグニト" ブラウジングモードを提供する場合、ユーザの通常のプロファイルで行われた許可がそのようなセッションに持ち越されないこと、およびこのセッションで与えられた許可がセッションが終了したときに保存されないことを確実にするべきです。 実装は、手で識別情報を入力するのと同様に、デバイス識別子および前述のデバイスとの通信から利用可能な他のユニークなプロパティが、セッションをまたいでユーザを識別するために使用される可能性があるため、このようなセッションではデバイスへのアクセスを許可する際にユーザに警告を出す事もあります。

デバイスへのアクセスを許可することで Web セキュリティモデルにおける従来の分離境界を破る方法を理解していないと、ユーザーはこの API によって付与される機能に驚くかもしれません。セキュリティ UI やドキュメントでは、サイトがデバイスにアクセスする事を許可することで、サイトがデバイスとその中に含まれるデータを完全に制御できるようになることを説明する必要があります。

非規範とマークされたセクションと同様に、本仕様書のすべてのオーサリングガイドライン、図、例、および注は非規範です。この仕様書の他のすべての項目は規範的なものです。

謝辞

The following people contributed to the development of this document.