1. 程式人生 > >chrome外掛開發(轉)

chrome外掛開發(轉)

作者原文:https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html

寫在前面

我花了將近一個多月的時間斷斷續續寫下這篇博文,並精心寫下完整demo,寫部落格的辛苦大家懂的,所以轉載務必保留出處。本文所有涉及到的大部分程式碼均在這個demo裡面:https://github.com/sxei/chrome-plugin-demo ,大家可以直接下載下來執行。

另外,本文圖片較多,且圖片伺服器頻寬有限,右下角的目錄滾動監聽必須等到圖片全部載入完畢之後才會觸發,所以請耐心等待載入完畢。

本文目錄:

demo部分截圖:

前言

2.1. 什麼是Chrome外掛

嚴格來講,我們正在說的東西應該叫Chrome擴充套件(Chrome Extension),真正意義上的Chrome外掛是更底層的瀏覽器功能擴充套件,可能需要對瀏覽器原始碼有一定掌握才有能力去開發。鑑於Chrome外掛的叫法已經習慣,本文也全部採用這種叫法,但讀者需深知本文所描述的Chrome外掛實際上指的是Chrome擴充套件。

Chrome外掛是一個用Web技術開發、用來增強瀏覽器功能的軟體,它其實就是一個由HTML、CSS、JS、圖片等資源組成的一個.crx字尾的壓縮包.

個人猜測crx可能是Chrome Extension如下3個字母的簡寫:

另外,其實不只是前端技術,Chrome外掛還可以配合C++編寫的dll動態連結庫實現一些更底層的功能(NPAPI),比如全螢幕截圖。

由於安全原因,Chrome瀏覽器42以上版本已經陸續不再支援NPAPI外掛,取而代之的是更安全的PPAPI。

2.2. 學習Chrome外掛開發有什麼意義

增強瀏覽器功能,輕鬆實現屬於自己的“定製版”瀏覽器,等等。

Chrome外掛提供了很多實用API供我們使用,包括但不限於:

  • 書籤控制;
  • 下載控制;
  • 視窗控制;
  • 標籤控制;
  • 網路請求控制,各類事件監聽;
  • 自定義原生選單;
  • 完善的通訊機制;
  • 等等;

2.3. 為什麼是Chrome外掛而不是Firefox外掛

  1. Chrome佔有率更高,更多人用;
  2. 開發更簡單;
  3. 應用場景更廣泛,Firefox外掛只能執行在Firefox上,而Chrome除了Chrome瀏覽器之外,還可以執行在所有webkit核心的國產瀏覽器,比如360極速瀏覽器、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等;
  4. 除此之外,Firefox瀏覽器也對Chrome外掛的執行提供了一定的支援;

開發與除錯

Chrome外掛沒有嚴格的專案結構要求,只要保證本目錄有一個manifest.json即可,也不需要專門的IDE,普通的web開發工具即可。

從右上角選單->更多工具->擴充套件程式可以進入 外掛管理頁面,也可以直接在位址列輸入 chrome://extensions 訪問。

勾選開發者模式即可以資料夾的形式直接載入外掛,否則只能安裝.crx格式的檔案。Chrome要求外掛必須從它的Chrome應用商店安裝,其它任何網站下載的都無法直接安裝,所以,其實我們可以把crx檔案解壓,然後通過開發者模式直接載入。

開發中,程式碼有任何改動都必須重新載入外掛,只需要在外掛管理頁按下Ctrl+R即可,以防萬一最好還把頁面重新整理一下。

核心介紹

4.1. manifest.json

這是一個Chrome外掛最重要也是必不可少的檔案,用來配置所有和外掛相關的配置,必須放在根目錄。其中,manifest_versionnameversion3個是必不可少的,descriptionicons是推薦的。

下面給出的是一些常見的配置項,均有中文註釋,完整的配置文件請戳這裡

