Chrome Extension插件開發概述

摘要:本文主要描述關於chrome plugin開發的相關開發知識,着重講述下popup/background/content-script三者之間的消息互通。

一、前言

在此之前,並未接觸到chrome插件開發,由於最近業務需要,不得不去了解一些相關內容。在實踐的過程中,總結了一些內容,一來方便自己日後查閱,二來希望給衆位同僚提供一些參考。如有不對之處,還望及時指正。

一、Chrome Extension還是Chrome Plugin?

事實上,本文所說的插件並不是嚴格意義上的Chrome Plugin,從真正意義上講,Chrome Plugin是瀏覽器底層的功能實現,是需要有一定的瀏覽器開發能力纔可以接觸到的層面。而我們習慣上更喜歡叫Chrome插件,但作爲一個正直的developer,我們需要知道這二者是不同的東西。所以,在本文中出現的chrome extension擴展 和chrome plugin插件都是指同一個東西。

二、爲什麼選擇Chrome瀏覽器?

衆所周知,Chrome瀏覽器在全球都擁有可觀的忠實用戶,拋去其佔據了瀏覽器市場的霸主地位不說,其具備了衆多的優點,如良好的用戶體驗,簡單的開發規範等等。

歸納爲以下幾點:

  1. 市場佔有率高,用戶基礎龐大;
  2. 開發流程簡單,方便快速上手;
  3. 應用場景廣泛,兼容webkit內核(360極速瀏覽器、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等);
  4. 可擴展性強,非weikit內核的瀏覽器也有一定的支持(如Firefox)

三、關於Chrome Extension

Chrome Extension簡單定義爲瀏覽器的功能性擴展,由html、css、js和一個描述文件manifest.json組成,在瀏覽器的地址欄邊上顯示擴展圖標。本質上其實就是一個由html、css、js、圖片等資源組成的一個.crx後綴的壓縮包。

四、開發調試

1. 調試彈出頁(popup)

右擊擴展圖標->審查彈出內容即可彈出開發者面板,這個面板與網頁調試面板一模一樣,操作方式也是相同的。值得一提的是第一次彈出面板,會錯過彈出頁,初始化的腳本,可以通過在對應的面板上按F5然它重新加載進入斷點

2. 調試選項頁(option)

右擊擴展圖標->選項,在選項頁按F12打開調試面板

3. 調試後臺頁(background)

點擊檢查視圖後的超鏈接,就會彈出後臺頁相關的調試面板。

4. 調試內容腳本(content script)

在內容腳本注入的網頁打開開發者面板->source->Content scripts(左側面板)

五、Manifest 文件

每一個擴展、可安裝的WebApp、皮膚,都有一個JSON格式的manifest文件用來配置所有和擴展相關的配置,而且必須放在擴展的根目錄。其中,名稱(name)、版本(version)和Manifest 版本(manifest_version)這3個是必須添加的(而且manifest_version 必須爲 2),描述(description)、圖標位置(icons)是推薦的。

歸納一下manifest文件常見的配置項

