読み取り可能なバイトストリームの使用 - Web API

読み取り可能なバイトストリームの使用
==================

 読み取り可能なバイトストリームとは、読み取り可能なストリームのうち `type: "bytes"` の基盤バイトソースがあり、（ストリーム内部のキューをバイパスして）基盤ソースからコンシューマーへの効率的なゼロコピー移譲に対応しています。
 これは、データが任意の大きさの、潜在的にとても大きなチャンクで供給されたりリクエストされたりする可能性があり、したがってコピーを避けることが効率を向上させる可能性がある使用される用途を意図しています。

この記事では、読み取り可能なバイトストリームと通常の「既定」ストリームとの比較、およびそれらをどのように作成し、使用するかについて説明します。

**メモ:** 読み取り可能なバイトストリームは、「通常の」読み取り可能なストリームとほとんど同じであり、概念もほとんどすべて同じです。
 この記事は、あなたがすでにそれらの概念を理解していることを想定しており、（もしそうであれば）表面的にしか取り上げません。
 関連する概念に慣れていない方は、先に読み取り可能なストリームの使用、ストリームの概念と使用法の概要、ストリーム API の概要 を読んでください。

概要
--

 読み取り可能なストリームは、ファイルやソケットなどの基盤となるソースから、リーダー、変換ストリーム、書き込み可能ストリームなどのコンシューマーにデータをストリーミングするための一貫したインターフェイスを提供します。
 通常の読み取り可能なストリームでは、基盤となるソースからのデータは常に内部キューを通ってコンシューマーに渡されます。
 読み取り可能なバイトストリームは、内部キューが空の場合、基盤となるソースがコンシューマーに直接書き込める（効率的なゼロコピー移譲）という点で異なります。

 読み取り可能なバイトストリームは `type: "bytes"` を `underlyingSource` オブジェクトに指定したものを、 `ReadableStream()` コンストラクターに渡すことで作成されます。
 この値を設定すると、ストリームは `ReadableByteStreamController` で作成され、`start(controller)` と `pull(controller)` コールバック関数を呼び出す際に、このオブジェクトが基盤内のソースに渡されます。

`ReadableByteStreamController` と既定のコントローラー (`ReadableStreamDefaultController`) との主な違いは、追加のプロパティ `ReadableByteStreamController.byobRequest` （`ReadableStreamBYOBRequest` 型）を持っていることです。
 これは、基盤ソースからのゼロコピー移譲として行われる、コンシューマーによる待機中の読み取りリクエストを表します。
 保留中のリクエストがない場合、このプロパティは `null` になります。

`byobRequest` は、読み取り可能なバイトストリームに対して読み取りリクエストが行われ、ストリームの内部キューにデータがない場合にのみ利用できます（データがある場合は、それらのキューからリクエストが満たされます）。

 データを転送する必要があるバイト基盤は `byobRequest` プロパティを調べ、利用できる場合はそれを使用してデータを転送する必要があります。
 プロパティが `null` の場合、受信データは代わりに `ReadableByteStreamController.enqueue()` を使用してストリーム内部のキューに追加する必要があります（これは、「既定の」 ストリームを使用している場合にデータを転送する唯一の方法です）。

`ReadableStreamBYOBRequest` は `view` プロパティを持っています。これは移譲のために割り当てられたバッファーに関するビューです。
 基盤となるソースからのデータはこのプロパティに書き込まれ、次に基盤となるソースは、書き込まれたバイト数を示す `respond()` を呼び出さなければなりません。
 これはデータが移譲されるべきことを指示し、コンシューマーによる待機中の読み取りリクエストは解決されます。
 `respond()` を呼び出した後、 `view` には書き込みができなくなります。

 追加のメソッド `ReadableStreamBYOBRequest.respondWithNewView()` もあります。このメソッドに、基盤となるソースは、移譲するデータを格納した「新しい」ビューを渡すことができます。
 この新しいビューは、元と同じメモリーバッファー上で、同じ開始オフセットからでなければなりません。
 このメソッドは、例えば、バイト基盤が最初にワーカースレッドにビューを移譲し、それを取得してから `byobRequest` に応答する必要がある場合に使用します。
 ほとんどの場合、このメソッドは必要ありません。

読み取り可能なバイトストリームは通常、 `ReadableStreamBYOBReader` を使用して読み取られます。これは、ストリーム上で `ReadableStream.getReader()` を呼び出し、 options 引数に `mode: "byob"` を指定します。

 読み取り可能なバイトストリームは、既定のリーダー (`ReadableStreamDefaultReader`) を使用して読み取ることもできますが、この場合 `byobRequest` オブジェクトは、ストリームに対して自動バッファー割り当てが有効になっている（`autoAllocateChunkSize` がストリームの `underlyingSource` に設定されている）場合にのみ作成されます。
 この用途では `autoAllocateChunkSize` で示されるサイズがバッファーサイズとして使用されることに注意してください。
 このプロパティを指定しなかった場合、既定値ではリーダーは「動作」しますが、基盤になるソースに `byobRequest` が提供されることはなく、すべてのデータはストリームの内部キューを通じて転送されます。

上記の異なる点の他にも、バイトストリームのコントローラーと基盤は、既定のストリームのコントローラーととてもよく似ており、ほぼ同じ方法で使用します。

例
-

### バイトリーダーの基盤のプッシュソース

このライブ例では、プッシュ基礎バイトソースで読み取り可能なバイトストリームを作成し、バイトリーダーを使用して読み取る方法を示します。

 プル型バイトソースの場合とは異なり、データは任意の時点で到着します。
 そのため、基礎となるソースは `controller.byobRequest` を使用して、受信データが存在する場合はそれを移譲し、そうでない場合はストリーム内部のキューにデータを入れる必要があります。
 さらに、データはいつでも到着する可能性があるため、監視動作は `underlyingSource.start()` コールバック関数で設定します。

 この例は、ストリーム仕様のプッシュバイトソースの例から大きな影響を受けています。
 この例では、任意のサイズのデータを供給する「仮想ソケット」ソースを使用しています。
 元の基盤がストリームにデータを送信するために移譲と待ち行列の両方を使用することができるように、リーダーは様々な点で意図的に遅延しています。
 背圧対応は実演されていません。

**メモ:** 既定のリーダーでは、基盤バイトソースも使用することができます。
 自動バッファー割り当てが有効になっている場合、コントローラーは、リーダーからの未処理のリクエストがあり、ストリームの内部キューが空である場合に、ゼロコピー移譲用に修正されたサイズのバッファーを供給します。
 自動バッファー割り当てが有効になっていない場合、バイトストリームのデータはすべて常に待ち行列に入れられます。
 これは、"pull: underlying byte source "の例で表示させた動作に似ています。

#### 模擬基盤ソケットソース

模擬基盤には 3 つの重要なメソッドがあります。

* `select2()` は未処理のリクエストを表します。
 データが利用できると解決するプロミスを返します。
* `readInto()` はソケットから与えられたバッファーにデータを読み込み、データをクリアします。
* `close()` はソケットを閉じます。

 実装するためにとても単純化しています。
 下記で示すように、 `select2()` はタイムアウト時にランダムなサイズのランダムなデータのバッファー を作成します。
 作成したデータはバッファーに読み込まれ、`readInto()` でクリアされます。

```js
class MockHypotheticalSocket {
  constructor() {
    this.max_data = 800; // total amount of data to stream from "socket"
    this.max_per_read = 100; // max data per read
    this.min_per_read = 40; // min data per read
    this.data_read = 0; // total data read so far (capped is maxdata)
    this.socketdata = null;
  }

  // Method returning promise when this socket is readable.
  select2() {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (this.data_read >= this.max_data) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Emulate slow read of data
      setTimeout(() => {
        const numberBytesReceived = this.getNumberRandomBytesSocket();
        this.data_read += numberBytesReceived;
        this.socketdata = this.randomByteArray(numberBytesReceived);
        resultobj["bytesRead"] = numberBytesReceived;
        resolve(resultobj);
      }, 500);
    });
  }

  /\* Read data into specified buffer offset \*/
  readInto(buffer, offset, length) {
    let length_data = 0;
    if (this.socketdata) {
      length_data = this.socketdata.length;
      const myview = new Uint8Array(buffer, offset, length);
      // Write the length of data specified into buffer
      // Code assumes buffer always bigger than incoming data
      for (let i = 0; i < length_data; i++) {
        myview[i] = this.socketdata[i];
      }
      this.socketdata = null; // Clear "socket" data after reading
    }
    return length_data;
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random number bytes in this call of socket
  getNumberRandomBytesSocket() {
    // Capped to remaining data and the max min return-per-read range
    const remaining_data = this.max_data - this.data_read;
    const numberBytesReceived =
      remaining_data < this.min_per_read
        ? remaining_data
        : this.getRandomIntInclusive(
            this.min_per_read,
            Math.min(this.max_per_read, remaining_data),
          );
    return numberBytesReceived;
  }

  // Return random number between two values
  getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() \* (max - min + 1) + min);
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  /\* Return random Uint8Array of bytes \*/
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### 読み取り可能なソケットプッシュバイトストリームの作成

以下のコードは、読み取り可能なソケット「プッシュ」バイトストリームを定義する方法を示しています。

`underlyingSource` オブジェクトの定義は `ReadableStream()` コンストラクターの最初の引数として渡されます。
 これを読み取り可能な「バイト」ストリームにするために、 `type: "bytes"` をオブジェクトのプロパティとして指定します。
 これにより、ストリームは確実に `ReadableByteStreamController` に（既定のコントローラー (`ReadableStreamDefaultController` の代わりに）渡されるようになります。

 データはコンシューマーが処理する準備ができる前にソケットに到着する可能性があるため、基盤の読み取りに関するすべての設定は `start()` コールバックメソッドで行います（データの処理を始めるにはプルを待ちません）。
 実装は "socket" を開き、データをリクエストするために `select2()` を呼び出します。
 返されたプロミスが解決されると、コードは `controller.byobRequest` が存在するか (`null` でないか) を調べ、存在する場合は `socket.readInto()` を呼び出してデータをリクエストにコピーして移譲します。
 もし `byobRequest` が存在しなければ、ゼロコピーで転送できる消費ストリームからの未処理のリクエストはありません。
 この場合、 `controller.enqueue()` を使用してストリーム内部のキューにデータをコピーします。

 さらにデータを要求する `select2()` リクエストは、データがないリクエストを返すまで再投稿されます。
 この点で、コントローラーはストリームを閉じるために使用されます。

```js
const stream = makeSocketStream("dummy host", "dummy port");

const DEFAULT\_CHUNK\_SIZE = 400;

function makeSocketStream(host, port) {
  const socket = new MockHypotheticalSocket();

  return new ReadableStream({
    type: "bytes",

    start(controller) {
      readRepeatedly().catch((e) => controller.error(e));
      function readRepeatedly() {
        return socket.select2().then(() => {
          // Since the socket can become readable even when there's
          // no pending BYOB requests, we need to handle both cases.
          let bytesRead;
          if (controller.byobRequest) {
            const v = controller.byobRequest.view;
            bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
            if (bytesRead === 0) {
              controller.close();
            }
            controller.byobRequest.respond(bytesRead);
            logSource(`byobRequest with ${bytesRead} bytes`);
          } else {
            const buffer = new ArrayBuffer(DEFAULT\_CHUNK\_SIZE);
            bytesRead = socket.readInto(buffer, 0, DEFAULT\_CHUNK\_SIZE);
            if (bytesRead === 0) {
              controller.close();
            } else {
              controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
            }
            logSource(`enqueue() ${bytesRead} bytes (no byobRequest)`);
          }

          if (bytesRead === 0) {
            return;
            // no more bytes in source
          }
          return readRepeatedly();
        });
      }
    },

    cancel() {
      socket.close();
      logSource(`cancel(): socket closed`);
    },
  });
}
```

`readRepeatedly()` はプロミスを返すことに注意してください。これを使用して、読み込み処理の設定や処理で発生したエラーをキャッチします。
 エラーは上で示したようにコントローラに渡されます（`readRepeatedly().catch((e) => controller.error(e));` を参照）。

最後に `cancel()` メソッドが指定され、基盤となっているソースを閉じます。`pull()` コールバックは不要なので実装しません。

#### プッシュバイトストリームの使用

 以下のコードはソケットのバイトストリーム用に `ReadableStreamBYOBReader` を作成し、それを使用してデータをバッファーに読み込みます。
 なお、 `processText()` が再帰的に呼び出され、バッファーが一杯になるまでデータを読み込まれることに注意してください。
 基盤がもうデータがないと指示すると、`reader.read()`には `done` が設定され、読み込み処理を完了します。

 このコードは、上記のバイトリーダー付きの基盤プルソースの例とほとんど同じです。
 唯一の違いは、リーダーが読み取りを遅くするコードを含んでいることです。

```js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(4000);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  while (offset < buffer.byteLength) {
    // read() returns a promise that resolves when a value has been received
    reader
      .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
      .then(async function processText({ done, value }) {
        // Result objects contain two properties:
        // done - true if the stream has already given all its data.
        // value - some data. Always undefined when done is true.

        if (done) {
          logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
          return;
        }

        buffer = value.buffer;
        offset += value.byteLength;
        bytesReceived += value.byteLength;

        //logConsumer(`Read ${bytesReceived} bytes: ${value}`);
        logConsumer(`Read ${bytesReceived} bytes`);
        result += value;

        // Add delay to emulate when data can't be read and data is enqueued
        if (bytesReceived > 300 && bytesReceived < 600) {
          logConsumer(`Delaying read to emulate slow stream reading`);
          const delay = (ms) =>
            new Promise((resolve) => setTimeout(resolve, ms));
          await delay(1000);
        }

        // Read some more, and call this function again
        return reader
          .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
          .then(processText);
      });
  }
}
```

#### リーダーと使用したストリームの取り消し

`ReadableStreamBYOBReader.cancel()` を使用してストリームを取り消すことができます。
 この例では、"user choice" の理由でボタンがクリックされた場合にメソッドを呼び出しています（ボタンの他の HTML やコードは表示していません）。
 取り消される可能性がある処理が完了したときもログ出力しています。

```js
button.addEventListener("click", () => {
  reader
    .cancel("user choice")
    .then(() => logConsumer("reader.cancel complete"));
});
```

`ReadableStreamBYOBReader.releaseLock()` を使用すると、ストリームを取り消すことなくリーダーを解放することができます。
 ただし、未処理の読み込みリクエストは即座に拒否されることに注意してください。
 残りのチャンクを読むために、後で新しいリーダーを取得することができます。

#### ストリームの close/error を監視

`ReadableStreamBYOBReader.closed` プロパティは、ストリームが閉じられたときに解決し、エラーがある場合は拒否されたプロミスを返します。
 このケースではエラーは期待されませんが、続くコードでは完了ケースをログ出力する必要があります。

```js
reader.closed
  .then(() => {
    logConsumer("ReadableStreamBYOBReader.closed: resolved");
  })
  .catch(() => {
    logConsumer("ReadableStreamBYOBReader.closed: rejected:");
  });