{
    // 清單檔案的版本,這個必須寫,而且必須是2
    "manifest_version": 2,
    // 外掛的名稱
    "name": "demo",
    // 外掛的版本
    "version": "1.0.0",
    // 外掛描述
    "description": "簡單的Chrome擴充套件demo",
    // 圖示,一般偷懶全部用一個尺寸的也沒問題
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 會一直常駐的後臺JS或後臺頁面
    "background":
    {
        // 2種指定方式,如果指定JS,那麼會自動生成一個背景頁
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
    // 瀏覽器右上角圖示設定,browser_action、page_action、app必須三選一
    "browser_action": 
    {
        "default_icon": "img/icon.png",
        // 圖示懸停時的標題,可選
        "default_title": "這是一個示例Chrome外掛",
        "default_popup": "popup.html"
    },
    // 當某些特定頁面開啟才顯示的圖示
    /*"page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },*/
    // 需要直接注入頁面的JS
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多個JS按順序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全域性樣式
            "css": ["css/custom.css"],
            // 程式碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最後一個表示頁面空閒時,預設document_idle
            "run_at": "document_start"
        },
        // 這裡僅僅是為了演示content-script可以配置多個規則
        {
            "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
            "js": ["js/show-image-content-size.js"]
        }
    ],
    // 許可權申請
    "permissions":
    [
        "contextMenus", // 右鍵選單
        "tabs", // 標籤
        "notifications", // 通知
        "webRequest", // web請求
        "webRequestBlocking",
        "storage", // 外掛本地儲存
        "http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
        "https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
    ],
    // 普通頁面能夠直接訪問的外掛資源列表,如果不設定是無法直接訪問的
    "web_accessible_resources": ["js/inject.js"],
    // 外掛主頁,這個很重要,不要浪費了這個免費廣告位
    "homepage_url": "https://www.baidu.com",
    // 覆蓋瀏覽器預設頁面
    "chrome_url_overrides":
    {
        // 覆蓋瀏覽器預設的新標籤頁
        "newtab": "newtab.html"
    },
    // Chrome40以前的外掛配置頁寫法
    "options_page": "options.html",
    // Chrome40以後的外掛配置頁寫法,如果2個都寫,新版Chrome只認後面這一個
    "options_ui":
    {
        "page": "options.html",
        // 新增一些預設的樣式,推薦使用
        "chrome_style": true
    },
    // 向地址欄註冊一個關鍵字以提供搜尋建議,只能設定一個關鍵字
    "omnibox": { "keyword" : "go" },
    // 預設語言
    "default_locale": "zh_CN",
    // devtools頁面入口,注意只能指向一個HTML檔案,不能是JS檔案
    "devtools_page": "devtools.html"
}

4.2. content-scripts

所謂content-scripts,其實就是Chrome外掛中向頁面注入指令碼的一種形式(雖然名為script,其實還可以包括css的),藉助content-scripts我們可以實現通過配置的方式輕鬆向指定頁面注入JS和CSS(如果需要動態注入,可以參考下文),最常見的比如:廣告遮蔽、頁面CSS定製,等等。

示例配置:

{
    // 需要直接注入頁面的JS
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多個JS按順序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全域性樣式
            "css": ["css/custom.css"],
            // 程式碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最後一個表示頁面空閒時,預設document_idle
            "run_at": "document_start"
        }
    ],
}

特別注意,如果沒有主動指定run_atdocument_start(預設為document_idle),下面這種程式碼是不會生效的:

document.addEventListener('DOMContentLoaded', function()
{
    console.log('我被執行了!');
});

content-scripts和原始頁面共享DOM,但是不共享JS,如要訪問頁面JS(例如某個JS變數),只能通過injected js來實現。content-scripts不能訪問絕大部分chrome.xxx.api,除了下面這4種:

  • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
  • chrome.i18n
  • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
  • chrome.storage

