【轉(zhuǎn)】chrome擴(kuò)展開發(fā)入門

chrome擴(kuò)展開發(fā)入門教程

最近在開發(fā)chrome插件尖淘,看到一篇非常適合入門的教程,特記錄一下

注:轉(zhuǎn)載

本文首發(fā)于 http://liuxianan.com屎即,原文地址:http://blog.liuxianan.com/chrome-plugin-develop.html蝗蛙,轉(zhuǎn)載請(qǐng)注明署名“l(fā)iuxianan”并在顯眼位置保留原文鏈接,謝謝吼旧!
author: liuxianan

以下是文章內(nèi)容

寫在前面

我花了將近一個(gè)多月的時(shí)間斷斷續(xù)續(xù)寫下這篇博文,并精心寫下完整demo未舟,所以轉(zhuǎn)載務(wù)必保留 http://blog.liuxianan.com/chrome-plugin-develop.html 圈暗。本文所有涉及到的大部分代碼均在這個(gè)demo里面:https://github.com/liuxianan/chrome-plugin-demo ,大家可以直接下載下來(lái)運(yùn)行裕膀。

另外厂置,本文圖片較多,請(qǐng)耐心等待加載完畢魂角。

本文目錄:

image

demo部分截圖:

image

前言

什么是Chrome插件

嚴(yán)格來(lái)講昵济,我們正在說(shuō)的東西應(yīng)該叫Chrome擴(kuò)展(Chrome Extension),真正意義上的Chrome插件是更底層的瀏覽器功能擴(kuò)展,可能需要對(duì)瀏覽器源碼有一定掌握才有能力去開發(fā)访忿。鑒于Chrome插件的叫法已經(jīng)習(xí)慣瞧栗,本文也全部采用這種叫法,但讀者需深知本文所描述的Chrome插件實(shí)際上指的是Chrome擴(kuò)展海铆。

Chrome插件是一個(gè)用Web技術(shù)開發(fā)迹恐、用來(lái)增強(qiáng)瀏覽器功能的軟件,它其實(shí)就是一個(gè)由HTML卧斟、CSS殴边、JS、圖片等資源組成的一個(gè).crx后綴的壓縮包.

個(gè)人猜測(cè)crx可能是Chrome Extension如下3個(gè)字母的簡(jiǎn)寫:

image

另外珍语,其實(shí)不只是前端技術(shù)锤岸,Chrome插件還可以配合C++編寫的dll動(dòng)態(tài)鏈接庫(kù)實(shí)現(xiàn)一些更底層的功能(NPAPI),比如全屏幕截圖板乙。

360搶票王插件dll截圖

由于安全原因是偷,Chrome瀏覽器42以上版本已經(jīng)陸續(xù)不再支持NPAPI插件,取而代之的是更安全的PPAPI募逞。

學(xué)習(xí)Chrome插件開發(fā)有什么意義

增強(qiáng)瀏覽器功能蛋铆,輕松實(shí)現(xiàn)屬于自己的“定制版”瀏覽器,等等放接。

Chrome插件提供了很多實(shí)用API供我們使用刺啦,包括但不限于:

  • 書簽控制;
  • 下載控制纠脾;
  • 窗口控制洪燥;
  • 標(biāo)簽控制;
  • 網(wǎng)絡(luò)請(qǐng)求控制乳乌,各類事件監(jiān)聽;
  • 自定義原生菜單市咆;
  • 完善的通信機(jī)制汉操;
  • 等等;

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

  1. Chrome占有率更高蒙兰,更多人用磷瘤;
  2. 開發(fā)更簡(jiǎn)單;
  3. 應(yīng)用場(chǎng)景更廣泛搜变,F(xiàn)irefox插件只能運(yùn)行在Firefox上采缚,而Chrome除了Chrome瀏覽器之外,還可以運(yùn)行在所有webkit內(nèi)核的國(guó)產(chǎn)瀏覽器挠他,比如360極速瀏覽器扳抽、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等贸呢;
  4. 除此之外镰烧,F(xiàn)irefox瀏覽器也對(duì)Chrome插件的運(yùn)行提供了一定的支持;

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

Chrome插件沒(méi)有嚴(yán)格的項(xiàng)目結(jié)構(gòu)要求楞陷,只要保證本目錄有一個(gè)manifest.json即可怔鳖,也不需要專門的IDE,普通的web開發(fā)工具即可固蛾。

從右上角菜單->更多工具->擴(kuò)展程序可以進(jìn)入 插件管理頁(yè)面结执,也可以直接在地址欄輸入 chrome://extensions 訪問(wèn)。

image

勾選開發(fā)者模式即可以文件夾的形式直接加載插件艾凯,否則只能安裝.crx格式的文件献幔。Chrome要求插件必須從它的Chrome應(yīng)用商店安裝,其它任何網(wǎng)站下載的都無(wú)法直接安裝览芳,所以斜姥,其實(shí)我們可以把crx文件解壓,然后通過(guò)開發(fā)者模式直接加載沧竟。

開發(fā)中铸敏,代碼有任何改動(dòng)都必須重新加載插件,只需要在插件管理頁(yè)按下Ctrl+R即可悟泵,以防萬(wàn)一最好還把頁(yè)面刷新一下杈笔。

核心介紹

manifest.json

這是一個(gè)Chrome插件最重要也是必不可少的文件,用來(lái)配置所有和插件相關(guān)的配置糕非,必須放在根目錄蒙具。其中,manifest_version朽肥、name禁筏、version3個(gè)是必不可少的,descriptionicons是推薦的衡招。

下面給出的是一些常見的配置項(xiàng)篱昔,均有中文注釋,完整的配置文檔請(qǐng)戳這里始腾。

