Chrome擴展開發(fā)

Chrome擴展開發(fā)

標簽(空格分隔): Chrome擴展


1、寫在前面

Chrome插件是一個用Web技術(shù)開發(fā)入桂、用來增強瀏覽器功能的軟件,Chrome瀏覽器擴展開發(fā)算是相當簡單的,基本只要掌握HTML+CSS+Javascript某宪,即可快速開發(fā)一個屬于你的Chrome插件悼做!它其實就是一個由HTML、CSS拍皮、JS歹叮、圖片等資源組成的一個.crx后綴的壓縮包.

2. 學習Chrome插件開發(fā)有什么意義

增強瀏覽器功能跑杭,輕松實現(xiàn)屬于自己的“定制版”瀏覽器铆帽,等等。

Chrome插件提供了很多實用API供我們使用德谅,包括但不限于:

  • 書簽控制爹橱;
  • 下載控制;
  • 窗口控制窄做;
  • 標簽控制愧驱;
  • 網(wǎng)絡(luò)請求控制,
  • 各類事件監(jiān)聽椭盏;
  • 自定義原生菜單组砚;
  • 完善的通信機制;
  • 等等掏颊;

3. 為什么是Chrome插件而不是Firefox插件

  • Chrome占有率更高糟红,更多人用;
  • 開發(fā)更簡單乌叶;
  • 應(yīng)用場景更廣泛盆偿,F(xiàn)irefox插件只能運行在Firefox上,而Chrome除了Chrome瀏覽器之外准浴,還可以運行在所有webkit內(nèi)核的國產(chǎn)瀏覽器事扭,比如360極速瀏覽器、360安全瀏覽器乐横、搜狗瀏覽器求橄、QQ瀏覽器等等;
  • 除此之外葡公,F(xiàn)irefox瀏覽器也對Chrome插件的運行提供了一定的支持罐农;

4. 開發(fā)與調(diào)試

Chrome插件沒有嚴格的項目結(jié)構(gòu)要求,只要保證本目錄有一個manifest.json即可匾南,也不需要專門的IDE啃匿,普通的web開發(fā)工具即可。
從右上角菜單->更多工具->擴展程序可以進入 插件管理頁面,也可以直接在地址欄輸入 chrome://extensions 訪問溯乒。

此處輸入圖片的描述
此處輸入圖片的描述

.
勾選開發(fā)者模式即可以文件夾的形式直接加載插件夹厌,否則只能安裝.crx格式的文件。Chrome要求插件必須從它的Chrome應(yīng)用商店安裝裆悄,其它任何網(wǎng)站下載的都無法直接安裝矛纹,所以,其實我們可以把crx文件解壓光稼,然后通過開發(fā)者模式直接加載或南。

開發(fā)中,代碼有任何改動都必須重新加載插件艾君,只需要在插件管理頁按下Ctrl+R即可采够,以防萬一最好還把頁面刷新一下。

5. 核心介紹

5.1 manifest.json

這是一個Chrome插件最重要也是必不可少的文件冰垄,用來配置所有和插件相關(guān)的配置蹬癌,必須放在根目錄。其中虹茶,manifest_version逝薪、name、version3個是必不可少的蝴罪,description和icons是推薦的董济。

下面給出的是一些常見的配置項,均有中文注釋要门,完整的配置文檔請戳 這里 虏肾。