其實看到這裡不要悲觀,這些API絕大部分時候都夠用了,非要呼叫其它API的話,你還可以通過通訊來實現讓background來幫你呼叫(關於通訊,後文有詳細介紹)。

好了,Chrome外掛給我們提供了這麼強大的JS注入功能,剩下的就是發揮你的想象力去玩弄瀏覽器了。

4.3. background

後臺(姑且這麼翻譯吧),是一個常駐的頁面,它的生命週期是外掛中所有型別頁面中最長的,它隨著瀏覽器的開啟而開啟,隨著瀏覽器的關閉而關閉,所以通常把需要一直執行的、啟動就執行的、全域性的程式碼放在background裡面。

background的許可權非常高,幾乎可以呼叫所有的Chrome擴充套件API(除了devtools),而且它可以無限制跨域,也就是可以跨域訪問任何網站而無需要求對方設定CORS

經過測試,其實不止是background,所有的直接通過chrome-extension://id/xx.html這種方式開啟的網頁都可以無限制跨域。

配置中,background可以通過page指定一張網頁,也可以通過scripts直接指定一個JS,Chrome會自動為這個JS生成一個預設的網頁:

{
    // 會一直常駐的後臺JS或後臺頁面
    "background":
    {
        // 2種指定方式,如果指定JS,那麼會自動生成一個背景頁
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
}

需要特別說明的是,雖然你可以通過chrome-extension://xxx/background.html直接開啟後臺頁,但是你開啟的後臺頁和真正一直在後臺執行的那個頁面不是同一個,換句話說,你可以開啟無數個background.html,但是真正在後臺常駐的只有一個,而且這個你永遠看不到它的介面,只能除錯它的程式碼。

4.4. event-pages

這裡順帶介紹一下event-pages,它是一個什麼東西呢?鑑於background生命週期太長,長時間掛載後臺可能會影響效能,所以Google又弄一個event-pages,在配置檔案上,它與background的唯一區別就是多了一個persistent引數:

{
    "background":
    {
        "scripts": ["event-page.js"],
        "persistent": false
    },
}

它的生命週期是:在被需要時載入,在空閒時被關閉,什麼叫被需要時呢?比如第一次安裝、外掛更新、有content-script向它傳送訊息,等等。

除了配置檔案的變化,程式碼上也有一些細微變化,個人這個簡單瞭解一下就行了,一般情況下background也不會很消耗效能的。

popup是點選browser_action或者page_action圖示時開啟的一個小視窗網頁,焦點離開網頁就立即關閉,一般用來做一些臨時性的互動。

部落格園網摘外掛popup效果

popup可以包含任意你想要的HTML內容,並且會自適應大小。可以通過default_popup欄位來指定popup頁面,也可以呼叫setPopup()方法。

配置方式:

{
    "browser_action":
    {
        "default_icon": "img/icon.png",
        // 圖示懸停時的標題,可選
        "default_title": "這是一個示例Chrome外掛",
        "default_popup": "popup.html"
    }
}

需要特別注意的是,由於單擊圖示開啟popup,焦點離開又立即關閉,所以popup頁面的生命週期一般很短,需要長時間執行的程式碼千萬不要寫在popup裡面。

在許可權上,它和background非常類似,它們之間最大的不同是生命週期的不同,popup中可以直接通過chrome.extension.getBackgroundPage()獲取background的window物件。

4.6. injected-script

這裡的injected-script是我給它取的,指的是通過DOM操作的方式向頁面注入的一種JS。為什麼要把這種JS單獨拿出來討論呢?又或者說為什麼需要通過這種方式注入JS呢?

這是因為content-script有一個很大的“缺陷”,也就是無法訪問頁面中的JS,雖然它可以操作DOM,但是DOM卻不能呼叫它,也就是無法在DOM中通過繫結事件的方式呼叫content-script中的程式碼(包括直接寫onclickaddEventListener2種方式都不行),但是,“在頁面上新增一個按鈕並呼叫外掛的擴充套件API”是一個很常見的需求,那該怎麼辦呢?其實這就是本小節要講的。

content-script中通過DOM方式向頁面注入inject-script程式碼示例:

// 向頁面注入JS
function injectCustomJs(jsPath)
{
    jsPath = jsPath || 'js/inject.js';
    var temp = document.createElement('script');
    temp.setAttribute('type', 'text/javascript');
    // 獲得的地址類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
    temp.src = chrome.extension.getURL(jsPath);
    temp.onload = function()
    {
        // 放在頁面不好看,執行完後移除掉
        this.parentNode.removeChild(this);
    };
    document.head.appendChild(temp);
}

你以為這樣就行了?執行一下你會看到如下報錯:

Denying load of chrome-extension://efbllncjkjiijkppagepehoekjojdclc/js/inject.js. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.

意思就是你想要在web中直接訪問外掛中的資源的話必須顯示宣告才行,配置檔案中增加如下:

{
    // 普通頁面能夠直接訪問的外掛資源列表,如果不設定是無法直接訪問的
    "web_accessible_resources": ["js/inject.js"],
}

至於inject-script如何呼叫content-script中的程式碼,後面我會在專門的一個訊息通訊章節詳細介紹。

4.7. homepage_url

開發者或者外掛主頁設定,一般會在如下2個地方顯示:

Chrome外掛的8種展示形式

5.1. browserAction(瀏覽器右上角)

通過配置browser_action可以在瀏覽器的右上角增加一個圖示,一個browser_action可以擁有一個圖示,一個tooltip,一個badge和一個popup

示例配置如下:

"browser_action":
{
    "default_icon": "img/icon.png",
    "default_title": "這是一個示例Chrome外掛",
    "default_popup": "popup.html"
}

5.1.1. 圖示

browser_action圖示推薦使用寬高都為19畫素的圖片,更大的圖示會被縮小,格式隨意,一般推薦png,可以通過manifest中default_icon欄位配置,也可以呼叫setIcon()方法。

5.1.2. tooltip

修改browser_action的manifest中default_title欄位,或者呼叫setTitle()方法。

5.1.3. badge

所謂badge就是在圖示上顯示一些文字,可以用來更新一些小的擴充套件狀態提示資訊。因為badge空間有限,所以只支援4個以下的字元(英文4個,中文2個)。badge無法通過配置檔案來指定,必須通過程式碼實現,設定badge文字和顏色可以分別使用setBadgeText()setBadgeBackgroundColor()

chrome.browserAction.setBadgeText({text: 'new'});
chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 255]});