{
    // 清單文件的版本州刽,這個(gè)必須寫,而且必須是2
    "manifest_version": 2,
    // 插件的名稱
    "name": "demo",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述
    "description": "簡(jiǎn)單的Chrome擴(kuò)展demo",
    // 圖標(biāo)浪箭,一般偷懶全部用一個(gè)尺寸的也沒(méi)問(wèn)題
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 會(huì)一直常駐的后臺(tái)JS或后臺(tái)頁(yè)面
    "background":
    {
        // 2種指定方式穗椅,如果指定JS,那么會(huì)自動(dòng)生成一個(gè)背景頁(yè)
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
    // 瀏覽器右上角圖標(biāo)設(shè)置奶栖,browser_action匹表、page_action门坷、app必須三選一
    "browser_action": 
    {
        "default_icon": "img/icon.png",
        // 圖標(biāo)懸停時(shí)的標(biāo)題,可選
        "default_title": "這是一個(gè)示例Chrome插件",
        "default_popup": "popup.html"
    },
    // 當(dāng)某些特定頁(yè)面打開才顯示的圖標(biāo)
    /*"page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },*/
    // 需要直接注入頁(yè)面的JS
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多個(gè)JS按順序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以隨便一點(diǎn)桑孩,但是CSS的注意就要千萬(wàn)小心了拜鹤,因?yàn)橐徊恍⌒木涂赡苡绊懭謽邮?            "css": ["css/custom.css"],
            // 代碼注入的時(shí)間,可選值: "document_start", "document_end", or "document_idle"流椒,最后一個(gè)表示頁(yè)面空閑時(shí)敏簿,默認(rèn)document_idle
            "run_at": "document_start"
        },
        // 這里僅僅是為了演示content-script可以配置多個(gè)規(guī)則
        {
            "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
            "js": ["js/show-image-content-size.js"]
        }
    ],
    // 權(quán)限申請(qǐng)
    "permissions":
    [
        "contextMenus", // 右鍵菜單
        "tabs", // 標(biāo)簽
        "notifications", // 通知
        "webRequest", // web請(qǐng)求
        "webRequestBlocking",
        "storage", // 插件本地存儲(chǔ)
        "http://*/*", // 可以通過(guò)executeScript或者insertCSS訪問(wèn)的網(wǎng)站
        "https://*/*" // 可以通過(guò)executeScript或者insertCSS訪問(wèn)的網(wǎng)站
    ],
    // 普通頁(yè)面能夠直接訪問(wèn)的插件資源列表,如果不設(shè)置是無(wú)法直接訪問(wèn)的
    "web_accessible_resources": ["js/inject.js"],
    // 插件主頁(yè)宣虾,這個(gè)很重要惯裕,不要浪費(fèi)了這個(gè)免費(fèi)廣告位
    "homepage_url": "https://www.baidu.com",
    // 覆蓋瀏覽器默認(rèn)頁(yè)面
    "chrome_url_overrides":
    {
        // 覆蓋瀏覽器默認(rèn)的新標(biāo)簽頁(yè)
        "newtab": "newtab.html"
    },
    // Chrome40以前的插件配置頁(yè)寫法
    "options_page": "options.html",
    // Chrome40以后的插件配置頁(yè)寫法,如果2個(gè)都寫绣硝,新版Chrome只認(rèn)后面這一個(gè)
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默認(rèn)的樣式蜻势,推薦使用
        "chrome_style": true
    },
    // 向地址欄注冊(cè)一個(gè)關(guān)鍵字以提供搜索建議,只能設(shè)置一個(gè)關(guān)鍵字
    "omnibox": { "keyword" : "go" },
    // 默認(rèn)語(yǔ)言
    "default_locale": "zh_CN",
    // devtools頁(yè)面入口鹉胖,注意只能指向一個(gè)HTML文件握玛,不能是JS文件
    "devtools_page": "devtools.html"
}

content-scripts

所謂content-scripts,其實(shí)就是Chrome插件中向頁(yè)面注入腳本的一種形式(雖然名為script甫菠,其實(shí)還可以包括css的)挠铲,借助content-scripts我們可以實(shí)現(xiàn)通過(guò)配置的方式輕松向指定頁(yè)面注入JS和CSS(如果需要?jiǎng)討B(tài)注入,可以參考下文)寂诱,最常見的比如:廣告屏蔽拂苹、頁(yè)面CSS定制,等等痰洒。

示例配置:

{
    // 需要直接注入頁(yè)面的JS
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多個(gè)JS按順序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以隨便一點(diǎn)瓢棒,但是CSS的注意就要千萬(wàn)小心了,因?yàn)橐徊恍⌒木涂赡苡绊懭謽邮?            "css": ["css/custom.css"],
            // 代碼注入的時(shí)間丘喻,可選值: "document_start", "document_end", or "document_idle"脯宿,最后一個(gè)表示頁(yè)面空閑時(shí),默認(rèn)document_idle
            "run_at": "document_start"
        }
    ],
}

特別注意泉粉,如果沒(méi)有主動(dòng)指定run_atdocument_start(默認(rèn)為document_idle)连霉,下面這種代碼是不會(huì)生效的:

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

content-scripts和原始頁(yè)面共享DOM搀继,但是不共享JS,如要訪問(wèn)頁(yè)面JS(例如某個(gè)JS變量)翠语,只能通過(guò)injected js來(lái)實(shí)現(xiàn)叽躯。content-scripts不能訪問(wèn)絕大部分chrome.xxx.api,除了下面這4種:

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

