內容腳本是在網頁的上下文中運行的JavaScript文件,它們可以通過標準的文檔對像模型(DOM)來獲得瀏覽器訪問的網頁的資訊,甚至可以對其DOM物件作出增刪除修改的動作,也能監聽來自網頁中的事件。
擴充功能籍由內容腳本的注入,便可間接與使用者載入的網頁溝通,進而提供與網頁內容相關的功能。
只能存取以下API:
不能存取extension裡其他類型腳本組件的方法
不能存取定義在網頁中其他JS中的變數跟方法
content Script雖然無法直接跟完整的chrome.* APIs溝通,但他能使用runtime AP裡的訊息API進行間接的溝通,我們稍晚在別的章節討論到訊息溝通的時後,會一併討論。
Content Script雖然只能在非常小的限度下存取Chrome提供的Extension API,但能作到其他腳本作不到的事:
內容腳本可以實現的一些功能的例子:
以下程式碼展示了,我們定義內容角本在指定的網址底下,載入JS檔與CSS檔。
"content_scripts" : [
{
"matches" : ["http://www.google.com/*"],
"css" : ["mystyles_A.css"],
"js" : ["jquery.js","myscript_A.js"]
} ]
content_scripts
項目可以包含以下屬性:
matches 拼配的網址(Matches Patterns)
exclude_matches 排除的網址(Matches Patterns)
match_about_blank 是否要在是否在 about:blank
(註1)以及about:srcdoc
(註2) 中插入內容腳本。
css 插入的css
js 插入的腳本
run_at 插入時機,可為 "document_start"、"document_end" 或 "document_idle",預設值 為 "document_idle"。
all_frames 是否在頁面嵌套的iframe中插入腳本
include_globs 包含的URL, 模擬 Greasemonkey(註3) 中的@include
關鍵字
exclude_globs 排除的URL, 模擬 Greasemonkey 中的@include
關鍵字
以上只是簡介,完整說明可參考這裡
還有一點要提醒的,許多擁有matches作為關鍵字的屬性,大多許都適用官網提供的匹配表達示Matches Patterns,想看匹配表達範例的可以看這裡
註1 about:srcdoc:HTML5 only
该属性值可以是HTML代码,这些代码会被渲染到iframe中,如果同时指定了src属性,srcdoc会覆盖src所指向的页面。该属性最好能与sandbox和seamless属性一起使用。(引用處)
註2 about:blank
打開瀏覽器空白指令
註3
Greasemonkey,簡稱GM,中文俗稱為「油猴子」,是Mozilla Firefox的一個附加元件。它讓使用者安裝一些腳本使大部分HTML為主的網頁於使用者端直接改變得更方便易用。隨著Greasemonkey腳本常駐於瀏覽器,每次隨著目的網頁開啟而自動做修改,使得執行腳本的使用者印象深刻地享受其固定便利性。(引用處)
也許你希望使內容腳本不要立刻載入,而是當使用者點擊瀏覽器按鈕時才動態插入,你可以使用以下方法:
chrome.tabs.insertCSS(integer tabId, object details, function callback)
chrome.tabs.executeScript(integer tabId, object details, function callback)
當你的內容腳本必需經由某些特定的事件(例如點擊page action按鈕),使用程式載入css跟js就會非常有用。
以chrome官方提供的make_page_red為例:這個例子顯示了當使用者點擊瀏覽器按鈕時,載入頁面的背景會變成紅色
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="red"'
});
});
記得要求權限activeTab
"permissions" : ["activeTab"],
通常狀態下,你會把chrome.tabs.executeScript
的code放在一個文件裡,利用Path載入
chrome.tabs.executeScript(null, {file: "content_script.js"});
內容腳本雖然可以操作網頁內容的dom物件,但基本上跟網頁的腳本是完全隔離的,內容腳本不能存取網頁腳本的方法跟變數。開發人員可以放心在內容腳本內使用自己定義的方法跟變數,而不用擔心跟網頁腳本衝突。
以下面這段程式碼為例:
假設這是使用者載入的Hello.html頁面
<html>
<button id="mybutton">點我</button>
<script>
var greeting = "你好";
var button = document.getElementById("mybutton");
button.person_name = "Bob";
button.addEventListener("click", function() {
alert(greeting + button.person_name + "。");
}, false);
</script>
</html>
將內容腳本注入到Hello.html
var greeting = "你好";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
alert(greeting + button.person_name + "。");
}, false);
最後的結果你會看到兩個Alert
這種隔離的運行環境,被稱為isolated world
內容腳本可以自由的操作頁面的DOM物件,看起來好像是與頁面腳本共用,但事實上兩個運行環境擁有各自的DOM副本(如下圖)。
兩個腳本之間甚至可以嵌入不同版本的Jquery而不會彼此干擾。
例如:當你的內容腳本,使用XMLHttpRequest來載入部份html,首選 innerText而不是 innerHTML。
在https中,獲取來自http的內容要特別小心,因為http可能會經由中間人攻擊而遭到破壞。
var data = document.getElementById("json-data")
//警告!data可能會是惡意腳本!
var parsed = eval("(" + data + ")")
var elmt_id = ...
//警告!elmt_id可能會故意加入 "); ~惡意腳本~"
window.setTimeout("animate(" + elmt_id + ")", 200);
安全的作法:
var data = document.getElementById("json-data")
// JSON.parse 讓data不會變成可運行的腳本
var parsed = JSON.parse(data);
var elmt_id = ...
// 使用方法作為setTimeout的回調
window.setTimeout(function() {
animate(elmt_id);
}, 200);
中間人攻擊:(引用維基)一個中間人攻擊能成功的前提條件是攻擊者能將自己偽裝成每一個參與對談的終端,並且不被其他終端識破。中間人攻擊是一個(缺乏)相互認證的攻擊。大多數的加密協定都專門加入了一些特殊的認證方法以阻止中間人攻擊。例如,SSL協定可以驗證參與通訊的一方或雙方使用的憑證是否是由權威的受信任的數位憑證認證機構頒發,並且能執行雙向身分認證。
//將img的網址,指定成 <擴充功能目錄>/images/myimage.png 的代码:
var imgURL = chrome.extension.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;
開頭有介紹到,除了插入內容腳本外,也能注入指定的樣式檔。我有試著去尋找能否在此樣式檔內引入擴充功能裡的圖片檔。我找到一個討論串(連結)在討論這件事。雖然有人說可以,下面還稱讚聲一片,但似乎不是正確答案。
總之我無法成功的在注入頁面的樣式檔中使用 predefined message,來插入擴充檔案裡的圖片。如果有網友知道怎麼作,請告訴我,如果確定沒有其他方法,那我只能說:請儘量少用圖片作為畫面的layout。
安裝這個插件後,將在臉書的個人檔案封面出現下雪的場景(要重新整理)。
安裝檔
{
"manifest_version" : 2,
"name" : "鐵人賽-ContentScript範例",
"description" : "到臉書的個人檔案,會下雪",
"version" : "2.0",
"browser_action" : {
"default_title" : "到臉書的個人檔案,會下雪",
"default_icon" : "icon.png"
},
"content_scripts" : [
{
"matches" : ["*://www.facebook.com/*"],
"js" : ["content.js"],
"css" : ["content.css"]
}
],
"permissions" : ["activeTab"]
}
CSS
canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
@keyframes zboing {
0% {
transform: scale3d(1, 1, 1)
}
30% {
transform: scale3d(1.2, 0.75, 1)
}
40% {
transform: scale3d(0.85, 1.25, 1)
}
50% {
transform: scale3d(1.05, 0.85, 1)
}
65% {
transform: scale3d(0.95, 1.05, 1)
}
75% {
transform: scale3d(1.05, 0.95, 1)
}
100% {
transform: scale3d(1, 1, 1)
}
}
#snow-button {
background: url('https://lh3.googleusercontent.com/nTqfRkzDUd9qFhLs2aN-ZgxiNc3QqAi4Zk5ChrCpC0KKMUzfSAfnE-kRwUItDR--m-m7s5c4z-wkm0C0cctForQUFJBWDjo=w10240-h5760-rw-no') no-repeat 0 0;
width: 200px;
height: 200px;
background-size: 100% auto;
-webkit-animation: zboing 1.3s infinite 1s;
position: absolute;
top: 30px;
left: 40%;
}
內容腳本
var canvas = document.createElement("canvas");
var cover = document.getElementsByClassName("coverBorder");
canvas.id = "canvasLaLa2016";
cover[0].appendChild(canvas);
var button = document.createElement("div");
button.id = "snow-button";
cover[0].appendChild(button);
下雪的程式碼引用了CodePen的作品,在上面不討論
結果
完整範例在:Github
你可以使用指定的網址來設定載入內容腳本的條件
也可以使用事件腳本,動態的決定插入內容腳本的時機
內容腳本可以自由的操作網頁的dom物件,並且跟網頁的腳本完全是兩個平行的世界,不會彼此干擾
注意跨網域的惡意攻擊,以及來自網頁內容本身的惡意腳本。
範例很應景XD
我們也可以使用createElement() ,籍由內容腳本在頁面中插入一個按鈕與使用者進行互動,這就是在前面提到過的Content UI/內容UI輸入組件,當使用點擊一個內容UI時,內容腳本組件要依靠Chrome的訊息API才能跟事件腳本溝通,我們在稍後幾篇文章將會介紹到。
完了愈寫愈長 / v \ 我到底能不能堅持下去,其實我覺得腳本介紹完剩下的API自已看就好(捂臉)。