Window Management API を使用して複数のディスプレイを管理する

接続されたディスプレイに関する情報を取得し、それらのディスプレイに対してウィンドウを相対的に配置します。

ウィンドウ管理 API

Window Management API を使用すると、マシンに接続されているディスプレイを列挙し、特定の画面にウィンドウを配置できます。

推奨されるユースケース

この API を使用するサイトの例:

  • Gimp のマルチウィンドウ グラフィック エディタでは、さまざまな編集ツールをウィンドウを正確に配置できます。
  • 仮想取引デスクは、複数のウィンドウで市場動向を表示できます。ウィンドウは全画面モードで表示できます。
  • スライドショー アプリでは、内部のメイン画面にスピーカー ノートを表示し、外部プロジェクターにプレゼンテーションを表示できます。

Window Management API の使用方法

問題

ウィンドウを制御する実績のあるアプローチである Window.open() では、残念ながら、追加の画面を認識していません。この API には、windowFeatures DOMString パラメータなど、少し古さを感じるものもありますが、それでも長年にわたって Google の役目を果たしてきました。ウィンドウの位置を指定するには、座標を lefttop(またはそれぞれ screenXscreenY)として渡し、目的のサイズwidthheight(またはそれぞれ innerWidthinnerHeight)として渡します。たとえば、左から 50 ピクセル、上から 50 ピクセルの位置に 400×300 のウィンドウを開くには、次のコードを使用します。

const popup = window.open(
  'https://example.com/',
  'My Popup',
  'left=50,top=50,width=400,height=300',
);

現在の画面に関する情報を取得するには、Screen オブジェクトを返す window.screen プロパティを確認します。MacBook Pro 13 インチでの出力は以下のようになります。

window.screen;
/* Output from my MacBook Pro 13″:
  availHeight: 969
  availLeft: 0
  availTop: 25
  availWidth: 1680
  colorDepth: 30
  height: 1050
  isExtended: true
  onchange: null
  orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
  pixelDepth: 30
  width: 1680
*/

テクノロジー業界で働くほとんどの人々と同様に、私も新しい仕事の現実に適応し、個人用のホームオフィスを設立する必要がありました。私のものは、以下の写真のように表示されます(興味があれば、私の設定の詳細をご覧ください)。MacBook の隣の iPad はサイドカー経由でノートパソコンに接続されているため、必要なときにすぐに iPad を 2 台目の画面に変えることができます。

椅子 2 脚の上のスクールベンチ。学校のベンチの上には、ノートパソコンとその周りを囲む 2 台の iPad を支える靴の箱がある。
マルチスクリーンの設定。

大きな画面を活用したい場合は、上記のコードサンプルのポップアップを 2 番目の画面に配置します。方法は次のとおりです。

popup.moveTo(2500, 50);

2 つ目の画面の寸法を把握する方法がないため、これはおおよその目安です。window.screen からの情報でカバーされるのは内蔵画面のみで、iPad の画面は対象外です。報告された組み込み画面の width1680 ピクセルでした。2500 ピクセルに移動すると、ウィンドウが iPad にシフトする可能性があります。なぜなら、それが MacBook の右側に位置していることがわかったからです。一般的なケースではどのようにすればよいですか?結局、推測するよりよい方法があります。これが Window Management API です

機能検出

Window Management API がサポートされているかどうかを確認するには、次のコマンドを使用します。

if ('getScreenDetails' in window) {
  // The Window Management API is supported.
}

window-management 権限

Window Management API を使用するには、ユーザーにその権限をリクエストする必要があります。window-management 権限は、次のように Permissions API を使用してクエリできます。

let granted = false;
try {
  const { state } = await navigator.permissions.query({ name: 'window-management' });
  granted = state === 'granted';
} catch {
  // Nothing.
}

古い権限名と新しい権限名を持つブラウザを使用している間は、権限をリクエストするときは、以下の例のように防御的なコードを使用するようにしてください。

async function getWindowManagementPermissionState() {
  let state;
  // The new permission name.
  try {
    ({ state } = await navigator.permissions.query({
      name: "window-management",
    }));
  } catch (err) {
    return `${err.name}: ${err.message}`;
  }
  return state;
}

document.querySelector("button").addEventListener("click", async () => {
  const state = await getWindowManagementPermissionState();
  document.querySelector("pre").textContent = state;
});

ブラウザは、新しい API のメソッドを最初に使用する際に権限プロンプトを動的に表示することを選択できます。詳しくは以下をご覧ください。

window.screen.isExtended プロパティ

複数の画面がデバイスに接続されているかどうかを確認するには、window.screen.isExtended プロパティにアクセスします。true または false を返します。私の設定では、true が返されます。