其實(shí)看到這里不要悲觀肌括,這些API絕大部分時(shí)候都?jí)蛴昧说闫铮且{(diào)用其它API的話酣难,你還可以通過(guò)通信來(lái)實(shí)現(xiàn)讓background來(lái)幫你調(diào)用(關(guān)于通信,后文有詳細(xì)介紹)黑滴。

好了憨募,Chrome插件給我們提供了這么強(qiáng)大的JS注入功能,剩下的就是發(fā)揮你的想象力去玩弄瀏覽器了袁辈。

background

后臺(tái)(姑且這么翻譯吧)菜谣,是一個(gè)常駐的頁(yè)面,它的生命周期是插件中所有類型頁(yè)面中最長(zhǎng)的晚缩,它隨著瀏覽器的打開而打開尾膊,隨著瀏覽器的關(guān)閉而關(guān)閉,所以通常把需要一直運(yùn)行的荞彼、啟動(dòng)就運(yùn)行的冈敛、全局的代碼放在background里面。

background的權(quán)限非常高鸣皂,幾乎可以調(diào)用所有的Chrome擴(kuò)展API(除了devtools)积蔚,而且它可以無(wú)限制跨域嗤疯,也就是可以跨域訪問(wèn)任何網(wǎng)站而無(wú)需要求對(duì)方設(shè)置CORS

經(jīng)過(guò)測(cè)試,其實(shí)不止是background募强,所有的直接通過(guò)chrome-extension://id/xx.html這種方式打開的網(wǎng)頁(yè)都可以無(wú)限制跨域。

配置中背亥,background可以通過(guò)page指定一張網(wǎng)頁(yè)般眉,也可以通過(guò)scripts直接指定一個(gè)JS,Chrome會(huì)自動(dòng)為這個(gè)JS生成一個(gè)默認(rèn)的網(wǎng)頁(yè):

{
    // 會(huì)一直常駐的后臺(tái)JS或后臺(tái)頁(yè)面
    "background":
    {
        // 2種指定方式慎宾,如果指定JS丐吓,那么會(huì)自動(dòng)生成一個(gè)背景頁(yè)
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
}

需要特別說(shuō)明的是,雖然你可以通過(guò)chrome-extension://xxx/background.html直接打開后臺(tái)頁(yè)趟据,但是你打開的后臺(tái)頁(yè)和真正一直在后臺(tái)運(yùn)行的那個(gè)頁(yè)面不是同一個(gè)券犁,換句話說(shuō),你可以打開無(wú)數(shù)個(gè)background.html汹碱,但是真正在后臺(tái)常駐的只有一個(gè)粘衬,而且這個(gè)你永遠(yuǎn)看不到它的界面,只能調(diào)試它的代碼咳促。

event-pages

這里順帶介紹一下event-pages稚新,它是一個(gè)什么東西呢?鑒于background生命周期太長(zhǎng)跪腹,長(zhǎng)時(shí)間掛載后臺(tái)可能會(huì)影響性能褂删,所以Google又弄一個(gè)event-pages,在配置文件上冲茸,它與background的唯一區(qū)別就是多了一個(gè)persistent參數(shù):

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

它的生命周期是:在被需要時(shí)加載屯阀,在空閑時(shí)被關(guān)閉缅帘,什么叫被需要時(shí)呢?比如第一次安裝难衰、插件更新钦无、有content-script向它發(fā)送消息,等等盖袭。

除了配置文件的變化失暂,代碼上也有一些細(xì)微變化,個(gè)人這個(gè)簡(jiǎn)單了解一下就行了苍凛,一般情況下background也不會(huì)很消耗性能的趣席。

popup

popup是點(diǎn)擊browser_action或者page_action圖標(biāo)時(shí)打開的一個(gè)小窗口網(wǎng)頁(yè),焦點(diǎn)離開網(wǎng)頁(yè)就立即關(guān)閉醇蝴,一般用來(lái)做一些臨時(shí)性的交互宣肚。

博客園網(wǎng)摘插件popup效果

popup可以包含任意你想要的HTML內(nèi)容,并且會(huì)自適應(yīng)大小悠栓∶拐牵可以通過(guò)default_popup字段來(lái)指定popup頁(yè)面,也可以調(diào)用setPopup()方法惭适。

配置方式:

{
    "browser_action":
    {
        "default_icon": "img/icon.png",
        // 圖標(biāo)懸停時(shí)的標(biāo)題笙瑟,可選
        "default_title": "這是一個(gè)示例Chrome插件",
        "default_popup": "popup.html"
    }
}
image

需要特別注意的是,由于單擊圖標(biāo)打開popup癞志,焦點(diǎn)離開又立即關(guān)閉往枷,所以popup頁(yè)面的生命周期一般很短,需要長(zhǎng)時(shí)間運(yùn)行的代碼千萬(wàn)不要寫在popup里面凄杯。

在權(quán)限上错洁,它和background非常類似,它們之間最大的不同是生命周期的不同戒突,popup中可以直接通過(guò)chrome.extension.getBackgroundPage()獲取background的window對(duì)象屯碴。

injected-script

這里的injected-script是我給它取的,指的是通過(guò)DOM操作的方式向頁(yè)面注入的一種JS膊存。為什么要把這種JS單獨(dú)拿出來(lái)討論呢导而?又或者說(shuō)為什么需要通過(guò)這種方式注入JS呢?

這是因?yàn)?code>content-script有一個(gè)很大的“缺陷”隔崎,也就是無(wú)法訪問(wèn)頁(yè)面中的JS今艺,雖然它可以操作DOM,但是DOM卻不能調(diào)用它爵卒,也就是無(wú)法在DOM中通過(guò)綁定事件的方式調(diào)用content-script中的代碼(包括直接寫onclickaddEventListener2種方式都不行)虚缎,但是,“在頁(yè)面上添加一個(gè)按鈕并調(diào)用插件的擴(kuò)展API”是一個(gè)很常見的需求技潘,那該怎么辦呢遥巴?其實(shí)這就是本小節(jié)要講的。

content-script中通過(guò)DOM方式向頁(yè)面注入inject-script代碼示例:

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

你以為這樣就行了铲掐?執(zhí)行一下你會(huì)看到如下報(bào)錯(cuò):

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

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

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

homepage_url

開發(fā)者或者插件主頁(yè)設(shè)置奔坟,一般會(huì)在如下2個(gè)地方顯示:

image
image

Chrome插件的8種展示形式

browserAction(瀏覽器右上角)

通過(guò)配置browser_action可以在瀏覽器的右上角增加一個(gè)圖標(biāo)携栋,一個(gè)browser_action可以擁有一個(gè)圖標(biāo),一個(gè)tooltip咳秉,一個(gè)badge和一個(gè)popup婉支。

示例配置如下:

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

圖標(biāo)

browser_action圖標(biāo)推薦使用寬高都為19像素的圖片,更大的圖標(biāo)會(huì)被縮小澜建,格式隨意向挖,一般推薦png,可以通過(guò)manifest中default_icon字段配置炕舵,也可以調(diào)用setIcon()方法何之。

tooltip

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

image

badge

所謂badge就是在圖標(biāo)上顯示一些文本溶推,可以用來(lái)更新一些小的擴(kuò)展?fàn)顟B(tài)提示信息。因?yàn)閎adge空間有限奸攻,所以只支持4個(gè)以下的字符(英文4個(gè)蒜危,中文2個(gè))。badge無(wú)法通過(guò)配置文件來(lái)指定舞箍,必須通過(guò)代碼實(shí)現(xiàn)舰褪,設(shè)置badge文字和顏色可以分別使用setBadgeText()setBadgeBackgroundColor()

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