{
    // 清單文件的版本,這個必須寫暂衡,而且必須是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"]
    },
    // 瀏覽器右上角圖標設(shè)置撑毛,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可以配置多個規(guī)則
        {
            "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
            "js": ["js/show-image-content-size.js"]
        }
    ],
    // 權(quán)限申請
    "permissions":
    [
        "contextMenus", // 右鍵菜單
        "tabs", // 標簽
        "notifications", // 通知
        "webRequest", // web請求
        "webRequestBlocking",
        "storage", // 插件本地存儲
        "http://*/*", // 可以通過executeScript或者insertCSS訪問的網(wǎng)站
        "https://*/*" // 可以通過executeScript或者insertCSS訪問的網(wǎng)站
    ],
    // 普通頁面能夠直接訪問的插件資源列表,如果不設(shè)置是無法直接訪問的
    "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
    },
    // 向地址欄注冊一個關(guān)鍵字以提供搜索建議,只能設(shè)置一個關(guān)鍵字
    "omnibox": { "keyword" : "go" },
    // 默認語言
    "default_locale": "zh_CN",
    // devtools頁面入口桐汤,注意只能指向一個HTML文件而克,不能是JS文件
    "devtools_page": "devtools.html"
}

5.2 content-scripts

所謂content-scripts,其實就是Chrome插件中向頁面注入腳本的一種形式(雖然名為script怔毛,其實還可以包括css的)员萍,借助content-scripts我們可以實現(xiàn)通過配置的方式輕松向指定頁面注入JS和CSS(如果需要動態(tài)注入,可以參考下文)拣度,最常見的比如:廣告屏蔽碎绎、頁面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_at為document_start(默認為document_idle)代箭,下面這種代碼是不會生效的

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

content-scripts和原始頁面共享DOM,但是不共享JS嗡综,如要訪問頁面JS(例如某個JS變量)乙帮,只能通過injected js來實現(xiàn)。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絕大部分時候都夠用了衣盾,非要調(diào)用其它API的話走趋,你還可以通過通信來實現(xiàn)讓background來幫你調(diào)用(關(guān)于通信县袱,后文有詳細介紹)练链。

好了,Chrome插件給我們提供了這么強大的JS注入功能府寒,剩下的就是發(fā)揮你的想象力去玩弄瀏覽器了唱凯。

5.3 background

后臺(姑且這么翻譯吧),是一個常駐的頁面喜庞,它的生命周期是插件中所有類型頁面中最長的,它隨著瀏覽器的打開而打開棋返,隨著瀏覽器的關(guān)閉而關(guān)閉延都,所以通常把需要一直運行的、啟動就運行的睛竣、全局的代碼放在background里面晰房。

background的權(quán)限非常高,幾乎可以調(diào)用所有的Chrome擴展API(除了devtools)射沟,而且它可以無限制跨域殊者,也就是可以跨域訪問任何網(wǎng)站而無需要求對方設(shè)置CORS。

經(jīng)過測試验夯,其實不止是background猖吴,所有的直接通過chrome-extension://id/xx.html這種方式打開的網(wǎng)頁都可以無限制跨域。

配置中挥转,background可以通過page指定一張網(wǎng)頁海蔽,也可以通過scripts直接指定一個JS共屈,Chrome會自動為這個JS生成一個默認的網(wǎng)頁:

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

5.4 event-pages

這里順帶介紹一下event-pages拗引,它是一個什么東西呢?鑒于background生命周期太長幌衣,長時間掛載后臺可能會影響性能矾削,所以Google又弄一個event-pages,在配置文件上豁护,它與background的唯一區(qū)別就是多了一個persistent參數(shù):

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

它的生命周期是:在被需要時加載怔软,在空閑時被關(guān)閉,什么叫被需要時呢择镇?比如第一次安裝挡逼、插件更新、有content-script向它發(fā)送消息腻豌,等等家坎。

除了配置文件的變化,代碼上也有一些細微變化吝梅,個人這個簡單了解一下就行了虱疏,一般情況下background也不會很消耗性能的。

5.5 popup

popup是點擊browser_action或者page_action圖標時打開的一個小窗口網(wǎng)頁苏携,焦點離開網(wǎng)頁就立即關(guān)閉做瞪,一般用來做一些臨時性的交互。

此處輸入圖片的描述
此處輸入圖片的描述

popup可以包含任意你想要的HTML內(nèi)容右冻,并且會自適應(yīng)大小装蓬。可以通過default_popup字段來指定popup頁面纱扭,也可以調(diào)用setPopup()方法牍帚。