效果:

5.2. pageAction(位址列右側)

所謂pageAction,指的是隻有當某些特定頁面開啟才顯示的圖示,它和browserAction最大的區別是一個始終都顯示,一個只在特定情況才顯示。

需要特別說明的是早些版本的Chrome是將pageAction放在位址列的最右邊,左鍵單擊彈出popup,右鍵單擊則彈出相關預設的選項選單:

而新版的Chrome更改了這一策略,pageAction和普通的browserAction一樣也是放在瀏覽器右上角,只不過沒有點亮時是灰色的,點亮了才是彩色的,灰色時無論左鍵還是右鍵單擊都是彈出選項:

具體是從哪一版本開始改的沒去仔細考究,反正知道v50.0的時候還是前者,v58.0的時候已改為後者。

調整之後的pageAction我們可以簡單地把它看成是可以置灰的browserAction

  • chrome.pageAction.show(tabId) 顯示圖示;
  • chrome.pageAction.hide(tabId) 隱藏圖示;

示例(只有開啟百度才顯示圖示):

// manifest.json
{
    "page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },
    "permissions": ["declarativeContent"]
}

// background.js
chrome.runtime.onInstalled.addListener(function(){
chrome.declarativeContent.onPageChanged.removeRules(undefined, function(){
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [
// 只有開啟百度才顯示pageAction
new chrome.declarativeContent.PageStateMatcher({pageUrl: {urlContains: baidu.com}})
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}
]);
});
});

