0、寫在前面的話
朋友上班時每天好幾個時段都有個客流信息需要匯報到微信里,都是照着網頁上的數據手動填寫,着實麻煩。所以給寫了個簡單的函數每次到控制台里去運行,但是體驗也並不好,今天就花了一整天的時間鼓搗chrome的擴展程序,並順利完成,可以說很棒了。
尤其感謝以下參考鏈接:
另外官方文檔如下,英文的還是有點吃力(手動微笑):
然后,以下部分僅作重點記錄,以備不時之需。又會一個小技能了,以后可以實現自己的小需求,太棒了!
1、chrome擴展的基本構成
這是我自己今天折騰寫完的擴展,如下圖:

可以看出,比較典型的擴展,實際上就是一個小的頁面而已,實際上也正是如此:
- 這個彈出式的頁面我們稱之為popup,實際上就是個html,你可以在里面引用JS和CSS,和普通網頁開發一樣
- popup就是個單獨頁面,這意味着不能共享標簽頁面的DOM,這意味着你不能使用popup中的JS來操作標簽頁面中的內容
- 為了能實現JS跨頁面執行,chrome提供了好幾種方式,如contentScript、background、injectedScript等
- 為了能夠傳輸一些信息(如JS的執行結果),chrome提供了相應的api來進行跨頁面通信
- 最后當然還有個核心文件叫做manifest.json,用來配置擴展的信息
- 還有很多...
大概的目錄結構也就如下了:

當然,你如果沒有JS、CSS等文件需要使用,那么這幾個文件夾完全不要也是可以的。同時也可以知道,既然就是HTML+CSS+JS,所以也就不需要什么專門的IDE之類的了,我就直接用IDEA建了個項目就寫上了。
然而我們知道,谷歌的擴展類型是 “.crx”,所以這些結構最終會用工具處理一下,這個工具其實就是谷歌瀏覽器自帶的(此處借用一下別人的圖片):


打包好后會在跟目錄同級的地方生成一個跟目錄同名的以 .crx 為后綴的擴展文件;同時還有個 .pem 的密鑰文件,用於擴展程序更新打包時使用,也就是同個擴展在第二次及以后打包時使用,當然你也完全可以把密鑰刪掉,重新打包。
此時crx直接安裝就可以使用嗎?並不行,如果不在谷歌商店上架的話,你安裝了也無法使用,只能在開發者模式下,以加載文件夾的形式進行安裝使用,顧名思義,開發者模式,如果實在不便上架,以后也只能這么使用了:

