Web App ManifestでWebアプリをインストール可能に

#はじめに
Thetaの360°画像にぼかしを入れるWebアプリ[SphereBlur.com](https://sphereblur.com)を[冬休みに作った](http://qiita.com/horo/items/abb8bf40c23b21ce6e5b)際に、Web App Manifestを登録することで、このWebアプリを簡単にインストール(ホーム画面に追加)できるようにした。このWebアプリは[Service Workerを使ってオフラインでも動くようにしている](http://qiita.com/horo/items/175c8fd7513138308930)ので、ホーム画面から起動し、ネイティブアプリと同等の体験を提供できる。もちろん、Manifestを使っていない通常のページでも、メニュー画面から「ホーム画面に追加」を選ぶことで同様のことはできるが、下の動画のように、下から出てくるAppインストールバナーのボタンを一回タップするだけ登録できるのは利便性が高い。

[![sphereblur.com](https://media.giphy.com/media/l0Nwz4PFYU1k03Ffi/giphy.gif)](https://youtu.be/Mt8fUwqyRhw)

#Appインストールバナーの出し方
[Google Developersのこの記事](https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android)によるとAppインストールバナーを出すには下記の条件を満たす必要がある。

>- Web App Manifestを登録していること
>- Service Workerを登録していること
>- HTTPSに対応していること
>- ユーザーが５分以上経過後に再び訪れること
>
>ただし、これらの条件は今後変更される可能性がある。

[SphereBlur.com](https://sphereblur.com)はすでにService Workerを使っているので、あとは、Web App Manifestを登録しておけば、頻繁に訪れてくれるユーザーに対してこのバナーを表示することができる。


#Manifestの登録の仕方
[SphereBlur.com](https://sphereblur.com)では、HTMLに下記のようなlinkエレメントを追加し、

```html:index.html
<link rel="manifest" href="manifest.json">
```

下記のようなmanifest.jsonを登録している。

```json:manifest.json
{
  "name": "Sphere Blur",
  "short_name": "Sphere Blur",
  "icons": [
    {"src": "img/sphereblur_36.png", "sizes": "36x36", "type": "image/png"},
    {"src": "img/sphereblur_48.png", "sizes": "48x48", "type": "image/png"},
    {"src": "img/sphereblur_72.png", "sizes": "72x72", "type": "image/png"},
    {"src": "img/sphereblur_96.png", "sizes": "96x96", "type": "image/png"},
    {"src": "img/sphereblur_144.png", "sizes": "144x144", "type": "image/png"},
    {"src": "img/sphereblur_192.png", "sizes": "192x192", "type": "image/png"},
    {"src": "img/sphereblur_256.png", "sizes": "256x256", "type": "image/png"},
    {"src": "img/sphereblur_512.png", "sizes": "512x512", "type": "image/png"}],
  "display": "standalone",
  "start_url": "/?homescreen",
  "display": "standalone"
}
```


Manifestファイルについては[こちらの記事](https://developers.google.com/web/updates/2014/11/Support-for-installable-web-apps-with-webapp-manifest-in-chrome-38-for-Android)がわかりやすい。こちらの[Web App Manifestの仕様](https://w3c.github.io/manifest/)にも書かれているが、"this specification is not stable"なので、今後変更される可能性があるので注意が必要。

#インストールのコントロール
Manifestファイルを`link`エレメントで登録するだけだと、Chromeによって勝手にバナーが表示されて、サイト管理者はユーザーの環境でバナーがいつ表示されたのか、ユーザーがインストールしたのかキャンセルしたのかを知ることができない。この問題に対処するため、Alex Russellが[こちら](https://github.com/slightlyoff/AppInstallImprovements/blob/master/explainer.md#controlling-installation)でBeforeInstallPromptイベントを提案している。BeforeInstallPromptイベントはインストールバナーが表示される直前に発行され、このイベントを使うと下記のようなことができる。


- インストールバナー表示のキャンセル
- インストールバナーの結果の検知
- インストールバナーを後で表示

下記のコードは[こちらのサンプル](https://googlechrome.github.io/samples/app-install-banner/)を参考にしている。

##インストールバナー表示をキャンセル

まだインストールバナーを出すべきではない場合は、BeforeInstallPromptEventオブジェクトの`preventDefault()`を呼ぶことで表示をキャンセルすることができる。

```js
window.addEventListener('beforeinstallprompt', function(e) {
    console.log('インストールバナーの表示をキャンセルします');
    e.preventDefault();
  });
```

##インストールバナーの結果の検知

`BeforeInstallPromptEvent.userChoice`が返すPromiseを使うことで、ユーザーがインストールをクリックしたのか、キャンセルしたのかを検知することができる。

```js
window.addEventListener('beforeinstallprompt', function(e) {
    console.log('インストールバナーを表示します');
    e.userChoice.then(function(result) {
        if(result.outcome == 'dismissed') {
          console.log('キャンセルされました');
        } else {
          console.log('インストールされました');
        }
      });
  });
```

##インストールバナーを後で表示

BeforeInstallPromptEventハンドラ内で`preventDefault()`を呼んでおき、後で`prompt()`を呼ぶことで、独自のボタンがクリックされた際に再度インストールバナーを表示することができる。

```js
var saveButton = document.getElementById('save_button');
var deferredPrompt;

window.addEventListener('beforeinstallprompt', function(e) {
    console.log('インストールバナーの表示をキャンセルします');
    e.preventDefault();
    saveButton.removeAttribute('disabled');
    deferredPrompt = e;
  });

saveButton.addEventListener('click', function() {
    if (deferredPrompt !== undefined) {
      deferredPrompt.prompt();
      deferredPrompt.userChoice.then(function(result) {
        if (result.outcome == 'dismissed') {
          console.log('キャンセルされました');
        } else {
          console.log('インストールされました');
        }
        deferredPrompt = null;
        saveButton.addAttribute('disabled');
      });
    }
  });
```


#デバッグ方法 その１
Manifestによるインストールバナーの表示は、"ユーザーが５分以上経過後に再び訪れる"必要が有るため、デバッグが大変である。ChromeのDeveloper ToolsのApplicationタブにManifestという項目があるので、こちらを使うと便利である。"Add to homescreen"をクリックすることで、Developer Toolsからインストールバナーを表示することができる。

<img width="477" alt="manifest_devtools.png" src="https://qiita-image-store.s3.amazonaws.com/0/110601/e431a7a1-ba23-abc8-51d5-5ceed407ccd3.png">

#デバッグ方法 その2
Chromeでは`bypass-app-banner-engagement-checks`というフラグが提供されている。[chrome://flags/#bypass-app-banner-engagement-checks](chrome://flags/#bypass-app-banner-engagement-checks)からこのフラグを有効にすることで、サイトに初めて訪れた場合でもインストールバナーを表示することができる。また、Win/Mac/Linux環境でも`enable-add-to-shelf`というフラグを有効にすることで、Android/ChromeOS環境と同様にインストールバナーを表示することができる。