效果圖:

5.3. 右鍵選單

通過開發Chrome外掛可以自定義瀏覽器的右鍵選單,主要是通過chrome.contextMenusAPI實現,右鍵選單可以出現在不同的上下文,比如普通頁面、選中的文字、圖片、連結,等等,如果有同一個外掛裡面定義了多個選單,Chrome會自動組合放到以外掛名字命名的二級選單裡,如下:

5.3.1. 最簡單的右鍵選單示例

// manifest.json
{"permissions": ["contextMenus"]}

// background.js
chrome.contextMenus.create({
title: “測試右鍵選單”,
onclick: function(){alert(‘您點選了右鍵選單!’);}
});

效果:

5.3.2. 新增右鍵百度搜索

// manifest.json
{"permissions": ["contextMenus""tabs"]}

// background.js
chrome.contextMenus.create({
title: ‘使用度娘搜尋:%s’, // %s表示選中的文字
contexts: [‘selection’], // 只有當選中文字時才會出現此右鍵選單
onclick: function(params)
{
// 注意不能使用location.href,因為location是屬於background的window物件
chrome.tabs.create({url: https://www.baidu.com/s?ie=utf-8&wd= + encodeURI(params.selectionText)});
}
});

效果如下:

5.3.3. 語法說明

這裡只是簡單列舉一些常用的,完整API參見:https://developer.chrome.com/extensions/contextMenus

chrome.contextMenus.create({
    type: 'normal'// 型別,可選:["normal", "checkbox", "radio", "separator"],預設 normal
    title: '選單的名字', // 顯示的文字,除非為“separator”型別否則此引數必需,如果型別為“selection”,可以使用%s顯示選定的文字
    contexts: ['page'], // 上下文環境,可選:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],預設page
    onclick: function(){}, // 單擊時觸發的方法
    parentId: 1, // 右鍵選單項的父選單項ID。指定父選單項將會使此選單項成為父選單項的子選單
    documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些頁面顯示此右鍵選單
});
// 刪除某一個選單項
chrome.contextMenus.remove(menuItemId);
// 刪除所有自定義右鍵選單
chrome.contextMenus.removeAll();
// 更新某一個選單項
chrome.contextMenus.update(menuItemId, updateProperties);

5.4. override(覆蓋特定頁面)

使用override頁可以將Chrome預設的一些特定頁面替換掉,改為使用擴充套件提供的頁面。

擴充套件可以替代如下頁面:

  • 歷史記錄:從工具選單上點選歷史記錄時訪問的頁面,或者從位址列直接輸入 chrome://history
  • 新標籤頁:當建立新標籤的時候訪問的頁面,或者從位址列直接輸入 chrome://newtab
  • 書籤:瀏覽器的書籤,或者直接輸入 chrome://bookmarks

注意:

  • 一個擴充套件只能替代一個頁面;
  • 不能替代隱身視窗的新標籤頁;
  • 網頁必須設定title,否則使用者可能會看到網頁的URL,造成困擾;

下面的截圖是預設的新標籤頁和被擴充套件替換掉的新標籤頁。

程式碼(注意,一個外掛只能替代一個預設頁,以下僅為演示):

"chrome_url_overrides":
{
    "newtab": "newtab.html",
    "history": "history.html",
    "bookmarks": "bookmarks.html"
}

5.5. devtools(開發者工具)

5.5.1. 預熱

使用過vue的應該見過這種型別的外掛:

是的,Chrome允許外掛在開發者工具(devtools)上動手腳,主要表現在:

  • 自定義一個和多個和ElementsConsoleSources等同級別的面板;
  • 自定義側邊欄(sidebar),目前只能自定義Elements面板的側邊欄;

先來看2張簡單的demo截圖,自定義面板(判斷當前頁面是否使用了jQuery):

自定義側邊欄(獲取當前頁面所有圖片):

5.5.2. devtools擴充套件介紹

主頁:https://developer.chrome.com/extensions/devtools

來一張官方圖片:

每開啟一個開發者工具視窗,都會建立devtools頁面的例項,F12視窗關閉,頁面也隨著關閉,所以devtools頁面的生命週期和devtools視窗是一致的。devtools頁面可以訪問一組特有的DevTools API以及有限的擴充套件API,這組特有的DevTools API只有devtools頁面才可以訪問,background都無權訪問,這些API包括:

  • chrome.devtools.panels:面板相關;
  • chrome.devtools.inspectedWindow:獲取被審查視窗的有關資訊;
  • chrome.devtools.network:獲取有關網路請求的資訊;

大部分擴充套件API都無法直接被DevTools頁面呼叫,但它可以像content-script一樣直接呼叫chrome.extensionchrome.runtimeAPI,同時它也可以像content-script一樣使用Message互動的方式與background頁面進行通訊。

5.5.3. 例項:建立一個devtools擴充套件

首先,要針對開發者工具開發外掛,需要在清單檔案宣告如下:

{
    // 只能指向一個HTML檔案,不能是JS檔案
    "devtools_page": "devtools.html"
}

這個devtools.html裡面一般什麼都沒有,就引入一個js:

<!DOCTYPE html>
<html>
<head></head>
<body>
    <script type="text/javascript" src="js/devtools.js"></script>
</body>
</html>

可以看出來,其實真正程式碼是devtools.js,html檔案是“多餘”的,所以這裡覺得有點坑,devtools_page幹嘛不允許直接指定JS呢?

再來看devtools.js的程式碼:

// 建立自定義面板,同一個外掛可以建立多個自定義面板
// 幾個引數依次為:panel標題、圖示(其實設定了也沒地方顯示)、要載入的頁面、載入成功後的回撥
chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel)
{
    console.log('自定義面板建立成功!'); // 注意這個log一般看不到
});