配置方式:

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

需要特別注意的是乳蛾,由于單擊圖標打開popup暗赶,焦點離開又立即關(guān)閉,所以popup頁面的生命周期一般很短肃叶,需要長時間運行的代碼千萬不要寫在popup里面蹂随。

在權(quán)限上,它和background非常類似因惭,它們之間最大的不同是生命周期的不同岳锁,popup中可以直接通過chrome.extension.getBackgroundPage()獲取background的window對象。

5.6 injected-script

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

這是因為content-script有一個很大的“缺陷”柱搜,也就是無法訪問頁面中的JS迟郎,雖然它可以操作DOM,但是DOM卻不能調(diào)用它聪蘸,也就是無法在DOM中通過綁定事件的方式調(diào)用content-script中的代碼(包括直接寫onclick和addEventListener2種方式都不行)宪肖,但是,“在頁面上添加一個按鈕并調(diào)用插件的擴展API”是一個很常見的需求健爬,那該怎么辦呢控乾?其實這就是本小節(jié)要講的。

在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()
    {
        // 放在頁面不好看娜遵,執(zhí)行完后移除掉
        this.parentNode.removeChild(this);
    };
    document.head.appendChild(temp);
}

你以為這樣就行了蜕衡?執(zhí)行一下你會看到如下報錯:

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中直接訪問插件中的資源的話必須顯示聲明才行,配置文件中增加如下:

{
    // 普通頁面能夠直接訪問的插件資源列表设拟,如果不設(shè)置是無法直接訪問的
    "web_accessible_resources": ["js/inject.js"],
}

至于inject-script如何調(diào)用content-script中的代碼慨仿,后面我會在專門的一個消息通信章節(jié)詳細介紹。

5.7 homepage_url

開發(fā)者或者插件主頁設(shè)置纳胧,一般會在如下2個地方顯示:


此處輸入圖片的描述
此處輸入圖片的描述
此處輸入圖片的描述
此處輸入圖片的描述

6. Chrome插件的8種展示形式

6.1. browserAction(瀏覽器右上角)

通過配置browser_action可以在瀏覽器的右上角增加一個圖標镰吆,一個browser_action可以擁有一個圖標,一個tooltip跑慕,一個badge和一個popup万皿。

示例配置如下:

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

6.1.1. 圖標

browser_action圖標推薦使用寬高都為19像素的圖片,更大的圖標會被縮小核行,格式隨意牢硅,一般推薦png,可以通過manifest中default_icon字段配置钮科,也可以調(diào)用setIcon()方法唤衫。

6.1.2. tooltip

修改browser_action的manifest中default_title字段,或者調(diào)用setTitle()方法绵脯。

6.1.3. badge

所謂badge就是在圖標上顯示一些文本,可以用來更新一些小的擴展狀態(tài)提示信息休里。因為badge空間有限蛆挫,所以只支持4個以下的字符(英文4個,中文2個)妙黍。badge無法通過配置文件來指定悴侵,必須通過代碼實現(xiàn),設(shè)置badge文字和顏色可以分別使用setBadgeText()和setBadgeBackgroundColor()拭嫁。

chrome.browserAction.setBadgeText({text: 'new'});
chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 255]});
此處輸入圖片的描述
此處輸入圖片的描述

6.2 pageAction(地址欄右側(cè))

pageAction和普通的browserAction一樣也是放在瀏覽器右上角可免,只不過沒有點亮時是灰色的抓于,點亮了才是彩色的,灰色時無論左鍵還是右鍵單擊都是彈出選項:

此處輸入圖片的描述
此處輸入圖片的描述

調(diào)整之后的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()]
            }
        ]);
    });
});

效果圖:

此處輸入圖片的描述
此處輸入圖片的描述

6.3 右鍵菜單

