原文地址: https://segmentfault.com/a/11...
轉載請在文首註明來源
TL;DRcss
本文歷時較久,2020年寫了一半,寫到迷茫,索性擱置,2021年翻出來繼續寫的;通過了半年多的時間再看,會有一些不同的理解,存在部分配圖風格不一致.可是我會確保內容的正確和表達的一致.
2020年的某一天我把以前寫的QQ空間批量刪除說說&留言的腳本寫了個chrome插件版
,名字也改爲了QQ空間小助手.
由於也是第一次寫插件,因此記錄這個過程,分享開發過程和期間遇到的問題,並作一些關鍵點的梳理.
因此,這並非一篇大而全的chrome extension開發教程,這是一篇以QQ空間助手爲實戰來切入插件開發可是側重講chrome extension開發的文章.html
QQ空間小助手功能很簡單,核心功能(其實也就這點功能)是批量刪除qq空間的說說和留言.
實現上也很簡單,只須要兩個步驟.前端
這裏面的困難一個在於找到獲取&刪除 留言和說說 所須要的參數(這個咱們不展開介紹),
另一個困難是這些功能怎麼在chrome extension
中實現.git
根據上面這些需求,我理解大概操做流程是這樣的,github
點擊icon
,或者點擊icon
彈出來的頁面而後就開始獲取列表,而後遍歷列表開始刪說說.web
裏面大概須要用到,組件,組件間通訊這些內容.chrome
可是由於是沒有接觸過這塊,因此得搞清楚都須要有什麼知識儲備,那就先從這裏開始介紹.json
總體上,插件開發這個東西沒那麼難,須要的技術也比較簡單.基本上就是前端那些知識:vim
外加一些軟技能segmentfault
chrome devtools
的使用和基本的調試能力基本上作過項目的前端都能hold住.
若是你在開發以前有些不肯定的問題的話須要提早了解的話,也能夠看這篇文檔高頻問題Q&A.
介紹完了知識儲備,咱們就得看文檔了.
接觸新的技術,文檔確定是不可少的,可是網上各類教程 + 文檔,魚龍混雜,咱們到底應該看教程仍是看文檔.
我我的理解是,無論教程仍是文檔,均可以看,可是要優先看官方的文檔和教程,看一手的資料,由於我看文檔的過程當中發現,非官方的東西(非一手文檔)信息傳達不完整,甚至有紕漏.因此盡着官方的來,除非有很好的非官方教程或者其餘的緣由.
也有一份非官方文檔不錯,可是不推薦直接看,建議結合官方文檔做爲對照來看,由於我在開發的時候發現這份文檔有些內容和官方最新的文檔不一致(好比對於browser action
和page action
的定義).
另外,在實際的閱讀過程當中,還會有些地方看文檔看不明白的,這時候有針對的搜一些博客/教程就能夠.好比此次組件通訊的地方有困惑,就找了些不錯的文檔看.
還有一些別的文章,都整理下來能夠參考.
看完文檔咱們大體會獲得一些信息.下面就來說講.
在這一部分你只須要了解一個插件會由哪些部分組成就能夠了,具體功能後面介紹.
正常狀況下咱們看到的瀏覽器和插件大概長這樣
當咱們鼠標點擊插件icon以後就變成了這樣
從上面的圖上來看,主要由
icon
(任務欄的圖標) 和 點擊以後的彈窗兩個大件組成,部分插件還會有一個專門的用於配置的頁面(這裏沒用到,就不介紹了).
另外,在UI以外,還會background
和content_script
存在.
再加上這兩個重要的概念以後整個結構就是這樣的
下面看看文件結構.
文件結構比較能直觀的反應組件的組成和功能.從文件結構來看。通常一個插件的文件結構長這樣的(之因此說通常是由於不是每一個插件都能用到所有的功能).
qzone_helper_extension │ background.js │ manifest.json │ popup.html │ popup.js │ qq_icon.png └─ content_script.js
其中各個文件的功能以下:
manifest.json
是咱們最早須要瞭解的部分,這個文件的功能相似於package.json
文件,這裏定義插件的配置信息,這些信息小到插件名稱,圖標,版本號等描述信息,大到你須要申請的權限和各類引入的資源,入口文件等重要配置。因此這裏應該是咱們最早須要瞭解的部分。qq_icon.png
是最不值得咱們理解的部分,這就是個圖標文件而已。popup.html
是比較直觀的一個文件,這個就是你點擊插件圖標以後彈出來的界面,那個界面是html寫的,一樣的,對應的popup.js
用來處理popup
頁面的交互和popup
模塊和別的模塊之間的通訊。background.js
和content_script.js
是核心.其中,background.js
能夠理解爲項目的app.js
文件,而content_script.js
是和網頁是一夥的,能夠簡單的理解爲是咱們在網頁那邊的代理,幫咱們作些網頁相關的操做.加上這些結構和文件之間的對應關係是這樣的
到這裏,你大概瞭解的一個插件的基本組成結構.瞭解完結構,咱們分來看看各部分的具體狀況.
通常來講,Icon
是咱們插件的功能入口,也能夠顯示一些徽標.
在chrome插件中,這種icon按鈕是分兩種的:
他們的區別與使用場景以下
類型 | 支持功能 | 應用場景 |
---|---|---|
page action | 不能使用徽章(就是提醒你有多少未讀消息的紅點和數字) | 非應用在全部頁面的狀況,好比我此次開發的QQ空間小助手就只是應用在user.qzone.qq.com的一個插件,使用的就是page action |
browser action | 擁有完整的功能(tooltip / popup / badges 對於這些功能再也不贅述) | 對全部頁面均可以使用的插件,好比adblock / vimium這種 |
你可能有點困惑,我瞭解了這個區別了,可是有什麼用嗎?有用,在定義manifest.json
的時候會用到.page action
對應的定義字段是page_action
,browser action
對應的定義字段是browser_action
.
pupop
這個頁面主要承載簡單的配置和信息展現功能.
這就是個普通的html
文件,裏面寫你的css
和js
邏輯.
須要注意的是,這裏的js
只能操做pupop
裏面的DOM
.
回到咱們的需求,咱們能夠在
popup
頁面放置一些用於觸發操做的按鈕.好比刪除說說按鈕
這個文件很重要,可是一上來就放這個文件,讓人有點摸不着頭腦,因此,放在這裏.
可是這個文件不必每一個字段都清楚啥意思,搞清楚你用到的就好了.
{ // Require "manifest_version": 2, // 不一樣的manifest版本會有不一樣的功能 "name": "My Extension", "version": "versionString", // Recommended "default_locale": "en", "description": "A plain text description", "icons": {...}, // icon文件路徑 // Pick one (or none) "browser_action": {...}, "page_action": {...}, // Optional "action": ..., "author": ..., "automation": ..., "background": { // background對應的配置 // Recommended "persistent": false, // Optional "service_worker": }, "chrome_settings_overrides": {...}, "chrome_ui_overrides": { "bookmarks_ui": { "remove_bookmark_shortcut": true, "remove_button": true } }, "chrome_url_overrides": {...}, "commands": {...}, "content_capabilities": ..., "content_scripts": [{...}], // content_script對應的配置 "content_security_policy": "policyString", "converted_from_user_script": ..., "current_locale": ..., "declarative_net_request": ..., "devtools_page": "devtools.html", "event_rules": [{...}], "externally_connectable": { "matches": ["*://*.example.com/*"] }, "file_browser_handlers": [...], "file_system_provider_capabilities": { "configurable": true, "multiple_mounts": true, "source": "network" }, "homepage_url": "http://path/to/homepage", "import": [{"id": "aaaa"}], "incognito": "spanning, split, or not_allowed", "input_components": ..., "key": "publicKey", "minimum_chrome_version": "versionString", "nacl_modules": [...], "oauth2": ..., "offline_enabled": true, "omnibox": { "keyword": "aString" }, "optional_permissions": ["tabs"], "options_page": "options.html", "options_ui": { "chrome_style": true, "page": "options.html" }, "permissions": ["tabs"], // 須要申請的權限 "platforms": ..., "replacement_web_app": ..., "requirements": {...}, "sandbox": [...], "short_name": "Short Name", "signature": ..., "spellcheck": ..., "storage": { "managed_schema": "schema.json" }, "system_indicator": ..., "tts_engine": {...}, "update_url": "http://path/to/updateInfo.xml", "version_name": "aString", "web_accessible_resources": [...] }
這基本能夠理解爲是一個常駐後臺的js
文件,在裏面能夠處理一些事件監聽.好比監聽頁面初始化的事件(chrome.runtime.onInstalled
),監聽通訊事件(chrome.runtime.onMessage
).
另外,從background
發出去的請求能夠跨域.
在V3的實現中,background引入了 service worker的概念.
在chrome的設置-更多工具-任務管理器裏面能夠看到咱們的background
任務進程.
content_script
文件是和瀏覽器打開的頁面一塊加載的,能夠操做打開頁面的DOM.
例如,點擊插件的圖標,而後頁面改變顏色.這種狀況是不能在background.js
中直接改變頁面顏色的,而是須要經過事件發送消息到content_script.js
中,經過content_script
來操做頁面DOM
.
瞭解完這些咱們大概知道了,咱們獲取發請求的哪些參數包括髮請求均可以在
content_script
中實現.由於只有之類能夠操做頁面的DOM.
上面介紹完了各個組成部分,下面介紹這些部分之間的通訊.chrome extension
的通訊是經過事件來進行的,通訊的內容是有效的JSON
對象.共有三種通訊方式:
Simple one-time requests
(就像短鏈接)Long-lived connections
(就像長鏈接)Cross-extension messaging
(多個插件間通訊)咱們這裏只展開此次用到的Simple one-time requests
.
插件內的通訊分爲這幾種:
content script => background
content script => popup
background => content script
background => popup
popup => content script
popup => background
content script => background
/content script => popup
/background => popup
/popup => background
這麼發送事件
chrome.runtime.sendMessage({greeting: "hello"}, function(response) { console.log(response.farewell); });
content script
發送的事件popup
和background
均可以收到,因此再發送的時候須要加上發給誰的標識,而後收到消息的時候處理popup => background
的通訊其實也能夠經過chrome.extension.getBackgroundPage()
來獲取到background的全部方法,直接調用
background => content script
/popup => content script
這麼發送事件
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) { console.log(response.farewell); }); });
- 由於可能打開了多個同一個url的tab,因此須要區分tab.
- 經過chrome.tabs.sendMessage發送的事件只有指定頁面的content能夠收到
而對於事件的監聽處理是同樣的
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { console.log(sender.tab ? "from a content script:" + sender.tab.url : "from the extension"); if (request.greeting == "hello") sendResponse({farewell: "goodbye"}); } );
整個通訊用一張圖表示就是
加上通訊的部分,咱們大概清楚了,咱們在popup
中觸發操做,而後popup
發送事件給content_script
,content_script
開始獲取請求參數,請求數據列表,而後遍歷數據作刪除操做.整個過程大概就這樣,沒用到background
.
在地址欄打開chrome://extensions/
,而後開啓右上角的開發者模式
而後點擊加載已解壓的拓展程序
,在打開的文件選擇框中選擇你的目錄就算安裝你開發的插件了.
background / popup / content script
的控制檯都是獨立的,能夠經過下面的方式打開
background
的控制檯chrome://extensions/
頁面,點擊對應的插件背景頁三個字便可打開background
的控制檯popup
的控制檯icon
在彈出的popup頁面
上右鍵,而後點擊檢查,就能夠打開popup
的控制檯.須要注意的是popup
頁面一旦關閉,控制檯也會隨之關閉.content script
的控制檯content script
的控制檯其實就是tab頁的控制檯,可是須要切換一下插件的調試和普通前端開發的debug方式是同樣的.
更多詳情能夠參考文檔:Debugging extensions
咱們的插件開發完成以後能夠選擇打包成crx
文件發佈到chrome 網上應用店,上傳到chrome是須要註冊開發者的,註冊帳號須要5$,註冊以後能夠發佈多個chrome插件.
更多詳情能夠參考文檔 建立和發佈自定義 Chrome 應用和擴展程序
打包功能在加載已解壓的拓展程序
按鈕的旁邊,而後按照提示走就能夠了
也能夠直接打成壓縮包放到網上讓別人下載使用,以前能夠本地安裝crx
文件,如今不支持了,都是經過加載已解壓的拓展程序
在本地使用.
開發的過程當中發現有的時候chrome.tabs.query
獲得的結果爲[]
,後來發現這是chrome的一個bug,在2015年就存在了,一直沒有修復.解決方法以下:
var activeTabId; chrome.tabs.onActivated.addListener(function(activeInfo) { activeTabId = activeInfo.tabId; }); // https://bugs.chromium.org/p/chromium/issues/detail?id=462939 function getActiveTab(callback) { chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { let tab = tabs[0]; if (tab) { callback(tab.id); } else { chrome.tabs.get(activeTabId, function (tab) { if (tab) { callback(tab.id); } else { console.log('No active tab identified.'); } }); } }); }
需求是在某些頁面插件的icon
才亮起來,點擊icon
纔會展現popup頁面
.
這個功能的實現思路比較多,這裏講兩種
一種是經過監聽conect事件,在事件的處理方法中setIcon
和初始化對應的popup
頁面.
Vimium是這麼作的,能夠看這裏.
還有一些利用chrome
的onPageChanged
事件的規則來實現,這種處理只有在符合規則的時候才展現popup
頁面.
chrome.runtime.onInstalled.addListener(function() { chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { chrome.declarativeContent.onPageChanged.addRules([{ conditions: [ new chrome.declarativeContent.PageStateMatcher({ pageUrl: { hostContains: 'qzone.qq.com' } }) ], actions: [new chrome.declarativeContent.ShowPageAction()] }]); }); });
這種狀況要留意插件管理頁面你的插件那裏有沒有報錯提示,像這樣
這裏的報錯必須點進去清除了,才能繼續向下進行,否則就會出現操做沒有響應的狀況.