// 建立自定義側邊欄
chrome.devtools.panels.elements.createSidebarPane(“Images”, function(sidebar)
{
// sidebar.setPage(’…/sidebar.html’); // 指定載入某個頁面
sidebar.setExpression(‘document.querySelectorAll(“img”)’, ‘All Images’); // 通過表示式來指定
//sidebar.setObject({aaa: 111, bbb: ‘Hello World!’}); // 直接設定顯示某個物件
});

setPage時的效果:

以下截圖示例的程式碼:

// 檢測jQuery
document.getElementById('check_jquery').addEventListener('click', function()
{
    // 訪問被檢查的頁面DOM需要使用inspectedWindow
    // 簡單例子:檢測被檢查頁面是否使用了jQuery
    chrome.devtools.inspectedWindow.eval("jQuery.fn.jquery", function(result, isException)
    {
        var html = '';
        if (isException) html = '當前頁面沒有使用jQuery。';
        else html = '當前頁面使用了jQuery,版本為:'+result;
        alert(html);
    });
});

// 開啟某個資源
document.getElementById(‘open_resource’).addEventListener(‘click’, function()
{
chrome.devtools.inspectedWindow.eval(“window.location.href”, function(result, isException)
{
chrome.devtools.panels.openResource(result, 20, function()
{
console.log(‘資源開啟成功!’);
});
});
});

// 審查元素
document.getElementById(‘test_inspect’).addEventListener(‘click’, function()
{
chrome.devtools.inspectedWindow.eval(“inspect(document.images[0])”, function(result, isException){});
});

// 獲取所有資源
document.getElementById(‘get_all_resources’).addEventListener(‘click’, function()
{
chrome.devtools.inspectedWindow.getResources(function(resources)
{
alert(JSON.stringify(resources));
});
});

5.5.4. 除錯技巧