通過開發(fā)Chrome插件可以自定義瀏覽器的右鍵菜單妇垢,主要是通過chrome.contextMenusAPI實現(xiàn)巾遭,右鍵菜單可以出現(xiàn)在不同的上下文,比如普通頁面闯估、選中的文字灼舍、圖片、鏈接涨薪,等等

6.3.1 最簡單的右鍵菜單示例

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

// background.js
chrome.contextMenus.create({
    title: "測試右鍵菜單",
    onclick: function(){alert('您點擊了右鍵菜單骑素!');}
});

效果:


此處輸入圖片的描述
此處輸入圖片的描述

添加右鍵百度搜索

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

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

效果如下:


此處輸入圖片的描述
此處輸入圖片的描述

6.3.2 右鍵菜單語法說明

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

chrome.contextMenus.create({
    type: 'normal', // 類型光督,可選:["normal", "checkbox", "radio", "separator"]阳距,默認 normal
    title: '菜單的名字', // 顯示的文字,除非為“separator”類型否則此參數(shù)必需结借,如果類型為“selection”筐摘,可以使用%s顯示選定的文本
    contexts: ['page'], // 上下文環(huán)境,可選:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"]船老,默認page
    onclick: function(){}, // 單擊時觸發(fā)的方法
    parentId: 1, // 右鍵菜單項的父菜單項ID咖熟。指定父菜單項將會使此菜單項成為父菜單項的子菜單
    documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些頁面顯示此右鍵菜單
});
// 刪除某一個菜單項
chrome.contextMenus.remove(menuItemId);
// 刪除所有自定義右鍵菜單
chrome.contextMenus.removeAll();
// 更新某一個菜單項
chrome.contextMenus.update(menuItemId, updateProperties);

6.4. override(覆蓋特定頁面)

使用override頁可以將Chrome默認的一些特定頁面替換掉柳畔,改為使用擴展提供的頁面馍管。

擴展可以替代如下頁面:

  • 歷史記錄:從工具菜單上點擊歷史記錄時訪問的頁面,或者從地址欄直接輸入 chrome://history
  • 新標簽頁:當創(chuàng)建新標簽的時候訪問的頁面薪韩,或者從地址欄直接輸入 chrome://newtab
  • 書簽:瀏覽器的書簽确沸,或者直接輸入 chrome://bookmarks

注意:

  • 一個擴展只能替代一個頁面;
  • 不能替代隱身窗口的新標簽頁俘陷;
  • 網(wǎng)頁必須設(shè)置title罗捎,否則用戶可能會看到網(wǎng)頁的URL,造成困擾拉盾;

下面的截圖是默認的新標簽頁和被擴展替換掉的新標簽頁桨菜。


此處輸入圖片的描述
此處輸入圖片的描述

代碼(注意,一個插件只能替代一個默認頁,以下僅為演示):

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

6.5 option(選項頁)

所謂options頁倒得,就是插件的設(shè)置頁面泻红,有2個入口,一個是右鍵圖標有一個“選項”菜單霞掺,還有一個在插件管理頁面:

此處輸入圖片的描述
此處輸入圖片的描述

此處輸入圖片的描述
此處輸入圖片的描述

在Chrome40以前谊路,options頁面和其它普通頁面沒什么區(qū)別,Chrome40以后則有了一些變化根悼。

我們先看老版的 options

{
    // Chrome40以前的插件配置頁寫法
    "ptions_page": "options.html"
}

這個頁面里面的內(nèi)容就隨你自己發(fā)揮了凶异,配置之后在插件管理頁就會看到一個選項按鈕入口,點進去就是打開一個網(wǎng)頁挤巡,沒啥好講的剩彬。

效果:

此處輸入圖片的描述
此處輸入圖片的描述

再來看新版的optionsV2:

  {
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默認的樣式,推薦使用
        "chrome_style": true
    },
}

options.html 的代碼我們沒有任何改動矿卑,只是配置文件改了喉恋,之后效果如下:

