在網頁上檢查自家 Chrome extension 是否已安裝

續上一篇文章

直接在網站頁面上安裝你的 Chrome extension

最後提到為了能夠在提供安裝的頁面展現出我們的誠意(明明上一篇好像不是這樣說的),總得有個辦法來各別針對 未安裝/已安裝 的使用者做頁面的客製化。

基本原理

其實就是官方文件提供的方法。

Extensions can communicate with the embedding page via content scripts to let it know that they are already installed.

可以歸納出大致兩種基本作法:

  • 在需要檢查的頁面讀入特定 content script 之後,檢查時利用 window.postMessagecontent script 對話,基本上只要約定一下傳輸的內容就可以知道 extension 安裝成功
  • 在需要檢查的頁面讀入特定 content script,讓它直接在該頁面上塞一個 <div id="my-awesome-extension"></div>,檢查的動作只要檢查該 div 是否存在即可

實作

這邊選擇第二種做法,節奏明快!

content script

首先準備好要跑的 content script,取名為 installed.js

/* file: installed.js */

var INSTALLED_NODE_ID = 'my-awesome-extension';

// 檢查 div 是否已存在
var getNode = function () {
  return document.getElementById(INSTALLED_NODE_ID);
};

// 擺上 div
var insertNode = function () {
  var isInstalledNode = document.createElement('div');
  isInstalledNode.id = INSTALLED_NODE_ID;
  isInstalledNode.style.visibility = 'hidden'
  document.body.appendChild(isInstalledNode);
};

if (!getNode()) {
  insertNode();
}

修改 manifest.json 以載入 installed.js

最標準的插入 content script 方法就是在 manifest.json 裡加上一筆!

/* file: manifest.json */
{
  "name": "威力彩上看18億",
  ...
  "content_scripts": [
    {
      "matches": ["*://你的網域/*"],
      "js": ["installed.js"]
    }
  ],
  ...
}

Progammatically inject installed.js

Programmatic injection(以下簡稱 PI) 在這個情況下,跟修改 manifest.json 相比真是吃力又不討好,偏偏本人的第一個 extension 就直接走上了 PI 之路… 也因此,原本我的 manifest.json 裡是沒有擺 "content_scripts" 這項的 XD

於是原本我也很自然的選用了 PI installed.js,學個經驗,還是來看一下 PI 做法。

首先是需要知道 PI 的時機 ─ 當瀏覽到需要知道 extension 是否已安裝的頁面的時候(對應上面的 manifest.json 就是 *://你的網域/*),這邊可以透過一個官方建議的方法來做到,它是一個使用在 event page 的小 trick(所以應該也可以在一般的 background page 使用,我的 extension 本身是採用 event page)。

chrome.webNavigation API 需要在 manifest.json 裡要求 webNavigation 權限。

/* file: background.js */

var urlFilter = {url: [{hostSuffix: '你的網域'}]};

// onCommitted, onDOMContentLoaded inject 的 timing 似乎差不多,有點神秘
chrome.webNavigation.onCommitted.addListener(function (detail) {
	chrome.tabs.executeScript(detail.tabId, 'installed.js');
}, urlFilter);

然而到這邊還不算結束,很遺憾的 PI 的動作相較於 manifest.json 的載入 content script 似乎就是慢了一些。

如果你的頁面不是在使用者進行了一些操作時才做檢查,而是在初始化的階段(比如說 DOMContentLoaded 前後),就先做檢查的動作的話,你可能會需要稍微將檢查 delay 個 100ms 左右。

我在 AngularJS 裡寫了一個 service 來做檢查,它會被當頁的 controller 呼叫而做檢查的動作,而除了 delay 過的位置之外,得到的結果都是 false

/* file: is-extension-installed.js */

app

.factory('$isExtensionInstalled', [
         '$document', '$timeout', '$log',
function ($document,   $timeout,   $log) {

  var INSTALLED_NODE_ID = 'my-awesome-extension',
      CHECK_DELAY = 100;

  $log.debug('init check', ($document[0].getElementById(INSTALLED_NODE_ID)) ? true : false);

  return function $isExtensionInstalled(callback) {
    $log.debug('function call check', ($document[0].getElementById(INSTALLED_NODE_ID)) ? true : false);

    $timeout(function () {
    	$log.debug('delayed check', ($document[0].getElementById(INSTALLED_NODE_ID)) ? true : false);
    
      var isInstalled = ($document[0].getElementById(INSTALLED_NODE_ID)) ? true : false;
      (callback || angular.noop)(isInstalled);
    }, CHECK_DELAY);
  };

}]);

參考資料