修改了devtools頁面的程式碼時,需要先在 chrome://extensions 頁面按下Ctrl+R重新載入外掛,然後關閉再開啟開發者工具即可,無需重新整理頁面(而且只重新整理頁面不重新整理開發者工具的話是不會生效的)。

由於devtools本身就是開發者工具頁面,所以幾乎沒有方法可以直接除錯它,直接用 chrome-extension://extid/devtools.html"的方式開啟頁面肯定報錯,因為不支援相關特殊API,只能先自己寫一些方法遮蔽這些錯誤,除錯通了再放開。

5.6. option(選項頁)

所謂options頁,就是外掛的設定頁面,有2個入口,一個是右鍵圖示有一個“選項”選單,還有一個在外掛管理頁面:

在Chrome40以前,options頁面和其它普通頁面沒什麼區別,Chrome40以後則有了一些變化。

我們先看老版的options

{
    // Chrome40以前的外掛配置頁寫法
    "options_page": "options.html",
}

這個頁面裡面的內容就隨你自己發揮了,配置之後在外掛管理頁就會看到一個選項按鈕入口,點進去就是開啟一個網頁,沒啥好講的。

效果:

再來看新版的optionsV2

{
    "options_ui":
    {
        "page": "options.html",
        // 新增一些預設的樣式,推薦使用
        "chrome_style": true
    },
}

options.html的程式碼我們沒有任何改動,只是配置檔案改了,之後效果如下:

看起來是不是高大上了?

幾點注意:

  • 為了相容,建議2種都寫,如果都寫了,Chrome40以後會預設讀取新版的方式;
  • 新版options中不能使用alert;
  • 資料儲存建議用chrome.storage,因為會隨使用者自動同步;

5.7. omnibox

omnibox是向用戶提供搜尋建議的一種方式。先來看個gif圖以便了解一下這東西到底是個什麼鬼:

註冊某個關鍵字以觸發外掛自己的搜尋建議介面,然後可以任意發揮了。

首先,配置檔案如下:

{
    // 向地址欄註冊一個關鍵字以提供搜尋建議,只能設定一個關鍵字
    "omnibox": { "keyword" : "go" },
}

然後background.js中註冊監聽事件:

// omnibox 演示
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
    console.log('inputChanged: ' + text);
    if(!text) return;
    if(text == '美女') {
        suggest([
            {content: '中國' + text, description: '你要找“中國美女”嗎?'},
            {content: '日本' + text, description: '你要找“日本美女”嗎?'},
            {content: '泰國' + text, description: '你要找“泰國美女或人妖”嗎?'},
            {content: '韓國' + text, description: '你要找“韓國美女”嗎?'}
        ]);
    }
    else if(text == '微博') {
        suggest([
            {content: '新浪' + text, description: '新浪' + text},
            {content: '騰訊' + text, description: '騰訊' + text},
            {content: '搜狐' + text, description: '搜尋' + text},
        ]);
    }
    else {
        suggest([
            {content: '百度搜索 ' + text, description: '百度搜索 ' + text},
            {content: '谷歌搜尋 ' + text, description: '谷歌搜尋 ' + text},
        ]);
    }
});

// 當用戶接收關鍵字建議時觸發
chrome.omnibox.onInputEntered.addListener((text) => {
console.log('inputEntered: ’ + text);
if(!text) return;
var href = ‘’;
if(text.endsWith(‘美女’)) href = http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word= + text;
else if(text.startsWith(‘百度搜索’)) href = https://www.baidu.com/s?ie=UTF-8&wd= + text.replace('百度搜索 ', ‘’);
else if(text.startsWith(‘谷歌搜尋’)) href = https://www.google.com.tw/search?q= + text.replace('谷歌搜尋 ', ‘’);
else href = https://www.baidu.com/s?ie=UTF-8&wd= + text;
openUrlCurrentTab(href);
});
// 獲取當前選項卡ID
function getCurrentTabId(callback)
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
if(callback) callback(tabs.length ? tabs[0].id: null);
});
}