此處輸入圖片的描述
此處輸入圖片的描述

看起來是不是高大上了?

幾點注意:

  • 為了兼容母廷,建議2種都寫轻黑,如果都寫了,Chrome40以后會默認讀取新版的方式琴昆;
  • 新版options中不能使用alert氓鄙;
  • 數(shù)據(jù)存儲建議用chrome.storage,因為會隨用戶自動同步业舍;

6.5 omnibox

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

此處輸入圖片的描述
此處輸入圖片的描述

注冊某個關(guān)鍵字以觸發(fā)插件自己的搜索建議界面,然后可以任意發(fā)揮了舷暮。

首先态罪,配置文件如下:

{
    // 向地址欄注冊一個關(guān)鍵字以提供搜索建議,只能設(shè)置一個關(guān)鍵字
    "omnibox": { "keyword" : "go" },
}

然后background.js中注冊監(jiān)聽事件:

// 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},
        ]);
    }
});

// 當用戶接收關(guān)鍵字建議時觸發(fā)
chrome.omnibox.onInputEntered.addListener((text) => {
    console.log('inputEntered: ' + text);
    if(!text) return;
    var href = '';
    if(text.endsWith('美女'))  + text;
    else if(text.startsWith('百度搜索'))  + text.replace('百度搜索 ', '');
    else if(text.startsWith('谷歌搜索'))  + text.replace('谷歌搜索 ', '');
    else  + 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});
    })
}

6.6 桌面通知

Chrome提供了一個chrome.notificationsAPI以便插件推送桌面通知耗啦,暫未找到chrome.notifications和HTML5自帶的Notification的顯著區(qū)別及優(yōu)勢。

在后臺JS中机杜,無論是使用chrome.notifications還是Notification都不需要申請權(quán)限(HTML5方式需要申請權(quán)限)芹彬,直接使用即可。

最簡單的通知:

此處輸入圖片的描述
此處輸入圖片的描述

代碼:

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

7. 五種類型的JS對比

Chrome插件的JS主要可以分為這5類:injected scriptcontent-script会喝、popup js陡叠、background jsdevtools js玩郊,

7.1. 權(quán)限對比

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 可以 可以 不可以

8. 消息通信

通信主頁:https://developer.chrome.com/extensions/messaging
前面我們介紹了Chrome插件中存在的5種JS,那么它們之間如何互相通信呢拙徽?下面先來系統(tǒng)概況一下刨沦,然后再分類細說。需要知道的是膘怕,popup和background其實幾乎可以視為一種東西想诅,因為它們可訪問的API都一樣、通信機制一樣岛心、都可以跨域来破。

8.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

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

- 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

8.2 通信詳細介紹

8.2.1 popup和background

popup可以直接調(diào)用background中的JS方法徘禁,也可以直接訪問background的DOM:

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

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

小插曲髓堪,今天碰到一個情況送朱,發(fā)現(xiàn)popup無法獲取background的任何方法,找了半天才發(fā)現(xiàn)是因為background的js報錯了旦袋,而你如果不主動查看background的js的話骤菠,是看不到錯誤信息的,特此提醒疤孕。

至于background訪問popup如下(前提是popup已經(jīng)打開):

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

8.2.2 popup或者bg向content主動發(fā)送消息

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('來自content的回復:'+response);
});

content-script.js 接收:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
    // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
    if(request.cmd == 'test') alert(request.value);
    sendResponse('我收到了你的消息祭阀!');
});

雙方通信直接發(fā)送的都是JSON對象鹉戚,不是JSON字符串,所以無需解析专控,很方便(當然也可以直接發(fā)送字符串)抹凳。

網(wǎng)上有些老代碼中用的是chrome.extension.onMessage,沒有完全查清二者的區(qū)別(貌似是別名)伦腐,但是建議統(tǒng)一使用chrome.runtime.onMessage赢底。