```

#### 結果

 基盤となるプッシュソース（左）とコンシューマー（右）からのログ出力を下記に示します。
 中央の期間は、データがゼロコピー処理として移譲されるのではなく、エンキューされる期間です。

### バイトリーダー付きの基盤プルソース

このライブ表示例は、ファイルなどの「プル」バイト基盤からデータを読み取り、ストリームによって `ReadableStreamBYOBReader` にゼロコピー移譲する方法を示しています。

#### 模擬基盤ファイルソース

 基盤となるプルソースには、以下のクラスを使用して、nodejs の `FileHandle`、特に `read()` メソッドを（とても表面的に）模倣します。
 このクラスは、ファイルを表すランダムなデータを生成します。
 `read()` メソッドはこのデータを指定された位置から提供されたバッファーに読み込みます。
 `close()` メソッドは何かするわけではありません。ストリームのコンストラクターを定義する際に、ソースを閉じる場所を示すために指定されただけです。

**メモ:** この同じクラスは、すべての「プルソース」の例に使用しています。
 ここで表示させているのは、あくまで情報です（模擬であることがわかるように）。

```js
class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 1300; // "file size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, length);
      // Write the length of data specified
      for (let i = 0; i < length; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i;
        if (position + i >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultobj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### 読み取り可能なファイルバイトストリームの作成

以下のコードは、読み取り可能なファイルのバイトストリームを定義する方法を示しています。

 前の例と同様に、 `underlyingSource` オブジェクト定義は `ReadableStream()` コンストラクターの最初の引数として渡されます。
 これを読み取り可能な「バイト」ストリームにするために、オブジェクトのプロパティに `type: "bytes"` をオブジェクトのプロパティとして指定します。
 これにより、ストリームに `ReadableByteStreamController` が渡されることを確実にします。

`start()` 関数は単にファイルハンドルを開き、これは `cancel()` コールバックで閉じられます。
 `cancel()` は `ReadableStream.cancel()` または `ReadableStreamDefaultController.close()` が呼び出された場合にリソースをクリーンアップするために指定されています。

 最も興味深いコードは `pull()` コールバックです。
 これはファイルから待機中の読み込みリクエストにデータをコピーし（`ReadableByteStreamController.byobRequest`）、 `respond()` を呼び出してバッファー内のデータ量を示し、それを転送します。
 ファイルから 0 バイトが転送された場合、すべてコピーされたことがわかり、コントローラーの `close()` を呼び出します。

```js
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.offset,
        theView.length,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}
```

#### バイトストリームの使用

 以下のコードはファイルのバイトストリーム用に `ReadableStreamBYOBReader` を作成し、それを使用してデータをバッファーに読み込んでいます。
 なお、 `processText()` が再帰的に呼び出され、バッファーが一杯になるまでデータを読み込みます。
 基盤となるソースがこれ以上データがないことを指示すると、 `reader.read()` の `done` が true に設定され、読み込み処理を完了します。

```js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(4000);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  while (offset < buffer.byteLength) {
    // read() returns a promise that resolves when a value has been received
    reader
      .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
      .then(function processText({ done, value }) {
        // Result objects contain two properties:
        // done - true if the stream has already given all its data.
        // value - some data. Always undefined when done is true.

        if (done) {
          logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
          return;
        }

        buffer = value.buffer;
        offset += value.byteLength;
        bytesReceived += value.byteLength;

        logConsumer(`Read ${bytesReceived} bytes: ${value}`);
        result += value;

        // Read some more, and call this function again
        return reader
          .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
          .then(processText);
      });
  }
}
```

最後に、ボタンがクリックされた場合にストリームを取り消される可能性のあるハンドラーを追加します（HTML とボタンのためのコードは示しません）。

```js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### 結果

 基盤となるプルソース（左）とコンシューマー（右）からのログ出力を下記に示します。
 特筆すべきことは次の通りです。

* `start()` 関数には `ReadableByteStreamController` が渡されます。
* リーダに渡されるバッファーが「ファイル」全体を包含するのに十分な大きさであるため、 1 回の処理でファイル全体が移譲されます。

### 既定のリーダー付きの基盤プルソース

 このライブ例では、既定のリーダー (`ReadableStreamDefaultReader`) を使用して、同じデータをゼロコピー移譲で読み取る方法を示します。
 この例では、先の例と同じ模擬基盤ファイルソースを使用しています。
```

class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 1300; // "file size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  /\* Read data from "file" at position/length into specified buffer offset \*/
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        // out of data
        resolve(resultobj);
        return;
      }

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, length);
      // Write the length of data specified
      for (let i = 0; i < length; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i;
        if (position + i >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => resolve(resultobj), 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### 自動バッファー割り当てによる読み取り可能なファイルバイトストリームの作成

唯一の基盤の違いは、`autoAllocateChunkSize` を指定しなければならないことと、コンシューマーから提供されるサイズではなく、`controller.byobRequest` のビューバッファサイズとして使用することです。

```js
const DEFAULT\_CHUNK\_SIZE = 200;
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.offset,
        theView.length,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
    autoAllocateChunkSize: DEFAULT\_CHUNK\_SIZE, // Only relevant if using a default reader
  });
}
```

#### 既定のリーダーでのバイトストリームの利用

 以下のコードでは、モードを指定せずに `stream.getReader();` を呼び出してファイルバイトストリーム用の `ReadableStreamDefaultReader` を作成し、それを使用してデータをバッファに読み込んでいます。
 コードの処理は、バッファがコンシューマではなくストリームから供給されることを除いて、前回の例と同じです。

```js
const reader = stream.getReader();
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
      return;
    }

    bytesReceived += value.length;
    logConsumer(`Read ${bytesReceived} bytes so far. Current bytes = ${value}`);
    result += value;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}
```

最後に、ボタンがクリックされた場合にストリームを取り消される可能性のあるハンドラーを追加します（ボタンのための他の HTML とコードは示しません）。

```js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### 結果

基盤のバイトプルソース（左）とコンシューマ（右）からのログ出力を下記に示します。

 なお、チャンクは基盤バイトソースで指定されているように、 200 バイト長になります。
 これらは、ゼロコピー移譲として行われます。

### 既定のリーダーと割り当てなしのプルソース基盤

完全を期すために、自動バッファー割り当てに対応していないバイトソースの既定のリーダーも使用することができます。
```

class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 1300; // "file size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, length);
      // Write the length of data specified
      for (let i = 0; i < length; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i;
        if (position + i >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultobj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

 しかし、この場合、コントローラーは `byobRequest` を基盤に書き込むことはできません。
 その代わりに、基盤ソースはデータをエンキューしなければなりません。
 なお、このケースに対応するために、 `pull()` で `byobRequest` が存在するかどうかを調べる必要があります。

```js
const stream = makeReadableByteFileStream("dummy file.txt");
const DEFAULT\_CHUNK\_SIZE = 300;

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      if (controller.byobRequest) {
        const theView = controller.byobRequest.view;
        const { bytesRead, buffer } = await fileHandle.read(
          theView.buffer,
          theView.offset,
          theView.length,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.byobRequest.respond(0);
          logSource(
            `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.byobRequest.respond(bytesRead);
          logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
        }
      } else {
        // No BYOBRequest so enqueue data to stream
        // NOTE, this branch would only execute for a default reader if autoAllocateChunkSize is not defined.
        const mynewBuffer = new Uint8Array(DEFAULT\_CHUNK\_SIZE);
        const { bytesRead, buffer } = await fileHandle.read(
          mynewBuffer.buffer,
          mynewBuffer.offset,
          mynewBuffer.length,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.enqueue(mynewBuffer);
          logSource(
            `pull() with no byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.enqueue(mynewBuffer);
          logSource(`pull() with no byobRequest. enqueue() ${bytesRead} bytes`);
        }
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}
```

```
const reader = stream.getReader();

readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
      return;
    }

    bytesReceived += value.length;
    logConsumer(`Read ${bytesReceived} bytes so far. Current bytes = ${value}`);
    result += value;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}
```

```
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### 結果

 基礎となるプルソース（左）とコンシューマ（右）からのログ出力を下記に示します。
 基盤となるソースの側は、データがゼロバイト移譲ではなく、エンキューされたことを示していることに注意してください。

関連情報
----

* ストリーム API の概念
* ストリームの概念と使用方法の概要
* 読み取り可能なストリームの使用


読み取り可能なストリームの使用 - Web API

読み取り可能なストリームの使用
===============

JavaScript 開発者として、ネットワークを介して受信したデータのストリームをチャンクごとにプログラムで読み取り、操作することはとても便利です。しかし、ストリーム API の読み取り可能なストリームの機能はどのように使用するのでしょうか。この記事では、その基本について説明します。

**メモ:** この記事は、読者が読み取り可能なストリームの用途を理解し、高レベルの概念を理解していることを前提としています。 そうでない場合は、まずストリームの概念と使用方法の概要とストリーム API の概念の記事を読んでから、戻ってくることをお勧めします。

**メモ:** 書き込み可能なストリームに関する情報を探しているのであれば、書き込み可能なストリームの使用を見てみてください。

いくつかの例を見つける
-----------

この記事では、dom-examples/streams リポジトリーから取得したさまざまな例を見ていきます。 そこには完全なソースコードと例へのリンクがあります。

フェッチをストリームとして消費する
-----------------

Fetch API は、ネットワークを通じてリソースをフェッチすることができ、 XHR に代わる現代的な方法を提供します。これには多くの利点があり、実に素晴らしいのは、ブラウザーが最近、フェッチしたレスポンスを読み取り可能なストリームとして消費する機能を追加したことです。

`Request.body` と `Response.body` プロパティが利用できますが、これらは本体の中身を読み取り可能なストリームとして取得できるゲッターです。

単純なストリームポンプ（Simple stream pump）の例が示しているように（ライブも参照）、それを公開することは、次のように応答の `body` プロパティにアクセスするだけのことです。

```js
// 元の画像を読み取る
fetch("./tortoise.png")
  // その body を ReadableStream として取得
  .then((response) => response.body);
```

これにより、 `ReadableStream` オブジェクトが提供されます。

### リーダーの取り付け

ストリームする本体が得られました。ストリームを読むには、リーダーを取り付ける必要があります。 これは、 `ReadableStream.getReader()` メソッドを使用して行われます。

```js
// 元の画像をフェッチ
fetch("./tortoise.png")
  // その body を ReadableStream として取得
  .then((response) => response.body)
  .then((body) => {
    const reader = body.getReader();
    // …
  });
```

このメソッドを呼び出すと、リーダーが作成され、ストリームにロックされます。このリーダーが解放されるまで、他のリーダーはこのストリームを読み取ることができません。 解放するには、例えば `ReadableStreamDefaultReader.releaseLock()` を呼び出します。

また、`response.body` は同期的であり、プロミスを必要としないため、前の例を 1 ステップ減らすことができることに注意してください。

```js
// 元の画像をフェッチ
fetch("./tortoise.png")
  // その body を ReadableStream として取得
  .then((response) => {
    const reader = response.body.getReader();
    // …
  });
```

### ストリームを読み取る

リーダーを取り付けたら、`ReadableStreamDefaultReader.read()` メソッドを使用してストリームからデータチャンクを読み取ることができます。 これにより、ストリームから 1 つチャンクを読み取って、好きなことを実行できます。 例えば、単純なストリームポンプの例では、新しいカスタム `ReadableStream` で各チャンクをキューに入れ（これについては次のセクションで詳しく説明します）、そこから新しい `Response` を作成し、`Blob` として使用し、 `URL.createObjectURL()` を使用してその blob からオブジェクト URL を取得し、それを `<img>` 要素で画面に表示して、元のフェッチした画像のコピーを効果的に作成します。

```js
// 元の画像を取得
fetch("./tortoise.png")
  // 本体を ReadableStream として取得
  .then((response) => {
    const reader = response.body.getReader();
    return new ReadableStream({
      start(controller) {
        return pump();
        function pump() {
          return reader.read().then(({ done, value }) => {
            // データを消費する必要がなくなったら、ストリームを閉じる
            if (done) {
              controller.close();
              return;
            }
            // 次のデータチャンクを対象のストリームのキューに入れる
            controller.enqueue(value);
            return pump();
          });
        }
      },
    });
  })
  // 新しいレスポンスをストリームの外に作成
  .then((stream) => new Response(stream))
  // レスポンスのオブジェクト URL を作成
  .then((response) => response.blob())
  .then((blob) => URL.createObjectURL(blob))
  // Update image
  .then((url) => console.log((image.src = url)))
  .catch((err) => console.error(err));
```

`read()` の使用方法を詳しく見てみましょう。 上記の `pump()` 関数では、最初に結果オブジェクトを含むプロミスを返す `read()` を呼び出します。 結果オブジェクトには、次のように読み取りの結果が `{ done, value }` の形式で含まれています。

```js
reader.read().then(({ done, value }) => {
  /\* … \*/
});
```

結果は、次の 3 つの異なる形式のいずれかになります。

* チャンクが読み取り可能であれば、 `{ value: theChunk, done: false }` の形式のオブジェクトでプロミスが履行されます。
* ストリームが閉じられると、`{ value: undefined, done: true }` という形式のオブジェクトでプロミスが履行されます。
* ストリームにエラーが発生すると、関連するエラーでプロミスが拒否されます。

次に、 `done` が `true` であるかどうかを確認します。 その場合、読み込むチャンクはもうないので（値は `undefined` です）、関数から戻り、`ReadableStreamDefaultController.close()` でカスタムストリームを閉じます。

```js
if (done) {
  controller.close();
  return;
}
```

**メモ:** `close()` は、ここで説明している元のストリームではなく、新しいカスタムストリームの一部です。 次の節でカスタムストリームについて詳しく説明します。

`done` が `true` でない場合、読み込んだ新しいチャンク（結果オブジェクトの `value` プロパティに含まれる）を処理してから、再度 `pump()` 関数を呼び出して次のチャンクを読み込みます。

```js
// 次のデータチャンクを対象のストリームのキューに入れます
controller.enqueue(value);
return pump();
```

これは、ストリームのリーダーを使用するときに示される次のような標準パターンです。

1. ストリームの読み取りを開始する関数を作成します。
2. 読み込むストリームがこれ以上ない場合は、関数から戻ります。
3. さらに読み込むストリームがある場合は、現在のチャンクを処理してから、関数を再度実行します。
4. 読み取るストリームがなくなるまで関数を再帰的に実行し続けます。 この場合、手順 2 が実行されます。

実際に「ポンプ」を実行するコードをすべて除去されると、コードは次のように一般化されるかもしれません。

```js
fetch("http://example.com/somefile.txt")
  // 本体を ReadableStream として受け取る
  .then((response) => {
    const reader = response.body.getReader();
    // read() は値を受け取ったときに解決するプロミスを返す
    reader.read().then(function pump({ done, value }) {
      if (done) {
        // データの最後のチャンクで何かを行い、リーダーを終了する
        return;
      }
      // そうでない場合は、ここで現在のチャンクを処理する

      // さらに読み進めて、この関数をもう一度呼び出す
      return reader.read().then(pump);
    });
  })
  .catch((err) => console.error(err));
```

**メモ:** この関数は `pump()` が自分自身を呼び出すかのように見え、深い再帰につながる可能性があります。しかし、 `pump` は非同期であり、それぞれの `pump()` の呼び出しはプロミスハンドラーの終わりにあるため、実際にはプロミスハンドラーの連鎖に類似しています。

ストリームの読み取りは、プロミスではなく async/awaitを使用して書くとさらに簡単です。

```js
async function readData(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      // データの最後のチャンクで何かを行い、リーダーを終了する
      return;
    }
    // そうでない場合は、ここで現在のチャンクを処理する
  }
}
```

非同期反復処理を用いた fetch() の使用
-----------------------

`fetch()` を使用するさらに簡単な方法があります。それは、`for await...of` 構文を使用して、返された `response.body` を反復処理することです。
 これは `response.body` が `ReadableStream` を返し、それが非同期反復可能なのでうまく動作します。

この手法を用いると、前節のコード例を次のように書き換えることができます。

```js
async function readData(url) {
  const response = await fetch(url);
  for await (const chunk of response.body) {
    // Do something with each "chunk"
  }
  // Exit when done
}
```

ストリームの反復処理を中止したい場合は、`AbortController` とそれに関連付けられた `AbortSignal` を使用して `fetch()` 処理をキャンセルすることができます：

```js
const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  const response = await fetch(url, signal);
  for await (const chunk of response.body) {
    // チャンクに対して何かを行う
  }
}
```

 また、下記のコードに示すように、`break` を使用してループを終了することもできます。
 ループ内のコードは、ストリームに新しいデータを持つことができたときにのみ実行されるので、シグナルが中断されてから `break` が呼び付けられるまでに多少の遅延があることに注意してください。

```js
const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  const response = await fetch(url);
  for await (const chunk of response.body) {
    if (signal.aborted) break; // ループから脱出するだけ
    // チャンクに対して何かを行う
  }
}
```

### 非同期リーダーの例
```

// A mock push source.
// Used to simulate some random data arriving
class MockPushSource {
  // total amount of data to stream from the push source
  static #maxData = 90;
  // total data read so far (capped to maxData)
  #dataRead = 0;

  // Method returning promise when this push source is readable.
  dataRequest() {
    const result = {
      bytesRead: 8,
      data: "",
    };

    return new Promise((resolve) => {
      if (this.#dataRead >= MockPushSource.#maxData) {
        // Out of data
        result.bytesRead = 0;
        result.data = "";
        resolve(result);
        return;
      }

      // Emulate slow read of data
      setTimeout(() => {
        const numberBytesReceived = 8;
        this.#dataRead += numberBytesReceived;
        result.data = MockPushSource.#randomChars();
        resolve(result);
      }, 500);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  static #randomChars(length = 8) {
    let string = "";
    const choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices[Math.floor(Math.random() \* choices.length)];
    }
    return string;
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

```
const stream = makePushSourceStream();

function makePushSourceStream() {
  const pushSource = new MockPushSource();

  return new ReadableStream({
    start(controller) {
      readRepeatedly().catch((e) => controller.error(e));
      function readRepeatedly() {
        return pushSource.dataRequest().then((result) => {
          if (result.data.length == 0) {
            logSource(`No data from source: closing`);
            controller.close();
            return;
          }

          logSource(`Enqueue data: ${result.data}`);
          controller.enqueue(result.data);
          return readRepeatedly();
        });
      }
    },

    cancel() {
      logSource(`cancel() called on underlying source`);
      pushSource.close();
    },
  });
}
```

```
// Monkey patch fetch() so it returns a response that is a mocked stream
window.fetch = async (...args) => {
  return { body: stream };
};
```

 下記のコードは、より完全な例を示しています。
 ここでは、try/catch ブロック内で反復可能オブジェクトを使用してフェッチストリームを使用しています。
 ループの反復処理ごとに、コードは単純に受信したバイト数をログ出力して数えます。
 エラーがある場合は、その問題をログ出力します。
 `fetch()` 処理は `AbortSignal` を使用して取り消される可能性があり、その場合もエラーとしてログ出力されます。

```js
let bytes = 0;

const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  try {
    const response = await fetch(url, signal);
    for await (const chunk of response.body) {
      if (signal.aborted) throw signal.reason;
      bytes += chunk.length;
      logConsumer(`Chunk: ${chunk}. Read ${bytes} characters.`);
    }
  } catch (e) {
    if (e instanceof TypeError) {
      console.log(e);
      logConsumer("TypeError: Browser may not support async iteration");
    } else {
      logConsumer(`Error in async iterator: ${e}.`);
    }
  }
}
```

 下記のログ出力例では、コードが実行されているか、ブラウザーが `ReadableStream` の非同期反復処理に対応していないことを表示しています。
 正しい辺には取得したチャンクが表示されます。フェッチを停止するにはキャンセルボタンを押します。

**メモ:** このフェッチ処理はデモのために模擬的に作成されたもので、ランダムにテキストのチャンクを生成する `ReadableStream` を返すだけです。
 下記左列の「基盤ソース」は模擬ソースで生成されるデータで、右列はコンシューマーからのログ出力です。
 （模擬ソースのコードは例に関係ないので表示しません。）

独自の読み取り可能なストリームを作成する
--------------------

この記事で学習している単純なストリームポンプの例には 2 番目の部分が含まれています — フェッチした本体から画像をチャンク単位で読み取った後、独自作成の別のカスタムストリームのキューに入れます。 これをどのように作成するのでしょうか？ `ReadableStream()` コンストラクターです。

### ReadableStream() コンストラクター

Fetch の場合のように、ブラウザーが提供したストリームから読み取るのは簡単ですが、時にはカスタムストリームを作成し、自分自身でチャンクを投入する必要がある場合があります。 `ReadableStream()` コンストラクターを使うと、最初のうちは複雑に見えますが、実はそれほど悪くない構文でこれを行うことができます。

一般的な構文の骨組みは次のようになります。

```js
const stream = new ReadableStream(
  {
    start(controller) {},
    pull(controller) {},
    cancel() {},
    type,
    autoAllocateChunkSize,
  },
  {
    highWaterMark: 3,
    size: () => 1,
  },
);
```

コンストラクターは、引数として 2 つのオブジェクトを取ります。 最初のオブジェクトは必須であり、データの読み取り元である基になるソースのモデルを JavaScript で作成します。 2 番目のオブジェクトはオプションであり、ストリームに使用するカスタムのキューイング戦略を指定できます。 これを行う必要はほとんどないため、ここでは最初のものに集中します。

次のように最初のオブジェクトには最大 5 つのメンバーを含めることができ、最初のオブジェクトのみが必須です。

1. `start(controller)` — `ReadableStream` が構築された直後に 1 回だけ呼び出されるメソッド。 このメソッド内には、ストリーム機能を設定するコードを含める必要があります。 例えば、データの生成を開始するか、ソースにアクセスします。
2. `pull(controller)` — 含まれている場合、ストリームの内部キューがいっぱいになるまで繰り返し呼び出されるメソッド。 これは、より多くのチャンクがキューに入れられるときにストリームを制御するために使用できます。
3. `cancel()` — 含まれている場合、ストリームをキャンセルすることをアプリが通知した場合に呼び出されるメソッド（例えば、 `ReadableStream.cancel()` が呼び出された場合）。 内容は、ストリームのソースへのアクセスを解放するために必要なことを行う必要があります。
4. `type` および `autoAllocateChunkSize` — これらが含まれている場合、ストリームがバイトストリームであることを示すために使用されます。
 バイトストリームは、通常の（既定の）ストリームとは用途や使用する場合が多少異なるため、読み取り可能なバイトストリームの使用で別に扱っています。

簡単な例のコードをもう一度見ると、次のように `ReadableStream()` コンストラクターには、フェッチしたストリームからすべてのデータを読み取るための単一のメソッド `start()` のみが含まれていることがわかります。

```js
// 元の画像をフェッチ
fetch("./tortoise.png")
  // 本体を ReadableStream として取得
  .then((response) => {
    const reader = response.body.getReader();
    return new ReadableStream({
      start(controller) {
        return pump();
        function pump() {
          return reader.read().then(({ done, value }) => {
            // データを消費する必要がなくなったら、ストリームを閉じます
            if (done) {
              controller.close();
              return;
            }
            // 次のデータチャンクを対象のストリームのキューに入れます
            controller.enqueue(value);
            return pump();
          });
        }
      },
    });
  });
```

### ReadableStream コントローラー

`ReadableStream()` コンストラクターに渡される `start()` メソッドと `pull()` メソッドには `controller` 引数が与えられます。 これらは、ストリームの制御に使用できる `ReadableStreamDefaultController` クラスのインスタンスです。

この例では、コントローラーの `enqueue()` メソッドを使用して、値をフェッチした本体から読み取った後、カスタムストリームのキューに入れます。

さらに、フェッチした本体の読み取りが完了したら、コントローラーの `close()` メソッドを使用してカスタムストリームを閉じます。 以前にキューに入れられたチャンクはそれから読み取ることができますが、キューに入れることはできません。 読み取りが終了すると、ストリームは閉じられます。

### カスタムストリームからの読み取り

単純なストリームポンプの例では、カスタムの読み取り可能なストリームを `Response` コンストラクターの呼び出しに渡すことで消費し、その後 `blob()` として消費します。

```js
readableStream
  .then((stream) => new Response(stream))
  .then((response) => response.blob())
  .then((blob) => URL.createObjectURL(blob))
  .then((url) => console.log((image.src = url)))
  .catch((err) => console.error(err));
```

ただし、カスタムストリームも `ReadableStream` インスタンスであるため、それにリーダーを取りつけることができます。 例として、単純なランダムストリーム（Simple random stream）のデモをご覧ください（ライブも参照）。これはカスタムストリームを作成し、いくつかのランダムな文字列をキューに入れてから、[文字列の生成を停止] ボタンが押されるとストリームからデータを再度読み取ります。

**メモ:** `FetchEvent.respondWith()` を使用してストリームを消費するためには、キューに入ったストリームコンテンツは `Uint8Array` 型でなければなりません。例えば、 `TextEncoder` を使用してエンコードされます。

カスタムストリームのコンストラクターには、`setInterval()` 呼び出しを使用して 1 秒ごとにランダムな文字列を生成する `start()` メソッドがあります。 次に、`ReadableStreamDefaultController.enqueue()` を使用してストリームに入れます。 ボタンが押されると、インターバルがキャンセルされ、 `readStream()` と呼ばれる関数が呼び出されて、データをストリームから再度読み取ります。 また、チャンクをストリームのキューへ入れることを止めたため、ストリームを閉じます。

```js
let interval;
const stream = new ReadableStream({
  start(controller) {
    interval = setInterval(() => {
      const string = randomChars();
      // ストリームに文字列を追加
      controller.enqueue(string);
      // それを画面に表示
      const listItem = document.createElement("li");
      listItem.textContent = string;
      list1.appendChild(listItem);
    }, 1000);
    button.addEventListener("click", () => {
      clearInterval(interval);
      readStream();
      controller.close();
    });
  },
  pull(controller) {
    // この例では実際には pull は必要ありません
  },
  cancel() {
    // リーダーがキャンセルされた場合に呼び出されるため、
    // 文字列の生成を停止する必要があります
    clearInterval(interval);
  },
});
```

`readStream()` 関数自体では、　`ReadableStream.getReader()` を使用してリーダーをストリームにロックし、先ほど見たのと同様のパターンに従います。 `read()` で各チャンクを読み取り、`done` が `true` であるかどうかを確認し、その場合はプロセスを終了し、そうでない場合は `read()` メソッドを再度実行する前に次のチャンクを読み取って処理します。

```js
function readStream() {
  const reader = stream.getReader();
  let charsReceived = 0;
  let result = "";

  // read() は、値を受け取ったときに解決するプロミスを返します
  reader.read().then(function processText({ done, value }) {
    // 結果オブジェクトには2つのプロパティが含まれます。
    // done - ストリームがすべてのデータを既に提供している場合は true。
    // value - 一部のデータ。 done が true の場合、常に undefined。
    if (done) {
      console.log("Stream complete");
      para.textContent = result;
      return;
    }

    charsReceived += value.length;
    const chunk = value;
    const listItem = document.createElement("li");
    listItem.textContent = `Read ${charsReceived} characters so far. Current chunk = ${chunk}`;
    list2.appendChild(listItem);

    result += chunk;

    // さらに読み、この関数を再度呼び出します
    return reader.read().then(processText);
  });
}
```

### ストリームのクローズとキャンセル

`ReadableStreamDefaultController.close()` を使用してリーダーを閉じる例を既に示しました。 前に言ったように、以前にキューに入れられたチャンクはすべて読み込まれますが、閉じられているため、それ以上キューに入れることはできません。

ストリームを完全に取り除き、キューに入れられたチャンクを破棄したい場合は、`ReadableStream.cancel()` または `ReadableStreamDefaultReader.cancel()` を使用します。

ストリームの分岐
--------

ストリームを 2 回同時に読み取りたい場合があります。 これは、`ReadableStream.tee()` メソッドを介して実現されます。元の読み取り可能なストリームの 2 つの同一コピーを含む配列を出力し、2 つの別々のリーダーで個別に読み取ることができます。

例えばサービスワーカーで、サーバーからレスポンスを取得してブラウザーにストリーミングしつつ、サービスワーカーのキャッシュにもストリーミングしたい場合、このようなことを行うかもしれません。レスポンス本文は複数回消費することができず、ストリームは一度に複数のリーダーから読めないので、これを行うには 2 つのコピーが必要です。

単純な分岐の例（Simple tee example）でこの例を示します（ライブも参照）。 この例は、単純なランダムストリームとほぼ同じように機能しますが、ボタンを押してランダムな文字列の生成を停止すると、カスタムストリームが取得されて分岐され、結果の両方のストリームが読み取られる点が異なります。

```js
function teeStream() {
  const teedOff = stream.tee();
  readStream(teedOff[0], list2);
  readStream(teedOff[1], list3);
}
```

パイプチェーン
-------

ストリームのもう一つの機能として、ストリームを互いにパイプ接続する機能（パイプチェーンと呼ばれる）があります。このメソッドには、読み取り可能なストリームをライターとリーダーのペアを通してパイプ接続し、あるデータ形式を別の形式に変換する `ReadableStream.pipeThrough()` と、パイプチェーンの終点として動作するライターに読み取り可能なストリームをパイプする `ReadableStream.pipeTo()` の 2 つが含まれます。

Unpack Chunks of a PNG (ライブでも確認) という簡単な例を見てみましょう。これは、画像をストリームとして取得し、それをカスタム PNG 変換ストリームに接続して、バイナリーデータストリームから PNG チャンクを取得するものです。

```js
// 元の画像をフェッチ
fetch("png-logo.png")
  // その body を ReadableStream として取得
  .then((response) => response.body)
  // 元の画像からグレースケール PNG ストリームを作成
  .then((rs) => logReadableStream("Fetch Response Stream", rs))
  .then((body) => body.pipeThrough(new PNGTransformStream()))
  .then((rs) => logReadableStream("PNG Chunk Stream", rs));
```

`TransformStream` を使用する例は、まだありません。

まとめ
---

以上が、「既定の」読み取り可能なストリームの基本的な説明です。

 読み取り可能なバイトストリームの使用方法については、読み取り可能なバイトストリームの使用 を参照してください.
 ストリームの内部キューをバイパスして、コンシューマーへのゼロコピー転送を効率的に実行できる基礎となるバイトソースのあるストリームです。


CSSKeywordValue: value プロパティ - Web API

CSSKeywordValue: value プロパティ
============================

**`value`** は `CSSKeywordValue` インターフェイスのプロパティで、 `CSSKeywordValue` の値を取得または設定します。

値
-

文字列です。

### 例外

`TypeError`

設定時に `value` プロパティが空文字列になった場合。

例
-

次の例では、CSS の `display` プロパティを既定値へリセットします。

```js
let indicator = document.getElementById("indicator");
indicator.attributeStyleMap.set("display", new CSSKeywordValue("initial"));
indicator.attributeStyleMap.get("display").value; // 'initial'
```

仕様書
---

| Specification |
| --- |
| CSS Typed OM Level 1 # dom-csskeywordvalue-value |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


CSSKeywordValue: CSSKeywordValue() コンストラクター - Web API

CSSKeywordValue: CSSKeywordValue() コンストラクター
===========================================

**`CSSKeywordValue()`** コンストラクターは、新しい `CSSKeywordValue` オブジェクトを生成します。これは CSS のキーワードおよびその他の識別子を表します。

構文
--

```js
new CSSKeywordValue(val)
```

### 引数

`value`

新しい `CSSKeywordValue` の値に設定または返却します。

### 例外

`TypeError`

`value` 引数が指定されていなかったり、文字列型ではなかったりした場合。

例
-

次の例は、CSS の `display` プロパティを既定値にリセットするために、インラインの `style` 属性を `style="display: initial"` に設定します。これは開発者ツールインスペクターで見られます。
```

#myElement {
  display: flex;
}
```

```
<div id="myElement">
  開発者ツールでコンソールのログを確認し、この div の style
  属性を調べてみてください。
</div>
```

```js
const keyword = new CSSKeywordValue("initial");
const myElement = document.getElementById("myElement").attributeStyleMap;
myElement.set("display", keyword);

console.log(myElement.get("display").value); // 'initial'
console.dir(keyword);
```

仕様書
---

| Specification |
| --- |
| CSS Typed OM Level 1 # dom-csskeywordvalue-csskeywordvalue |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Worker: message イベント - Web API

Worker: message イベント
====================

`message` イベントは `Worker` オブジェクトで、ワーカーの親がワーカーからメッセージを受け取ったとき（すなわち、ワーカーが `DedicatedWorkerGlobalScope.postMessage()` を用いてメッセージを送信したとき）に発生します。

このイベントはキャンセル不可で、バブリングしません。

構文
--

このイベント名を `addEventListener()` などのメソッドで使用するか、イベントハンドラープロパティを設定するかしてください。

```js
addEventListener("message", (event) => {});

onmessage = (event) => {};
```

イベント型
-----

`MessageEvent` です。 `Event` を継承しています。

Event

MessageEventイベントプロパティ
---------

*このインターフェイスには、親である `Event` から継承したプロパティもあります。*

`MessageEvent.data` 読取専用

メッセージ送信者から送信されたデータです。

`MessageEvent.origin` 読取専用

文字列で、メッセージ送信者のオリジンを表します。

`MessageEvent.lastEventId` 読取専用

文字列で、このイベントの固有の ID を表します。

`MessageEvent.source` 読取専用

`MessageEventSource` （`WindowProxy`, `MessagePort`, `ServiceWorker` の何れかのオブジェクトがなれる）で、メッセージ送信者を表します。

`MessageEvent.ports` 読取専用

`MessagePort` オブジェクトの配列で、メッセージが送信されるチャネルに関連するポートを表します（チャネルメッセージングや共有ワーカーにメッセージを送信する場合など、必要に応じて）。

例
-

このコードでは、新しいワーカーを生成して、 `addEventListener()` を用いてメッセージを待ち受けします。

```js
const worker = new Worker("static/scripts/worker.js");

worker.addEventListener("message", (event) => {
  console.log(`Received message from worker: ${event.data}`);
});
```

他にも、 `onmessage` イベントハンドラープロパティを使用して待ち受けすることもできます。

```js
const worker = new Worker("static/scripts/worker.js");

worker.onmessage = (event) => {
  console.log(`Received message from worker: ${event.data}`);
};
```

ワーカーは `self.postMessage()` を使用してメッセージを送信します。

```js
// static/scripts/worker.js

self.postMessage("I'm alive!");
```

仕様書
---

| Specification |
| --- |
| HTML Standard # event-message |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* 関連イベント: `messageerror`.
* `DedicatedWorkerGlobalScope.postMessage()`.


Worker.postMessage() - Web API

Worker.postMessage()
====================

**`postMessage()`** は `Worker` インターフェイスのメソッドで、ワーカーの内部スコープにメッセージを送信します。これは、ワーカーに送信するデータを単一の引数として受け取ります。このデータは任意の値、または構造化複製アルゴリズムで扱う JavaScript オブジェクト (循環参照を含んでもよい) が許可されます。

`Worker` の `postMessage()` メソッドは `MessagePort` の `postMessage()` メソッドに委任して、受信する `MessagePort` に対応するイベントループのタスクを追加します。

ワーカーは `DedicatedWorkerGlobalScope.postMessage` メソッド使用して自分を起動したスレッドに情報を送り返すことができます。

構文
--

```js
postMessage(message);
postMessage(message, transfer);
```

### 引数

`message`

ワーカーに送るオブジェクトです。これは `DedicatedWorkerGlobalScope.message_event` イベントに配信されるイベントの `data` フィールドに入ります。このデータは任意の値、または構造化複製アルゴリズムで扱う JavaScript オブジェクト (循環参照を含んでもよい) が許可されます。

`message` 引数が提供されて*いない*場合は、 `TypeError` が発生します。ワーカーに渡すデータが重要でない場合は、 `null` または `undefined` を明示的に渡すことができます。

`transfer` 省略可

オプションで、所有権を移転する移転可能オブジェクトの配列です。オブジェクトの所有権が移転されると、そのオブジェクトは送信元のコンテキストでは使用できなくなり、送信先のワーカーのみが使用できるようになります。

移転可能オブジェクトは `ArrayBuffer`、`MessagePort`、`ImageBitmap` のような移転可能なクラスのインスタンスです。 `null` を `transfer` の値として受け付けることはできません。

### 返値

なし (`undefined`)。

例
-

次のコードスニペットは、 `Worker()` コンストラクターを使用して `Worker` オブジェクトを作成しています。2 つのフォーム入力要素 (`first` と `second`) のどちらかの値が変更されると、 `change` イベントによって `postMessage()` が呼び出され、両方の入力要素の値が現在のワーカーに送信されます。

```js
const myWorker = new Worker("worker.js");

first.onchange = () => {
  myWorker.postMessage([first.value, second.value]);
  console.log("Message posted to worker");
};

second.onchange = () => {
  myWorker.postMessage([first.value, second.value]);
  console.log("Message posted to worker");
};
```

完全な例は、簡単なワーカーの例 （例を実行）を参照してください。

**メモ:** `postMessage()` は同時に一つしかオブジェクトを送信できません。上記のように、複数の値を渡したい場合は配列を送信してください。

### 移転を伴う例

この最小限の例では、 `main` が `ArrayBuffer` を作成して `myWorker` に移転し、次に `myWorker` がそれを `main` に再移転して、それぞれの段階で大きさを記録します。

#### main.js のコード

```js
// ワーカーの生成
const myWorker = new Worker("myWorker.js");

// myWorker を待ち受けしてバッファーを main に再移転する
myWorker.addEventListener("message", function handleMessageFromWorker(msg) {
  console.log("message from worker received in main:", msg);

  const bufTransferredBackFromWorker = msg.data;

  console.log(
    "buf.byteLength in main AFTER transfer back from worker:",
    bufTransferredBackFromWorker.byteLength,
  );
});

// バッファーの生成
const myBuf = new ArrayBuffer(8);

console.log(
  "buf.byteLength in main BEFORE transfer to worker:",
  myBuf.byteLength,
);

// myBuf を myWorker に送信し、配下の ArrayBuffer を移転する
myWorker.postMessage(myBuf, [myBuf]);

console.log(
  "buf.byteLength in main AFTER transfer to worker:",
  myBuf.byteLength,
);
```

#### myWorker.js のコード

```js
// main を待ち受けしてバッファーを myWorker に移転する
self.onmessage = function handleMessageFromMain(msg) {
  console.log("message from main received in worker:", msg);

  const bufTransferredFromMain = msg.data;

  console.log(
    "buf.byteLength in worker BEFORE transfer back to main:",
    bufTransferredFromMain.byteLength,
  );

  // バッファーを main に送信し返し、配下の ArrayBuffer を移転する
  self.postMessage(bufTransferredFromMain, [bufTransferredFromMain]);

  console.log(
    "buf.byteLength in worker AFTER transfer back to main:",
    bufTransferredFromMain.byteLength,
  );
};
```

#### ログ出力

```bash
buf.byteLength in main BEFORE transfer to worker:        8                     main.js:19
buf.byteLength in main AFTER transfer to worker:         0                     main.js:27

message from main received in worker:                    MessageEvent { ... }  myWorker.js:3
buf.byteLength in worker BEFORE transfer back to main:   8                     myWorker.js:7
buf.byteLength in worker AFTER transfer back to main:    0                     myWorker.js:15

message from worker received in main:                    MessageEvent { ... }  main.js:6
buf.byteLength in main AFTER transfer back from worker:  8                     main.js:10
```

`byteLength` は `ArrayBuffer` が移転されると 0 になります。 `ArrayBuffer` の移転のより洗練された完全な動作例については、 GitHub :: ChromeWorker - demo-transfer-arraybuffer の Firefox デモアドオンを参照してください。

仕様書
---

| Specification |
| --- |
| HTML Standard # dom-worker-postmessage-dev |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* 所属先の `Worker` インターフェイス


Worker() - Web API

Worker()
========

**`Worker()`** コンストラクターは、指定された URL で指定されたスクリプトを実行する `Worker` オブジェクトを作成します。このスクリプトは 同一オリジンポリシーに従わなければいけません。

**メモ:** data URL が 同一オリジンであるかどうかについては、ブラウザーベンダーの間で合意されていません。 Firefox 10 以降のリリースでは data URL を受け入れますが、すべてのブラウザーでそうなるとは限りません。

構文
--

```js
new Worker(aURL);
new Worker(aURL, options);
```

### 引数

`aURL`

文字列で、ワーカーが実行するスクリプトの URL を表します。同一オリジンポリシーに従っていなければいけません。

`options` 省略可

オブジェクトを作成するときに設定できるオプションプロパティを持つオブジェクトです。以下のプロパティが使用できます。

`type`

文字列で、作成するワーカーの種類を指定します。使用できる値は `classic` または `module` です。指定しない場合の既定値は `classic` です。

`credentials`

文字列で、ワーカーで使用する資格情報の種類を指定します。使用できる値は `omit`、`same-origin`、`include` です。指定しない場合、または type が `classic` である場合、既定値は `omit` （資格情報は不要）です。

`name`

文字列で、ワーカーのスコープを表す `DedicatedWorkerGlobalScope` を識別する名前を指定します。これは主に、デバッグで役に立ちます。

### 例外

`SecurityError` `DOMException`

文書がワーカーを起動することが許可されていない場合、例えば、 URL が不正な構文であったり、同一オリジンポリシーに違反していたりした場合に発生します。

`NetworkError` `DOMException`

ワーカースクリプトの MIME タイプが不正な場合に発生します。これは常に `text/javascript` であるべきです（経緯上、他の JavaScript の MIME タイプも受け入れられる場合があります）。

`SyntaxError` `DOMException`

*aURL* が解釈できなかった場合に発生します。

例
-

次のコードスニペットは `Worker()` コンストラクターを使って `Worker` オブジェクトを作成し、続いてそのオブジェクトの使い方を表しています。

```js
const myWorker = new Worker("worker.js");

first.onchange = () => {
  myWorker.postMessage([first.value, second.value]);
  console.log("Message posted to worker");
};
```

完全な例を見るには、基本的な専用ワーカーの例（専用ワーカーを実行）を参照してください。

仕様書
---

| Specification |
| --- |
| HTML Standard # dom-worker-dev |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

所属する `Worker` インターフェイス。


Worker.terminate() - Web API

Worker.terminate()
==================

**`terminate()`** は `Worker` インターフェイスのメソッドで、このワーカーを即座に停止させます。これはワーカーに対してその動作を終了する機会を提供するものではありません。即時に停止させます。

構文
--

```js
terminate();
```

### 引数

なし。

### 返値

なし (`undefined`)。

例
-

次のコードでは `Worker` オブジェクトを `Worker()` コンストラクターで作成したあと、すぐに停止させます。

```js
const myWorker = new Worker("worker.js");

myWorker.terminate();
```

仕様書
---

| Specification |
| --- |
| HTML Standard # dom-worker-terminate-dev |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

所属先の `Worker` インターフェイス。


Worker: messageerror イベント - Web API

Worker: messageerror イベント
=========================

`messageerror` イベントは、 `Worker` オブジェクトに、解読 (deserialize) できないメッセージを受け取ったときに発生します。

このイベントはキャンセル不可で、バブリングしません。

構文
--

このイベント名を `addEventListener()` などのメソッドで使用するか、イベントハンドラープロパティを設定するかしてください。

```js
addEventListener("messageerror", (event) => {});

onmessageerror = (event) => {};
```

イベント型
-----

`MessageEvent` です。 `Event` を継承しています。

Event

MessageEventイベントプロパティ
---------

*このインターフェイスには、親である `Event` から継承したプロパティもあります。*

`MessageEvent.data` 読取専用

メッセージ送信者から送信されたデータです。

`MessageEvent.origin` 読取専用

文字列で、メッセージ送信者のオリジンを表します。

`MessageEvent.lastEventId` 読取専用

文字列で、このイベントの固有の ID を表します。

`MessageEvent.source` 読取専用

`MessageEventSource` （`WindowProxy`, `MessagePort`, `ServiceWorker` の何れかのオブジェクトがなれる）で、メッセージ送信者を表します。

`MessageEvent.ports` 読取専用

`MessagePort` オブジェクトの配列で、メッセージが送信されるチャネルに関連するポートを表します（チャネルメッセージングや共有ワーカーにメッセージを送信する場合など、必要に応じて）。

例
-

ワーカーを作成し、 `message` および `messageerror` イベントを `addEventListener()` で待ち受けします。

```js
// main.js

const worker = new Worker("static/scripts/worker.js");

worker.addEventListener("message", (event) => {
  console.error(`Received message from worker: ${event}`);
});

worker.addEventListener("messageerror", (event) => {
  console.error(`Error receiving message from worker: ${event}`);
});
```

同じですが、 `onmessageerror` イベントハンドラープロパティを使用した例です。

```js
// main.js

const worker = new Worker("static/scripts/worker.js");

worker.onmessage = (event) => {
  console.error(`Received message from worker: ${event}`);
};

worker.onmessageerror = (event) => {
  console.error(`Error receiving message from worker: ${event}`);
};
```

仕様
--

| Specification |
| --- |
| HTML Standard # event-messageerror |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連項目
----

* `Worker.postMessage()`
* 関連イベント: `message`


Worker: error イベント - Web API

Worker: error イベント
==================

**`error`** は `Worker` インターフェイスのイベントで、ワーカーでエラーが発生したときに発行されます。

構文
--

このイベント名を `addEventListener()` などのメソッドで使用するか、イベントハンドラープロパティを設定するかしてください。

```js
addEventListener("error", (event) => {});

onerror = (event) => {};
```

イベント型
-----

一般的な `Event` です。

例
-

以下の例では `Worker` オブジェクトを `Worker()` コンストラクターを使用して生成し、結果のオブジェクトに `onerror` ハンドラーを設定しています。

```js
const myWorker = new Worker("worker.js");

myWorker.onerror = (event) => {
  console.log("There is an error with your worker!");
};
```

仕様書
---

| Specification |
| --- |
| HTML Standard # handler-abstractworker-onerror |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


キーボードイベントの code の値 - Web API

キーボードイベントの code の値
==================

以下の表は、主要なプラットフォームで各ネイティブスキャンコードまたは仮想キーコードにどのようなコード値を使用しているかを示しています。ブラウザーによっては物理キーの解釈が異なるため、どのキーがどのコードに割り当てられたかという違いがあるからです。この表は、それらの既知の違いを示しています。

Windows におけるコード値
----------------

この表は、キーを表す Windows のスキャンコードと、それらのハードウェアキーに対応する `KeyboardEvent.code` 値を示しています。 Windows 上でスキャンコードを生成するキーのみを掲載しています。

 セル内の「（❌ 検出不可）」は、このコード値がこのブラウザーで検出できないことを意味しています。
 「（⚠ xyz とは異なる）」は、この文字列がブラウザー xyz とはコード値が異なることを示し、使用する際には特別な注意が必要であることを意味しています。

|  | **`KeyboardEvent.code`** 値 |
| --- | --- |
| コード | Firefox | Chrome |
| `0x0000` | `"Unidentified"` | `""` |
| `0x0001` | `"Escape"` | `"Escape"` |
| `0x0002` | `"Digit1"` | `"Digit1"` |
| `0x0003` | `"Digit2"` | `"Digit2"` |
| `0x0004` | `"Digit3"` | `"Digit3"` |
| `0x0005` | `"Digit4"` | `"Digit4"` |
| `0x0006` | `"Digit5"` | `"Digit5"` |
| `0x0007` | `"Digit6"` | `"Digit6"` |
| `0x0008` | `"Digit7"` | `"Digit7"` |
| `0x0009` | `"Digit8"` | `"Digit8"` |
| `0x000A` | `"Digit9"` | `"Digit9"` |
| `0x000B` | `"Digit0"` | `"Digit0"` |
| `0x000C` | `"Minus"` | `"Minus"` |
| `0x000D` | `"Equal"` | `"Equal"` |
| `0x000E` | `"Backspace"` | `"Backspace"` |
| `0x000F` | `"Tab"` | `"Tab"` |
| `0x0010` | `"KeyQ"` | `"KeyQ"` |
| `0x0011` | `"KeyW"` | `"KeyW"` |
| `0x0012` | `"KeyE"` | `"KeyE"` |
| `0x0013` | `"KeyR"` | `"KeyR"` |
| `0x0014` | `"KeyT"` | `"KeyT"` |
| `0x0015` | `"KeyY"` | `"KeyY"` |
| `0x0016` | `"KeyU"` | `"KeyU"` |
| `0x0017` | `"KeyI"` | `"KeyI"` |
| `0x0018` | `"KeyO"` | `"KeyO"` |
| `0x0019` | `"KeyP"` | `"KeyP"` |
| `0x001A` | `"BracketLeft"` | `"BracketLeft"` |
| `0x001B` | `"BracketRight"` | `"BracketRight"` |
| `0x001C` | `"Enter"` | `"Enter"` |
| `0x001D` | `"ControlLeft"` | `"ControlLeft"` |
| `0x001E` | `"KeyA"` | `"KeyA"` |
| `0x001F` | `"KeyS"` | `"KeyS"` |
| `0x0020` | `"KeyD"` | `"KeyD"` |
| `0x0021` | `"KeyF"` | `"KeyF"` |
| `0x0022` | `"KeyG"` | `"KeyG"` |
| `0x0023` | `"KeyH"` | `"KeyH"` |
| `0x0024` | `"KeyJ"` | `"KeyJ"` |
| `0x0025` | `"KeyK"` | `"KeyK"` |
| `0x0026` | `"KeyL"` | `"KeyL"` |
| `0x0027` | `"Semicolon"` | `"Semicolon"` |
| `0x0028` | `"Quote"` | `"Quote"` |
| `0x0029` | `"Backquote"` | `"Backquote"` |
| `0x002A` | `"ShiftLeft"` | `"ShiftLeft"` |
| `0x002B` | `"Backslash"` | `"Backslash"` |
| `0x002C` | `"KeyZ"` | `"KeyZ"` |
| `0x002D` | `"KeyX"` | `"KeyX"` |
| `0x002E` | `"KeyC"` | `"KeyC"` |
| `0x002F` | `"KeyV"` | `"KeyV"` |
| `0x0030` | `"KeyB"` | `"KeyB"` |
| `0x0031` | `"KeyN"` | `"KeyN"` |
| `0x0032` | `"KeyM"` | `"KeyM"` |
| `0x0033` | `"Comma"` | `"Comma"` |
| `0x0034` | `"Period"` | `"Period"` |
| `0x0035` | `"Slash"` | `"Slash"` |
| `0x0036` | `"ShiftRight"` | `"ShiftRight"` |
| `0x0037` | `"NumpadMultiply"` | `"NumpadMultiply"` |
| `0x0038` | `"AltLeft"` | `"AltLeft"` |
| `0x0039` | `"Space"` | `"Space"` |
| `0x003A` | `"CapsLock"` | `"CapsLock"` |
| `0x003B` | `"F1"` | `"F1"` |
| `0x003C` | `"F2"` | `"F2"` |
| `0x003D` | `"F3"` | `"F3"` |
| `0x003E` | `"F4"` | `"F4"` |
| `0x003F` | `"F5"` | `"F5"` |
| `0x0040` | `"F6"` | `"F6"` |
| `0x0041` | `"F7"` | `"F7"` |
| `0x0042` | `"F8"` | `"F8"` |
| `0x0043` | `"F9"` | `"F9"` |
| `0x0044` | `"F10"` | `"F10"` |
| `0x0045` | `"Pause"` | `"Pause"` |
| `0x0046` | `"ScrollLock"` | `"ScrollLock"` |
| `0x0047` | `"Numpad7"` | `"Numpad7"` |
| `0x0048` | `"Numpad8"` | `"Numpad8"` |
| `0x0049` | `"Numpad9"` | `"Numpad9"` |
| `0x004A` | `"NumpadSubtract"` | `"NumpadSubtract"` |
| `0x004B` | `"Numpad4"` | `"Numpad4"` |
| `0x004C` | `"Numpad5"` | `"Numpad5"` |
| `0x004D` | `"Numpad6"` | `"Numpad6"` |
| `0x004E` | `"NumpadAdd"` | `"NumpadAdd"` |
| `0x004F` | `"Numpad1"` | `"Numpad1"` |
| `0x0050` | `"Numpad2"` | `"Numpad2"` |
| `0x0051` | `"Numpad3"` | `"Numpad3"` |
| `0x0052` | `"Numpad0"` | `"Numpad0"` |
| `0x0053` | `"NumpadDecimal"` | `"NumpadDecimal"` |
| `0x0054 (`Alt` + `PrintScreen`)` | `"PrintScreen"`（⚠ Chrome とは異なる） | `""`（❌ 検出不可） |
| `0x0055` | `"Unidentified"` | `""` |
| `0x0056` | `"IntlBackslash"` | `"IntlBackslash"` |
| `0x0057` | `"F11"` | `"F11"` |
| `0x0058` | `"F12"` | `"F12"` |
| `0x0059` | `"NumpadEqual"` | `"NumpadEqual"`（Chrome 48 より前は `""`） |
| `0x005A` | `"Unidentified"` | `""` |
| `0x005B` | `"Unidentified"` | `""`（Chrome 48 より前は `"F13"`） |
| `0x005C` | `"Unidentified"` | `""`（Chrome 48 より前は `"F14"`） |
| `0x005D` | `"Unidentified"` | `""`（Chrome 48 より前は `"F15"`） |
| `0x005E` | `"Unidentified"` | `""` |
| `0x005F` | `"Unidentified"` | `""` |
| `0x0060` | `"Unidentified"` | `""` |
| `0x0061` | `"Unidentified"` | `""` |
| `0x0062` | `"Unidentified"` | `""` |
| `0x0063` | `"Unidentified"` | `""`（Chrome 48 より前は `"F16"`） |
| `0x0064` | `"F13"` | `"F13"`（Chrome 48 より前は `"F17"`） |
| `0x0065` | `"F14"` | `"F14"`（Chrome 48 より前は `"F18"`） |
| `0x0066` | `"F15"` | `"F15"`（Chrome 48 より前は `"F19"`） |
| `0x0067` | `"F16"` | `"F16"`（Chrome 48 より前は `"F20"`） |
| `0x0068` | `"F17"` | `"F17"`（Chrome 48 より前は `"F21"`） |
| `0x0069` | `"F18"` | `"F18"`（Chrome 48 より前は `"F22"`） |
| `0x006A` | `"F19"` | `"F19"`（Chrome 48 より前は `"F23"`） |
| `0x006B` | `"F20"` | `"F20"`（Chrome 48 より前は `"F24"`） |
| `0x006C` | `"F21"` | `"F21"`（Chrome 48 より前は `""`） |
| `0x006D` | `"F22"` | `"F22"`（Chrome 48 より前は `""`） |
| `0x006E` | `"F23"` | `"F23"`（Chrome 48 より前は `""`） |
| `0x006F` | `"Unidentified"` | `""` |
| `0x0070` | `"KanaMode"` | `"KanaMode"`（Chrome 48 より前は `""`） |
| `0x0071`（韓国のキーボードレイアウトでない場合は `Hanja` キー） | `"Lang2"` | `"Lang2"`（Chrome 48 より前は `""`） |
| `0x0072`（韓国のキーボードレイアウトでない場合は `Han/Yeong` キー） | `"Lang1"` | `"Lang1"`（Chrome 48 より前は `""`） |
| `0x0073` | `"IntlRo"` | `"IntlRo"`（Chrome 48 より前は `""`） |
| `0x0074`, `0x0075` | `"Unidentified"` | `""` |
| `0x0076` | `"F24"` | `"F24"`（Chrome 48 より前は `""`） |
| `0x0077` | `"Unidentified"` （❌ 検出不可） | `"Lang4"`（Chrome 48 より前は `""`）（⚠ Firefox とは異なる） |
| `0x0078` | `"Unidentified"` （❌ 検出不可） | `"Lang3"`（Chrome 48 より前は `""`）（⚠ Firefox とは異なる） |
| `0x0079` | `"Convert"` | `"Convert"`（Chrome 48 より前は `""`） |
| `0x007A` | `"Unidentified"` | `""` |
| `0x007B` | `"NonConvert"` | `"NonConvert"`（Chrome 48 より前は `""`） |
| `0x007C` | `"Unidentified"` | `""` |
| `0x007D` | `"IntlYen"` | `"IntlYen"` |
| `0x007E` | `"NumpadComma"` | `"NumpadComma"`（Chrome 48 より前は `""`） |
| `0x007F` | `"Unidentified"` | `""` |
| `0xE000` ～ `0xE007` | `"Unidentified"` | `""` |
| `0xE008` | `"Unidentified"` （❌ 検出不可） | `"Undo"`（⚠ Firefox とは異なる） |
| `0xE009` | `"Unidentified"` | `""` |
| `0xE00A` | `""` （❌ 検出不可） | `"Paste"`（⚠ Firefox とは異なる） |
| `0xE00B` ～ `0xE00F` | "" | `""` |
| `0xE010` | `"MediaTrackPrevious"` | `"MediaTrackPrevious"` |
| `0xE011` ～ `0xE016` | `""` | `""` |
| `0xE017` | `"Unidentified"` （❌ 検出不可） | `"Cut"`（⚠ Firefox とは異なる） |
| `0xE018` | `"Unidentified"` （❌ 検出不可） | `"Copy"`（⚠ Firefox とは異なる） |
| `0xE019` | `"MediaTrackNext"` | `"MediaTrackNext"` |
| `0xE01A, 0xE01B` | `"Unidentified"` | `""` |
| `0xE01C` | `"NumpadEnter"` | `"NumpadEnter"` |
| `0xE01D` | `"ControlRight"` | `"ControlRight"` |
| `0xE01E` | `"Unidentified"` | `""`（Chrome 48 より前は `"LaunchMail"`） |
| `0xE01F` | `"Unidentified"` | `""` |
| `0xE020` | `"AudioVolumeMute"` | `"AudioVolumeMute"` |
| `0xE021` | `"LaunchApp2"` | `"LaunchApp2"`（Chrome 48 より前は `""`） |
| `0xE022` | `"MediaPlayPause"` | `"MediaPlayPause"` |
| `0xE023` | `"Unidentified"` | `""` |
| `0xE024` | `"MediaStop"` | `"MediaStop"` |
| `0xE025` ～ `0xE02B` | `"Unidentified"` | `""` |
| `0xE02C` | `"Unidentified"` （❌ 検出不可） | `"Eject"`（⚠ Firefox とは異なる） |
| `0xE02D` | `"Unidentified"` | `""` |
| `0xE02E` | `"VolumeDown"`（⚠ Chrome とは異なる） | `"AudioVolumeDown"`（Chrome 52 より前は `"VolumeDown"`）（⚠ Firefox とは異なる） |
| `0xE02F` | `"Unidentified"` | `""` |
| `0xE030` | `"VolumeUp"`（⚠ Chrome とは異なる） | `"AudioVolumeUp"`（Chrome 52 より前は `"VolumeUp"`）（⚠ Firefox とは異なる） |
| `0xE031` | `"Unidentified"` | `""` |
| `0xE032` | `"BrowserHome"` | `"BrowserHome"` |
| `0xE033`, `0xE034` | `"Unidentified"` | `""` |
| `0xE035` | `"NumpadDivide"` | `"NumpadDivide"` |
| `0xE036` | `"Unidentified"` | `""` |
| `0xE037` | `"PrintScreen"` | `"PrintScreen"` |
| `0xE038` | `"AltRight"` | `"AltRight"` |
| `0xE039`, `0xE03A` | `"Unidentified"` | `""` |
| `0xE03B` | `"Unidentified"` （❌ 検出不可） | `"Help"`（⚠ Firefox とは異なる） |
| `0xE03C`～ `0xE044` | `"Unidentified"` | `""` |
| `0xE045` | `"NumLock"` | `"NumLock"` |
| `0xE046` (`Ctrl` + `Pause`) | `"Pause"` | `"Pause"` |
| `0xE047` | `"Home"` | `"Home"` |
| `0xE048` | `"ArrowUp"` | `"ArrowUp"` |
| `0xE049` | `"PageUp"` | `"PageUp"` |
| `0xE04A` | `"Unidentified"` | `""` |
| `0xE04B` | `"ArrowLeft"` | `"ArrowLeft"` |
| `0xE04C` | `"Unidentified"` | `""` |
| `0xE04D` | `"ArrowRight"` | `"ArrowRight"` |
| `0xE04E` | `"Unidentified"` | `""` |
| `0xE04F` | `"End"` | `"End"` |
| `0xE050` | `"ArrowDown"` | `"ArrowDown"` |
| `0xE051` | `"PageDown"` | `"PageDown"` |
| `0xE052` | `"Insert"` | `"Insert"` |
| `0xE053` | `"Delete"` | `"Delete"` |
| `0xE054` ～ `0xE05A` | `"Unidentified"` | `""` |
| `0xE05B` | `"MetaLeft"`（Firefox 118 より前は `"OSLeft"`） | `"MetaLeft"`（Chrome 52 より前は `"OSLeft"`） |
| `0xE05C` | `"MetaRight"`（Firefox 118 より前は `"OSRight"`） | `"MetaRight"`（Chrome 52 より前は `"OSRight"`） |
| `0xE05D` | `"ContextMenu"` | `"ContextMenu"` |
| `0xE05E` | `"Power"` | `"Power"`（Chrome 48 より前は `""`） |
| `0xE05F` | `"Unidentified"` （❌ 検出不可） | `"Sleep"`（Chrome 48 より前は `""`）（⚠ Firefox とは異なる） |
| `0xE060` ～ `0xE062` | `"Unidentified"` | `""` |
| `0xE063` | `"Unidentified"` （❌ 検出不可） | `"WakeUp"`（Chrome 48 より前は `""`）（⚠ Firefox とは異なる） |
| `0xE064` | `"Unidentified"` | `""` |
| `0xE065` | `"BrowserSearch"` | `"BrowserSearch"` |
| `0xE066` | `"BrowserFavorites"` | `"BrowserFavorites"` |
| `0xE067` | `"BrowserRefresh"` | `"BrowserRefresh"` |
| `0xE068` | `"BrowserStop"` | `"BrowserStop"` |
| `0xE069` | `"BrowserForward"` | `"BrowserForward"` |
| `0xE06A` | `"BrowserBack"` | `"BrowserBack"` |
| `0xE06B` | `"LaunchApp1"` | `"LaunchApp1"`（Chrome 48 より前は `""`） |
| `0xE06C` | `"LaunchMail"` | `"LaunchMail"`（Chrome 48 より前は `""`） |
| `0xE06D` | `"MediaSelect"` | `"MediaSelect"`（Chrome 48 より前は `""`） |
| `0xE06E ～ 0xE0F0` | `"Unidentified"` | `""` |
| `0xE0F1`（韓国語キーボードレイアウトの `Hanja`） | `"Lang2"`（⚠ Chrome とは異なる） | `""` （❌ 検出不可） |
| `0xE0F2`（韓国語キーボードレイアウトの `Han/Yeong`） | `"Lang1"`（⚠ Chrome とは異なる） | `""` （❌ 検出不可） |

Mac でのコード値
----------

 macOS では、物理キーとキーイベントを判別するスキャンコードの類ものを取得するのは困難です。
 そのため、 Firefox では常に仮想キーコードから `code` 値を割り当てています。

セル内の凡例です。

* 「（❌ 検出不可）」は、このコード値がこのブラウザーで検出できないことを意味しています。
* 「（⚠ xyz とは異なる）」は、この文字列がブラウザー xyz とはコード値が異なることを示し、使用する際には特別な注意が必要であることを意味しています。
* 「（⚠ `0xab` と同じ文字列）」は、このキーと `0xab` に一致するキーが区別できないことを意味しています。
* 「（⚠ 実際にはイベントが発生しない）」は、技術的にこのコードに固有の文字列があったとしても、イベントは配信されないという意味です。

| 仮想キーコード | Firefox | Chromium |
| --- | --- | --- |
| `kVK_ANSI_A (0x00)` | `"KeyA"` | `"KeyA"` |
| `kVK_ANSI_S (0x01)` | `"KeyS"` | `"KeyS"` |
| `kVK_ANSI_D (0x02)` | `"KeyD"` | `"KeyD"` |
| `kVK_ANSI_F (0x03)` | `"KeyF"` | `"KeyF"` |
| `kVK_ANSI_H (0x04)` | `"KeyH"` | `"KeyH"` |
| `kVK_ANSI_G (0x05)` | `"KeyG"` | `"KeyG"` |
| `kVK_ANSI_Z (0x06)` | `"KeyZ"` | `"KeyZ"` |
| `kVK_ANSI_X (0x07)` | `"KeyX"` | `"KeyX"` |
| `kVK_ANSI_C (0x08)` | `"KeyC"` | `"KeyC"` |
| `kVK_ANSI_V (0x09)` | `"KeyV"` | `"KeyV"` |
| `kVK_ISO_Section (0x0A)` | `"IntlBackslash"` | `"IntlBackslash"` |
| `kVK_ANSI_B (0x0B)` | `"KeyB"` | `"KeyB"` |
| `kVK_ANSI_Q (0x0C)` | `"KeyQ"` | `"KeyQ"` |
| `kVK_ANSI_W (0x0D)` | `"KeyW"` | `"KeyW"` |
| `kVK_ANSI_E (0x0E)` | `"KeyE"` | `"KeyE"` |
| `kVK_ANSI_R (0x0F)` | `"KeyR"` | `"KeyR"` |
| `kVK_ANSI_Y (0x10)` | `"KeyY"` | `"KeyY"` |
| `kVK_ANSI_T (0x11)` | `"KeyT"` | `"KeyT"` |
| `kVK_ANSI_1 (0x12)` | `"Digit1"` | `"Digit1"` |
| `kVK_ANSI_2 (0x13)` | `"Digit2"` | `"Digit2"` |
| `kVK_ANSI_3 (0x14)` | `"Digit3"` | `"Digit3"` |
| `kVK_ANSI_4 (0x15)` | `"Digit4"` | `"Digit4"` |
| `kVK_ANSI_6 (0x16)` | `"Digit6"` | `"Digit6"` |
| `kVK_ANSI_5 (0x17)` | `"Digit5"` | `"Digit5"` |
| `kVK_ANSI_Equal (0x18)` | `"Equal"` | `"Equal"` |
| `kVK_ANSI_9 (0x19)` | `"Digit9"` | `"Digit9"` |
| `kVK_ANSI_7 (0x1A)` | `"Digit7"` | `"Digit7"` |
| `kVK_ANSI_Minus (0x1B)` | `"Minus"` | `"Minus"` |
| `kVK_ANSI_8 (0x1C)` | `"Digit8"` | `"Digit8"` |
| `kVK_ANSI_0 (0x1D)` | `"Digit0"` | `"Digit0"` |
| `kVK_ANSI_RightBracket (0x1E)` | `"BracketRight"` | `"BracketRight"` |
| `kVK_ANSI_O (0x1F)` | `"KeyO"` | `"KeyO"` |
| `kVK_ANSI_U (0x20)` | `"KeyU"` | `"KeyU"` |
| `kVK_ANSI_LeftBracket (0x21)` | `"BracketLeft"` | `"BracketLeft"` |
| `kVK_ANSI_I (0x22)` | `"KeyI"` | `"KeyI"` |
| `kVK_ANSI_P (0x23)` | `"KeyP"` | `"KeyP"` |
| `kVK_Return (0x24)` | `"Enter"` | `"Enter"` |
| `kVK_ANSI_L (0x25)` | `"KeyL"` | `"KeyL"` |
| `kVK_ANSI_J (0x26)` | `"KeyJ"` | `"KeyJ"` |
| `kVK_ANSI_Quote (0x27)` | `"Quote"` | `"Quote"` |
| `kVK_ANSI_K (0x28)` | `"KeyK"` | `"KeyK"` |
| `kVK_ANSI_Semicolon (0x29)` | `"Semicolon"` | `"Semicolon"` |
| `kVK_ANSI_Backslash (0x2A)` | `"Backslash"` | `"Backslash"` |
| `kVK_ANSI_Comma (0x2B)` | `"Comma"` | `"Comma"` |
| `kVK_ANSI_Slash (0x2C)` | `"Slash"` | `"Slash"` |
| `kVK_ANSI_N (0x2D)` | `"KeyN"` | `"KeyN"` |
| `kVK_ANSI_M (0x2E)` | `"KeyM"` | `"KeyM"` |
| `kVK_ANSI_Period (0x2F)` | `"Period"` | `"Period"` |
| `kVK_Tab (0x30)` | `"Tab"` | `"Tab"` |
| `kVK_Space (0x31)` | `"Space"` | `"Space"` |
| `kVK_ANSI_Grave (0x32)` | `"Backquote"` | `"Backquote"` |
| `kVK_Delete (0x33)` | `"Backspace"` | `"Backspace"` |
| Enter key on keypad of PowerBook (`0x34`) | `"NumpadEnter"`（⚠ `0x4C` と同じ文字列）（⚠ Chromium とは異なる） | `""` （❌ 検出不可） |
| `kVK_Escape (0x35)` | `"Escape"` | `"Escape"` |
| right-command key (`0x36`) | `"MetaRight"`（Firefox 118 より前は `"OSRight"`） | `"MetaRight"`（Chromium 52 より前は `"OSRight"`） |
| `kVK_Command (0x37)` | `"MetaLeft"`（Firefox 118 より前は `"OSLeft"`） | `"MetaLeft"`（Chromium 52 より前は `"OSLeft"`） |
| `kVK_Shift (0x38)` | `"ShiftLeft"` | `"ShiftLeft"` |
| `kVK_CapsLock (0x39)` | `"CapsLock"` | `"CapsLock"` |
| `kVK_Option (0x3A)` | `"AltLeft"` | `"AltLeft"` |
| `kVK_Control (0x3B)` | `"ControlLeft"` | `"ControlLeft"` |
| `kVK_RightShift (0x3C)` | `"ShiftRight"` | `"ShiftRight"` |
| `kVK_RightOption (0x3D)` | `"AltRight"` | `"AltRight"` |
| `kVK_RightControl (0x3E)` | `"ControlRight"` | `"ControlRight"` |
| `kVK_Function (0x3F)` | `"Fn"` （⚠ 実際にはイベントが発生しない） | `""` （❌ 検出不可） （⚠ 実際にはイベントが発生しない） |
| `kVK_F17 (0x40)` | `"F17"` | `"F17"` |
| `kVK_ANSI_KeypadDecimal (0x41)` | `"NumpadDecimal"` | `"NumpadDecimal"` |
| `kVK_ANSI_KeypadMultiply (0x43)` | `"NumpadMultiply"` | `"NumpadMultiply"` |
| `kVK_ANSI_KeypadPlus (0x45)` | `"NumpadAdd"` | `"NumpadAdd"` |
| `kVK_ANSI_KeypadClear (0x47)` | `"NumLock"` | `"NumLock"` |
| `kVK_VolumeUp (0x48)` | `"VolumeUp"`（⚠ Chromium とは異なる） | `"AudioVolumeUp"` （Chromium 1 より前は `"VolumeUp"`）（⚠ Firefox とは異なる） |
| `kVK_VolumeDown (0x49)` | `"VolumeDown"`（⚠ Chromium とは異なる） | `"AudioVolumeDown"`（Chromium 52 より前は `"VolumeDown"`）（⚠ Firefox とは異なる） |
| `kVK_Mute (0x4A)` | `"VolumeMute"`（⚠ Chromium とは異なる） | `"AudioVolumeMute"`（Chromium 52 より前は `"VolumeMute"`）（⚠ Firefox とは異なる） |
| `kVK_ANSI_KeypadDivide (0x4B)` | `"NumpadDivide"` | `"NumpadDivide"` |
| `kVK_ANSI_KeypadEnter (0x4C)` | `"NumpadEnter"` | `"NumpadEnter"` |
| `kVK_ANSI_KeypadMinus (0x4E)` | `"NumpadSubtract"` | `"NumpadSubtract"` |
| `kVK_F18 (0x4F)` | `"F18"` | `"F18"` |
| `kVK_F19 (0x50)` | `"F19"` | `"F19"` |
| `kVK_ANSI_KeypadEquals (0x51)` | `"NumpadEqual"` | `"NumpadEqual"` |
| `kVK_ANSI_Keypad0 (0x52)` | `"Numpad0"` | `"Numpad0"` |
| `kVK_ANSI_Keypad1 (0x53)` | `"Numpad1"` | `"Numpad1"` |
| `kVK_ANSI_Keypad2 (0x54)` | `"Numpad2"` | `"Numpad2"` |
| `kVK_ANSI_Keypad3 (0x55)` | `"Numpad3"` | `"Numpad3"` |
| `kVK_ANSI_Keypad4 (0x56)` | `"Numpad4"` | `"Numpad4"` |
| `kVK_ANSI_Keypad5 (0x57)` | `"Numpad5"` | `"Numpad5"` |
| `kVK_ANSI_Keypad6 (0x58)` | `"Numpad6"` | `"Numpad6"` |
| `kVK_ANSI_Keypad7 (0x59)` | `"Numpad7"` | `"Numpad7"` |
| `kVK_F20 (0x5A)` | `"F20"` | `"F20"` |
| `kVK_ANSI_Keypad8 (0x5B)` | `"Numpad8"` | `"Numpad8"` |
| `kVK_ANSI_Keypad9 (0x5C)` | `"Numpad9"` | `"Numpad9"` |
| `kVK_JIS_Yen (0x5D)` | `"IntlYen"` | `"IntlYen"` |
| `kVK_JIS_Underscore (0x5E)` | `"IntlRo"` | `"IntlRo"` |
| `kVK_JIS_KeypadComma (0x5F)` | `"NumpadComma"` | `"NumpadComma"` |
| `kVK_F5 (0x60)` | `"F5"` | `"F5"` |
| `kVK_F6 (0x61)` | `"F6"` | `"F6"` |
| `kVK_F7 (0x62)` | `"F7"` | `"F7"` |
| `kVK_F3 (0x63)` | `"F3"` | `"F3"` |
| `kVK_F8 (0x64)` | `"F8"` | `"F8"` |
| `kVK_F9 (0x65)` | `"F9"` | `"F9"` |
| `kVK_JIS_Eisu (0x66)` | `"Lang2"` | `"Lang2"`（Chromium 82 より前は `""`）（⚠ 実際にはイベントが発生しない） |
| `kVK_F11 (0x67)` | `"F11"` | `"F11"` |
| `kVK_JIS_Kana (0x68)` | `"Lang1"` | `"Lang1"`（Chromium 82 より前は `"KanaMode"`）（⚠ 実際にはイベントが発生しない） |
| `kVK_F13 (0x69)` | `"F13"` | `"F13"` |
| `kVK_F16 (0x6A)` | `"F16"` | `"F16"` |
| `kVK_F14 (0x6B)` | `"F14"` | `"F14"` |
| `kVK_F10 (0x6D)` | `"F10"` | `"F10"` |
| コンテキストメニューキー (`0x6E`) | `"ContextMenu"` | `"ContextMenu"` |
| `kVK_F12 (0x6F)` | `"F12"` | `"F12"` |
| `kVK_F15 (0x71)` | `"F15"` | `"F15"` |
| `kVK_Help (0x72)` | `"Help"`（⚠ Chromium とは異なる） | `"Insert"`（⚠ Firefox とは異なる） |
| `kVK_Home (0x73)` | `"Home"` | `"Home"` |
| `kVK_PageUp (0x74)` | `"PageUp"` | `"PageUp"` |
| `kVK_ForwardDelete (0x75)` | `"Delete"` | `"Delete"` |
| `kVK_F4 (0x76)` | `"F4"` | `"F4"` |
| `kVK_End (0x77)` | `"End"` | `"End"` |
| `kVK_F2 (0x78)` | `"F2"` | `"F2"` |
| `kVK_PageDown (0x79)` | `"PageDown"` | `"PageDown"` |
| `kVK_F1 (0x7A)` | `"F1"` | `"F1"` |
| `kVK_LeftArrow (0x7B)` | `"ArrowLeft"` | `"ArrowLeft"` |
| `kVK_RightArrow (0x7C)` | `"ArrowRight"` | `"ArrowRight"` |
| `kVK_DownArrow (0x7D)` | `"ArrowDown"` | `"ArrowDown"` |
| `kVK_UpArrow (0x7E)` | `"ArrowUp"` | `"ArrowUp"` |

Linux (X11) のコード値
-----------------

Xはキーがあまりにも多く、通常のキーボードではテストできないものがあることに注意してください。そこで、以下の表はスキャンコードからコード値への割り当てられたソースコードから作成したものです。

セル内の「（❌ 検出不可）」は、このコード値がこのブラウザーで検出できないことを意味しています。

| スキャンコード (hardware\_keycode) | Firefox | Chromium |
| --- | --- | --- |
| `0x0009` | `"Escape"` | `"Escape"` |
| `0x000A` | `"Digit1"` | `"Digit1"` |
| `0x000B` | `"Digit2"` | `"Digit2"` |
| `0x000C` | `"Digit3"` | `"Digit3"` |
| `0x000D` | `"Digit4"` | `"Digit4"` |
| `0x000E` | `"Digit5"` | `"Digit5"` |
| `0x000F` | `"Digit6"` | `"Digit6"` |
| `0x0010` | `"Digit7"` | `"Digit7"` |
| `0x0011` | `"Digit8"` | `"Digit8"` |
| `0x0012` | `"Digit9"` | `"Digit9"` |
| `0x0013` | `"Digit0"` | `"Digit0"` |
| `0x0014` | `"Minus"` | `"Minus"` |
| `0x0015` | `"Equal"` | `"Equal"` |
| `0x0016` | `"Backspace"` | `"Backspace"` |
| `0x0017` | `"Tab"` | `"Tab"` |
| `0x0018` | `"KeyQ"` | `"KeyQ"` |
| `0x0019` | `"KeyW"` | `"KeyW"` |
| `0x001A` | `"KeyE"` | `"KeyE"` |
| `0x001B` | `"KeyR"` | `"KeyR"` |
| `0x001C` | `"KeyT"` | `"KeyT"` |
| `0x001D` | `"KeyY"` | `"KeyY"` |
| `0x001E` | `"KeyU"` | `"KeyU"` |
| `0x001F` | `"KeyI"` | `"KeyI"` |
| `0x0020` | `"KeyO"` | `"KeyO"` |
| `0x0021` | `"KeyP"` | `"KeyP"` |
| `0x0022` | `"BracketLeft"` | `"BracketLeft"` |
| `0x0023` | `"BracketRight"` | `"BracketRight"` |
| `0x0024` | `"Enter"` | `"Enter"` |
| `0x0025` | `"ControlLeft"` | `"ControlLeft"` |
| `0x0026` | `"KeyA"` | `"KeyA"` |
| `0x0027` | `"KeyS"` | `"KeyS"` |
| `0x0028` | `"KeyD"` | `"KeyD"` |
| `0x0029` | `"KeyF"` | `"KeyF"` |
| `0x002A` | `"KeyG"` | `"KeyG"` |
| `0x002B` | `"KeyH"` | `"KeyH"` |
| `0x002C` | `"KeyJ"` | `"KeyJ"` |
| `0x002D` | `"KeyK"` | `"KeyK"` |
| `0x002E` | `"KeyL"` | `"KeyL"` |
| `0x002F` | `"Semicolon"` | `"Semicolon"` |
| `0x0030` | `"Quote"` | `"Quote"` |
| `0x0031` | `"Backquote"` | `"Backquote"` |
| `0x0032` | `"ShiftLeft"` | `"ShiftLeft"` |
| `0x0033` | `"Backslash"` | `"Backslash"` |
| `0x0034` | `"KeyZ"` | `"KeyZ"` |
| `0x0035` | `"KeyX"` | `"KeyX"` |
| `0x0036` | `"KeyC"` | `"KeyC"` |
| `0x0037` | `"KeyV"` | `"KeyV"` |
| `0x0038` | `"KeyB"` | `"KeyB"` |
| `0x0039` | `"KeyN"` | `"KeyN"` |
| `0x003A` | `"KeyM"` | `"KeyM"` |
| `0x003B` | `"Comma"` | `"Comma"` |
| `0x003C` | `"Period"` | `"Period"` |
| `0x003D` | `"Slash"` | `"Slash"` |
| `0x003E` | `"ShiftRight"` | `"ShiftRight"` |
| `0x003F` | `"NumpadMultiply"` | `"NumpadMultiply"` |
| `0x0040` | `"AltLeft"` | `"AltLeft"` |
| `0x0041` | `"Space"` | `"Space"` |
| `0x0042` | `"CapsLock"` | `"CapsLock"` |
| `0x0043` | `"F1"` | `"F1"` |
| `0x0044` | `"F2"` | `"F2"` |
| `0x0045` | `"F3"` | `"F3"` |
| `0x0046` | `"F4"` | `"F4"` |
| `0x0047` | `"F5"` | `"F5"` |
| `0x0048` | `"F6"` | `"F6"` |
| `0x0049` | `"F7"` | `"F7"` |
| `0x004A` | `"F8"` | `"F8"` |
| `0x004B` | `"F9"` | `"F9"` |
| `0x004C` | `"F10"` | `"F10"` |
| `0x004D` | `"NumLock"` | `"NumLock"` |
| `0x004E` | `"ScrollLock"` | `"ScrollLock"` |
| `0x004F` | `"Numpad7"` | `"Numpad7"` |
| `0x0050` | `"Numpad8"` | `"Numpad8"` |
| `0x0051` | `"Numpad9"` | `"Numpad9"` |
| `0x0052` | `"NumpadSubtract"` | `"NumpadSubtract"` |
| `0x0053` | `"Numpad4"` | `"Numpad4"` |
| `0x0054` | `"Numpad5"` | `"Numpad5"` |
| `0x0055` | `"Numpad6"` | `"Numpad6"` |
| `0x0056` | `"NumpadAdd"` | `"NumpadAdd"` |
| `0x0057` | `"Numpad1"` | `"Numpad1"` |
| `0x0058` | `"Numpad2"` | `"Numpad2"` |
| `0x0059` | `"Numpad3"` | `"Numpad3"` |
| `0x005A` | `"Numpad0"` | `"Numpad0"` |
| `0x005B` | `"NumpadDecimal"` | `"NumpadDecimal"` |
| `0x005C` | `"Unidentified"` | `""` |
| `0x005D` | `"Unidentified"` （❌ 検出不可） | `"Lang5"`（Chromium 48 より前は `""`）（⚠ Firefox とは異なる） |
| `0x005E` | `"IntlBackslash"` | `"IntlBackslash"` |
| `0x005F` | `"F11"` | `"F11"` |
| `0x0060` | `"F12"` | `"F12"` |
| `0x0061` | `"IntlRo"` | `"IntlRo"` |
| `0x0062` | `"Unidentified"` （❌ 検出不可） | `"Lang3"`（⚠ Firefox とは異なる） |
| `0x0063` | `"Unidentified"` （❌ 検出不可） | `"Lang4"`（⚠ Firefox とは異なる） |
| `0x0064` | `"Convert"` | `"Convert"` |
| `0x0065` | `"KanaMode"` | `"KanaMode"` |
| `0x0066` | `"NonConvert"` | `"NonConvert"` |
| `0x0067` | `"Unidentified"` | `""` |
| `0x0068` | `"NumpadEnter"` | `"NumpadEnter"` |
| `0x0069` | `"ControlRight"` | `"ControlRight"` |
| `0x006A` | `"NumpadDivide"` | `"NumpadDivide"` |
| `0x006B` | `"PrintScreen"` | `"PrintScreen"` |
| `0x006C` | `"AltRight"` | `"AltRight"` |
| `0x006D` | `"Unidentified"` | `""` |
| `0x006E` | `"Home"` | `"Home"` |
| `0x006F` | `"ArrowUp"` | `"ArrowUp"` |
| `0x0070` | `"PageUp"` | `"PageUp"` |
| `0x0071` | `"ArrowLeft"` | `"ArrowLeft"` |
| `0x0072` | `"ArrowRight"` | `"ArrowRight"` |
| `0x0073` | `"End"` | `"End"` |
| `0x0074` | `"ArrowDown"` | `"ArrowDown"` |
| `0x0075` | `"PageDown"` | `"PageDown"` |
| `0x0076` | `"Insert"` | `"Insert"` |
| `0x0077` | `"Delete"` | `"Delete"` |
| `0x0078` | `"Unidentified"` | `""` |
| `0x0079` | `"VolumeMute"`（⚠ Chromium とは異なる） | `"AudioVolumeMute"`（Chromium 52 以前では `"VolumeMute"`）（⚠ Firefox とは異なる） |
| `0x007A` | `"VolumeDown"`（⚠ Chromium とは異なる） | `"AudioVolumeDown"`（Chromium 52 以前では `"VolumeDown"`）（⚠ Firefox とは異なる） |
| `0x007B` | `"VolumeUp"`（⚠ Chromium とは異なる） | `"AudioVolumeUp"`（Chromium 52 以前では `"VolumeUp"`）（⚠ Firefox とは異なる） |
| `0x007C` | `"Unidentified"` （❌ 検出不可） | `"Power"`（⚠ Firefox とは異なる） |
| `0x007D` | `"NumpadEqual"` | `"NumpadEqual"` |
| `0x007E` | `"Unidentified"` | `""` |
| `0x007F` | `"Pause"` | `"Pause"` |
| `0x0080` | `"Unidentified"` | `""` |
| `0x0081` | `"NumpadComma"` | `"NumpadComma"` |
| `0x0082` | `"Lang1"` | `"Lang1"` |
| `0x0083` | `"Lang2"` | `"Lang2"` |
| `0x0084` | `"IntlYen"` | `"IntlYen"` |
| `0x0085` | `"MetaLeft"`（Firefox 118 より前は `"OSLeft"`） | `"MetaLeft"`（Chromium 52 より前は `"OSLeft"`） |
| `0x0086` | `"MetaRight"`（Firefox 118 より前は `"OSRight"`） | `"MetaRight"`（Chromium 52 より前は `"OSRight"`） |
| `0x0087` | `"ContextMenu"` | `"ContextMenu"` |
| `0x0088` | `"BrowserStop"` | `"BrowserStop"`（Chromium 48 より前は `"Abort"`） |
| `0x0089` | `"Again"` | `"Again"` |
| `0x008A` | `"Props"`（⚠ Chromium とは異なる） | `""` （❌ 検出不可） |
| `0x008B` | `"Undo"` | `"Undo"` |
| `0x008C` | `"Select"` | `"Select"`（Chromium 48 より前は `""`） |
| `0x008D` | `"Copy"` | `"Copy"` |
| `0x008E` | `"Open"` | `"Open"`（Chromium 48 より前は `""`） |
| `0x008F` | `"Paste"` | `"Paste"` |
| `0x0090` | `"Find"` | `"Find"` |
| `0x0091` | `"Cut"` | `"Cut"` |
| `0x0092` | `"Help"` | `"Help"` |
| `0x0093` | `"Unidentified"` | `""` |
| `0x0094` | `"LaunchApp2"` | `"LaunchApp2"` |
| `0x0095` | `"Unidentified"` | `""` |
| `0x0096` | `"Unidentified"` （❌ 検出不可） | `"Sleep"`（⚠ Firefox とは異なる） |
| `0x0097` | `"WakeUp"` | `"WakeUp"` |
| `0x0098` | `"LaunchApp1"` | `"LaunchApp1"`（Chromium 48 より前は `""`） |
| `0x0099` ～ `0x00A2` | `"Unidentified"` | `""` |
| `0x00A3` | `"LaunchMail"` | `"LaunchMail"`（Chromium 51 より前は `""`） |
| `0x00A4` | `"BrowserFavorites"` | `"BrowserFavorites"` |
| `0x00A5` | `"Unidentified"` | `""` |
| `0x00A6` | `"BrowserBack"` | `"BrowserBack"` |
| `0x00A7` | `"BrowserForward"` | `"BrowserForward"` |
| `0x00A8` | `"Unidentified"` | `""` |
| `0x00A9` | `"Eject"` | `"Eject"`（Chromium 48 より前は `""`） |
| `0x00AA` | `"Unidentified"` | `""` |
| `0x00AB` | `"MediaTrackNext"` | `"MediaTrackNext"`（Chromium 48 より前は `""`） |
| `0x00AC` | `"MediaPlayPause"` | `"MediaPlayPause"`（Chromium 48 より前は `""`） |
| `0x00AD` | `"MediaTrackPrevious"` | `"MediaTrackPrevious"`（Chromium 48 より前は `""`） |
| `0x00AE` | `"MediaStop"` | `"MediaStop"`（Chromium 48 より前は `""`） |
| `0x00AF` ～ `0x00B2` | `"Unidentified"` | `""` |
| `0x00B3` | `"MediaSelect"` | `"MediaSelect"`（Chromium 48 より前は `""`） |
| `0x00B4` | `"BrowserHome"` | `"BrowserHome"`（Chromium 48 より前は `""`） |
| `0x00B5` | `"BrowserRefresh"` | `"BrowserRefresh"` |
| `0x00B6` ～ `0x00BA` | `"Unidentified"` | `""` |
| `0x00BB` | `"Unidentified"` （❌ 検出不可） | `"NumpadParenLeft"`（⚠ Firefox とは異なる） |
| `0x00BC` | `"Unidentified"` （❌ 検出不可） | `"NumpadParenRight"`（⚠ Firefox とは異なる） |
| `0x00BD`, `0x00BE` | `"Unidentified"` | `""` |
| `0x00BF` | `"F13"` | `"F13"` |
| `0x00C0` | `"F14"` | `"F14"` |
| `0x00C1` | `"F15"` | `"F15"` |
| `0x00C2` | `"F16"` | `"F16"` |
| `0x00C3` | `"F17"` | `"F17"` |
| `0x00C4` | `"F18"` | `"F18"` |
| `0x00C5` | `"F19"` | `"F19"` |
| `0x00C6` | `"F20"` | `"F20"` |
| `0x00C7` | `"F21"` | `"F21"` |
| `0x00C8` | `"F22"` | `"F22"` |
| `0x00C9` | `"F23"` | `"F23"` |
| `0x00CA` | `"F24"` | `"F24"` |
| `0x00CB ～ 0x00E0` | `"Unidentified"` | `""` |
| `0x00E1` | `"BrowserSearch"`（⚠ Chromium とは異なる） | `"BrowserSearch"`（Chromium 48 より前は `"BrightnessUp"`） |

Android 版 Firefox のコード値
-----------------------

| スキャンコード | Firefox |
| --- | --- |
| `0x0001` | `"Escape"` |
| `0x0002` | `"Digit1"` |
| `0x0003` | `"Digit2"` |
| `0x0004` | `"Digit3"` |
| `0x0005` | `"Digit4"` |
| `0x0006` | `"Digit5"` |
| `0x0007` | `"Digit6"` |
| `0x0008` | `"Digit7"` |
| `0x0009` | `"Digit8"` |
| `0x000A` | `"Digit9"` |
| `0x000B` | `"Digit0"` |
| `0x000C` | `"Minus"` |
| `0x000D` | `"Equal"` |
| `0x000E` | `"Backspace"` |
| `0x000F` | `"Tab"` |
| `0x0010` | `"KeyQ"` |
| `0x0011` | `"KeyW"` |
| `0x0012` | `"KeyE"` |
| `0x0013` | `"KeyR"` |
| `0x0014` | `"KeyT"` |
| `0x0015` | `"KeyY"` |
| `0x0016` | `"KeyU"` |
| `0x0017` | `"KeyI"` |
| `0x0018` | `"KeyO"` |
| `0x0019` | `"KeyP"` |
| `0x001A` | `"BracketLeft"` |
| `0x001B` | `"BracketRight"` |
| `0x001C` | `"Enter"` |
| `0x001D` | `"ControlLeft"` |
| `0x001E` | `"KeyA"` |
| `0x001F` | `"KeyS"` |
| `0x0020` | `"KeyD"` |
| `0x0021` | `"KeyF"` |
| `0x0022` | `"KeyG"` |
| `0x0023` | `"KeyH"` |
| `0x0024` | `"KeyJ"` |
| `0x0025` | `"KeyK"` |
| `0x0026` | `"KeyL"` |
| `0x0027` | `"Semicolon"` |
| `0x0028` | `"Quote"` |
| `0x0029` | `"Backquote"` |
| `0x002A` | `"ShiftLeft"` |
| `0x002B` | `"Backslash"` |
| `0x002C` | `"KeyZ"` |
| `0x002D` | `"KeyX"` |
| `0x002E` | `"KeyC"` |
| `0x002F` | `"KeyV"` |
| `0x0030` | `"KeyB"` |
| `0x0031` | `"KeyN"` |
| `0x0032` | `"KeyM"` |
| `0x0033` | `"Comma"` |
| `0x0034` | `"Period"` |
| `0x0035` | `"Slash"` |
| `0x0036` | `"ShiftRight"` |
| `0x0037` | `"NumpadMultiply"` |
| `0x0038` | `"AltLeft"` |
| `0x0039` | `"Space"` |
| `0x003A` | `"CapsLock"` |
| `0x003B` | `"F1"` |
| `0x003C` | `"F2"` |
| `0x003D` | `"F3"` |
| `0x003E` | `"F4"` |
| `0x003F` | `"F5"` |
| `0x0040` | `"F6"` |
| `0x0041` | `"F7"` |
| `0x0042` | `"F8"` |
| `0x0043` | `"F9"` |
| `0x0044` | `"F10"` |
| `0x0045` | `"NumLock"` |
| `0x0046` | `"ScrollLock"` |
| `0x0047` | `"Numpad7"` |
| `0x0048` | `"Numpad8"` |
| `0x0049` | `"Numpad9"` |
| `0x004A` | `"NumpadSubtract"` |
| `0x004B` | `"Numpad4"` |
| `0x004C` | `"Numpad5"` |
| `0x004D` | `"Numpad6"` |
| `0x004E` | `"NumpadAdd"` |
| `0x004F` | `"Numpad1"` |
| `0x0050` | `"Numpad2"` |
| `0x0051` | `"Numpad3"` |
| `0x0052` | `"Numpad0"` |
| `0x0053` | `"NumpadDecimal"` |
| `0x0054`, `0x0055` | `"Unidentified"` |
| `0x0056` | `"IntlBackslash"` |
| `0x0057` | `"F11"` |
| `0x0058` | `"F12"` |
| `0x0059` | `"IntlRo"` |
| `0x005A`, `0x005B` | `"Unidentified"` |
| `0x005C` | `"Convert"` |
| `0x005D` | `"KanaMode"` |
| `0x005E` | `"NonConvert"` |
| `0x005F` | `"Unidentified"` |
| `0x0060` | `"NumpadEnter"` |
| `0x0061` | `"ControlRight"` |
| `0x0062` | `"NumpadDivide"` |
| `0x0063` | `"PrintScreen"` |
| `0x0064` | `"AltRight"` |
| `0x0065` | `"Unidentified"` |
| `0x0066` | `"Home"` |
| `0x0067` | `"ArrowUp"` |
| `0x0068` | `"PageUp"` |
| `0x0069` | `"ArrowLeft"` |
| `0x006A` | `"ArrowRight"` |
| `0x006B` | `"End"` |
| `0x006C` | `"ArrowDown"` |
| `0x006D` | `"PageDown"` |
| `0x006E` | `"Insert"` |
| `0x006F` | `"Delete"` |
| `0x0070` | `"Unidentified"` |
| `0x0071` | `"VolumeMute"` |
| `0x0072` | `"VolumeDown"` |
| `0x0073` | `"VolumeUp"` |
| `0x0074` | `"Power"` |
| `0x0075` | `"NumpadEqual"` |
| `0x0076` | `"Unidentified"` |
| `0x0077` | `"Pause"` |
| `0x0078` | `"Unidentified"` |
| `0x0079` | `"NumpadComma"` |
| `0x007A` | `"Lang1"` |
| `0x007B` | `"Lang2"` |
| `0x007C` | `"IntlYen"` |
| `0x007D` | `"MetaLeft"`（Firefox 118 より前は `"OSLeft"`） |
| `0x007E` | `"MetaRight"`（Firefox 118 より前は `"OSRight"`） |
| `0x007F` | `"ContextMenu"` |
| `0x0080` | `"BrowserStop"` |
| `0x0081` | "Again" |
| `0x0082` | `"Props"` |
| `0x0083` | `"Undo"` |
| `0x0084` | `"Select"` |
| `0x0085` | `"Copy"` |
| `0x0086` | `"Open"` |
| `0x0087` | `"Paste"` |
| `0x0088` | `"Find"` |
| `0x0089` | `"Cut"` |
| `0x008A` | `"Help"` |
| `0x008B` ～ `0x008D` | `"Unidentified"` |
| `0x008E` | `"Sleep"` |
| `0x008F` | `"WakeUp"` |
| `0x0090` | `"LaunchApp1"` |
| `0x0091` ～ `0x009B` | `"Unidentified"` |
| `0x009C` | `"BrowserFavorites"` |
| `0x009D` | `"Unidentified"` |
| `0x009E` | `"BrowserBack"` |
| `0x009F` | `"BrowserForward"` |
| `0x00A0` | `"Unidentified"` |
| `0x00A1` | `"Eject"` |
| `0x00A2` | `"Unidentified"` |
| `0x00A3` | `"MediaTrackNext"` |
| `0x00A4` | `"MediaPlayPause"` |
| `0x00A5` | `"MediaTrackPrevious"` |
| `0x00A6` | `"MediaStop"` |
| `0x00A7` ～ `0x00AC` | `"Unidentified"` |
| `0x00AD` | `"BrowserRefresh"` |
| `0x00AE` ～ `0x00B6` | "Unidentified" |
| `0x00B7` | `"F13"` |
| `0x00B8` | `"F14"` |
| `0x00B9` | `"F15"` |
| `0x00BA` | `"F16"` |
| `0x00BB` | `"F17"` |
| `0x00BC` | `"F18"` |
| `0x00BD` | `"F19"` |
| `0x00BE` | `"F20"` |
| `0x00BF` | `"F21"` |
| `0x00C0` | `"F22"` |
| `0x00C1` | `"F23"` |
| `0x00C2` | `"F24"` |
| `0x00C3` ～ `0x00D8` | `"Unidentified"` |
| `0x00D9` | `"BrowserSearch"` |
| `0x00DA` ～ `0x01CF` | `"Unidentified"` |
| `0x01D0` | `"Fn"` |