// 當前標籤開啟某個連結
function openUrlCurrentTab(url)
{
getCurrentTabId(tabId => {
chrome.tabs.update(tabId, {url: url});
})
}

5.8. 桌面通知

Chrome提供了一個chrome.notificationsAPI以便外掛推送桌面通知,暫未找到chrome.notifications和HTML5自帶的Notification的顯著區別及優勢。

在後臺JS中,無論是使用chrome.notifications還是Notification都不需要申請許可權(HTML5方式需要申請許可權),直接使用即可。

最簡單的通知:

程式碼:

chrome.notifications.create(null, {
    type: 'basic',
    iconUrl: 'img/icon.png',
    title: '這是標題',
    message: '您剛才點選了自定義右鍵選單!'
});

通知的樣式可以很豐富:

這個沒有深入研究,有需要的可以去看官方文件。

5種類型的JS對比

Chrome外掛的JS主要可以分為這5類:injected scriptcontent-scriptpopup jsbackground jsdevtools js

6.1. 許可權對比

JS種類 可訪問的API DOM訪問情況 JS訪問情況 直接跨域
injected script 和普通JS無任何差別,不能訪問任何擴充套件API 可以訪問 可以訪問 不可以
content script 只能訪問 extension、runtime等部分API 可以訪問 不可以 不可以
popup js 可訪問絕大部分API,除了devtools系列 不可直接訪問 不可以 可以
background js 可訪問絕大部分API,除了devtools系列 不可直接訪問 不可以 可以
devtools js 只能訪問 devtools、extension、runtime等部分API 可以 可以 不可以

6.2. 除錯方式對比

JS型別 除錯方式 圖片說明
injected script 直接普通的F12即可 懶得截圖
content-script 開啟Console,如圖切換
popup-js popup頁面右鍵審查元素
background 外掛管理頁點選背景頁即可
devtools-js 暫未找到有效方法 -

訊息通訊

通訊主頁:https://developer.chrome.com/extensions/messaging

前面我們介紹了Chrome外掛中存在的5種JS,那麼它們之間如何互相通訊呢?下面先來系統概況一下,然後再分類細說。需要知道的是,popup和background其實幾乎可以視為一種東西,因為它們可訪問的API都一樣、通訊機制一樣、都可以跨域。

7.1. 互相通訊概覽

注:-表示不存在或者無意義,或者待驗證。

injected-script content-script popup-js background-js
injected-script - window.postMessage - -
content-script window.postMessage - chrome.runtime.sendMessage chrome.runtime.connect chrome.runtime.sendMessage chrome.runtime.connect
popup-js - chrome.tabs.sendMessage chrome.tabs.connect - chrome.extension. getBackgroundPage()
background-js - chrome.tabs.sendMessage chrome.tabs.connect chrome.extension.getViews -
devtools-js chrome.devtools. inspectedWindow.eval - chrome.runtime.sendMessage chrome.runtime.sendMessage

7.2. 通訊詳細介紹

7.2.1. popup和background

popup可以直接呼叫background中的JS方法,也可以直接訪問background的DOM:

// background.js
function test()
{
    alert('我是background!');
}

// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 訪問bg的函式
alert(bg.document.body.innerHTML); // 訪問bg的DOM

小插曲,今天碰到一個情況,發現popup無法獲取background的任何方法,找了半天才發現是因為background的js報錯了,而你如果不主動檢視background的js的話,是看不到錯誤資訊的,特此提醒。

至於background訪問popup如下(前提是popup已經開啟):

var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) {
    console.log(views[0].location.href);
}

7.2.2. popup或者bg向content主動傳送訊息

background.js或者popup.js:

function sendMessageToContentScript(message, callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        chrome.tabs.sendMessage(tabs[0].id, message, function(response)
        {
            if(callback) callback(response);
        });
    });
}
sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
{
    console.log(