效果:

image

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

所謂pageAction疏橄,指的是只有當(dāng)某些特定頁(yè)面打開才顯示的圖標(biāo)占拍,它和browserAction最大的區(qū)別是一個(gè)始終都顯示,一個(gè)只在特定情況才顯示捎迫。

需要特別說(shuō)明的是早些版本的Chrome是將pageAction放在地址欄的最右邊晃酒,左鍵單擊彈出popup,右鍵單擊則彈出相關(guān)默認(rèn)的選項(xiàng)菜單:

image

而新版的Chrome更改了這一策略窄绒,pageAction和普通的browserAction一樣也是放在瀏覽器右上角贝次,只不過(guò)沒(méi)有點(diǎn)亮?xí)r是灰色的,點(diǎn)亮了才是彩色的彰导,灰色時(shí)無(wú)論左鍵還是右鍵單擊都是彈出選項(xiàng):

image

具體是從哪一版本開始改的沒(méi)去仔細(xì)考究蛔翅,反正知道v50.0的時(shí)候還是前者敲茄,v58.0的時(shí)候已改為后者。

調(diào)整之后的pageAction我們可以簡(jiǎn)單地把它看成是可以置灰的browserAction山析。

  • chrome.pageAction.show(tabId) 顯示圖標(biāo)堰燎;
  • chrome.pageAction.hide(tabId) 隱藏圖標(biāo);

示例(只有打開百度才顯示圖標(biāo)):

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

效果圖:

image

右鍵菜單

通過(guò)開發(fā)Chrome插件可以自定義瀏覽器的右鍵菜單笋轨,主要是通過(guò)chrome.contextMenusAPI實(shí)現(xiàn)秆剪,右鍵菜單可以出現(xiàn)在不同的上下文,比如普通頁(yè)面爵政、選中的文字仅讽、圖片、鏈接钾挟,等等洁灵,如果有同一個(gè)插件里面定義了多個(gè)菜單,Chrome會(huì)自動(dòng)組合放到以插件名字命名的二級(jí)菜單里掺出,如下:

image

最簡(jiǎn)單的右鍵菜單示例

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

// background.js
chrome.contextMenus.create({
    title: "測(cè)試右鍵菜單",
    onclick: function(){alert('您點(diǎn)擊了右鍵菜單处渣!');}
});

效果:

image

添加右鍵百度搜索

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

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

效果如下:

image

語(yǔ)法說(shuō)明

這里只是簡(jiǎn)單列舉一些常用的罐栈,完整API參見:https://developer.chrome.com/extensions/contextMenus

chrome.contextMenus.create({
    type: 'normal', // 類型泥畅,可選:["normal", "checkbox", "radio", "separator"]荠诬,默認(rèn) normal
    title: '菜單的名字', // 顯示的文字,除非為“separator”類型否則此參數(shù)必需位仁,如果類型為“selection”柑贞,可以使用%s顯示選定的文本
    contexts: ['page'], // 上下文環(huán)境,可選:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"]聂抢,默認(rèn)page
    onclick: function(){}, // 單擊時(shí)觸發(fā)的方法
    parentId: 1, // 右鍵菜單項(xiàng)的父菜單項(xiàng)ID钧嘶。指定父菜單項(xiàng)將會(huì)使此菜單項(xiàng)成為父菜單項(xiàng)的子菜單
    documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些頁(yè)面顯示此右鍵菜單
});
// 刪除某一個(gè)菜單項(xiàng)
chrome.contextMenus.remove(menuItemId);
// 刪除所有自定義右鍵菜單
chrome.contextMenus.removeAll();
// 更新某一個(gè)菜單項(xiàng)
chrome.contextMenus.update(menuItemId, updateProperties);

override(覆蓋特定頁(yè)面)

使用override頁(yè)可以將Chrome默認(rèn)的一些特定頁(yè)面替換掉琳疏,改為使用擴(kuò)展提供的頁(yè)面有决。

擴(kuò)展可以替代如下頁(yè)面:

  • 歷史記錄:從工具菜單上點(diǎn)擊歷史記錄時(shí)訪問(wèn)的頁(yè)面,或者從地址欄直接輸入 chrome://history
  • 新標(biāo)簽頁(yè):當(dāng)創(chuàng)建新標(biāo)簽的時(shí)候訪問(wèn)的頁(yè)面空盼,或者從地址欄直接輸入 chrome://newtab
  • 書簽:瀏覽器的書簽书幕,或者直接輸入 chrome://bookmarks

注意:

  • 一個(gè)擴(kuò)展只能替代一個(gè)頁(yè)面;
  • 不能替代隱身窗口的新標(biāo)簽頁(yè)揽趾;
  • 網(wǎng)頁(yè)必須設(shè)置title台汇,否則用戶可能會(huì)看到網(wǎng)頁(yè)的URL,造成困擾;

下面的截圖是默認(rèn)的新標(biāo)簽頁(yè)和被擴(kuò)展替換掉的新標(biāo)簽頁(yè)苟呐。