{
	//(必須)manifest版本,而且必須是2
	"manifest_version": 2,
	// (必須)名稱
	"name": "demo",
	// (必須)版本
	"version": "1.0.0",
	// (推薦)描述
	"description": "簡單的Chrome擴展demo",
	// (推薦)圖標,懶加載可用一個尺寸
	"icons":
	{
	    "16": "images/icon-16.png",
	    "32": "images/icon-32.png",
	    "48": "images/icon-48.png",
	    "64": "images/icon-64.png",
	    "128": "images/icon-128.png"
	},
	// background script即插件運行的環境,會一直常駐的後臺JS或後臺頁面
	"background":
	{
		// 2種指定方式,如果指定JS,那麼會自動生成一個背景頁
		"page": "background.html"
		//"scripts": ["js/background.js"]
	},
	// 瀏覽器右上角圖標設置,browser_action、page_action、app必須三選一
	//	注意: Packaged apps 不能使用browser actions.
	"browser_action": 
	{
		"default_icon": "images/icon.png",
		"default_title": "hello", // 圖標懸停時的標題,可選
		"default_popup": "popup.html"
	},
	// 當某些特定頁面打開才顯示的圖標
	/*"page_action":
	{
		"default_icon": "images/icon.png",
		"default_title": "hello",
		"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"],
	// 擴展的主頁 url。擴展的管理界面裏面將有一個鏈接指向這個url。如果你將擴展放在自己的網站上,這個url就很有用了。如果你通過了Extensions Gallery和Chrome Web Store來分發擴展,主頁 缺省就是擴展的頁面。
	"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"
}

從Chrome 18版本開始,Manifest V1就開始進入了淘汰的過程。Chrome內核對 Manifest V1 的支持計劃具體可參見 Google 開發者網站上的日程表:manifest version 1 support schedule

六、content-script文件

content-script文件是嵌入到匹配的網頁中的腳本,但是又與頁面中的腳本隔離開。雖然可以操縱頁面上的DOM元素,但卻不能夠使用頁面腳本的API。也就是運行環境與頁面的腳本是隔離開的。

content-script和原始頁面共享DOM,但是不共享JS,如要訪問頁面JS(例如某個JS變量),只能通過injected js來實現。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

重要!!!除了可以在manifest中配置需要注入的頁面以外,還可以動態的注入到頁面中。

    //直接注入代碼
    chrome.browserAction.onClicked.addListener(function(tab) {
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="red"'
      });
    });
    //注入腳本文件
    chrome.tabs.executeScript(null, {file: "content_script.js"});

需要在manifest文件中配置權限:

    "permissions": [
      "activeTab"
    ],

七、background文件

background 可以使擴展常駐後臺,比較常用的是指定子屬性scripts,表示在擴展啓動時自動創建一個包含所有指定腳本的頁面。它的生命週期是插件中所有類型頁面中最長的,它隨着瀏覽器的打開而打開,隨着瀏覽器的關閉而關閉,所以通常把需要一直運行的、啓動就運行的、全局的代碼放在background裏面。

background的權限非常高,幾乎可以調用所有的Chrome擴展API(除了devtools),而且它可以無限制跨域,也就是可以跨域訪問任何網站而無需要求對方設置CORS。

經過測試,其實不止是background,所有的直接通過chrome-extension://id/xx.html這種方式打開的網頁都可以無限制跨域。

八、popup文件

如果browser action擁有一個popup,popup 會在用戶點擊圖標後出現。popup 可以包含任意你想要的HTML內容,並且會自適應大小。所以一般用來做臨時性的交互。

在你的browser action中添加一個popup,創建彈出的內容的HTML文件。 修改browser_action的manifest中default_popup字段來指定HTML文件, 或者調用setPopup()方法。

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

九、消息通信機制

廢話不多說,都在代碼裏了。源代碼

消息交互圖

-_-,圖略醜陋,將就看一下,手動微笑。

1. popup.html文件
<!doctype html>
<html lang="zh">
<head>
	<meta charset="UTF-8">
</head>
<body style="width: 300px">	
    <div width="200px">
      <button style="margin:5px 5px 5px 5px" id="con">popup發送消息給content_scripts</button>
      <button style="margin:5px 5px 5px 5px" id="bg">popup調用background的js函數</button>
      <button style="margin:5px 5px 5px 5px" id="bgtocon">background發送消息給content_scripts</button>
    </div>
</body>
</html>

<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/popup.js"></script>
2. popup.js文件

// popup調用background的js函數
$('#bg').click(() => {
	//alert("調用background的js函數");
	var bg = chrome.extension.getBackgroundPage();
	console.log(123123, bg)
	bg.bgtest();
});

 // popup主動發消息給content-script
$('#con').click(() => {
	alert("popup發送消息給content-script");
	sendMessageToContentScript('你好,我是popup!', (response) => {
		if(response) alert('收到來自content-script的回覆:'+response);
	});
});

// popup調用background的js函數
$('#bgtocon').click(() => {
	var bg = chrome.extension.getBackgroundPage();
	bg.TT();
});

// 獲取當前選項卡ID
function getCurrentTabId(callback)
{
	chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
	{
		if(callback) callback(tabs.length ? tabs[0].id: null);
	});
}

// 向content-script主動發送消息
function sendMessageToContentScript(message, callback)
{
	getCurrentTabId((tabId) =>
	{
		chrome.tabs.sendMessage(tabId, message, function(response)
		{
			if(callback) callback(response);
		});
	});
}
3. background.js文件
function bgtest()
{
	alert("background的bgtest函數!");
}

// 監聽來自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
	console.log('收到來自content-script的消息:');
	console.log(request, sender, sendResponse);
	sendResponse('我是background,我已收到你的消息:' + JSON.stringify(request));
});

// backgrond向context_scripts發送消息
function TT(){
	
	sendMessageToContentScript('context_scripts你好,我是backgrond!', (response) => {
	if(response) alert('backgrond收到來自content-script的回覆:'+response);
	});
}

// 獲取當前選項卡ID
function getCurrentTabId(callback)
{
	chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
	{
		if(callback) callback(tabs.length ? tabs[0].id: null);
	});
}

// 向content-script主動發送消息
function sendMessageToContentScript(message, callback)
{
	getCurrentTabId((tabId) =>
	{
		chrome.tabs.sendMessage(tabId, message, function(response)
		{
			if(callback) callback(response);
		});
	});
}
4. content-script文件
//常駐後臺,並且會注入到頁面中
alert("content-script.js 已經注入");

//直接調用注入的其他的js函數 注入的js可以有多個在mainfest中配置
aa();

// 接收來自後臺的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
	console.log('收到來自 ' + (sender.tab ? "content-script(" + sender.tab.url + ")" : "popup或者background") + ' 的消息:', request);
	tip(JSON.stringify(request));
	sendResponse('我收到你的消息了:'+JSON.stringify(request));

});


 //與後端background進行消息交互	 執行6秒
var startTime = new Date().getTime(); 
var interval = setInterval(function ()
{ 
	if(new Date().getTime() - startTime > 6000)
	{
		clearInterval(interval);
		return;
	}
  chrome.runtime.sendMessage
  (
	{
		doc: "yes",
		data:"123",
	},
	function(response) 
	{
		
   		tip(JSON.stringify("content-script向background 發送消息"));	
   		tip('收到來自background的回覆:' + response);
	}
  );
},500);




var tipCount = 0;
// 簡單的消息通知
function tip(info) {
	info = info || '';
	var ele = document.createElement('div');
	ele.className = 'chrome-plugin-simple-tip slideInLeft';
	ele.style.top = tipCount * 70 + 20 + 'px';
	ele.innerHTML = `<div>${info}</div>`;
	document.body.appendChild(ele);
	ele.classList.add('animated');
	tipCount++;
	setTimeout(() => {
		ele.style.top = '-100px';
		setTimeout(() => {
			ele.remove();
			tipCount--;
		}, 4000);
	}, 5000);
}

十、參考資料

360安全瀏覽器開發文檔(推薦)
360極速瀏覽器Chrome擴展開發文檔
chrome擴展開發官方文檔
chrome API支持
【乾貨】Chrome插件(擴展)開發全攻略(很詳細,文中部分內容來源於此)
Chrome擴展開發概述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章