window.screen.isExtended;
// Returns `true` or `false`.

getScreenDetails() メソッド

現在の設定がマルチスクリーンであることがわかったので、Window.getScreenDetails() を使用して 2 番目の画面に関する詳細情報を取得できます。この関数を呼び出すと、サイトがウィンドウを開いて画面に配置することを許可するかどうかを尋ねる権限プロンプトが表示されます。この関数は、ScreenDetailed オブジェクトで解決される Promise を返します。iPad が接続された MacBook Pro 13 では、2 つの ScreenDetailed オブジェクトを持つ screens フィールドがあります。

await window.getScreenDetails();
/* Output from my MacBook Pro 13″ with the iPad attached:
{
  currentScreen: ScreenDetailed {left: 0, top: 0, isPrimary: true, isInternal: true, devicePixelRatio: 2, …}
  oncurrentscreenchange: null
  onscreenschange: null
  screens: [{
    // The MacBook Pro
    availHeight: 969
    availLeft: 0
    availTop: 25
    availWidth: 1680
    colorDepth: 30
    devicePixelRatio: 2
    height: 1050
    isExtended: true
    isInternal: true
    isPrimary: true
    label: "Built-in Retina Display"
    left: 0
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 30
    top: 0
    width: 1680
  },
  {
    // The iPad
    availHeight: 999
    availLeft: 1680
    availTop: 25
    availWidth: 1366
    colorDepth: 24
    devicePixelRatio: 2
    height: 1024
    isExtended: true
    isInternal: false
    isPrimary: false
    label: "Sidecar Display (AirPlay)"
    left: 1680
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 24
    top: 0
    width: 1366
  }]
}
*/

接続された画面に関する情報は screens 配列で取得できます。iPad の left の値は 1680 から始まります。これは組み込みディスプレイの width とまったく同じです。これにより、画面がどのように論理的に(隣り合う、互いに上など)配置されているかを正確に判断できます。また、各画面に、それが isInternal であるかどうかと isPrimary であるかどうかを示すデータも追加されました。なお、組み込み画面は必ずしもプライマリ画面ではありません

currentScreen フィールドは、現在の window.screen に対応するライブ オブジェクトです。クロススクリーン ウィンドウの配置時やデバイスの変更時にオブジェクトが更新されます。

screenschange イベント

現在不足しているのは、画面設定が変わったことを検出する手段だけです。新しいイベント screenschange は、画面コンステレーションが変更されるたびに発生します。(イベント名では「screens」が複数形になっていることに注意してください)。つまり、新しい画面または既存の画面が(サイドカーの場合は物理的または仮想的に)接続または電源から外されると、イベントが発生します。

新しい画面の詳細は非同期的にルックアップする必要があり、screenschange イベント自体はこのデータを提供しません。画面の詳細を検索するには、キャッシュに保存された Screens インターフェースからライブ オブジェクトを使用します。

const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
screenDetails.addEventListener('screenschange', (event) => {
  if (screenDetails.screens.length !== cachedScreensLength) {
    console.log(
      `The screen count changed from ${cachedScreensLength} to ${screenDetails.screens.length}`,
    );
    cachedScreensLength = screenDetails.screens.length;
  }
});

currentscreenchange イベント

現在の画面に対する変更(つまり、ライブ オブジェクト currentScreen の値)のみに関心がある場合は、currentscreenchange イベントをリッスンできます。

const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
  const details = screenDetails.currentScreen;
  console.log('The current screen has changed.', event, details);
});

change イベント

最後に、具体的な画面に対する変更のみに関心がある場合は、その画面の change イベントをリッスンできます。

const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
  console.log('The first screen has changed.', event, firstScreen);
});

新しい全画面オプション

これまでは、その名前がちょうどよい requestFullScreen() メソッドを使って、要素を全画面モードで表示するようにリクエストできました。このメソッドは、FullscreenOptions を渡せる options パラメータを受け取ります。これまでのところ、プロパティは navigationUI のみです。Window Management API で、全画面ビューを開始する画面を決定できる新しい screen プロパティが追加されました。たとえば、プライマリ画面を全画面表示にする場合は、次のようにします。

try {
  const primaryScreen = (await getScreenDetails()).screens.filter((screen) => screen.isPrimary)[0];
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}

ポリフィル

Window Management API をポリフィルすることはできませんが、その形状をシム化することで、新しい API に対してのみコーディングを行うことができます。

if (!('getScreenDetails' in window)) {
  // Returning a one-element array with the current screen,
  // noting that there might be more.
  window.getScreenDetails = async () => [window.screen];
  // Set to `false`, noting that this might be a lie.
  window.screen.isExtended = false;
}

