Chrome瀏覽器插件的寫法

1. 什么是Chrome插件

Chrome插件是一個用Web技術(shù)開發(fā)、用來增強瀏覽器功能的軟件柠座,它其實就是一個由HTML邑雅、CSS、JS妈经、圖片等資源組成的一個.crx后綴的壓縮包.

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

1. Chrome占有率更高淮野,更多人用;
2. 開發(fā)更簡單吹泡;
3. 應(yīng)用場景更廣泛骤星,F(xiàn)irefox插件只能運行在Firefox上,而Chrome除了Chrome瀏覽器之外爆哑,還可以運行在所有webkit內(nèi)核的國產(chǎn)瀏覽器洞难,比如360極速瀏覽器、360安全瀏覽器揭朝、搜狗瀏覽器队贱、QQ瀏覽器等等;
4. 除此之外潭袱,F(xiàn)irefox瀏覽器也對Chrome插件的運行提供了一定的支持柱嫌;
5.```

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

Chrome插件沒有嚴(yán)格的項目結(jié)構(gòu)要求,只要保證本目錄有一個manifest.json即可屯换,也不需要專門的IDE编丘,普通的web開發(fā)工具即可。

從右上角菜單->更多工具->擴展程序可以進入 插件管理頁面彤悔,也可以直接在地址欄輸入 chrome://extensions 訪問嘉抓。

勾選開發(fā)者模式即可以文件夾的形式直接加載插件,否則只能安裝.crx格式的文件晕窑。Chrome要求插件必須從它的Chrome應(yīng)用商店安裝掌眠,其它任何網(wǎng)站下載的都無法直接安裝,所以幕屹,其實我們可以把crx文件解壓蓝丙,然后通過開發(fā)者模式直接加載级遭。

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

3.核心介紹

manifest_version鸥跟、name丢郊、version3個是必不可少的,descriptionicons是推薦的

{
    // 清單文件的版本医咨,這個必須寫枫匾,而且必須是2
    "manifest_version": 2,
    // 插件的名稱
    "name": "demo",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述
    "description": "簡單的Chrome擴展demo",
    // 圖標(biāo),一般偷懶全部用一個尺寸的也沒問題
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 會一直常駐的后臺JS或后臺頁面
    "background":
    {
        // 2種指定方式拟淮,如果指定JS干茉,那么會自動生成一個背景頁
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
    // 瀏覽器右上角圖標(biāo)設(shè)置,browser_action很泊、page_action角虫、app必須三選一
    "browser_action": 
    {
        "default_icon": "img/icon.png",
        // 圖標(biāo)懸停時的標(biāo)題,可選
        "default_title": "這是一個示例Chrome插件",
        "default_popup": "popup.html"
    },
    // 當(dāng)某些特定頁面打開才顯示的圖標(biāo)
    /*"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"昏兆,最后一個表示頁面空閑時枫虏,默認(rèn)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", // 標(biāo)簽
        "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",
    // 覆蓋瀏覽器默認(rèn)頁面
    "chrome_url_overrides":
    {
        // 覆蓋瀏覽器默認(rèn)的新標(biāo)簽頁
        "newtab": "newtab.html"
    },
    // Chrome40以前的插件配置頁寫法
    "options_page": "options.html",
    // Chrome40以后的插件配置頁寫法,如果2個都寫饮潦,新版Chrome只認(rèn)后面這一個
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默認(rèn)的樣式燃异,推薦使用
        "chrome_style": true
    },
    // 向地址欄注冊一個關(guān)鍵字以提供搜索建議,只能設(shè)置一個關(guān)鍵字
    "omnibox": { "keyword" : "go" },
    // 默認(rèn)語言
    "default_locale": "zh_CN",
    // devtools頁面入口继蜡,注意只能指向一個HTML文件回俐,不能是JS文件
    "devtools_page": "devtools.html"
}

4.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"凌停,最后一個表示頁面空閑時粱年,默認(rèn)document_idle
            "run_at": "document_start"
        }
    ],
}

特別注意,如果沒有主動指定run_atdocument_start(默認(rèn)為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

5. 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生成一個默認(rèn)的網(wǎng)頁:

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

需要特別說明的是编整,

雖然你可以通過chrome-extension://xxx/background.html直接打開后臺頁,但是你打開的后臺頁和真正一直在后臺運行的那個頁面不是同一個乳丰,換句話說掌测,你可以打開無數(shù)個background.html,但是真正在后臺常駐的只有一個产园,而且這個你永遠(yuǎn)看不到它的界面汞斧,只能調(diào)試它的代碼夜郁。

6. event-pages

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

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

它的生命周期是:在被需要時加載,在空閑時被關(guān)閉埃撵,什么叫被需要時呢赵颅?比如第一次安裝、插件更新暂刘、有content-script向它發(fā)送消息饺谬,等等。

7. popup

popup是點擊browser_action或者page_action圖標(biāo)時打開的一個小窗口網(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",
        // 圖標(biāo)懸停時的標(biāo)題,可選
        "default_title": "這是一個示例Chrome插件",
        "default_popup": "popup.html"
    }
}

需要特別注意的是宾茂,由于單擊圖標(biāo)打開popup瓷马,焦點離開又立即關(guān)閉,所以popup頁面的生命周期一般很短跨晴,需要長時間運行的代碼千萬不要寫在popup里面欧聘。

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

8. injected-script

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

這是因為content-script有一個很大的“缺陷”凉敲,也就是無法訪問頁面中的JS,雖然它可以操作DOM,但是DOM卻不能調(diào)用它爷抓,也就是無法在DOM中通過綁定事件的方式調(diào)用content-script中的代碼(包括直接寫onclickaddEventListener2種方式都不行)势决,但是,“在頁面上添加一個按鈕并調(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é)詳細(xì)介紹。

9.homepage_url

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

10. browserAction(瀏覽器右上角)

通過配置browser_action可以在瀏覽器的右上角增加一個圖標(biāo)车酣,一個browser_action可以擁有一個圖標(biāo),一個tooltip索绪,一個badge和一個popup湖员。

示例配置如下:

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

10.1 圖標(biāo)

browser_action圖標(biāo)推薦使用寬高都為19像素的圖片,更大的圖標(biāo)會被縮小瑞驱,格式隨意娘摔,一般推薦png,可以通過manifest中default_icon字段配置钱烟,也可以調(diào)用setIcon()方法晰筛。

10.2 tooltip

修改browser_action的manifest中default_title字段嫡丙,或者調(diào)用setTitle()方法拴袭。

10.3. badge

所謂badge就是在圖標(biāo)上顯示一些文本,可以用來更新一些小的擴展?fàn)顟B(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]});

效果:

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

所謂pageAction蒸眠,指的是只有當(dāng)某些特定頁面打開才顯示的圖標(biāo),它和browserAction最大的區(qū)別是一個始終都顯示杆融,一個只在特定情況才顯示楞卡。

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

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

具體是從哪一版本開始改的沒去仔細(xì)考究焦除,反正知道v50.0的時候還是前者,v58.0的時候已改為后者作彤。

調(diào)整之后的pageAction我們可以簡單地把它看成是可以置灰的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()]
            }
        ]);
    });
});

效果圖:

12. 右鍵菜單

通過開發(fā)Chrome插件可以自定義瀏覽器的右鍵菜單瓣距,主要是通過chrome.contextMenusAPI實現(xiàn),右鍵菜單可以出現(xiàn)在不同的上下文代咸,比如普通頁面蹈丸、選中的文字、圖片呐芥、鏈接逻杖,等等,如果有同一個插件里面定義了多個菜單思瘟,Chrome會自動組合放到以插件名字命名的二級菜單里荸百,如下:

12.1 最簡單的右鍵菜單示例

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

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

效果:

12.2 添加右鍵百度搜索

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

// background.js
chrome.contextMenus.create({
    title: '使用度娘搜索:%s', // %s表示選中的文字
    contexts: ['selection'], // 只有當(dāng)選中文字時才會出現(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)});
    }
});

效果如下:

12.3 語法說明

這里只是簡單列舉一些常用的,完整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(){}, // 單擊時觸發(fā)的方法
    parentId: 1, // 右鍵菜單項的父菜單項ID。指定父菜單項將會使此菜單項成為父菜單項的子菜單
    documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些頁面顯示此右鍵菜單
});
// 刪除某一個菜單項
chrome.contextMenus.remove(menuItemId)慧起;
// 刪除所有自定義右鍵菜單
chrome.contextMenus.removeAll();
// 更新某一個菜單項
chrome.contextMenus.update(menuItemId, updateProperties);

12.4 override(覆蓋特定頁面)

使用override頁可以將Chrome默認(rèn)的一些特定頁面替換掉菇晃,改為使用擴展提供的頁面。

擴展可以替代如下頁面:

  • 歷史記錄:從工具菜單上點擊歷史記錄時訪問的頁面蚓挤,或者從地址欄直接輸入 chrome://history
  • 新標(biāo)簽頁:當(dāng)創(chuàng)建新標(biāo)簽的時候訪問的頁面磺送,或者從地址欄直接輸入 chrome://newtab
  • 書簽:瀏覽器的書簽剩失,或者直接輸入 chrome://bookmarks

注意:

  • 一個擴展只能替代一個頁面;
  • 不能替代隱身窗口的新標(biāo)簽頁册着;
  • 網(wǎng)頁必須設(shè)置title拴孤,否則用戶可能會看到網(wǎng)頁的URL,造成困擾甲捏;

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

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

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

13. option(選項頁)

所謂options頁芒粹,就是插件的設(shè)置頁面,有2個入口大溜,一個是右鍵圖標(biāo)有一個“選項”菜單化漆,還有一個在插件管理頁面:

在Chrome40以前,options頁面和其它普通頁面沒什么區(qū)別钦奋,Chrome40以后則有了一些變化座云。

我們先看老版的options

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

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

效果:

再來看新版的optionsV2

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

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

看起來是不是高大上了富寿?

幾點注意:

  • 為了兼容睬隶,建議2種都寫,如果都寫了页徐,Chrome40以后會默認(rèn)讀取新版的方式苏潜;
  • 新版options中不能使用alert;
  • 數(shù)據(jù)存儲建議用chrome.storage泞坦,因為會隨用戶自動同步窖贤;

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

// 當(dāng)用戶接收關(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);
});
// 獲取當(dā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)簽打開某個鏈接
function openUrlCurrentTab(url)
{
    getCurrentTabId(tabId => {
        chrome.tabs.update(tabId, {url: url});
    })
}

15. 桌面通知

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: '這是標(biāo)題',
    message: '您剛才點擊了自定義右鍵菜單锻拘!'
});

通知的樣式可以很豐富:

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

16.5種類型的JS對比

Chrome插件的JS主要可以分為這5類:injected script署拟、content-scriptpopup js歌豺、background jsdevtools js推穷,

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

16.2 調(diào)試方式對比

JS類型 調(diào)試方式 圖片說明
injected script 直接普通的F12即可 懶得截圖
content-script 打開Console,如圖切換
popup-js popup頁面右鍵審查元素
background 插件管理頁點擊背景頁即可
devtools-js 暫未找到有效方法 -

17.消息通信

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

前面我們介紹了Chrome插件中存在的5種JS,那么它們之間如何互相通信呢血巍?下面先來系統(tǒng)概況一下萧锉,然后再分類細(xì)說。需要知道的是述寡,popup和background其實幾乎可以視為一種東西柿隙,因為它們可訪問的API都一樣、通信機制一樣鲫凶、都可以跨域禀崖。

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

17.2. 通信詳細(xì)介紹

7.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);
}
7.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的回復(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對象碎罚,不是JSON字符串磅废,所以無需解析,很方便(當(dāng)然也可以直接發(fā)送字符串)荆烈。

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

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

content-script.js:

chrome.runtime.sendMessage({greeting: '你好谜喊,我是content-script呀,我主動發(fā)消息給后臺倦始!'}, function(response) {
    console.log('收到來自后臺的回復(fù):' + 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ā)送就無效;
7.2.4. injected script和content-script

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

  1. 可以通過window.postMessagewindow.addEventListener來實現(xiàn)二者消息通訊遵堵;
  2. 通過自定義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);
});

17.3. 長連接和短連接

其實上面已經(jīng)涉及到了波丰,這里再單獨說明一下壳坪。Chrome插件中有2種通信方式,一個是短連接(chrome.tabs.sendMessagechrome.runtime.sendMessage)掰烟,一個是長連接(chrome.tabs.connectchrome.runtime.connect)爽蝴。

短連接的話就是擠牙膏一樣,我發(fā)送一下纫骑,你收到了再回復(fù)一下蝎亚,如果對方不回復(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: '我是你爸鳖枕!'});
        });
    }
});

18.其他重要知識點

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

雖然在backgroundpopup中無法直接訪問頁面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'});

18.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'});

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

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

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

一般有2種方法:

// 獲取當(dā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)前選項卡id的另一種方法辣苏,大部分時候都類似,只有少部分時候會不一樣(例如當(dāng)窗口最小化時)

// 獲取當(dā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);
        });
    });
}

18.5. 本地存儲

本地存儲建議用chrome.storage而不是普通的localStorage哄褒,區(qū)別有好幾點稀蟋,個人認(rèn)為最重要的2點區(qū)別是:

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

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

// 讀取數(shù)據(jù)包个,第一個參數(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('保存成功刷允!');
});

18.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"]);

18.7. 國際化

插件根目錄新建一個名為_locales的文件夾熄驼,再在下面新建一些語言的文件夾像寒,如en烘豹、zh_CNzh_TW诺祸,然后再在每個文件夾放入一個messages.json携悯,同時必須在清單文件中設(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": "一個簡單的Chrome插件demo"},
    "helloWorld": {"message": "你好啊筷笨,世界憔鬼!"}
}

manifest.jsonCSS文件中通過__MSG_messagename__引入,如:

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

JS中則直接chrome.i18n.getMessage("helloWorld")胃夏。

測試時轴或,通過給chrome建立一個不同的快捷方式chrome.exe --lang=en來切換語言,如:

img

英文效果:

img

中文效果:

img

18.8. API總結(jié)

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

  • chrome.tabs
  • chrome.runtime
  • chrome.webRequest
  • chrome.window
  • chrome.storage
  • chrome.contextMenus
  • chrome.devtools
  • chrome.extension
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仰禀,一起剝皮案震驚了整個濱河市照雁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悼瘾,老刑警劉巖囊榜,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亥宿,居然都是意外死亡卸勺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門烫扼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曙求,“玉大人,你說我怎么就攤上這事映企∥蛴” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵堰氓,是天一觀的道長挤渐。 經(jīng)常有香客問我,道長双絮,這世上最難降的妖魔是什么浴麻? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮囤攀,結(jié)果婚禮上软免,老公的妹妹穿的比我還像新娘。我一直安慰自己焚挠,他們只是感情好膏萧,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般榛泛。 火紅的嫁衣襯著肌膚如雪蝌蹂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天挟鸠,我揣著相機與錄音叉信,去河邊找鬼亩冬。 笑死艘希,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的硅急。 我是一名探鬼主播覆享,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼营袜!你這毒婦竟也來了撒顿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤荚板,失蹤者是張志新(化名)和其女友劉穎凤壁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跪另,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡拧抖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了免绿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唧席。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嘲驾,靈堂內(nèi)的尸體忽然破棺而出淌哟,到底是詐尸還是另有隱情,我是刑警寧澤辽故,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布徒仓,位于F島的核電站,受9級特大地震影響誊垢,放射性物質(zhì)發(fā)生泄漏掉弛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一彤枢、第九天 我趴在偏房一處隱蔽的房頂上張望狰晚。 院中可真熱鬧,春花似錦缴啡、人聲如沸壁晒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秒咐。三九已至谬晕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間携取,已是汗流浹背攒钳。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雷滋,地道東北人不撑。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像晤斩,于是被迫代替她去往敵國和親焕檬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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