8.2.3 content-script主動發(fā)消息給后臺

content-script.js:

chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主動發(fā)消息給后臺幸冻!'}, function(response) {
    console.log('收到來自后臺的回復:' + response);
});

background.js 或者 popup.js:

// 監(jiān)聽來自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
    console.log('收到來自content-script的消息:');
    console.log(request, sender, sendResponse);
    sendResponse('我是后臺粹庞,我已收到你的消息:' + JSON.stringify(request));
});

注意事項:

  • content_scripts向popup主動發(fā)消息的前提是popup必須打開!否則需要利用background作中轉(zhuǎn)洽损;
  • 如果background和popup同時監(jiān)聽庞溜,那么它們都可以同時收到消息,但是只有一個可以sendResponse碑定,一個先發(fā)送了流码,那么另外一個再發(fā)送就無效;

8.2.4 injected script和content-script

content-script和頁面內(nèi)的腳本(injected-script自然也屬于頁面內(nèi)的腳本)之間唯一共享的東西就是頁面的DOM元素延刘,有2種方法可以實現(xiàn)二者通訊:

  • 可以通過window.postMessage和window.addEventListener來實現(xiàn)二者消息通訊漫试;
  • 通過自定義DOM事件來實現(xiàn);

第一種方法(推薦):
injected-script中:

window.postMessage({"test": '你好访娶!'}, '*');

content script中:

window.addEventListener("message", function(e)
{
    console.log(e.data);
}, false);

第二種方法:
injected-script中:

var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
    hiddenDiv = document.getElementById('myCustomEventDiv');
    hiddenDiv.innerText = data
    hiddenDiv.dispatchEvent(customEvent);
}
fireCustomEvent('你好商虐,我是普通JS!');

content-script.js中:

var hiddenDiv = document.getElementById('myCustomEventDiv');
if(!hiddenDiv) {
    hiddenDiv = document.createElement('div');
    hiddenDiv.style.display = 'none';
    document.body.appendChild(hiddenDiv);
}
hiddenDiv.addEventListener('myCustomEvent', function() {
    var eventData = document.getElementById('myCustomEventDiv').innerText;
    console.log('收到自定義事件消息:' + eventData);
});

8.3 長連接和短連接

其實上面已經(jīng)涉及到了崖疤,這里再單獨說明一下秘车。Chrome插件中有2種通信方式,一個是短連接(chrome.tabs.sendMessage和chrome.runtime.sendMessage)劫哼,一個是長連接(chrome.tabs.connect和chrome.runtime.connect)叮趴。

短連接的話就是擠牙膏一樣,我發(fā)送一下权烧,你收到了再回復一下眯亦,如果對方不回復,你只能重新發(fā)般码,而長連接類似WebSocket會一直建立連接妻率,雙方可以隨時互發(fā)消息。

短連接上面已經(jīng)有代碼示例了板祝,這里只講一下長連接宫静。

popup.js:

getCurrentTabId((tabId) => {
    var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
    port.postMessage({question: '你是誰啊券时?'});
    port.onMessage.addListener(function(msg) {
        alert('收到消息:'+msg.answer);
        if(msg.answer && msg.answer.startsWith('我是'))
        {
            port.postMessage({question: '哦孤里,原來是你啊橘洞!'});
        }
    });
});

content-script.js:

// 監(jiān)聽長連接
chrome.runtime.onConnect.addListener(function(port) {
    console.log(port);
    if(port.name == 'test-connect') {
        port.onMessage.addListener(function(msg) {
            console.log('收到長連接消息:', msg);
            if(msg.question == '你是誰鞍仆唷?') port.postMessage({answer: '我是你爸炸枣!'});
        });
    }
});

9. 其它補充

9.1 動態(tài)注入或執(zhí)行JS