image

代碼(注意痒芝,一個(gè)插件只能替代一個(gè)默認(rèn)頁(yè),以下僅為演示):

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

devtools(開發(fā)者工具)

預(yù)熱

使用過(guò)vue的應(yīng)該見過(guò)這種類型的插件:

image

是的牵素,Chrome允許插件在開發(fā)者工具(devtools)上動(dòng)手腳吼野,主要表現(xiàn)在:

  • 自定義一個(gè)和多個(gè)和ElementsConsole两波、Sources等同級(jí)別的面板;
  • 自定義側(cè)邊欄(sidebar)闷哆,目前只能自定義Elements面板的側(cè)邊欄腰奋;

先來(lái)看2張簡(jiǎn)單的demo截圖,自定義面板(判斷當(dāng)前頁(yè)面是否使用了jQuery):

image

自定義側(cè)邊欄(獲取當(dāng)前頁(yè)面所有圖片):

image

devtools擴(kuò)展介紹

主頁(yè):https://developer.chrome.com/extensions/devtools

來(lái)一張官方圖片:

image

每打開一個(gè)開發(fā)者工具窗口抱怔,都會(huì)創(chuàng)建devtools頁(yè)面的實(shí)例劣坊,F(xiàn)12窗口關(guān)閉,頁(yè)面也隨著關(guān)閉屈留,所以devtools頁(yè)面的生命周期和devtools窗口是一致的局冰。devtools頁(yè)面可以訪問(wèn)一組特有的DevTools API以及有限的擴(kuò)展API,這組特有的DevTools API只有devtools頁(yè)面才可以訪問(wèn)灌危,background都無(wú)權(quán)訪問(wèn)康二,這些API包括:

  • chrome.devtools.panels:面板相關(guān);
  • chrome.devtools.inspectedWindow:獲取被審查窗口的有關(guān)信息勇蝙;
  • chrome.devtools.network:獲取有關(guān)網(wǎng)絡(luò)請(qǐng)求的信息沫勿;

大部分?jǐn)U展API都無(wú)法直接被DevTools頁(yè)面調(diào)用,但它可以像content-script一樣直接調(diào)用chrome.extensionchrome.runtimeAPI味混,同時(shí)它也可以像content-script一樣使用Message交互的方式與background頁(yè)面進(jìn)行通信产雹。

實(shí)例:創(chuàng)建一個(gè)devtools擴(kuò)展

首先,要針對(duì)開發(fā)者工具開發(fā)插件翁锡,需要在清單文件聲明如下:

{
    // 只能指向一個(gè)HTML文件蔓挖,不能是JS文件
    "devtools_page": "devtools.html"
}

這個(gè)devtools.html里面一般什么都沒(méi)有,就引入一個(gè)js:

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

可以看出來(lái)馆衔,其實(shí)真正代碼是devtools.js瘟判,html文件是“多余”的,所以這里覺得有點(diǎn)坑角溃,devtools_page干嘛不允許直接指定JS呢荒适?

再來(lái)看devtools.js的代碼:

// 創(chuàng)建自定義面板,同一個(gè)插件可以創(chuàng)建多個(gè)自定義面板
// 幾個(gè)參數(shù)依次為:panel標(biāo)題开镣、圖標(biāo)(其實(shí)設(shè)置了也沒(méi)地方顯示)刀诬、要加載的頁(yè)面、加載成功后的回調(diào)
chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel)
{
    console.log('自定義面板創(chuàng)建成功!'); // 注意這個(gè)log一般看不到
});

// 創(chuàng)建自定義側(cè)邊欄
chrome.devtools.panels.elements.createSidebarPane("Images", function(sidebar)
{
    // sidebar.setPage('../sidebar.html'); // 指定加載某個(gè)頁(yè)面
    sidebar.setExpression('document.querySelectorAll("img")', 'All Images'); // 通過(guò)表達(dá)式來(lái)指定
    //sidebar.setObject({aaa: 111, bbb: 'Hello World!'}); // 直接設(shè)置顯示某個(gè)對(duì)象
});

setPage時(shí)的效果:

image

以下截圖示例的代碼:

image
// 檢測(cè)jQuery
document.getElementById('check_jquery').addEventListener('click', function()
{
    // 訪問(wèn)被檢查的頁(yè)面DOM需要使用inspectedWindow
    // 簡(jiǎn)單例子:檢測(cè)被檢查頁(yè)面是否使用了jQuery
    chrome.devtools.inspectedWindow.eval("jQuery.fn.jquery", function(result, isException)
    {
        var html = '';
        if (isException) html = '當(dāng)前頁(yè)面沒(méi)有使用jQuery陕壹。';
        else html = '當(dāng)前頁(yè)面使用了jQuery质欲,版本為:'+result;
        alert(html);
    });
});

// 打開某個(gè)資源
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));
    });
});

調(diào)試技巧

修改了devtools頁(yè)面的代碼時(shí)糠馆,需要先在 chrome://extensions 頁(yè)面按下Ctrl+R重新加載插件嘶伟,然后關(guān)閉再打開開發(fā)者工具即可,無(wú)需刷新頁(yè)面(而且只刷新頁(yè)面不刷新開發(fā)者工具的話是不會(huì)生效的)又碌。

由于devtools本身就是開發(fā)者工具頁(yè)面九昧,所以幾乎沒(méi)有方法可以直接調(diào)試它,直接用 chrome-extension://extid/devtools.html"的方式打開頁(yè)面肯定報(bào)錯(cuò)毕匀,因?yàn)椴恢С窒嚓P(guān)特殊API铸鹰,只能先自己寫一些方法屏蔽這些錯(cuò)誤,調(diào)試通了再放開皂岔。

option(選項(xiàng)頁(yè))

所謂options頁(yè)蹋笼,就是插件的設(shè)置頁(yè)面,有2個(gè)入口躁垛,一個(gè)是右鍵圖標(biāo)有一個(gè)“選項(xiàng)”菜單剖毯,還有一個(gè)在插件管理頁(yè)面:

