1.
拡張: Navigator
インターフェース
[Exposed =Window ,SecureContext ]partial interface Navigator { [SameObject ]readonly attribute Serial serial ; };
1.1. serial 属性
取得時、serial
属性が常に返すのは、同じ
インスタンスであり、それは Serial オブジェクトである。
2. 拡張: WorkerNavigator
インターフェース
[Exposed =DedicatedWorker ,SecureContext ]partial interface WorkerNavigator { [SameObject ]readonly attribute Serial serial ; };
2.1. serial
属性
取得時、serial
属性が常に返すのは、同じ
インスタンスであり、それは Serial オブジェクトである。
3. 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
3.1. requestPort() メソッド
requestPort()。
この呼び出しは、
ブラウザーがユーザーに尋ねる機会を与えます。どのデバイスをサイトが
制御することを許可できるかについてです。サイトが、
常に USB 経由で接続される特定のデバイスで動作するよう設計されている場合、サイトは
フィルターを提供し、ユーザーが選択できるデバイスを、
互換性のあるものだけに制限できます。たとえば、Arduino を搭載したロボットをプログラムするサイトは、
次のように指定して、選択可能なポートの集合を USB に限定できます。
その対象は、Arduino の USB ベンダー ID を持つデバイスです。
const filter= { usbVendorId: 0x2341 }; const port= await navigator. serial. requestPort({ filters: [ filter] });
一方、サイトが幅広い種類の デバイスや、USB シリアル変換器を通じて接続されるデバイスで使用されることを想定している場合は、 フィルターを一切指定せず、適切なデバイスの選択をユーザーに委ねることができます。
const port= await navigator. serial. requestPort();
ユーザーにポートを選択してもらうには、プロンプトを表示する必要があるため、 サイトは 一時的な活性化 を得ている必要があります。これは、ユーザーが ボタンをクリックするなどの操作によって得られます。
< button id = "connect" > 接続</ button >
const connectButton= document. getElementById( "connect" ); connectButton. addEventListener( 'click' , () => { try { const port= await navigator. serial. requestPort(); // |port| に接続されたデバイスへの接続処理を続ける。 } catch ( e) { // デバイスが選択されずにプロンプトが閉じられた。 } });
ユーザーはデバイスを選択しないこともでき、その場合、Promise
は、
次の例外で却下されます:"NotFoundError"
DOMException。
サイトは、この例外を
処理する必要があります。
requestPort()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this の 関連するグローバルオブジェクト の 関連付けられた Document に対して、 使用が許可されていない ならば(対象の ポリシーで制御される機能 の名前は "serial")、却下する: promise を、次の例外で: "
SecurityError"DOMException。 そして promise を返す。 -
もし 関連するグローバルオブジェクト(this のもの)が持たないならば、一時的な活性化 を、却下する: promise を、"
SecurityError"DOMExceptionで。 そして promise を返す。 -
もし options["
filters"] が存在するならば、反復する: 各 filter について、options["filters"] 内のものを対象として、 実行するのは、 次の手順である:-
もし filter["
bluetoothServiceClassId"] が存在するならば:-
もし filter["
usbVendorId"] が存在するならば、 却下する:promise をTypeErrorで。 そして promise を返す。 -
もし filter["
usbProductId"] が存在するならば、 却下する:promise をTypeErrorで。 そして promise を返す。
-
-
もし filter["
usbVendorId"] が存在しないならば、 却下する:promise をTypeErrorで。 そして promise を返す。注: この検査は、 次の複合的な規則を実装する:
SerialPortFilterは空であってはならず、もしusbProductIdが指定されているならば、usbVendorIdも指定されていなければならない。
-
-
次の手順を実行する(並列に):
-
allPorts を空の リスト とする。
-
各要素について、システムに登録されている Bluetooth デバイスを対象として:
-
各要素について、
BluetoothServiceUUIDuuid を対象として、これをサポートするのは、 そのデバイスである:-
もし uuid が ブロックされた Bluetooth サービスクラス UUID でないならば:
-
もし uuid が次と等しいならば:シリアル ポートプロファイルのサービスクラス ID、または
-
options["
allowedBluetoothServiceClassIds"] が存在し、かつ 含む ならば、uuid を:-
port を
SerialPortとする。 これはサービスを表すものであり、 そのサービスは Bluetooth デバイス上にある。 -
追加する:port を allPorts に。
-
-
-
-
-
各要素について、利用可能 な Bluetooth 以外のシリアルポートを対象として:
-
port を
SerialPortとする。 これはそのポートを表すものである。 -
追加する:port を allPorts に。
-
-
ユーザーにサイトへのシリアルポートのアクセス許可を求める。その際に提示するのは、 allPorts 内のポートのうち いずれかのフィルターに一致する もののリストであり、そのフィルターは options["
filters"] 内のものである。 これは当該メンバーが存在する場合であり、 そうでなければ allPorts を提示する。 -
ユーザーがポートを選択しない場合、グローバルタスクをキューに入れる。その対象は、 関連するグローバルオブジェクト(this のもの)であり、使用するのは シリアルポート タスクソース である。タスクでは 却下する: promise を、"
NotFoundError"DOMExceptionで。 そして、これらの手順を中止する。 -
port を
SerialPortとする。 これはユーザーが選択したポートを表すものである。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、 port で。
-
-
promise を返す。
シリアルポートが 利用可能 であるのは、 それが有線シリアルポートであり、そのポートが システムに物理的に接続されている場合、または無線シリアルポートであり、 そのポートを提供する無線デバイスがシステムに登録されている場合である。
3.1.1. SerialPortRequestOptions
辞書
dictionary SerialPortRequestOptions {sequence <SerialPortFilter >filters ;sequence <BluetoothServiceUUID >allowedBluetoothServiceClassIds ; };
filtersメンバー、 その 型は sequence<SerialPortFilter>- シリアルポートのフィルター
allowedBluetoothServiceClassIdsメンバー、 型は sequence<BluetoothServiceUUID>-
次の型の値からなるリスト:
BluetoothServiceUUID。 これらの値が表すのは Bluetooth サービスの クラス ID である。独自のサービスクラス ID を持つ Bluetooth ポートは除外される: ユーザーに提示されるポートのリストから。ただし、そのサービスクラス ID が このリストに含まれる場合を除く。
3.1.2.
SerialPortFilter 辞書
dictionary SerialPortFilter {unsigned short usbVendorId ;unsigned short usbProductId ;BluetoothServiceUUID bluetoothServiceClassId ; };
usbVendorIdメンバー、 その 型は unsigned short- USB ベンダー ID
usbProductIdメンバー、 その 型は unsigned short- USB 製品 ID
bluetoothServiceClassIdメンバー、 型は BluetoothServiceUUID- Bluetooth サービスクラス ID
true を返すことをいう:
-
info を、次の呼び出し結果とする:port.
getInfo()。 -
もし filter["
bluetoothServiceClassId"] が存在するならば:-
シリアルポートが Bluetooth デバイスの一部でない場合、
falseを返す。 -
もし filter["
bluetoothServiceClassId"] が次と等しいならば: info["bluetoothServiceClassId"]、trueを返す。 -
そうでなければ、
falseを返す。
-
-
もし filter["
usbVendorId"] が存在しないならば、返すのはtrueである。 -
シリアルポートが USB デバイスの一部でない場合、
falseを返す。 -
もし info["
usbVendorId"] が次と等しくないならば: filter["usbVendorId"]、falseを返す。 -
もし filter["
usbProductId"] が存在しないならば、返すのはtrueである。 -
もし info["
usbProductId"] が次と等しくないならば: filter["usbProductId"]、falseを返す。 -
そうでなければ、
trueを返す。
SerialPortFilter)
とは、次の手順が true を返すことをいう:
-
シーケンス内の各 filter について、次の下位手順を実行する:
-
もし port が フィルターに一致する(フィルターは filter)ならば、返すのは
trueである。
-
-
falseを返す。
3.2. getPorts() メソッド
// ページの読み込み時に、どのポートが利用可能かを確認する。 document. addEventListener( 'DOMContentLoaded' , async () => { let ports= await navigator. serial. getPorts(); // ユーザーが選択できる項目を UI に追加するか、 // デバイスに自動的に接続する。 }); navigator. serial. addEventListener( 'connect' , e=> { // |e.target| を UI に追加するか、自動的に接続する。 }); navigator. serial. addEventListener( 'disconnect' , e=> { // |e.target| を UI から削除する。デバイスが開かれていた場合、 // 切断はストリームエラーとしても観測できる。 });
getPorts()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this の 関連するグローバルオブジェクト の 関連付けられた Document に対して、 使用が許可されていない ならば(対象の ポリシーで制御される機能 の名前は
"serial")、 却下する:promise を、"SecurityError"DOMExceptionで。 そして返すのは、 promise である。 -
次の手順を実行する(並列に):
-
availablePorts を次のシーケンスとする:利用可能 なシリアルポートのうち、 ユーザーが以前の呼び出しの結果としてサイトにアクセスを許可したもの。 その呼び出しは
requestPort()である。 -
ports を、次の型のオブジェクトのシーケンスとする:
SerialPort。 これらが表すのは、 availablePorts 内のポートである。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、 ports で。
-
-
promise を返す。
3.3. onconnect 属性
onconnect
は イベントハンドラー IDL 属性 であり、対象は
connect イベント型である。
3.4. ondisconnect 属性
ondisconnect
は イベントハンドラー IDL 属性 であり、対象は
disconnect イベント型である。
4. SerialPort インターフェース
[Exposed =(DedicatedWorker ,Window ),SecureContext ]interface SerialPort :EventTarget {attribute EventHandler onconnect ;attribute EventHandler ondisconnect ;readonly attribute boolean connected ;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 ();Promise <undefined >forget (); };
このインターフェースのメソッドは通常、非同期に完了し、次のソースで処理をキューに入れる: シリアルポートタスク ソース。
この 親を取得する アルゴリズムの対象は SerialPort
であり、
返すのは同じ Serial
インスタンスである。これを返すのは、SerialPort
の
関連するグローバルオブジェクト の
Navigator
オブジェクトの serial
ゲッターである。
次の型のインスタンス:SerialPort
は、記載された内部スロットを持つものとして作成される。
その記載は次の表のとおりである:
| 内部スロット | 初期値 | 説明(非規範的) |
|---|---|---|
[[state]]
| "closed"
| アクティブな状態を追跡する。対象は SerialPort
|
[[bufferSize]]
| undefined | 送受信のためにバッファリングするデータ量 |
[[connected]]
| false
| シリアルポートの論理的な接続状態を示すフラグ |
[[readable]]
| null
| この ReadableStream
は、ポートからデータを受信する
|
[[readFatal]]
| false
| ポートで致命的な読み取りエラーが発生したことを示すフラグ |
[[writable]]
| null
| この WritableStream
は、ポートにデータを送信する
|
[[writeFatal]]
| false
| ポートで致命的な書き込みエラーが発生したことを示すフラグ |
[[pendingClosePromise]]
| null
|
この Promise
は、待機に使用される。対象は readable
および
writable
が閉じられることである
|
4.1. onconnect 属性
onconnect
は イベントハンドラー IDL 属性 であり、対象は
connect
イベント型である。
requestPort()
である場合、
ポートが 論理的に
接続されている 状態になったとき、実行するのは、
次の手順である:
-
port を
SerialPortとする。 これはそのポートを表すものである。 -
port.
[[connected]]をtrueに設定する。 -
イベントを発火する。その名前は
connect、 対象は port とし、そのbubbles属性をtrueに初期化する。
シリアルポートが 論理的に接続されている のは、それが有線シリアルポートであり、 そのポートがシステムに物理的に接続されている場合、または無線シリアル ポートであり、システムがその無線デバイスへの有効な接続を持つ場合である(たとえば、開かれた Bluetooth L2CAP チャネル)。
4.2. ondisconnect 属性
ondisconnect
は イベントハンドラー IDL 属性 であり、対象は
disconnect
イベント型である。
requestPort()
である場合、
もはや 論理的に
接続されている 状態でなくなったとき、次の手順を実行する:
-
port を
SerialPortとする。 これはそのポートを表すものである。 -
port.
[[connected]]をfalseに設定する。 -
イベントを発火する。その名前は
disconnect、 対象は port とし、そのbubbles属性をtrueに初期化する。
4.3. getInfo() メソッド
getInfo()
メソッドの手順は次のとおりである:
-
info を空の 順序付きマップ とする。
-
ポートが USB デバイスの一部である場合、次の手順を実行する:
-
info["
usbVendorId"] に設定するのは、ベンダー ID であり、 そのデバイスのものである。 -
info["
usbProductId"] に設定するのは、製品 ID であり、 そのデバイスのものである。
-
-
ポートが Bluetooth デバイス上のサービスである場合、次の手順を実行する:
-
info["
bluetoothServiceClassId"] に設定するのは、サービスの クラス UUID であり、その Bluetooth サービスのものである。
-
-
info を返す。
4.3.1.
SerialPortInfo 辞書
dictionary SerialPortInfo {unsigned short usbVendorId ;unsigned short usbProductId ;BluetoothServiceUUID bluetoothServiceClassId ; };
usbVendorIdメンバー、 型は unsigned short-
ポートが USB デバイスの一部である場合、このメンバーは 16 ビットのベンダー ID となる。
これはそのデバイスのものである。そうでなければ
undefinedとなる。 usbProductIdメンバー、 型は unsigned short-
ポートが USB デバイスの一部である場合、このメンバーは 16 ビットの製品
ID となり、そのデバイスのものである。そうでなければ
undefinedとなる。 bluetoothServiceClassIdメンバー、 型は BluetoothServiceUUID-
ポートが Bluetooth デバイス上のサービスである場合、このメンバーは次の型の値となる:
BluetoothServiceUUID。 この値はサービスクラス UUID を含む。そうでなければ、undefinedとなる。
4.4. open()
メソッド
await port. open({ baudRate: /* ボーレートを選択する */ });
この open()
が解決すると、readable
および
writable
属性にアクセスして取得できるのは、
ReadableStream
および WritableStream
のインスタンスであり、データの受信と、
接続先デバイスへのデータ送信に使用します。
open()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this.
[[state]]が"closed"でないならば、promise を却下する。 その理由は "InvalidStateError"DOMExceptionとし、 promise を返す。 -
もし options["
baudRate"] が 0 ならば、promise を次の例外で却下する:TypeError。 そして promise を返す。 -
もし options["
dataBits"] が 7 でも 8 でもないならば、promise を却下する。 その理由はTypeErrorとし、 promise を返す。 -
もし options["
stopBits"] が 1 でも 2 でもないならば、promise を却下する。 その理由はTypeErrorとし、 promise を返す。 -
もし options["
bufferSize"] が 0 ならば、promise を次の例外で却下する:TypeError。 そして promise を返す。 -
任意で、もし options["
bufferSize"] が次の範囲を超えるならば: 実装がサポートできる範囲、promise を次の例外で却下する:TypeError。 そして、 promise を返す。 -
次の手順を実行する(並列に)。
-
オペレーティングシステムを呼び出して、シリアルポートを開く。その際に使用する接続 パラメーターは、options で指定された値(またはその既定値)とする。
-
何らかの理由でこれに失敗した場合、グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、使用するのは シリアルポート タスクソース である。タスクでは、 却下する:promise を、"
NetworkError"DOMExceptionで。 そして、 これらの手順を中止する。 -
this.
[[bufferSize]]に設定する値は、 options["bufferSize"] とする。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、
undefinedで。
-
-
promise を返す。
4.4.1.
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メンバー、 型は unsigned long-
正の非ゼロ値で、ボーレートを示す。そのレートでシリアル
通信を確立すべきである。
注:
baudRateは唯一の 必須 メンバーであり、この 辞書に属する。その他の接続パラメーターには一般的な既定値があるものの、 開発者が検討し、ドキュメントを参照することが重要である。 接続を意図するデバイスのドキュメントを参照して、正しい 値を決定する。一般的な値はいくつかあるが、標準のボーレートは存在しない。 このパラメーターを必須にすることで、任意の既定値が この仕様によって選択された場合に生じ得る混乱を軽減する。 dataBitsメンバー、 型は octet、既定値は8- フレームあたりのデータビット数。7 または 8。
stopBitsメンバー、 型は octet、既定値は1- フレーム末尾のストップビット数。1 または 2。
parityメンバー、 型は ParityType、既定値は"none"- パリティモード。
bufferSizeメンバー、 型は unsigned long、既定値は255- 正の非ゼロ値で、読み取りバッファーと書き込みバッファーのサイズを示す。 これらは、そのサイズで作成すべきである。
flowControlメンバー、 型は FlowControlType、既定値は"none"- フロー制御モード。
4.4.1.1. ParityType 列挙型
enum ParityType {"none" ,"even" ,"odd" };
none- 各データワードに対してパリティビットを送信しない。
even- データワードとパリティビットを合わせたものが偶数パリティになる。
odd- データワードとパリティビットを合わせたものが奇数パリティになる。
4.4.1.2.
FlowControlType 列挙型
enum FlowControlType {"none" ,"hardware" };
none- フロー制御を有効にしない。
hardware- RTS 信号と CTS 信号を使用するハードウェアフロー制御を有効にする。
4.5. connected 属性
connected
ゲッターの手順は次のとおりである:
-
返すのは this.
[[connected]]である。
4.6. readable 属性
while ( port. readable) { const reader= port. readable. getReader(); try { while ( true ) { const { value, done} = await reader. read(); if ( done) { // |reader| はキャンセルされた。 break ; } // |value| を使って何らかの処理を行う... } } catch ( error) { // |error| を処理する... } finally { reader. releaseLock(); } }
内側のループは、ポートからデータのチャンクを読み取り、エラーが
発生するまで続けます。エラーが発生すると、"catch" ブロック内のコードが実行されます。
外側のループは、パリティ検査の失敗など、回復可能なエラーを処理します。
そのために、新しいリーダーを開きます。致命的なエラーが発生すると、readable
は、
null になり、ループが終了します。
シリアルポートが開いている限り、データは生成され続ける可能性があり、
返される各チャンクに含まれるデータ量は、
read()
によって返されるもので、基本的には任意です。
その量は、呼び出しのタイミングに依存します。デバイスと、それと
通信するコードが、何を完全なメッセージとみなすかを決定します。たとえば、
デバイスは ASCII 形式のテキストを使用してホストと通信し、
各メッセージを改行(またはシーケンス "\r\n")で終える場合があります。パイプラインを、
次の型で構成できます:TransformStream。
これを使用すると、自動的に変換できます:
Uint8Array
チャンクを、その提供元である readable
から、DOMString
へ。
それぞれには、テキストの 1 行全体が含まれます。
class LineBreakTransformer{ constructor () { this . container= '' ; } transform( chunk, controller) { this . container+= chunk; const lines= this . container. split( '\r\n' ); this . container= lines. pop(); lines. forEach( line=> controller. enqueue( line)); } flush( controller) { controller. enqueue( this . container); } } const decoder= new TextDecoderStream(); const streamClosed= port. readable. pipeTo( decoder. writable); const lineReader= decoder. readable. pipeThrough( new TransformStream( new LineBreakTransformer())) . getReader();
例 7 と同様に、パイプの連鎖は、
pipeThrough() だけでは構築できません。pipeTo() を使用する必要があります。
最初の TransformStream
を SerialPort
に接続する際に使用することで、
ポートを閉じたいときに、パイプの連鎖が閉じられるのを待機できます。
lineReader. cancel(); await streamClosed; await port. close();
メッセージの境界を符号化する他の方法には、各メッセージの先頭に
その長さを付加する方法や、定められた時間だけ待機してから次の
メッセージを送信する方法があります。TransformStream
を、これらの種類のメッセージ
境界に対応するよう実装することは、読者の演習として残します。
この read()
メソッドは非同期であり、
実行をブロックしませんが、async/await 構文を使用するコードでは、
ブロックしているように見えることがあります。この状況では、タイムアウトの実装が役立つ場合があります。
これにより、データを受信しない状態が一定の
時間続いた場合に、コードの実行を継続できます。次の例では、releaseLock()
メソッドを使用して、次の呼び出しを中断します:read()。
そのタイミングは、
タイマーの満了後です。これはストリームを閉じないため、その後に受信されたデータも、
タイムアウト後であっても、後から読み取ることができます。次を呼び出すと、
getReader()、
再び読み取れます。
async function readWithTimeout( port, timeout) { const reader= port. readable. getReader(); const timer= setTimeout(() => { reader. releaseLock(); }, timeout); const result= await reader. read(); clearTimeout( timer); reader. releaseLock(); return result; }
この機能は、releaseLock()
に、次の変更で追加されました:
whatwg/streams#1168。
ブラウザーに実装されたのは、つい最近のことです。
readable
ゲッターの手順は次のとおりである:
-
もし this.
[[readable]]がnullでないならば、返すのは this.[[readable]]である。 -
もし this.
[[readFatal]]がtrueならば、nullを返す。 -
stream を 新しい
ReadableStreamとする。 -
pullAlgorithm を次の手順とする:
-
desiredSize を次の 必要な サイズ(高水位標まで満たすための) とする。その対象は this.
[[readable]]である。 -
もし this.
[[readable]]の 現在の BYOB リクエストビュー が null でないならば、desiredSize を次の値に設定する: this.[[readable]]の 現在の BYOB リクエスト ビュー の バイト長。 -
promise を 1 つの 新しいプロミス とする。
-
次の手順を実行する(並列に):
-
オペレーティングシステムを呼び出して、最大 desiredSize バイトを読み取る。 読み取り元はポートであり、結果を格納するのは バイト列 bytes とする。
注: this.
[[state]]が"forgotten"になった場合は、 the port was disconnected であるかのように扱ってもよい。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアル ポートタスクソース とし、実行するのは次の 手順である:
-
エラーが発生しなかった場合:
-
もし this.
[[readable]]の 現在の BYOB リクエストビュー が null でないならば、 書き込む: bytes を、 this.[[readable]]の 現在の BYOB リクエストビュー に。そして view を設定する。 その値は this.[[readable]]の 現在の BYOB リクエストビュー とする。 -
そうでなければ、view を、次の 作成 の結果に設定する。型は
Uint8Array、 入力は、 bytes、作成先は this の 関連する Realm とする。 -
キューに入れる: view を、 this.
[[readable]]に。 -
解決する:promise を、
undefinedで。
-
-
バッファーオーバーランが発生した場合、呼び出すのは、 エラーにする 処理である。対象は this.
[[readable]]とし、 "BufferOverrunError"DOMExceptionを用いる。 そして、 次の手順を呼び出す:クローズを処理する( 読み取り可能なストリーム)。 -
ブレーク状態が発生した場合、呼び出すのは、 エラーにする 処理である。対象は this.
[[readable]]とし、 "BreakError"DOMExceptionを用いる。 そして呼び出すのは、 次の手順である:クローズを処理する( 読み取り可能なストリーム)。 -
フレーミングエラーが発生した場合、呼び出すのは、 エラーにする 処理である。対象は this.
[[readable]]とし、 "FramingError"DOMExceptionを用いる。 そして呼び出すのは、 次の手順である:クローズを処理する( 読み取り可能なストリーム)。 -
パリティエラーが発生した場合、呼び出すのは、 エラーにする 処理である。対象は this.
[[readable]]とし、 "ParityError"DOMExceptionを用いる。 そして呼び出すのは、 次の手順である:クローズを処理する( 読み取り可能なストリーム)。 -
オペレーティングシステムのエラーが発生した場合、呼び出すのは、 エラーにする 処理である。対象は this.
[[readable]]とし、 次の例外を用いる:"UnknownError"DOMException。 そして次の手順を呼び出す: クローズを処理する( 読み取り可能なストリーム)。 -
the port was disconnected の場合、次の手順を実行する:
-
this.
[[readFatal]]をtrueに設定する。 -
呼び出すのは エラーにする 処理である。対象は、 this.
[[readable]]とし、 用いる例外は、 "NetworkError"DOMExceptionとする。 -
次の手順を呼び出す:処理する( 読み取り可能なストリームのクローズ)。
-
-
-
-
promise を返す。
-
-
cancelAlgorithm を次の手順とする:
-
promise を 1 つの 新しいプロミス とする。
-
次の手順を実行する(並列に)。
-
オペレーティングシステムを呼び出して、すべてのソフトウェア およびハードウェアの、当該ポートの受信バッファーの内容を破棄する。
-
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアル ポートタスクソース とし、実行するのは次の 手順である:
-
次の手順を呼び出す:クローズを処理する( 読み取り可能なストリーム)。
-
解決する:promise を、
undefinedで。
-
-
-
promise を返す。
-
-
バイト読み取りをサポートするように 設定する:stream を、次の指定で: pullAlgorithm に設定する値は、 pullAlgorithm、cancelAlgorithm に設定する値は cancelAlgorithm、そして、 highWaterMark に設定する値は、 this.
[[bufferSize]]とする。 -
this.
[[readable]]を stream に設定する。 -
stream を返す。
-
this.
[[readable]]をnullに設定する。 -
もし this.
[[writable]]がnullであり、かつ、 this.[[pendingClosePromise]]がnullでないならば、解決する: this.[[pendingClosePromise]]をundefinedで。
4.7. writable 属性
WritableStreamDefaultWriter
を必要に応じて作成し、解放できます。この
例では TextEncoder を使用して、DOMString
を、必要な形式である
Uint8Array
に符号化して送信します。
const encoder= new TextEncoder(); const writer= port. writable. getWriter(); await writer. write( encoder. encode( "PING" )); writer. releaseLock();
大きなチャンクを書き込む場合、ポートがバック
プレッシャーを適用し、シリアル送信器の送信が大きく遅れないようにすることが重要となります。
対象は、アプリケーションが生成するデータです。この
write()
メソッドは Promise
を返し、
これはデータが書き込まれると解決します。ある程度のデータを
送信バッファー内に保持することは、良好なスループットの維持に重要ですが、この
Promise
を待機してから大量のデータチャンクを生成することは、
過剰なバッファリングを避けるための良い方法です。
writable
ゲッターの手順は次のとおりである:
-
もし this.
[[writable]]がnullでないならば、返すのは this.[[writable]]である。 -
もし this.
[[writeFatal]]がtrueならば、nullを返す。 -
stream を 新しい
WritableStreamとする。 -
signal を stream の シグナル とする。
-
writeAlgorithm を、chunk が与えられたときの次の手順とする:
-
promise を 1 つの 新しいプロミス とする。
-
表明:signal は 中止済み ではない。
-
もし chunk を IDL 値に変換 できないならば、その 型は
BufferSourceとして、 promise を次の例外で却下する:TypeError。 そして返すのは、 promise である。そうでなければ、変換の結果を source に保存する。 -
バッファーソースのコピーを取得する。 対象は source とし、その結果を保存するのは、 bytes とする。
-
並列に、次の手順を実行する:
-
オペレーティングシステムを呼び出して、bytes をポートに書き込む。 または、後で結合するためにチャンクを保存する。
注: オペレーティングシステムは、 この操作から戻ってもよい。その時点は、 bytes が送信キューに入れられた時点でよく、 送信が完了した後である必要はない。
注: this.
[[state]]が"forgotten"になった場合は、 the port was disconnected であるかのように扱ってもよい。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアル ポートタスクソース とし、実行するのは次の 手順である:
-
チャンクの書き込みに成功した場合、または後で 結合するために保存された場合、解決する:promise を、
undefinedで。注: [STREAMS] は、次のように規定している: writeAlgorithm が 呼び出されるのは、次の
Promise、 すなわち以前の呼び出しで返されたものが、 解決した後だけである。効率のために、実装は、 このPromiseを早期に解決することが許される。これは、 複数のチャンクを結合するためであり、それらはWritableStreamの 内部キューで待機している。結合先は、オペレーティングシステムへの単一の要求となる。 -
オペレーティングシステムのエラーが発生した場合、却下する: promise を、"
UnknownError"DOMExceptionで。 -
the port was disconnected の場合、次の手順を実行する:
-
this.
[[writeFatal]]をtrueに設定する。 -
却下する:promise を、次の例外で: "
NetworkError"DOMException。 -
次の手順を呼び出す:処理する( 書き込み可能なストリームのクローズ)。
-
-
-
-
promise を返す。
-
-
abortAlgorithm を次の手順とする:
-
promise を 1 つの 新しいプロミス とする。
-
次の手順を実行する(並列に)。
-
オペレーティングシステムを呼び出して、すべてのソフトウェア およびハードウェアの、当該ポートの送信バッファーの内容を破棄する。
-
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアル ポートタスクソース とし、実行するのは次の 手順である:
-
次の手順を呼び出す:クローズを処理する( 書き込み可能なストリーム)。
-
解決する:promise を、
undefinedで。
-
-
-
promise を返す。
-
-
closeAlgorithm を次の手順とする:
-
promise を 1 つの 新しいプロミス とする。
-
次の手順を実行する(並列に)。
-
オペレーティングシステムを呼び出して、すべてのソフトウェア およびハードウェアの、当該ポートの送信バッファーの内容をフラッシュする。
-
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアル ポートタスクソース とし、実行するのは次の 手順である:
-
次の手順を呼び出す:クローズを処理する( 書き込み可能なストリーム)。
-
そうでなければ、解決する:promise を、
undefinedで。
-
-
-
promise を返す。
-
-
設定する:stream を、次の指定で: writeAlgorithm に設定する値は writeAlgorithm、 abortAlgorithm に設定する値は abortAlgorithm、 closeAlgorithm に設定する値は closeAlgorithm、 highWaterMark に設定する値は this.
[[bufferSize]]、 そして、 sizeAlgorithm に設定するのは、バイト数を数える サイズ計算 アルゴリズムとする。 -
追加する:次の中止手順を signal に:
-
ポートへの書き込みを行うオペレーティングシステムの呼び出しを、 書き込まれたデータ量にかかわらず、できるだけ早く戻らせる。
-
-
this.
[[writable]]を stream に設定する。 -
stream を返す。
-
this.
[[writable]]をnullに設定する。 -
もし this.
[[readable]]がnullであり、かつ、 this.[[pendingClosePromise]]がnullでないならば、解決する: this.[[pendingClosePromise]]をundefinedで。
4.8. setSignals() メソッド
await port. setSignals({ dataTerminalReady: false }); await new Promise( resolve=> setTimeout( resolve, 200 )); await port. setSignals({ dataTerminalReady: true });
setSignals()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this.
[[state]]が"opened"でないならば、promise を却下する。 その理由は "InvalidStateError"DOMExceptionとし、 promise を返す。 -
signals の規定されたメンバーがいずれも存在しない場合、却下する: promise を、
TypeErrorで。 そして promise を返す。 -
次の手順を実行する(並列に):
注: 理想的には、指定された変更、 すなわち signals 内の変更を不可分に適用することが望ましい。 しかし、これは POSIX API と Windows API のいずれでもサポートされておらず、 ユーザーエージェントはこれらの API で当該手順を実装する。そのため、以下の順序に アプリケーションが依存する可能性が高い。
-
もし signals["
dataTerminalReady"] が存在するならば、 オペレーティングシステムを呼び出して、アサート(trueの場合)またはデアサートする(falseの場合)。対象は、シリアルポートの「データ端末準備完了」または「DTR」信号である。 -
もし signals["
requestToSend"] が存在するならば、呼び出す: オペレーティングシステムを、アサート(trueの場合)またはデアサートするために(falseの場合)。対象は、シリアルポートの「送信要求」または「RTS」信号である。 -
もし signals["
break"] が存在するならば、呼び出す: オペレーティングシステムを、アサート(trueの場合)またはデアサートするために(falseの場合)。 対象は、シリアルポートの「ブレーク」信号である。注: 「ブレーク」信号は、 通常、帯域内信号として実装される。 送信線を「マーク」電圧に維持することで実現されるため、 アサートされている間はデータ送信を妨げる。
-
オペレーティングシステムがこれらのいずれかの 信号の状態変更に何らかの理由で失敗した場合、グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、使用するのは シリアルポート タスクソース である。タスクでは却下する: promise を、"
NetworkError"DOMExceptionで。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、
undefinedで。
-
-
promise を返す。
4.8.1.
SerialOutputSignals 辞書
dictionary SerialOutputSignals {boolean dataTerminalReady ;boolean requestToSend ;boolean break ; };
dataTerminalReady, 型は boolean- データ端末準備完了 (DTR)
requestToSend, その 型は boolean- 送信要求 (RTS)
break, 型は boolean- ブレーク
4.9. getSignals() メソッド
getSignals()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this.
[[state]]が"opened"でないならば、promise を却下する。 その理由は "InvalidStateError"DOMExceptionとし、 promise を返す。 -
次の手順を実行する(並列に):
-
オペレーティングシステムに制御信号の状態を問い合わせる。これらの信号は、 シリアルポートに接続されたデバイスによってアサートされ得る。
-
オペレーティングシステムがこれらの信号の状態の判定に 何らかの理由で失敗した場合、グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、使用するのは シリアルポート タスクソース である。タスクでは却下する: promise を、"
NetworkError"DOMExceptionで。 そして、これらの 手順を中止する。 -
dataCarrierDetect を
trueとするのは、「データキャリア検出」または 「DCD」 信号がデバイスによってアサートされている場合であり、そうでなければfalseとする。 -
clearToSend を
trueとするのは、「送信可」または「CTS」信号が、 すでに、 デバイスによってアサートされている場合であり、そうでなければfalseとする。 -
ringIndicator を
trueとするのは、「着信表示」または「RI」信号が、 すでに、 デバイスによってアサートされている場合であり、そうでなければfalseとする。 -
dataSetReady を
trueとするのは、「データセット準備完了」または「DSR」信号が、 すでに、 デバイスによってアサートされている場合であり、そうでなければfalseとする。 -
signals を次の 順序付き マップ とする:«[ "
dataCarrierDetect" → dataCarrierDetect, "clearToSend" → clearToSend, "ringIndicator" → ringIndicator, "dataSetReady" → dataSetReady ]»。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、 signals で。
-
-
promise を返す。
4.9.1.
SerialInputSignals 辞書
dictionary SerialInputSignals {required boolean dataCarrierDetect ;required boolean clearToSend ;required boolean ringIndicator ;required boolean dataSetReady ; };
dataCarrierDetectメンバー、 型は boolean- データキャリア検出 (DCD)
clearToSendメンバー、 その 型は boolean- 送信可 (CTS)
ringIndicatorメンバー、 型は boolean- 着信表示 (RI)
dataSetReadyメンバー、 型は boolean- データセット準備完了 (DSR)
4.10. close() メソッド
呼び出すと、port.close()
を、
暗黙的に呼び出されるのは、
port.readable.cancel()
および
port.writable.abort()
です。
これは消去するためであり、
対象はバッファリングされたすべてのデータです。アプリケーションが呼び出している場合、
port.readable.getReader()
または
port.writable.getWriter()
を、
ストリームは、
ロックされ、ポートを閉じることはできません。そのため、開発者は方法を決める必要があります。
進行中の読み取りや書き込み操作をどのように扱うかについてです。たとえば、
バッファリングされたすべてのデータが、ポートを閉じる前に送信済みであることを保証するには、
アプリケーションは次の Promise
を待機する必要があります。
その返却元は、
writer.close()
です。
const encoder= new TextEncoder(); const writer= port. writable. getWriter(); writer. write( encoder. encode( "A long message that will take..." )); await writer. close(); await port. close();
未送信のデータを破棄するには、アプリケーションは代わりに次を呼び出せます:
writer.abort()。
もし TransformStream
がパイプ接続されており、その接続先が port.writable
である場合、
次の Promise
を待機すること、
すなわち、返却元が
writer.close()
であるものの解決を待つだけでは不十分です。
アプリケーションは、パイプの連鎖が閉じられるまで待機する必要があります。そのために待つのは、
Promise、
その返却元は pipeTo()
であり、
代わりにこれが解決するのを待ちます。
const encoder= new TextEncoderStream(); const writableStreamClosed= encoder. readable. pipeTo( port. writable); const writer= encoder. writable. getWriter(); writer. write( "A long message that will take..." ); writer. close(); await writableStreamClosed; await port. close();
ポートからチャンクを読み取るためにループを使用している場合、
例 4 のように、まずそのループを終了する必要があります。
その後に呼び出すのは port.close()
です。
let keepReading= true ; let reader; async function readUntilClosed() { while ( port. readable&& keepReading) { reader= port. readable. getReader(); try { while ( true ) { const { value, done} = await reader. read(); if ( done) { // |reader| はキャンセルされた。 break ; } // |value| を使って何らかの処理を行う... } } catch ( error) { // |error| を処理する... } finally { reader. releaseLock(); } } await port. close(); } const closed= readUntilClosed(); // しばらくしてから... keepReading= false ; reader. cancel(); await closed;
呼び出すと、reader.cancel()
を、
次の呼び出し、
reader.read()
が直ちに戻り、終了します:
内側のループを。そして呼び出します:
reader.releaseLock()。
続いて外側のループも、
keepReading が false に設定されているため終了し、ストリームの
ロックが解除されているので、port.close()
は正常に完了できます。
次を呼び出すことも可能です:port.close()。
そのタイミングは直後であり、
次の Promise
の待機後です。
その返却元は、
reader.cancel()
です。
しかし、配置するのがより適切なのは、
次の呼び出し:port.close()
を、
readUntilClosed() の最後の手順としてです。
これにより、致命的なエラーが発生したときにもポートが閉じられます。
port.readable
が null になったときにも対応できます。
close()
メソッドの手順は次のとおりである:
-
promise を 新しい プロミス とする。
-
もし this.
[[state]]が"opened"でないならば、promise を却下する。 その理由は "InvalidStateError"DOMExceptionとし、 promise を返す。 -
cancelPromise を、次を呼び出した結果とする:キャンセルする。その対象は、 this.
[[readable]]とする。 または、次の値で解決されたプロミス とする:undefined。こちらを使用するのは、this.[[readable]]がnullの場合である。 -
abortPromise を、次を呼び出した結果とする:中止する。その対象は、 this.
[[writable]]とする。 または、次の値で解決されたプロミス とする:undefined。こちらを使用するのは、this.[[writable]]がnullの場合である。 -
pendingClosePromise を 新しい プロミス とする。
-
もし this.
[[readable]]および this.[[writable]]がnullならば、解決する: pendingClosePromise をundefinedで。 -
this.
[[pendingClosePromise]]に設定する値は、 pendingClosePromise とする。 -
combinedPromise を次の結果とする:すべてを待機するプロミスを取得する。 その引数は «cancelPromise, abortPromise, pendingClosePromise» とする。
-
反応する: combinedPromise に。
-
combinedPromise が履行された場合:
-
次の手順を実行する(並列に):
-
オペレーティングシステムを呼び出して、シリアルポートを閉じ、解放する: 関連するすべてのリソースを。
-
this.
[[readFatal]]および this.[[writeFatal]]をfalseに設定する。 -
this.
[[pendingClosePromise]]をnullに設定する。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアルポートタスクソース とする。 タスクでは 解決する: promise を
undefinedで。
-
-
-
combinedPromise が理由 r で却下された場合:
-
this.
[[pendingClosePromise]]をnullに設定する。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト であり、その所有者は this である。使用するのは シリアルポートタスクソース とする。タスクでは 却下する: promise を r で。
-
-
-
promise を返す。
4.11. forget() メソッド
// シリアルポートを要求する。 const port= await navigator. serial. requestPort(); // その後... シリアルポートへのアクセス権限を取り消す。 await port. forget();
forget()
メソッドの手順は次のとおりである:
-
ユーザーエージェントがこの操作を実行できない場合(たとえば、権限が 管理者ポリシーによって付与されている場合)、返すのは 次の値で解決されたプロミス とする:
undefined。 -
次の手順を実行する(並列に):
-
削除する:this を、システム上のシリアルポートのシーケンスから。その対象は、 ユーザーが以前の呼び出しの結果としてサイトにアクセスを許可したものであり、 その呼び出しは
requestPort()である。 -
グローバルタスクをキューに入れる。その対象は 関連するグローバルオブジェクト(this のもの)であり、 使用するのは シリアルポートタスクソース である。タスクでは 解決する:promise を、
undefinedで。
-
promise を返す。
5. ブロックリスト
この仕様は、ブロックリストファイルに依存する。その所在は、 https://github.com/whatwg/serial リポジトリーであり、ウェブサイトがアクセスできるポートの集合を制限するために使用する。
次の Bluetooth サービスクラス ID
ブロックリストの構文解析 の結果は、その所在が
URL url であるとき、リスト となる。その要素は UUID
値であり、独自の
サービス ID を表す。
この シリアルポートプロファイルのサービスクラス ID は、
BluetoothServiceUUID
であり、その値は "00001101-0000-1000-8000-00805f9b34fb" である。
BluetoothServiceUUID
serviceUuid が ブロックされた Bluetooth サービス
クラス UUID であるのは、次の手順が true を返す場合である:
-
uuid を、次を呼び出した結果とする:
BluetoothUUID.getService()。 引数は serviceUuid とする。 -
blocklist を次の結果とする:Bluetooth サービス クラス ID ブロックリストの構文解析。その所在は、 https://github.com/whatwg/serial/blob/main/bluetooth-service-blocklist.txt とする。
-
もし blocklist が 含む ならば、 uuid を、
trueを返す。 -
もし uuid が 一致する ならば、次に:シリアルポートプロファイルのサービスクラス ID、 返すのは、
falseである。 -
もし uuid が 次で終わる ならば:"
-0000-1000-8000-00805f9b34fb"、 返すのは、trueである。 -
そうでなければ、
falseを返す。
6. 統合
6.1. 権限ポリシー
この仕様は、公開されるメソッドを使用できるかどうかを制御する機能を定義する。
その公開元は serial
属性であり、この属性が属するのは Navigator
オブジェクトである。
この機能の機能名は "serial" である。
この機能の 既定の許可リスト は、
'self' である。
7. セキュリティに関する考慮事項
この節は非規範的である。
この API は、次と同様のセキュリティ上のリスクをもたらす:[WEBBLUETOOTH] および [WEBUSB]。したがって、 それらから得られた教訓は、ここにも適用できる。主な脅威は次のとおりである:
-
悪意のあるサイトが、ユーザーをだましてアクセスを許可させた デバイスの、本来の機能を悪意のある目的に使用する。たとえば、 ロボットに物理的な損害を引き起こさせる。
-
悪意のあるサイトが、ユーザーをだましてアクセスを許可させた デバイスに独自のファームウェアをインストールし、 デバイスの本来の機能を悪意のある目的で変更したり、接続先のホストを 攻撃したりする。たとえば、他の ホストソフトウェアがデバイスと通信するときに、バッファーオーバーフローを引き起こす。
-
信頼されたサイトに悪意のあるコードが注入され、そのサイトにアクセスが許可されている デバイスに対して、上記のいずれかが行われる。たとえば、オンラインのファームウェア更新 ユーティリティが侵害され、悪意のあるファームウェアを配布する。
-
攻撃者がユーザーを説得して、悪意のあるデバイスを自身の システムに接続させる。そのデバイスが悪意のあるサイトや侵害されたサイトと連携し、 攻撃者との通信に使用するウェブベースのチャネルを作成する。
これらすべての攻撃に対する主な緩和策は、requestPort()
パターンである。これはユーザー操作を必要とし、一度にアクセスを許可できるのは、
単一のデバイスだけである。これにより、閲覧しただけで起こる攻撃を防ぐ。サイトは、
接続されたすべてのデバイスを列挙し、脆弱なデバイスが存在するかを判定することができず、
代わりに、アクセスを求めていることをユーザーに明示的に知らせる必要があるためである。
実装は、サイトが現在デバイスと通信していることを視覚的に示す表示や、
その権限をいつでも取り消すための操作手段を
提供してもよい。
この仕様は、サイトが 安全なコンテキスト から提供されることを要求する。 ネットワーク上の攻撃者によって悪意のあるコードが注入されるのを防ぐためである。 これにより、権限を判断するときにユーザーに表示されるサイトの識別情報が、 正確であることを保証する。また、この仕様は、トップレベル文書が、 [PERMISSIONS-POLICY] を通じて明示的に許可することを要求する。別オリジンの iframe が API を使用することを認める前にである。[CSP3] と組み合わせると、これらの仕組みは、 悪意のあるコードの注入攻撃に対する保護を提供する。
残る懸念は、接続されたデバイスが悪用されることである。その手段は、 ユーザーを説得して、悪意のあるサイトに デバイスへのアクセスを許可させるフィッシング攻撃である。これらの攻撃は、設計どおりのデバイスの機能を悪用したり、 悪意のあるファームウェアをデバイスにインストールし、それを使って ホストコンピューターを攻撃したりするために使用され得る。ホストソフトウェアが攻撃に対して脆弱なのは、 接続されたデバイスからの入力を適切に検証しないためかもしれない。この 分野のセキュリティ研究は、ソフトウェアベンダーに対し、接続されたデバイスを、 信頼できないものとして扱うことを促してきた。
この種類の攻撃を完全に防ぐ仕組みは存在しない。なぜなら、 ページからデバイスに送信されるデータは、意味を解釈できないバイト列だからである。 特定の種類のデータの送信を阻止しようとしても、回避策が講じられる可能性が高い。 それでもなお、その種類のデータを送信したい デバイスメーカーによってである。
ユーザーエージェントは、デバイスへのアクセスを制御する追加の仕組みを実装できる:
-
サイトが次を呼び出すことを禁止する設定:
requestPort()。 ただし、 明示的な許可リストに追加されている場合を除く。システム管理者は、そのような設定を管理対象の機器全体に適用できる。 そのために企業向けのポリシー制御を使用する。このような制御により、管理者は、 選択されたサイトに特定のデバイスへのアクセスを自動的に許可し、 その他へのアクセスは許可しないようにできる。
-
悪用可能であることが知られているハードウェアのデバイス ID のリストを、 ユーザーエージェントとともに配布できる。リストにあるデバイスへの接続は、 ブロックされる。実装は、自動更新や実験の 管理システムを使用して、このリストの更新を随時配布し、 進行中の攻撃を阻止できる。
次の実装:[WEBBLUETOOTH] および [WEBUSB] では、 これらの緩和策が試されてきたが、その有効性には限界がある。第一に、 デバイスが悪用可能かどうかを定義することは難しい。たとえば、この API は、 サイトがマイクロコントローラー開発ボードにファームウェアをアップロードすることを可能にする。これは、 この API の重要なユースケースである。これらのデバイスは、教育分野や 愛好家向けの市場で広く使われているためである。これらのボードはファームウェアの署名検証を実装しておらず、 そのため、容易に悪意のあるデバイスに変えられる。これらのボードは明らかに 悪用可能だが、ブロックすべきではない。
さらに、脆弱なデバイスのリストを維持する方法は、USB や Bluetooth ではうまく機能する。これらのプロトコルは、デバイスの メタデータを収集する帯域外の仕組みを定義しているためである。そのため、そのようなデバイスのメーカーとモデルは容易に識別できる。 ホストに対して仮想シリアルポートとして提示される場合でも同様である。しかし、 汎用の USB または Bluetooth からシリアルへの変換アダプターや、「本物の」 シリアルポートを備え、DB-25、DE-9、RJ-45 コネクターを使用するシステムもある。これらには、 接続先のデバイスを識別するために読み取れるメタデータが存在しない。 そのため、これらのポートに接続されたデバイスへのアクセスをブロックすることはできない。
8. プライバシーに関する考慮事項
この節は非規範的である。
シリアルポートとシリアルデバイスには、2 種類の機微情報が含まれる。 ポートが USB または Bluetooth デバイスである場合、ベンダー ID や 製品 ID(メーカーとモデルを識別する)のような識別子に加え、シリアル番号や MAC アドレスが存在する。シリアルデバイス自体にも独自の識別子が存在する場合があり、 シリアルポート経由で送信されるコマンドを通じて取得できる。また、デバイスは、 識別につながる場合もつながらない場合もある、他の私的な情報を保存している可能性がある。
デバイスの権限を管理するために、実装はおそらく、デバイスの 識別子、たとえば USB ベンダー ID、製品 ID、シリアル番号を、ユーザーの 設定ファイルに保存する。これらは、ユーザーが サイトにアクセスを許可したデバイスの、安定した識別子として使用される。これらはサイトと直接共有されず、 権限が取り消されたとき、またはサイトデータ全般が消去されたときに消去される。
ページは、アクセスを許可された後にデバイスへ送信できるコマンドを通じて、 デバイスに保存されている他の機微情報にも、 アクセスできる場合がある。§ 7 セキュリティに関する考慮事項 で述べた理由により、これは非現実的であり、 また、 ページによるこの情報へのアクセスを防ごうとすることは望ましくない。
実装は、サイトがアクセスできるデバイスについて、ユーザーに完全な制御を提供し、
ユーザー操作なしにデバイスへのアクセスを許可すべきではない。これが、
次の requestPort()
メソッドの意図である。これにより、サイトが、
接続されたすべてのデバイスをひそかに列挙し、データを収集することを防ぐ。これは、
ファイル選択 UI と似ている。サイトはファイルシステム全体を知ることはなく、
ユーザーが選択したファイルやディレクトリーだけを知る。実装は、
サイトがこれらの権限を使用しているときに、表示アイコンを用いてユーザーに通知できる。
そのアイコンは、タブやアドレスバーに表示される。
「プライベート」または「シークレット」のブラウジングモードを提供する実装は、 通常のユーザープロファイルの権限が、そのような セッションに引き継がれず、そのセッションで付与された権限が、 セッションの終了時に永続化されないことを保証すべきである。実装は、アクセスを許可するときにユーザーに警告してもよい。 そのようなセッション内でデバイスへのアクセスを許可することは、識別情報を手動で入力することと似ており、 デバイスの識別子や、通信によって得られる他の固有の特性が、 前述のとおり、デバイスとの通信を通じて取得され、識別に使用され得るためである。 セッションをまたいでユーザーを識別するために。
ユーザーは、この API によって付与される能力に驚く可能性がある。 デバイスへのアクセスを許可することが、従来の ウェブのセキュリティモデルの分離境界を、どのように崩すかを理解していない場合である。セキュリティ UI とドキュメントは、 サイトにデバイスへのアクセスを許可することで、サイトが完全な 制御を得る可能性があることを説明すべきである。その対象は、デバイスと、その中に含まれるすべてのデータである。
謝辞
以下の方々に感謝する: Anatol Ulrich, Chris Mumford, Clément Menard, Domenic Denicola, Dominique Hazael-Massieux, Florian Loitsch, Florian Scholz, Francis Gulotta, François Beaufort, Jack Hsieh, Keavon Chambers, Kenneth Rohde Christiansen, Marcos Cáceres, Matt Reynolds, Jesse Melhuish, Michael Kohler, Ms2ger, Rick Waldron, Sankha Narayan Guria, Simon Pieters, Suz Hinton, Travis Leithead, および Vincent Scheib その素晴らしい働きに対して。
この標準の執筆者は Reilly Grant である。 (Google, reillyg@google.com)。
知的財産権
著作権 © WHATWG (Apple, Google, Mozilla, Microsoft)。この作品は、クリエイティブ・コモンズ 表示 4.0 国際ライセンス の下で提供される。その一部がソースコードに組み込まれる場合、 ソースコード内のその部分には、代わりに BSD 3 条項ライセンス が適用される。