雖然在background和popup中無法直接訪問頁面DOM虏等,但是可以通過chrome.tabs.executeScript來執(zhí)行腳本弄唧,從而實現(xiàn)訪問web頁面的DOM(注意,這種方式也不能直接訪問頁面JS)博其。

示例manifest.json配置:

{
    "name": "動態(tài)JS注入演示",
    ...
    "permissions": [
        "tabs", "http://*/*", "https://*/*"
    ],
    ...
}

JS:

// 動態(tài)執(zhí)行JS代碼
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
// 動態(tài)執(zhí)行JS文件
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});

9.2 動態(tài)注入CSS

示例manifest.json配置:

{
    "name": "動態(tài)CSS注入演示",
    ...
    "permissions": [
        "tabs", "http://*/*", "https://*/*"
    ],
    ...
}

JS代碼:

// 動態(tài)執(zhí)行CSS代碼套才,TODO,這里有待驗證
chrome.tabs.insertCSS(tabId, {code: 'xxx'});
// 動態(tài)執(zhí)行CSS文件
chrome.tabs.insertCSS(tabId, {file: 'some-style.css'});

9.3 獲取當前窗口ID

chrome.windows.getCurrent(function(currentWindow)
{
    console.log('當前窗口ID:' + currentWindow.id);
});

9.4 獲取當前標簽頁ID

一般有2種方法:

// 獲取當前選項卡ID
function getCurrentTabId(callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        if(callback) callback(tabs.length ? tabs[0].id: null);
    });
}

獲取當前選項卡id的另一種方法慕淡,大部分時候都類似,只有少部分時候會不一樣(例如當窗口最小化時)

// 獲取當前選項卡ID
function getCurrentTabId2()
{
    chrome.windows.getCurrent(function(currentWindow)
    {
        chrome.tabs.query({active: true, windowId: currentWindow.id}, function(tabs)
        {
            if(callback) callback(tabs.length ? tabs[0].id: null);
        });
    });
}

9.5 本地存儲

本地存儲建議用chrome.storage而不是普通的localStorage沸毁,區(qū)別有好幾點峰髓,個人認為最重要的2點區(qū)別是

  • chrome.storage是針對插件全局的,即使你在background中保存的數(shù)據(jù)息尺,在content-script也能獲取到携兵;
  • chrome.storage.sync可以跟隨當前登錄用戶自動同步,這臺電腦修改的設(shè)置會自動同步到其它電腦搂誉,很方便徐紧,如果沒有登錄或者未聯(lián)網(wǎng)則先保存到本地,等登錄了再同步至網(wǎng)絡(luò)炭懊;

需要聲明storage權(quán)限并级,有chrome.storage.sync和chrome.storage.local2種方式可供選擇,使用示例如下

// 讀取數(shù)據(jù)侮腹,第一個參數(shù)是指定要讀取的key以及設(shè)置默認值
chrome.storage.sync.get({color: 'red', age: 18}, function(items) {
    console.log(items.color, items.age);
});
// 保存數(shù)據(jù)
chrome.storage.sync.set({color: 'blue'}, function() {
    console.log('保存成功嘲碧!');
});

9.6 webRequest

通過webRequest系列API可以對HTTP請求進行任性地修改、定制父阻,這里通過beforeRequest來簡單演示一下它的冰山一角:

//manifest.json
{
    // 權(quán)限申請
    "permissions":
    [
        "webRequest", // web請求
        "webRequestBlocking", // 阻塞式web請求
        "storage", // 插件本地存儲
        "http://*/*", // 可以通過executeScript或者insertCSS訪問的網(wǎng)站
        "https://*/*" // 可以通過executeScript或者insertCSS訪問的網(wǎng)站
    ],
}