image
image

在Chrome40以前,options頁(yè)面和其它普通頁(yè)面沒(méi)什么區(qū)別教馆,Chrome40以后則有了一些變化逊谋。

我們先看老版的options

{
    // Chrome40以前的插件配置頁(yè)寫法
    "options_page": "options.html",
}

這個(gè)頁(yè)面里面的內(nèi)容就隨你自己發(fā)揮了,配置之后在插件管理頁(yè)就會(huì)看到一個(gè)選項(xiàng)按鈕入口土铺,點(diǎn)進(jìn)去就是打開一個(gè)網(wǎng)頁(yè)涣狗,沒(méi)啥好講的。

效果:

image

再來(lái)看新版的optionsV2

{
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默認(rèn)的樣式舒憾,推薦使用
        "chrome_style": true
    },
}

options.html的代碼我們沒(méi)有任何改動(dòng)镀钓,只是配置文件改了,之后效果如下:

image

看起來(lái)是不是高大上了镀迂?

幾點(diǎn)注意:

  • 為了兼容丁溅,建議2種都寫,如果都寫了探遵,Chrome40以后會(huì)默認(rèn)讀取新版的方式窟赏;
  • 新版options中不能使用alert;
  • 數(shù)據(jù)存儲(chǔ)建議用chrome.storage箱季,因?yàn)闀?huì)隨用戶自動(dòng)同步涯穷;

omnibox

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

image

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

首先,配置文件如下:

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

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

// omnibox 演示
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
    console.log('inputChanged: ' + text);
    if(!text) return;
    if(text == '美女') {
        suggest([
            {content: '中國(guó)' + text, description: '你要找“中國(guó)美女”嗎赚瘦?'},
            {content: '日本' + text, description: '你要找“日本美女”嗎粟誓?'},
            {content: '泰國(guó)' + text, description: '你要找“泰國(guó)美女或人妖”嗎?'},
            {content: '韓國(guó)' + text, description: '你要找“韓國(guó)美女”嗎起意?'}
        ]);
    }
    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},
        ]);
    }
});

// 當(dāng)用戶接收關(guān)鍵字建議時(shí)觸發(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);
});
// 獲取當(dāng)前選項(xiàng)卡ID
function getCurrentTabId(callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        if(callback) callback(tabs.length ? tabs[0].id: null);
    });
}

// 當(dāng)前標(biāo)簽打開某個(gè)鏈接
function openUrlCurrentTab(url)
{
    getCurrentTabId(tabId => {
        chrome.tabs.update(tabId, {url: url});
    })
}

桌面通知

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

在后臺(tái)JS中揽咕,無(wú)論是使用chrome.notifications還是Notification都不需要申請(qǐng)權(quán)限(HTML5方式需要申請(qǐng)權(quán)限)悲酷,直接使用即可。

最簡(jiǎn)單的通知:

image

代碼:

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

通知的樣式可以很豐富:

image

這個(gè)沒(méi)有深入研究设易,有需要的可以去看官方文檔。

5種類型的JS對(duì)比

Chrome插件的JS主要可以分為這5類:injected script逗爹、content-scriptpopup js嚎于、background jsdevtools js掘而,

權(quán)限對(duì)比

JS種類 可訪問(wèn)的API DOM訪問(wèn)情況 JS訪問(wèn)情況 直接跨域
injected script 和普通JS無(wú)任何差別,不能訪問(wèn)任何擴(kuò)展API 可以訪問(wèn) 可以訪問(wèn) 不可以
content script 只能訪問(wèn) extension于购、runtime等部分API 可以訪問(wèn) 不可以 不可以
popup js 可訪問(wèn)絕大部分API袍睡,除了devtools系列 不可直接訪問(wèn) 不可以 可以
background js 可訪問(wèn)絕大部分API,除了devtools系列 不可直接訪問(wèn) 不可以 可以
devtools js 只能訪問(wèn) devtools肋僧、extension斑胜、runtime等部分API 可以 可以 不可以

調(diào)試方式對(duì)比

| JS類型 | 調(diào)試方式 | 圖片說(shuō)明 |
| ------------ | ------------ |
| injected script | 直接普通的F12即可 | 懶得截圖 |

| content-script | 打開Console,如圖切換 |
image
|
| popup-js | popup頁(yè)面右鍵審查元素 |
image
|

| background | 插件管理頁(yè)點(diǎn)擊背景頁(yè)即可 |
image
|
| devtools-js | 暫未找到有效方法 | - |

消息通信

通信主頁(yè):https://developer.chrome.com/extensions/messaging

前面我們介紹了Chrome插件中存在的5種JS,那么它們之間如何互相通信呢嫌吠?下面先來(lái)系統(tǒng)概況一下止潘,然后再分類細(xì)說(shuō)。需要知道的是辫诅,popup和background其實(shí)幾乎可以視為一種東西凭戴,因?yàn)樗鼈兛稍L問(wèn)的API都一樣、通信機(jī)制一樣炕矮、都可以跨域么夫。

互相通信概覽

注:-表示不存在或者無(wú)意義,或者待驗(yàn)證肤视。

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

通信詳細(xì)介紹

popup和background

popup可以直接調(diào)用background中的JS方法档痪,也可以直接訪問(wèn)background的DOM:

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

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

小插曲邢滑,今天碰到一個(gè)情況腐螟,發(fā)現(xiàn)popup無(wú)法獲取background的任何方法,找了半天才發(fā)現(xiàn)是因?yàn)閎ackground的js報(bào)錯(cuò)了,而你如果不主動(dòng)查看background的js的話遭垛,是看不到錯(cuò)誤信息的尼桶,特此提醒。

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

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