2、核心文件說明
2.1 manifest.json
{
// (必需)清單文件的版本,這個必須寫,且必須是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"]
},
// 瀏覽器右上角圖標設置,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可以配置多個規則
{
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}
],
// 權限申請
"permissions":
[
"contextMenus", // 右鍵菜單
"tabs", // 標簽
"notifications", // 通知
"webRequest", // web請求
"webRequestBlocking",
"storage", // 插件本地存儲
"http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
"https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
],
// 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
"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
},
// 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字
"omnibox": { "keyword" : "go" },
// 默認語言
"default_locale": "zh_CN",
// devtools頁面入口,注意只能指向一個HTML文件,不能是JS文件
"devtools_page": "devtools.html"
}
1
{
2
// (必需)清單文件的版本,這個必須寫,且必須是2
3
"manifest_version": 2,
4
// (必需)插件的名稱
5
"name": "demo",
6
// (必需)插件的版本
7
"version": "1.0.0",
8
// 插件描述
9
"description": "簡單的Chrome擴展demo",
10
// 圖標,一般偷懶全部用一個尺寸的也沒問題
11
"icons":
12
{
13
"16": "img/icon.png",
14
"48": "img/icon.png",
15
"128": "img/icon.png"
16
},
17
// 會一直常駐的后台JS或后台頁面
18
"background":
19
{
20
// 2種指定方式,如果指定JS,那么會自動生成一個背景頁
21
"page": "background.html"
22
//"scripts": ["js/background.js"]
23
},
24
// 瀏覽器右上角圖標設置,browser_action、page_action、app必須三選一
25
"browser_action":
26
{
27
"default_icon": "img/icon.png",
28
// 圖標懸停時的標題,可選
29
"default_title": "這是一個示例Chrome插件",
30
"default_popup": "popup.html"
31
},
32
// 當某些特定頁面打開才顯示的圖標
33
/*"page_action":
34
{
35
"default_icon": "img/icon.png",
36
"default_title": "我是pageAction",
37
"default_popup": "popup.html"
38
},*/
39
// 需要直接注入頁面的JS
40
"content_scripts":
41
[
42
{
43
//"matches": ["http://*/*", "https://*/*"],
44
// "<all_urls>" 表示匹配所有地址
45
"matches": ["<all_urls>"],
46
// 多個JS按順序注入
47
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
48
// JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全局樣式
49
"css": ["css/custom.css"],
50
// 代碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最后一個表示頁面空閑時,默認document_idle
51
"run_at": "document_start"
52
},
53
// 這里僅僅是為了演示content-script可以配置多個規則
54
{
55
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
56
"js": ["js/show-image-content-size.js"]
57
}
58
],
59
// 權限申請
60
"permissions":
61
[
62
"contextMenus", // 右鍵菜單
63
"tabs", // 標簽
64
"notifications", // 通知
65
"webRequest", // web請求
66
"webRequestBlocking",
67
"storage", // 插件本地存儲
68
"http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
69
"https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
70
],
71
// 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
72
"web_accessible_resources": ["js/inject.js"],
73
// 插件主頁,這個很重要,不要浪費了這個免費廣告位
74
"homepage_url": "https://www.baidu.com",
75
// 覆蓋瀏覽器默認頁面
76
"chrome_url_overrides":
77
{
78
// 覆蓋瀏覽器默認的新標簽頁
79
"newtab": "newtab.html"
80
},
81
// Chrome40以前的插件配置頁寫法
82
"options_page": "options.html",
83
// Chrome40以后的插件配置頁寫法,如果2個都寫,新版Chrome只認后面這一個
84
"options_ui":
85
{
86
"page": "options.html",
87
// 添加一些默認的樣式,推薦使用
88
"chrome_style": true
89
},
90
// 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字
91
"omnibox": { "keyword" : "go" },
92
// 默認語言
93
"default_locale": "zh_CN",
94
// devtools頁面入口,注意只能指向一個HTML文件,不能是JS文件
95
"devtools_page": "devtools.html"
96
}
2.2 contentScript
其實就是Chrome插件中向頁面注入腳本的一種形式,我們可以通過manifest.json配置的方式向頁面注入指定JS和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"
}
],
}
17
1
{
2
// 需要直接注入頁面的JS
3
"content_scripts":
4
[
5
{
6
//"matches": ["http://*/*", "https://*/*"],
7
// "<all_urls>" 表示匹配所有地址
8
"matches": ["<all_urls>"],
9
// 多個JS按順序注入
10
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
11
// JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全局樣式
12
"css": ["css/custom.css"],
13
// 代碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最后一個表示頁面空閑時,默認document_idle
14
"run_at": "document_start"
15
}
16
],
17
}
contentScript和原始頁面共享DOM,但是不共享JS,這意味着:
contentScript中的JS可以操作標簽頁面的DOM,但是不能調用標簽頁面的JS
2.3 background
后台(姑且這么翻譯吧),是一個常駐的頁面,它的生命周期是插件中所有類型頁面中最長的,它隨着瀏覽器的打開而打開,隨着瀏覽器的關閉而關閉,所以通常把需要一直運行的、啟動就運行的、全局的代碼放在background里面。示例配置如下:
{
// 會一直常駐的后台JS或后台頁面
"background":
{
// 2種指定方式,如果指定JS,那么會自動生成一個背景頁
"page": "background.html"
//"scripts": ["js/background.js"]
},
}
9
1
{
2
// 會一直常駐的后台JS或后台頁面
3
"background":
4
{
5
// 2種指定方式,如果指定JS,那么會自動生成一個背景頁
6
"page": "background.html"
7
//"scripts": ["js/background.js"]
8
},
9
}
2.4 injectedScript
官方並沒有這樣的說法,是引用文中網友的說法,姑且就這樣吧。該類型的腳本不是通過配置實現了,而是通過DOM操作的方式往頁面注入的JS,就是強行寫進去再說了,如下示例:
// 向頁面注入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()
{
// 放在頁面不好看,執行完后移除掉
this.parentNode.removeChild(this);
};
document.head.appendChild(temp);
}
15
1
// 向頁面注入JS
2
function injectCustomJs(jsPath)
3
{
4
jsPath = jsPath || 'js/inject.js';
5
var temp = document.createElement('script');
6
temp.setAttribute('type', 'text/javascript');
7
// 獲得的地址類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
8
temp.src = chrome.extension.getURL(jsPath);
9
temp.onload = function()
10
{
11
// 放在頁面不好看,執行完后移除掉
12
this.parentNode.removeChild(this);
13
};
14
document.head.appendChild(temp);
15
}
當然,還需要在配置中聲明一下網頁允許使用的資源地址,否則會報錯:
{
// 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
"web_accessible_resources": ["js/inject.js"],
}
4
1
{
2
// 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
3
"web_accessible_resources": ["js/inject.js"],
4
}
contentScript可以控制DOM但是不能共享頁面的JS,但injectedScript則可以,畢竟是強行寫入的JS。
2.5 popup
這個頁面就是個臨時交互使用的HTML頁面,這之中的JS自然也就只屬於popup頁面了
2.6 動態注入或執行
雖然在background和popup中無法直接訪問頁面DOM,但是可以通過chrome.tabs.executeScript來執行腳本,從而實現訪問web頁面的DOM(注意,這種方式也不能直接訪問頁面JS)。
當然,這種方式還是需要先在配置文件中說明:
{
"name": "動態JS注入演示",
...
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
...
}
8
1
{
2
"name": "動態JS注入演示",
3
4
"permissions": [
5
"tabs", "http://*/*", "https://*/*"
6
],
7
8
}
而執行時也有兩種,直接執行JS Code,或者執行指定的JS文件:
// 動態執行JS代碼
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
// 動態執行JS文件
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});
4
1
// 動態執行JS代碼
2
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
3
// 動態執行JS文件
4
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});
2.7 其他補充