API の他の機能、すなわち FullscreenOptions のさまざまな画面変更イベントと screen プロパティは、非対応ブラウザでは起動しないか、暗黙的に無視されます。

デモ

私のような人なら、さまざまな暗号通貨の発展を注視しているのでしょう。(実際には、私はこの星を愛しているからはほとんどそうしていませんが、この記事では私がそうであったと仮定します)。所有する暗号通貨を追跡するために、あらゆる状況で市場を観察できるウェブアプリを開発しました。ベッドに座り、適切な 1 画面構成で市場を観察できるのです。

著者の脚が部分的に見えている、ベッドの端にある巨大なテレビ画面。画面には、偽の暗号通貨取引デスク。
リラックスして市場を見る。

暗号通貨に関することなので、市場は常に慌ただしくなる可能性があります。マルチスクリーン設定になっているデスクに すぐに移動できます任意の通貨のウィンドウをクリックすると 全画面表示で反対側の画面の詳細を確認できます以下は、前回の YCY Bangbath で撮影した最近の写真です。完全に油断し、両手を顔に当てた状態でした。

パニックの顔に手を当てて偽の暗号通貨取引デスクを見つめている著者。
Panicky、YCY の流血を目撃している。

以下に埋め込まれたデモを試すか、Glitch に関するソースコードを確認してください。

セキュリティと権限

Chrome チームは、強力なウェブ プラットフォーム機能へのアクセスの制御で定義されている基本原則(ユーザー制御、透明性、エルゴノミクスなど)を使用して Window Management API を設計、実装しています。Window Management API は、デバイスに接続されている画面に関する新しい情報を公開するため、特にデバイスに複数の画面が一貫して接続されているユーザーのフィンガープリント サーフェスが増加します。プライバシーに関する懸念を軽減するため、露出した画面のプロパティは、一般的なプレースメントのユースケースに必要な最小限の範囲に限定されます。サイトがマルチスクリーン情報を取得し、他の画面にウィンドウを配置するには、ユーザー権限が必要です。Chromium は詳細な画面ラベルを返しますが、ブラウザはよりわかりやすいもの(または空のラベル)を自由に返します。

ユーザー コントロール

ユーザーは、セットアップの露出を完全に制御できます。ユーザーはブラウザのサイト情報機能を使用して、権限プロンプトを承認または拒否したり、以前に付与された権限を取り消すことができます。

エンタープライズ コントロール

Chrome Enterprise のユーザーは、アトミック ポリシー グループ設定の関連セクションで説明されているように、Window Management API のいくつかの要素を制御できます。

透明性

Window Management API を使用する権限が付与されているかどうかは、ブラウザのサイト情報で公開され、Permissions API を使用してクエリすることもできます。

権限の永続性

ブラウザは権限の付与を継続します。権限は、ブラウザのサイト情報を使用して取り消すことができます。

フィードバック

Chrome チームでは、Window Management API の感想をお聞かせください。

API の設計についてお聞かせください

API に関して、想定したとおりに動作しない点はありますか。あるいは、アイデアを実装するために必要なメソッドやプロパティが欠落していないか?セキュリティ モデルについてご質問やご意見がある場合は、

  • 対応する GitHub リポジトリで仕様の問題を提出するか、既存の問題にご意見をお寄せください。

実装に関する問題を報告する

Chrome の実装にバグが見つかりましたか?それとも、実装が仕様と異なりますか?

  • new.crbug.com でバグを報告します。できる限り詳細な情報と再現手順を記載し、[Components] ボックスに「Blink>Screen>MultiScreen」と入力します。Glitch を使えば、再現をすばやく簡単に共有できます。

API のサポートを表示する

Window Management API を使用する予定はありますか。一般公開のサポートにより、Chrome チームは機能の優先順位付けを行うことができ、他のブラウザ ベンダーに対してサポートがいかに重要であるかを示します。

  • その使用方法を WICG Discourse スレッドで共有します。
  • ハッシュタグ #WindowManagement を使用して @ChromiumDev 宛てにツイートを送信し、使用場所と使用方法をお知らせください。
  • 他のブラウザ ベンダーに API の実装を依頼します。

関連情報

謝辞

Window Management API の仕様は、Victor CostanJoshua BellMike Wasserman が編集しました。API は Mike WassermanAdrienne Walker によって実装されました。この記事は、Joe MedleyFrançois BeaufortKayce Basques によってレビューされました。写真を提供してくれた Laura Torrent Puig 氏に感謝いたします。