popup或者bg向content主動(dòng)發(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('來(lái)自content的回復(fù):'+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對(duì)象庶喜,不是JSON字符串小腊,所以無(wú)需解析,很方便(當(dāng)然也可以直接發(fā)送字符串)久窟。

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

content-script主動(dòng)發(fā)消息給后臺(tái)

content-script.js:

chrome.runtime.sendMessage({greeting: '你好入问,我是content-script呀,我主動(dòng)發(fā)消息給后臺(tái)稀颁!'}, function(response) {
    console.log('收到來(lái)自后臺(tái)的回復(fù):' + response);
});

background.js 或者 popup.js:

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

注意事項(xiàng):

  • content_scripts向popup主動(dòng)發(fā)消息的前提是popup必須打開!否則需要利用background作中轉(zhuǎn)匾灶;
  • 如果background和popup同時(shí)監(jiān)聽棱烂,那么它們都可以同時(shí)收到消息,但是只有一個(gè)可以sendResponse阶女,一個(gè)先發(fā)送了颊糜,那么另外一個(gè)再發(fā)送就無(wú)效;

injected script和content-script

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

  1. 可以通過(guò)window.postMessagewindow.addEventListener來(lái)實(shí)現(xiàn)二者消息通訊衬鱼;
  2. 通過(guò)自定義DOM事件來(lái)實(shí)現(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);
});

長(zhǎng)連接和短連接

其實(shí)上面已經(jīng)涉及到了芍秆,這里再單獨(dú)說(shuō)明一下惯疙。Chrome插件中有2種通信方式,一個(gè)是短連接(chrome.tabs.sendMessagechrome.runtime.sendMessage)妖啥,一個(gè)是長(zhǎng)連接(chrome.tabs.connectchrome.runtime.connect)霉颠。

短連接的話就是擠牙膏一樣,我發(fā)送一下荆虱,你收到了再回復(fù)一下蒿偎,如果對(duì)方不回復(fù)朽们,你只能重新發(fā),而長(zhǎng)連接類似WebSocket會(huì)一直建立連接诉位,雙方可以隨時(shí)互發(fā)消息骑脱。

短連接上面已經(jīng)有代碼示例了,這里只講一下長(zhǎng)連接苍糠。

popup.js:

getCurrentTabId((tabId) => {
    var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
    port.postMessage({question: '你是誰(shuí)叭ァ?'});
    port.onMessage.addListener(function(msg) {
        alert('收到消息:'+msg.answer);
        if(msg.answer && msg.answer.startsWith('我是'))
        {
            port.postMessage({question: '哦岳瞭,原來(lái)是你坝德Α!'});
        }
    });
});

content-script.js:

// 監(jiān)聽長(zhǎng)連接
chrome.runtime.onConnect.addListener(function(port) {
    console.log(port);
    if(port.name == 'test-connect') {
        port.onMessage.addListener(function(msg) {
            console.log('收到長(zhǎng)連接消息:', msg);
            if(msg.question == '你是誰(shuí)巴ぁ稚瘾?') port.postMessage({answer: '我是你爸!'});
        });
    }
});

其它補(bǔ)充

動(dòng)態(tài)注入或執(zhí)行JS

雖然在backgroundpopup中無(wú)法直接訪問(wèn)頁(yè)面DOM姚炕,但是可以通過(guò)chrome.tabs.executeScript來(lái)執(zhí)行腳本摊欠,從而實(shí)現(xiàn)訪問(wèn)web頁(yè)面的DOM(注意,這種方式也不能直接訪問(wèn)頁(yè)面JS)柱宦。

示例manifest.json配置:

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

JS:

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

動(dòng)態(tài)注入CSS

示例manifest.json配置:

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

JS代碼:

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

獲取當(dāng)前窗口ID

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

獲取當(dāng)前標(biāo)簽頁(yè)ID

一般有2種方法:

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

獲取當(dāng)前選項(xiàng)卡id的另一種方法捷沸,大部分時(shí)候都類似摊沉,只有少部分時(shí)候會(huì)不一樣(例如當(dāng)窗口最小化時(shí))

// 獲取當(dāng)前選項(xiàng)卡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);
        });
    });
}

本地存儲(chǔ)

本地存儲(chǔ)建議用chrome.storage而不是普通的localStorage狐史,區(qū)別有好幾點(diǎn)痒给,個(gè)人認(rèn)為最重要的2點(diǎn)區(qū)別是:

  • chrome.storage是針對(duì)插件全局的,即使你在background中保存的數(shù)據(jù)骏全,在content-script也能獲取到苍柏;
  • chrome.storage.sync可以跟隨當(dāng)前登錄用戶自動(dòng)同步,這臺(tái)電腦修改的設(shè)置會(huì)自動(dòng)同步到其它電腦姜贡,很方便试吁,如果沒(méi)有登錄或者未聯(lián)網(wǎng)則先保存到本地,等登錄了再同步至網(wǎng)絡(luò)楼咳;

需要聲明storage權(quán)限熄捍,有chrome.storage.syncchrome.storage.local2種方式可供選擇吕漂,使用示例如下:

// 讀取數(shù)據(jù)荚孵,第一個(gè)參數(shù)是指定要讀取的key以及設(shè)置默認(rèn)值
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('保存成功!');
});

webRequest

通過(guò)webRequest系列API可以對(duì)HTTP請(qǐng)求進(jìn)行任性地修改辩蛋、定制苹熏,這里通過(guò)beforeRequest來(lái)簡(jiǎn)單演示一下它的冰山一角:

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


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

國(guó)際化

插件根目錄新建一個(gè)名為_locales的文件夾,再在下面新建一些語(yǔ)言的文件夾朱巨,如en史翘、zh_CNzh_TW蔬崩,然后再在每個(gè)文件夾放入一個(gè)messages.json恶座,同時(shí)必須在清單文件中設(shè)置default_locale

_locales\en\messages.json內(nèi)容:

{
    "pluginDesc": {"message": "A simple chrome extension demo"},
    "helloWorld": {"message": "Hello World!"}
}

_locales\zh_CN\messages.json內(nèi)容:

{
    "pluginDesc": {"message": "一個(gè)簡(jiǎn)單的Chrome插件demo"},
    "helloWorld": {"message": "你好啊沥阳,世界跨琳!"}
}