3、通信交互
通信部分直接就引用
官方文檔的消息傳遞部分的了吧,首先由兩種連接方式:
- 簡單的一次性請求
- 長時間的連接
3.1 簡單的一次性請求
如果您只需要向您的擴展程序的另一部分發送一個簡單消息(以及可選地獲得回應),您應該使用比較簡單的 runtime.sendMessage 方法。這些方法允許您從內容腳本向擴展程序發送可通過 JSON 序列化的消息,可選的 callback 參數允許您在需要的時候從另一邊處理回應。
如下列代碼所示從內容腳本中發送請求:
chrome.runtime.sendMessage({greeting: "您好"}, function(response) {
console.log(response.farewell);
});
3
1
chrome.runtime.sendMessage({greeting: "您好"}, function(response) {
2
console.log(response.farewell);
3
});
從擴展程序向內容腳本發送請求與上面類似,唯一的區別是您需要指定發送至哪一個標簽頁。這一例子演示如何向選定標簽頁中的內容腳本發送消息。
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "您好"}, function(response) {
console.log(response.farewell);
});
});
5
1
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
2
chrome.tabs.sendMessage(tabs[0].id, {greeting: "您好"}, function(response) {
3
console.log(response.farewell);
4
});
5
});
在接收端,您需要設置一個 runtime.onMessage 事件監聽器來處理消息。
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"來自內容腳本:" + sender.tab.url :
"來自擴展程序");
if (request.greeting == "您好")
sendResponse({farewell: "再見"});
});
8
1
chrome.runtime.onMessage.addListener(
2
function(request, sender, sendResponse) {
3
console.log(sender.tab ?
4
"來自內容腳本:" + sender.tab.url :
5
"來自擴展程序");
6
if (request.greeting == "您好")
7
sendResponse({farewell: "再見"});
8
});
注意:
如果多個頁面都監聽 onMessage 事件,對於某一次事件只有第一次調用 sendResponse() 能成功發出回應,所有其他回應將被忽略。
3.2 其他
長時間連接以及跨域等連接訪問此處不再展開了,因為我自己沒有用到,需要用到的時再查吧。
3.3 互相通信概覽

如下橫向看,如第三行,表示 popup-js 只能和 content-script / background-js 通信,通信方法如表格中所示:

4、跳坑
4.1 不支持inline javascript代碼
從Chrome Extenstion V2開始,不允許執行任何inline javascript代碼(也就是html內的任何js代碼都不允許執行),比如下面的代碼:
<input type="submit" name="btn_submit" value="收藏" id="btn_submit" class="btn_submit" onclick="addwz()"/>
1
1
<input type="submit" name="btn_submit" value="收藏" id="btn_submit" class="btn_submit" onclick="addwz()"/>
onclick中的addwz()函數不允許執行,點擊時會報錯。只能在內部引用的JS文件中綁定事件:
$('#btn_submit').click(function () {
addwz();
});
3
1
$('#btn_submit').click(function () {
2
addwz();
3
});
4.2 通信在調試過程中失敗的原因
很有可能你需要的是關掉瀏覽器,重新打開(手動微笑,為此我浪費了不少時間
4.3 JS的ready時機
window.onload = function () {
//JS
}
1
window.onload = function () {
2
//JS
3
}
4.4 其他
明明下午時候記得還有一些,晚上回憶就記不得了... 看來以后遇到坑在解決之后要立馬記錄,囧,如果又遇到以前有過的坑,又得重新爬
附件列表