OculusQuestのブラウザのブックマークや履歴にPCからアクセスする



Oculus QuestのブラウザのブックマークをPCからインポート/エクスポートする方法が見当たらなくて調べたときのメモです．
仕様はOculusBrowserのアップデートで変わることもあるので，バージョンによってはそのまま動かないかもしれません．

# Chromeのリモートデバッグで接続する

### 事前準備
- ADBで接続可能である必要があります． https://qiita.com/kamejojo/items/11ddfa2b52c026885c61
- WiFi経由でADBにつないでおくと便利です． https://qiita.com/divideby_zero/items/1151ab760cbaa3d43d45

ADBでOculusQuestに接続できるのを確認した上で PC の Chrome で `chrome://inspect` を開きます．

![inspect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/3cc9ba0a-1233-c5ae-7d94-38dae4128282.png)

数秒待つと，OculusQuestのブラウザで開いているタブが列挙されると思います．
その中から，`Panel App Nav UI` (`chrome://panel-app-nav/`) というタブを探して `inspect` をクリックします．

# コンソールからブラウザを操作する

OculusQuestのブラウザのUIはHTMLで実装されていて，`Panel App Nav UI` というタブとして扱われているので，コンソールからJavaScriptで操作できます．

`window.contentNavUI.webUIChannelRouter` を介して，ブラウザのネイティブコードとUIの間のやり取りをしているようです．あまりjsのフレームワークに明るくないですがReactっぽい．

とりあえず，以下のように`doAction` (UI→ネイティブコード) と `command` (ネイティブコード→UI) にログ仕込むと，ブラウザを操作したときにどんなやり取りがされてるのかが見れます．

```js
let orgaction = window.contentNavUI.webUIChannelRouter.doAction.bind(window.contentNavUI.webUIChannelRouter);
window.contentNavUI.webUIChannelRouter.doAction = (a,e) => {console.log("action", a,e); return orgaction(a,e)};
let orgcommand = window.contentNavUI.webUIChannelRouter.command.bind(window.contentNavUI.webUIChannelRouter);
window.contentNavUI.webUIChannelRouter.command = (c) => {console.log("command", c); return orgcommand(c)};
```

`doAction` の呼び出して，ブラウザの操作はほぼ全てできます．必要なパラメータは実際の呼び出しを見たり実装から推測してください．想定されてないパラメータを渡したりすると，ブラウザがクラッシュするので注意．

アクションの一覧は `window.contentNavUI.UiActions` に入っています．将来のバージョンのための実装が先に追加されていたりするので，定期的に眺めてると近いうちに実装される機能が想像できたりもします．最近のバージョンで `SelectAll`, `Copy`, `Paste` などが追加されたので近いうちにコピペできるようになるのかもしれません．(doActionで呼び出せますが，クリップボードを使いたいだけなら，通常のClipboard APIを呼び出すブックマークレットを登録しておくほうが便利です)

MessageBusのpublish/subscribeを呼べば，もう少し抽象度の高いAPIが使えそうですが，こちらはオブジェクトへのアクセスが面倒です．

## Tabの操作

タブを開く，閉じる，すべて閉じる等．

```js
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.NewTab, {launchUrl: "https://www.binzume.net/"});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.CloseCurrentTab, {});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.CloseAllTabs, {});
```

## ブックマークを追加する

`SaveBookmark` に，urlとtitleを渡します．既存のブックマークを変更する場合はidも指定します．

例：

```js
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'https://www.google.co.jp/', title:"Google", id:undefined});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'javascript:alert("hello!")', title:"Hello", id:undefined});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'chrome://quit', title:"Quit", id:undefined});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'chrome://crash', title:"Crash!", id:undefined});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'javascript:(()=>{let r=prompt("playbackRate","2");document.querySelectorAll("video").forEach(el=>el.playbackRate=r)})()', title:"PlaybackRate", id:undefined});
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'javascript:!function(){for(let a of document.querySelectorAll("A")){a.target="_blank"}}()', title:"OpenAsNewTab", id:undefined});
```

最近のOculusBrowserは http, https から始まるURLのみしかブックマークできず，ブックマークレット等を使えなくて不便ですが，直接 SaveBookmarkを呼べばjavascriptを登録できるので便利です．

あと，OculusBrowserのWebXRで動くコンテンツを作っているとよくブラウザが不安定になったり壊れたりするので`chrome://quit`をブックマークに入れておくと捗ります．

## ブックマークを取得

Bookmark一覧を取得する専用のアクションは見当たらないので，適当なブックマークを登録して，変更の通知を受け取ります．

```js
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.SaveBookmark, {url:'about:blank', title:"blank",});
```

結果は webUIChannelRouter.command() 関数に渡されます．
setBookmarksに全てのブックマークが入っています．

```js
{
  setBookmarks:[{id, title, url},...]
}
```

ブックマークをエクスポートしたいなら，command関数でJSONでログを出すようにして，Chrome上でコピーするのが手っ取り早いです．

## History を取得

通常のUIからは使えませんが，searchTextでキーワードを指定できます．履歴は3ヶ月で消えるようです．

```js
contentNavUI.webUIChannelRouter.doAction(contentNavUI.UiActions.QueryHistory, {searchText: "", maxResults: 1000});
```

結果は webUIChannelRouter.command() 関数に渡されます．

```js
{
  updateHistory:{
    items:[{date, host, id, url, title},...],
    maxResults: 1000,
    reachedBeginning: false,
    searchText: ""
  }
}
```

## ブラウザが真っ白になる

インスペクタからPanel App Nav UIのタブを操作したり，エラーが発生すると，ブラウザの表示が壊れたり真っ白になったりします．

UIからは明示的にブラウザを終了できない気がするので，`chrome://quit` をブックマークしておくと便利です．