manifest.jsonCSS文件中通過(guò)__MSG_messagename__引入,如:

{
    "description": "__MSG_pluginDesc__",
    // 默認(rèn)語(yǔ)言
    "default_locale": "zh_CN",
}

JS中則直接chrome.i18n.getMessage("helloWorld")桐罕。

測(cè)試時(shí)脉让,通過(guò)給chrome建立一個(gè)不同的快捷方式chrome.exe --lang=en來(lái)切換語(yǔ)言,如:

image

英文效果:

image

中文效果:

image

API總結(jié)

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

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

經(jīng)驗(yàn)總結(jié)

查看已安裝插件路徑

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

image

如何查看某個(gè)插件的ID薪伏?進(jìn)入 chrome://extensions 滚澜,然后勾線開發(fā)者模式即可看到了。

image

特別注意background的報(bào)錯(cuò)

很多時(shí)候你發(fā)現(xiàn)你的代碼會(huì)莫名其妙的失效嫁怀,找來(lái)找去又找不到原因设捐,這時(shí)打開background的控制臺(tái)才發(fā)現(xiàn)原來(lái)某個(gè)地方寫錯(cuò)了導(dǎo)致代碼沒(méi)生效,正式由于background報(bào)錯(cuò)的隱蔽性(需要主動(dòng)打開對(duì)應(yīng)的控制臺(tái)才能看到錯(cuò)誤)塘淑,所以特別注意這點(diǎn)萝招。

如何讓popup頁(yè)面不關(guān)閉

在對(duì)popup頁(yè)面審查元素的時(shí)候popup會(huì)被強(qiáng)制打開無(wú)法關(guān)閉,只有控制臺(tái)關(guān)閉了才可以關(guān)閉popup存捺,原因很簡(jiǎn)單:如果popup關(guān)閉了控制臺(tái)就沒(méi)用了槐沼。這種方法在某些情況下很實(shí)用!

不支持內(nèi)聯(lián)JavaScript的執(zhí)行

也就是不支持將js直接寫在html中捌治,比如:

<input id="btn" type="button" value="收藏" onclick="test()"/>

報(bào)錯(cuò)如下:

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

解決方法就是用JS綁定事件:

$('#btn').on('click', function(){alert('測(cè)試')});

另外岗钩,對(duì)于A標(biāo)簽,這樣寫href="javascript:;"然后用JS綁定事件雖然控制臺(tái)會(huì)報(bào)錯(cuò)肖油,但是不受影響兼吓,當(dāng)然強(qiáng)迫癥患者受不了的話只能寫成href="#"了。

如果這樣寫:

<a href="javascript:;" id="get_secret">請(qǐng)求secret</a>

報(bào)錯(cuò)如下:

Refused to execute JavaScript URL because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

注入CSS的時(shí)候必須小心

由于通過(guò)content_scripts注入的CSS優(yōu)先級(jí)非常高构韵,幾乎僅次于瀏覽器默認(rèn)樣式周蹭,稍不注意可能就會(huì)影響一些網(wǎng)站的展示效果趋艘,所以盡量不要寫一些影響全局的樣式。

之所以強(qiáng)調(diào)這個(gè)凶朗,是因?yàn)檫@個(gè)帶來(lái)的問(wèn)題非常隱蔽瓷胧,不太容易找到,可能你正在寫某個(gè)網(wǎng)頁(yè)棚愤,昨天樣式還是好好的搓萧,怎么今天就突然不行了?然后你辛辛苦苦找來(lái)找去宛畦,找了半天才發(fā)現(xiàn)竟然是因?yàn)椴寮锩娴囊粋€(gè)樣式影響的瘸洛!

image

打包與發(fā)布

打包的話直接在插件管理頁(yè)有一個(gè)打包按鈕:

image

然后會(huì)生成一個(gè).crx文件,要發(fā)布到Google應(yīng)用商店的話需要先登錄你的Google賬號(hào)次和,然后花5個(gè)$注冊(cè)為開發(fā)者反肋,本人太窮,就懶得親自驗(yàn)證了踏施,有發(fā)布需求的自己去整吧石蔗。

image

參考

官方資料

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

第三方資料

部分中文資料,不是特別推薦:

附圖

附圖:Chrome高清png格式logo:

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末日熬,一起剝皮案震驚了整個(gè)濱河市棍厌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌竖席,老刑警劉巖耘纱,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怕敬,居然都是意外死亡揣炕,警方通過(guò)查閱死者的電腦和手機(jī)帘皿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門东跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鹰溜,你說(shuō)我怎么就攤上這事虽填。” “怎么了曹动?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵斋日,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我墓陈,道長(zhǎng)恶守,這世上最難降的妖魔是什么第献? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮兔港,結(jié)果婚禮上庸毫,老公的妹妹穿的比我還像新娘。我一直安慰自己衫樊,他們只是感情好飒赃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著科侈,像睡著了一般载佳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臀栈,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天蔫慧,我揣著相機(jī)與錄音,去河邊找鬼权薯。 笑死藕漱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崭闲。 我是一名探鬼主播肋联,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刁俭!你這毒婦竟也來(lái)了橄仍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牍戚,失蹤者是張志新(化名)和其女友劉穎侮繁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體如孝,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宪哩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了第晰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锁孟。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茁瘦,靈堂內(nèi)的尸體忽然破棺而出品抽,到底是詐尸還是另有隱情,我是刑警寧澤甜熔,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布圆恤,位于F島的核電站,受9級(jí)特大地震影響腔稀,放射性物質(zhì)發(fā)生泄漏盆昙。R本人自食惡果不足惜羽历,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淡喜。 院中可真熱鬧窄陡,春花似錦、人聲如沸拆火。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)们镜。三九已至币叹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間模狭,已是汗流浹背颈抚。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嚼鹉,地道東北人贩汉。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像锚赤,于是被迫代替她去往敵國(guó)和親匹舞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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