// background.js
// 是否顯示圖片
var showImage;
chrome.storage.sync.get({showImage: true}, function(items) {
    showImage = items.showImage;
});
// web請求監(jiān)聽愈涩,最后一個參數(shù)表示阻塞式,需單獨聲明權(quán)限:webRequestBlocking
chrome.webRequest.onBeforeRequest.addListener(details => {
    // cancel 表示取消本次請求
    if(!showImage && details.type == 'image') return {cancel: true};
    // 簡單的音視頻檢測
    // 大部分網(wǎng)站視頻的type并不是media加矛,且視頻做了防下載處理履婉,所以這里僅僅是為了演示效果,無實際意義
    if(details.type == 'media') {
        chrome.notifications.create(null, {
            type: 'basic',
            iconUrl: 'img/icon.png',
            title: '檢測到音視頻',
            message: '音視頻地址:' + details.url,
        });
    }
}, {urls: ["<all_urls>"]}, ["blocking"]);

9.7 API總結(jié)

比較常用用的一些API系列:

  • chrome.tabs
  • chrome.runtime
  • chrome.webRequest
  • chrome.window
  • chrome.storage
  • chrome.contextMenus
  • chrome.devtools
  • chrome.extension

10. 經(jīng)驗總結(jié)

10.1 查看已安裝插件路徑

已安裝的插件源碼路徑:C:\Users\用戶名\AppData\Local\Google\Chrome\User Data\Default\Extensions斟览,每一個插件被放在以插件ID為名的文件夾里面毁腿,想要學習某個插件的某個功能是如何實現(xiàn)的,看人家的源碼是最好的方法了:


此處輸入圖片的描述
此處輸入圖片的描述

如何查看某個插件的ID趣惠?進入 chrome://extensions 狸棍,然后勾線開發(fā)者模式即可看到了。

此處輸入圖片的描述
此處輸入圖片的描述

10.2 特別注意background的報錯

很多時候你發(fā)現(xiàn)你的代碼會莫名其妙的失效味悄,找來找去又找不到原因草戈,這時打開background的控制臺才發(fā)現(xiàn)原來某個地方寫錯了導致代碼沒生效,正式由于background報錯的隱蔽性(需要主動打開對應(yīng)的控制臺才能看到錯誤)侍瑟,所以特別注意這點唐片。

11. 打包與發(fā)布

打包的話直接在插件管理頁有一個打包按鈕:


此處輸入圖片的描述
此處輸入圖片的描述

然后會生成一個.crx文件

12. 參考

官方資料

推薦查看官方文檔丙猬,雖然是英文,但是全且新费韭,國內(nèi)的中文資料都比較舊(注意以下全部需要翻墻):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茧球,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子星持,更是在濱河造成了極大的恐慌抢埋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件督暂,死亡現(xiàn)場離奇詭異揪垄,居然都是意外死亡,警方通過查閱死者的電腦和手機逻翁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門饥努,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人八回,你說我怎么就攤上這事酷愧。” “怎么了缠诅?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵溶浴,是天一觀的道長。 經(jīng)常有香客問我滴铅,道長戳葵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任汉匙,我火速辦了婚禮拱烁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘噩翠。我一直安慰自己戏自,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布伤锚。 她就那樣靜靜地躺著擅笔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屯援。 梳的紋絲不亂的頭發(fā)上猛们,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音狞洋,去河邊找鬼弯淘。 笑死,一個胖子當著我的面吹牛吉懊,可吹牛的內(nèi)容都是我干的庐橙。 我是一名探鬼主播假勿,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼态鳖!你這毒婦竟也來了转培?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤浆竭,失蹤者是張志新(化名)和其女友劉穎浸须,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兆蕉,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡羽戒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了虎韵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缸废,死狀恐怖包蓝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情企量,我是刑警寧澤测萎,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站届巩,受9級特大地震影響硅瞧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恕汇,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一腕唧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘾英,春花似錦枣接、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至湿蛔,卻和暖如春膀曾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阳啥。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工添谊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苫纤。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓碉钠,卻偏偏與公主長得像纲缓,于是被迫代替她去往敵國和親尉尾。 傳聞我的和親對象是個殘疾皇子蔼两,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容