架構
-
總括:
- Manifest:程序清單
- Background:插件運行環境/主程序
- Pop up:彈出頁面
- Content scripts:與瀏覽頁面交互的腳本
-
Manifest
每一個擴展都有一個JSON格式的manifest文件,叫manifest.json,相當于程序清單,其中定義了插件的一些元數據等信息,示例如下:
{
// 必須添加
"manifest_version": 2, //固定的,定義manifest版本信息
"name": "My Extension", //插件名稱
"version": "1.0", //插件本身的版本
// 推薦添加
"default_locale": "en",
"description": "This is my first extension.", //插件的描述
"icons": {...},
//browser_action和page_action只能添加一個
"browser_action": { //瀏覽器級別行為,所有頁面均生效
"default_icon": "cc.gif",//圖標的圖片
"default_title": "Hello CC", //鼠標移到圖標顯示的文字
"default_popup": "popup.html" //單擊圖標后彈窗頁面
},
"page_action":{ //頁面級別的行為,只在特定頁面下生效
"default_icon":{
"24":"icon_24.png",
"38":"icon_38.png"
},
"default_popup": "popup.html",
"default_title":"MyTitle"
},
// 可選
"author": ...,
"automation": ...,
//background script即插件運行的環境
"background":{
"page":"background.html", //page和scripts只能設置一個
"persistent": false
//scripts定義一個腳本文件的數組,chrome會在擴展啟動時自動創建一個包含所有指定腳本的頁面
// "scripts": ["js/jquery-1.9.1.min.js","js/background.js"]
},
"background_page": ...,
"chrome_settings_overrides": {...},
"chrome_ui_overrides": {
"bookmarks_ui": {
"remove_bookmark_shortcut": true,
"remove_button": true
}
},
"chrome_url_overrides": {...},
"commands": {...},
"content_capabilities": ...,
//定義對頁面內容進行操作的腳本
"content_scripts": [{
"matches": ["http://*/*","https://*/*"],//只在這些站點下 content_scripts會運行
"js": ["js/jquery-1.9.1.min.js", "js/js.js"],
"run_at": "document_start", //在document加載時執行該腳本,如果不指定,則在document加載完成后執行
}]
"content_security_policy": "policyString",
"converted_from_user_script": ...,
"current_locale": ...,
"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": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
"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": [
"http://*/",
"bookmarks",
"tabs",
"history",
"activeTab",
"storage"
],
"platforms": ...,
"plugins": [...],
"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": [...]
}
-
Background Page
- background是插件的運行環境,姑且叫做主程序。一旦插件被啟用,chrome就會為該插件開辟一個獨立運行環境,用來執行background script。若設置了scripts字段,瀏覽器的擴展系統會自動根據scripts字段指定的所有js文件自動生成背景頁。也可以直接page字段,指定背景頁。兩者只能設置一個。
- 一般情況下,我們會讓將擴展的主要邏輯都放在 background 中比較便于管理。其它頁面可以通過消息傳遞的機制與 background 進行通訊。理論上 content script 與 popup 之間也可以傳遞消息,但不建議這么做。
- 包含background.html和background.js兩個文件,包括如下兩種:
- Persistent background pages:一直開啟
- Event pages:需要時開啟,可通過將persistent設置為false來設置
- 開啟時機:
- 首次安裝或更新完版本時
- 觸發特定事件時
- content script 向其發送消息時
- 其他頁面(例如popup)調用
runtime.getBackgroundPage
時
- 注冊到Manifest中:
- 開啟時機:
{
"name": "My extension",
...
"background": {
"scripts": ["eventPage.js"],
"persistent": false
},
...
}
-
UI Page
-
popup:
- popup.html和popup.js:popup和background page在同一個運行環境中,均在插件主程序中。也就是說popup可以通過特定的方式調background里的方法。注:出于安全考慮,javascript必須與html分開存放且寫在html里的js無效。
-
option:
-
-
Content Scripts
Content Scripts相當于一個對當前瀏覽頁面(符合matches定義的模式)的補充腳本,稱作內容腳本,與popup不同,內容腳本幾乎與主程序運行環境相隔離、獨立。與主程序的交互只能通過發送消息的方式進行。- CanDo:
可以訪問和修改單前瀏覽頁面的DOM,但不能訪問該頁面腳本定義的變量和方法,并且不會和該頁面原有js的方法和變量沖突。 - CanNot:
- 不能使用chrome獨有API,除了以下幾點:
- extension(getURL,inIncognitoContext,lastError,onRequest,sendRequest)
- i18n
- runtime ( connect , getManifest , getURL , id , onConnect , onMessage , sendMessage )
- storage
- 不能使用插件里其他頁面定義的變量和方法
- 不能使用chrome獨有API,除了以下幾點:
- 向訪問的頁面注入腳本,訪問和修改DOM的方式:
只有為要訪問的頁面賦予權限才可以,需在Manifest中添加permissions節點。
向頁面插入腳本的API: - 注入腳本:chrome.tabs.executeScript,如下:
- CanDo:
//將訪問頁面的背景變為紅色
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="red"'
});
});
- 注入CSS:chrome.tabs.insertCSS
- 注冊到Manifest中
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.google.com/*"],//只有在符合該pattern的站點才會運行
"css": ["mystyles.css"],
"js": ["jquery.js", "myscript.js"]
}
],
//為以下頁面賦予訪問并向注入腳本的權限
"permissions": [
"tabs", "http://www.google.com/*",
"activeTab"
],
...
}
- 消息傳遞:
雖然內容腳本的執行環境和托管它們的頁面是彼此隔離的,但它們共享對頁面DOM的訪問權限,如果頁面希望與內容腳本(或通過內容腳本進行擴展)進行通信,那么它必須通過共享的DOM進行。示例如下:
//訪問的頁面:
document.getElementById("theButton").addEventListener("click",
function() {
window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);
//content_scripts腳本;
var port = chrome.runtime.connect();
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
插件頁面間通訊:
插件各個部分之間的通訊有如下兩種模式:
-
簡單的單次請求
- 內容腳本到主程序:
chrome.extension.sendMessage({hello: "Cissy"}, function(response) {
console.log(response.farewell);
});
- 主程序到內容腳本
chrome.tabs.query({active:true}, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
- 接收消息
chrome.extension.sendMessage()向擴展內的其它監聽者發送一條消息。此消息發送后會觸發擴展內每個頁面的chrome.extension.onMessage()事件。
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"});
});
-
長連接
- background 和 popup
background和popup運行在同一個進程中,都是運行在主程序中的,所以background 和 popup 之間可以直接相互調用對方的方法,不需要消息傳遞。- popup調用background中變量或方法
- background 和 popup
var bg = chrome.extension.getBackgroundPage();//獲取background頁面
console.log(bg.a);//調用background的變量或方法。
注: 使用chrome.extension.getBackgroundPage()時要確保此時的backgroundpage在運行,否則會返回null,解決方案:
- 將manifest的background的persistent屬性值設置為true,確保backgroundpage處于常開狀態(我使用的該方法);
"background": {
"scripts": [
"background.js"
],
"persistent": true
}
- 使用異步的 chrome.runtime.getBackgroundPage代替(該方法沒測試)
- background調用popup中變量或方法
background是一個運行在擴展進程中的HTML頁面。它在你的擴展的整個生命周期都存在,而popup是在你點擊了圖標之后才存在,所以,在獲取popup變量時,請確認popup已打開。
var pop = chrome.extension.getViews({type:'popup'});//獲取popup頁面
console.log(pop[0].b);//調用第一個popup的變量或方法。
- background 和 content
持續長時間的保持會話需要在content script和擴展建立一個長時間存在的通道。當建立連接,兩端都有一個runtime.Port 對象通過這個連接發送和接收消息。- 內容腳本發送消息到擴展程序
var bac = chrome.extension.connect({name: "ConToBg"});//建立通道,并給通道命名
bac.postMessage({hello: "Cissy"});//利用通道發送一條消息。
- 擴展程序發送消息到內容腳本
var cab = chrome.tabs.connect(tabId, {name: "BgToCon"});//建立通道,指定tabId,并命名
cab.postMessage({ hello: "Cissy"});//利用通道發送一條消息。
- 接收消息
接收消息為了處理正在等待的連接,需要用chrome.extension.onConnect 事件監聽器,對于content script或者擴展頁面,這個方法都是一樣的
chrome.extension.onConnect.addListener(function(bac) {//監聽是否連接,bac為Port對象
bac.onMessage.addListener(function(msg) {//監聽是否收到消息,msg為消息對象
console.log(msg.hello);
})
})
數據保存及隱身模式
可以使用HTML5的localStorage或保存到服務器,但是,無論何種方式,盡量保證在隱身模式的情況下不要保存任何數據,或僅將數據保存在內存中,如下:
function saveTabData(tab, data) {
if (tab.incognito) {
chrome.runtime.getBackgroundPage(function(bgPage) {
bgPage[tab.url] = data; // 匿名模式,保存為當前頁面的數據,只在內存中
});
} else {
localStorage[tab.url] = data; // 保存到本地
}
}
調試
插件各部分的調試方式各不相同,具體如下:
- Content script:直接F12打開開發者工具,在Sources欄下的Content scripts即可調試
- background:在chrome的擴展程序設置頁面chrome://extention 下找到相應的插件,點擊檢查視圖即可調試
- popup:右擊工具欄上插件的圖標,選擇審查彈出內容即可調試
Chrome API
除了web本身的API以外,Chrome插件還支持一些獨有的API可供使用
- 同步方法vs異步方法
例:
同步方法:返回string的chrome.runtime.getURL()
方法
異步方法:
//獲取當前頁面,并修改頁面url
chrome.tabs.query({'active': true}, function(tabs) {
chrome.tabs.update(tabs[0].id, {url: newUrl});
});
- 常用API:
- chrome.extension.getURL():獲取插件文件的URL,
例:chrome.extension.getURL("images/myimage.png");獲取文件myimage.png的URL - chrome.tabs.executeScript():向訪問頁面注入腳本
- chrome.extension.getURL():獲取插件文件的URL,
最后附上一個我自己用的兩個小demo,一個可以自動調整簡書文章頁面的寬度,一個可以修改頁面樣式:
簡書調寬:https://github.com/rascalquan/JianShuNoteWidth
簡書調寬.png
CSS修改器:https://github.com/rascalquan/ChangePageStyle
Paste_Image.png