摘要:這一步可以參考應(yīng)用商店上傳擴(kuò)展程序一文最后終于搞定,線上可見學(xué)習(xí)資源建立擴(kuò)展程序插件開發(fā)攻略如何成為一名應(yīng)用開發(fā)者擴(kuò)展的開發(fā)下一步插件功能豐富化插件可在網(wǎng)頁上高亮展示標(biāo)記的文本用重構(gòu)需要使用框架嗎注本文源碼位于倉庫,線上產(chǎn)品見和
概述 chrome擴(kuò)展程序十一在家無聊時(shí)開發(fā)了這個(gè)項(xiàng)目。其出發(fā)點(diǎn)是想通過chrome插件,來保存網(wǎng)頁上選中的文本。后來就順手把前后端都做了(Koa2 + React):
chrome插件源碼
插件對應(yīng)的前后端源碼
chrome擴(kuò)展程序大家應(yīng)該都很熟悉了,它可以通過腳本幫我們完成一些快速的操作。通過插件可以捕捉到網(wǎng)頁內(nèi)容、標(biāo)簽頁、本地存儲,或者用戶的操作行為;它也可以在一定程度上改變?yōu)g覽器的UI,例如頁面上右鍵的菜單、瀏覽器右上角點(diǎn)擊插件logo后的彈窗,或者瀏覽器新標(biāo)簽頁
開發(fā)緣由按照慣例,開發(fā)前多問問自己 why? how?
why:
我在平??床┪臅r(shí),對于一些段落想進(jìn)行摘抄或者備注,又懶得復(fù)制粘貼
how:
一個(gè)chrome擴(kuò)展程序,可以通過鼠標(biāo)右鍵的菜單,或者鍵盤快捷鍵快速保存當(dāng)前頁面上選擇的文本
如果沒有選擇文本,則保存網(wǎng)頁鏈接
要有對應(yīng)的后臺服務(wù),保存 user、cliper、page (后話,本文不涉及)
還要有對應(yīng)的前端,以便瀏覽我的保存記錄 (后話,本文不涉及)
先上個(gè)成果圖:
clip 有剪輯之意,因此項(xiàng)目命名為 cliper
這兩天終于安奈不住買了服務(wù)器,終于把網(wǎng)址部署了,也上線了chrome插件:
cliper
cliper extension
manifest.json在項(xiàng)目根目錄下創(chuàng)建manifest.json文件,其中會涵蓋擴(kuò)展程序的基本信息,并指明需要的權(quán)限和資源文件
{ // 以下為必寫 "manifest_version": 2, // 必須為2,1號版本已棄用 "name": "cliper", // 擴(kuò)展程序名稱 "version": "0.01", // 版本號 // 以下為選填 // 推薦 "description": "描述", "icons": { "16": "icons/icon_16.png", "48": "icons/icon_48.png", "64": "icons/icon_64.png", "128": "icons/icon_128.png" }, "author": "ecmadao", // 根據(jù)自己使用的權(quán)限填寫 "permissions": [ // 例如 "tab", "storage", // 如果會在js中請求外域API或者資源,則要把外域鏈接加入 "http://localhost:5000/*" ], // options_page,指右鍵點(diǎn)擊右上角里的插件logo時(shí),彈出列表中的“選項(xiàng)”是否可點(diǎn),以及在可以點(diǎn)擊時(shí),左鍵點(diǎn)擊后打開的頁面 "options_page": "view/options.html", // browser_action,左鍵點(diǎn)擊右上角插件logo時(shí),彈出的popup框。不填此項(xiàng)則點(diǎn)擊logo不會有用 "browser_action": { "default_icon": { "38": "icons/icon_38.png" }, "default_popup": "view/popup.html", // popup頁面,其實(shí)就是普通的html "default_title" : "保存到cliper" }, // background,后臺執(zhí)行的文件,一般只需要指定js即可。會在瀏覽器打開后全局范圍內(nèi)后臺運(yùn)行 "background": { "scripts": ["js/vendor/jquery-3.1.1.min.js", "js/background.js"], // persistent代表“是否持久”。如果是一個(gè)單純的全局后臺js,需要一直運(yùn)行,則不需配置persistent(或者為true)。當(dāng)配置為false時(shí)轉(zhuǎn)變?yōu)槭录s,依舊存在于后臺,在需要時(shí)加載,空閑時(shí)卸載 "persistent": false }, // content_scripts,在各個(gè)瀏覽器頁面里運(yùn)行的文件,可以獲取到當(dāng)前頁面的上下文DOM "content_scripts": [ { // matches 匹配 content_scripts 可以在哪些頁面運(yùn)行 "matches" : ["http://*/*", "https://*/*"], "js": ["js/vendor/jquery-3.1.1.min.js", "js/vendor/keyboard.min.js", "js/selection.js", "js/notification.js"], "css": ["css/notification.css"] } ] }
綜上,我們一共有三種資源文件,針對著三個(gè)運(yùn)行環(huán)境:
browser_action
控制logo點(diǎn)擊后出現(xiàn)的彈窗,涵蓋相關(guān)的html/js/css
在彈窗中,會進(jìn)行登錄/注冊的操作,并將用戶信息保存在本地儲存中。已登錄用戶則展現(xiàn)基本信息
background
在后臺持續(xù)運(yùn)行,或者被事件喚醒后運(yùn)行
右鍵菜單的點(diǎn)擊和異步保存事件將在這里觸發(fā)
content_scripts
當(dāng)前瀏覽的頁面里運(yùn)行的文件,可以操作DOM
因此,我會在這個(gè)文件里監(jiān)聽用戶的選擇事件
注:
content_scripts中如果沒有matches,則擴(kuò)展程序無法正常加載,也不能通過“加載未封裝的擴(kuò)展程序”來添加。如果你的content_scripts中有js可以針對所有頁面運(yùn)行,則填寫"matches" : ["http://*/*", "https://*/*"]即可
推薦將background中的persistent設(shè)置為false,根據(jù)事件來運(yùn)行后臺js
不同運(yùn)行環(huán)境JS的繩命周期如上所述,三種JS有著三種運(yùn)行環(huán)境,它們的生命周期、可操作DOM/接口也不同
content_scriptscontent_scripts會在每個(gè)標(biāo)簽頁初始化加載的時(shí)候進(jìn)行調(diào)用,關(guān)閉頁面時(shí)卸載
內(nèi)容腳本,在每個(gè)標(biāo)簽頁下運(yùn)行。雖然它可以訪問到頁面DOM,但無法訪問到這個(gè)里面里,其他JS文件創(chuàng)建的全局變量或者函數(shù)。也就是說,各個(gè)content_scripts(以及外部JS文件)之間是相互獨(dú)立的,只有:
"content_scripts": [ { "js": [...] } ]
js所定義的一個(gè)Array里的各個(gè)JS可以相互影響。
background官方建議將后臺js配置為"persistent": false,以便在需要時(shí)加載,再次進(jìn)入空閑狀態(tài)后卸載
什么時(shí)候會讓background的資源文件加載呢?
應(yīng)用程序第一次安裝或者更新
監(jiān)聽某個(gè)事件觸發(fā)(例如chrome.runtime.onInstalled.addListener)
監(jiān)聽其他環(huán)境的JS文件發(fā)送消息(例如chrome.runtime.onMessage.addListener)
擴(kuò)展程序的其他資源文件調(diào)用了runtime.getBackgroundPage
browser_actionbrowser_action里的資源會在彈窗打開時(shí)初始化,關(guān)閉時(shí)卸載
browser_action里定義的JS/CSS運(yùn)行環(huán)境僅限于popup,并且會在每次點(diǎn)開彈窗的時(shí)候初始化。但是它可以調(diào)用一些chrome api,以此來和其他js進(jìn)行交互
除此以外:
browser_action的HTML文件里使用的JS,不能直接以的形式行內(nèi)寫入HTML里,需要獨(dú)立成JS文件再引入
如果有其他第三方依賴,比如jQuery等文件,也無法通過CDN引入,而需要保持資源文件到項(xiàng)目目錄后再引入
不同運(yùn)行環(huán)境JS之間的交互雖然運(yùn)行環(huán)境和繩命周期都不相同,但幸運(yùn)的是,chrome為我們提供了一些三種JS都通用的API,可以起到JS之間相互通訊的效果。
chrome.runtime消息傳遞
通過runtime的onMessage、sendMessage等方法,可以在各個(gè)JS之間傳遞并監(jiān)聽消息。舉個(gè)栗子:
在popup.js中,我們讓它初始化之后發(fā)送一個(gè)消息:
chrome.runtime.sendMessage({ method: "showAlert" }, function(response) {});
然后在background.js中,監(jiān)聽消息的接收,并進(jìn)行處理:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { if (message.method === "showAlert") { alert("showAlert"); } });
以上代碼,會在每次打開插件彈窗的時(shí)候彈出一個(gè)Alert。
chrome.runtime的常用方法:
// 獲取當(dāng)前擴(kuò)展程序中正在運(yùn)行的后臺網(wǎng)頁的 JavaScript window 對象 chrome.runtime.getBackgroundPage(function (backgroundPage) { // backgroundPage 即 window 對象 }); // 發(fā)送消息 chrome.runtime.sendMessage(message, function(response) { // response 代表消息回復(fù),可以接受到通過 sendResponse 方法發(fā)送的消息回復(fù) }); // 監(jiān)聽消息 chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { // message 就是你發(fā)送的 message // sender 代表發(fā)送者,可以通過 sender.tab 判斷消息是否是從內(nèi)容腳本發(fā)出 // sendResponse 可以直接發(fā)送回復(fù),如: sendResponse({ method: "response", message: "send a response" }); });
需要注意的是,即便你在多個(gè)JS中注冊了消息監(jiān)聽onMessage.addListener,也只有一個(gè)監(jiān)聽者能收到通過runtime.sendMessage發(fā)送出去的消息。如果需要不同的監(jiān)聽者分別監(jiān)聽消息,則需要使用chrome.tab API來指定消息接收對象
舉個(gè)栗子:
上文說過,需要在content_scripts中監(jiān)聽選擇事件,獲取選擇的文本,而對于右鍵菜單的點(diǎn)擊則是在background中監(jiān)聽的。那么需要把選擇的文本作為消息,發(fā)送給background,在background完成異步保存。
// content_scripts 中獲取選擇,并發(fā)送消息 // js/selection.js // 獲取選擇的文本 function getSelectedText() { if (window.getSelection) { return window.getSelection().toString(); } else if (document.getSelection) { return document.getSelection(); } else if (document.selection) { return document.selection.createRange().text; } } // 組建信息 function getSelectionMessage() { var text = getSelectedText(); var title = document.title; var url = window.location.href; var data = { text: text, title: title, url: url }; var message = { method: "get_selection", data: data } return message; } // 發(fā)送消息 function sendSelectionMessage(message) { chrome.runtime.sendMessage(message, function(response) {}); } // 監(jiān)聽鼠標(biāo)松開的事件,只有在右鍵點(diǎn)擊時(shí),才會去獲取文本 window.onmouseup = function(e) { if (!e.button === 2) { return; } var message = getSelectionMessage(); sendSelectionMessage(message); };
// background 中接收消息,監(jiān)聽右鍵菜單的點(diǎn)擊,并異步保存數(shù)據(jù) // js/background.js // 創(chuàng)建一個(gè)全局對象,來保存接收到的消息值 var selectionObj = null; // 首先要?jiǎng)?chuàng)建菜單 chrome.runtime.onInstalled.addListener(function() { chrome.contextMenus.create({ type: "normal", title: "save selection", id: "save_selection", // 有選擇才會出現(xiàn) contexts: ["selection"] }); }); // 監(jiān)聽菜單的點(diǎn)擊 chrome.contextMenus.onClicked.addListener(function(menuItem) { if (menuItem.menuItemId === "save_selection") { addCliper(); } }); // 消息監(jiān)聽,接收從 content_scripts 傳遞來的消息,并保存在一個(gè)全局對象中 chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { if (message.method === "get_selection") { selectionObj = message.data; } }); // 異步保存 function addCliper() { $.ajax({ // ... }); }
通過chrome.runtime.connect(或者chrome.tabs.connect)可以建立起不同類型JS之間的長鏈接。
信息的發(fā)送者需要制定獨(dú)特的信息類型,發(fā)送并監(jiān)聽信息:
var port = chrome.runtime.connect({type: "connection"}); port.postMessage({ method: "add", datas: [1, 2, 3] }); port.onMessage.addListener(function(msg) { if (msg.method === "answer") { console.log(msg.data); } });
而接受者則要注冊監(jiān)聽,并判斷消息的類型:
chrome.runtime.onConnect.addListener(function(port) { console.assert(port.type == "connection"); port.onMessage.addListener(function(msg) { if (msg.method == "add") { var result = msg.datas.reduce(function(previousValue, currentValue, index, array){ return previousValue + currentValue; }); port.postMessage({ method: "answer", data: result }); } }); });chrome.tabs
要使用這個(gè)API則需要先在manifest.json中注冊:
"permissions": [ "tabs", // ... ]
// 獲取到當(dāng)前的Tab chrome.tabs.getCurrent(function(tab) { // 通過 tab.id 可以拿到標(biāo)簽頁的ID }); // 通過 queryInfo,以Array的形式篩選出符合條件的tabs chrome.tabs.query(queryInfo, function(tabs) {}) // 精準(zhǔn)的給某個(gè)頁面的`content_scripts`發(fā)送消息 chrome.tabs.sendMessage(tabId, message, function(response) {});
舉個(gè)栗子:
在background.js中,我們獲取到當(dāng)前Tab,并發(fā)送消息:
chrome.tabs.getCurrent(function(tab) { chrome.tabs.sendMessage(tab.id, { method: "tab", message: "get active tab" }, function(response) {}); }); // 或者 chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, { method: "tab", message: "get active tab" }, function(response) { }); });
然后在content_scripts中,進(jìn)行消息監(jiān)聽:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { if (message.method === "tab") { console.log(message.message); } });chrome.storage
chrome.storage是一個(gè)基于localStorage的本地儲存,但chrome對其進(jìn)行了IO的優(yōu)化,可以儲存對象形式的數(shù)據(jù),也不會因?yàn)闉g覽器完全關(guān)閉而清空。
同樣,使用這個(gè)API需要先在manifest.json中注冊:
"permissions": [ "storage", // ... ]
chrome.storage有兩種形式,chrome.storage.sync和chrome.storage.local:
chrome.storage.local是基于本地的儲存,而chrome.storage.sync會先判斷當(dāng)前用戶是否登錄了google賬戶,如果登錄,則會將儲存的數(shù)據(jù)通過google服務(wù)自動(dòng)同步,否則,會使用chrome.storage.local僅進(jìn)行本地儲存
注:因?yàn)閮Υ鎱^(qū)沒有加密,所以不應(yīng)該儲存用戶的敏感信息
API:
// 數(shù)據(jù)儲存 StorageArea.set(object items, function callback) // 數(shù)據(jù)獲取 StorageArea.get(string or array of string or object keys, function callback) // 數(shù)據(jù)移除 StorageArea.remove(string or array of string keys, function callback) // 清空全部儲存 StorageArea.clear(function callback) // 監(jiān)聽儲存的變化 chrome.storage.onChanged.addListener(function(changes, namespace) {});
舉栗子:
我們在browser_action完成了用戶的登錄/注冊操作,將部分用戶信息儲存在storage中。每次初始化時(shí),都會檢查是否有儲存,沒有的話則需要用戶登錄,成功后再添加:
// browser_action // js.popup.js chrome.storage.sync.get("user", function(result) { // 通過 result.user 獲取到儲存的 user 對象 result && setPopDOM(result.user); }); function setPopDOM(user) { if (user && user.userId) { // show user UI } else { // show login UI } }; document.getElementById("login").onclick = function() { // login user.. // 通過 ajax 請求異步登錄,獲取到成功的回調(diào)后,將返回的 user 對象儲存在 storage 中 chrome.storage.sync.set({user: user}, function(result) {}); }
而在其他環(huán)境的JS里,我們可以監(jiān)聽storage的變化:
// background // js/background.js // 一個(gè)全局的 user 對象,用來保存用戶信息,以便在異步時(shí)發(fā)生 userId var user = null; chrome.storage.onChanged.addListener(function(changes, namespace) { for (key in changes) { if (key === "user") { console.log("user storage changed!"); user = changes[key]; } } });
正式發(fā)布大體上,我們目前為止理清了三種環(huán)境下JS的不同,以及他們交流和儲存的方式。除此以外,還有popup彈窗、右鍵菜單的創(chuàng)建和使用。其實(shí)使用這些知識就足夠做出一個(gè)簡單的chrome擴(kuò)展了。
其實(shí)我覺得整個(gè)過程中最蛋疼的一步就是把插件正式發(fā)布到chrome商店了。
首先,你要在開發(fā)者信息中心進(jìn)行登記,繳費(fèi)5刀。這一步可以參照如何成為一名Chrome應(yīng)用開發(fā)者一文來通過驗(yàn)證和支付。但需要注意的是,我在嘗試時(shí)使用的賬戶為中國google賬戶,因此完全無法支付,直到重新注冊了一個(gè)香港賬戶才搞定
之后,要填寫一系列的發(fā)布信息。google對icon和banner的尺寸要求的相當(dāng)嚴(yán)格。。這一步可以參考Google Chrome 應(yīng)用商店上傳擴(kuò)展程序一文
最后終于搞定,線上可見:cliper extension
學(xué)習(xí)資源建立 Chrome 擴(kuò)展程序
Chrome插件(Extensions)開發(fā)攻略
如何成為一名Chrome應(yīng)用開發(fā)者
chrome擴(kuò)展的開發(fā)
下一步?插件功能豐富化
插件可在網(wǎng)頁上高亮展示標(biāo)記的文本
用es6 + babel重構(gòu)
需要使用框架嗎?
注:本文源碼位于github倉庫:cliper-chrome,線上產(chǎn)品見:cliper 和 cliper extension
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/87969.html
摘要:了解擴(kuò)展程序開發(fā)本文大量借鑒圖靈電子書擴(kuò)展及應(yīng)用開發(fā)首發(fā)版首先,我嘗試來用簡單幾句話描述一下擴(kuò)展程序擴(kuò)展主要用于對瀏覽器功能的增強(qiáng),它強(qiáng)調(diào)與瀏覽器相結(jié)合。提供了接口,允許擴(kuò)展對用戶的歷史進(jìn)行管理。 了解Chrome擴(kuò)展程序開發(fā) 本文大量借鑒圖靈電子書-Chrome擴(kuò)展及應(yīng)用開發(fā)(首發(fā)版) 首先,我嘗試來用簡單幾句話描述一下Chrome擴(kuò)展程序: Chrome擴(kuò)展主要用于對瀏覽器功能的增...
摘要:關(guān)于我的博客掘金專欄路易斯專欄原文鏈接擴(kuò)展開發(fā)定制請求響應(yīng)頭域本文共字,閱讀需分鐘。那么,我會放棄嗎反向代理顯然不會,既然問題出在上,我去掉就行了。然而無論多少次的學(xué)習(xí)和模仿,最終的目的還是為了使用,故開發(fā)一款定制請求的勢在必行。 本文首發(fā)于《程序員》雜志2017年第9、10、11期,下面的版本又經(jīng)過進(jìn)一步的修訂。 關(guān)于 Github:IHeader 我的博客:louis blog ...
摘要:擴(kuò)展應(yīng)用模塊功能介紹擴(kuò)展應(yīng)用由很多部分組成,其中主要模塊為為了避免由于翻譯原因?qū)е碌膯栴},因此在下文中對相關(guān)模塊的稱呼一律采用上面的英文。附錄官方開發(fā)文檔英建議有英文閱讀能力的人閱讀此文檔。 概述 本文通過對chrome插件的各個(gè)部分進(jìn)行快速的介紹,從而讓大家了解插件各個(gè)部分的關(guān)系,并且知道如何將其進(jìn)行組裝成一個(gè)完整的chrome插件。 由于chrome官方文檔中對于如何從零開發(fā)一個(gè)c...
摘要:一般而言,擴(kuò)展會對用戶瀏覽的頁面進(jìn)行相應(yīng)的操作和一些數(shù)據(jù)傳遞,本案例的本質(zhì)是,當(dāng)用戶瀏覽網(wǎng)頁版微博時(shí),擴(kuò)展會向當(dāng)前頁面注入預(yù)先寫好的,這樣便對微博網(wǎng)頁版進(jìn)行了樣式重構(gòu)。采用這樣的方法依次處理所有你不想看到的元素,你的微博便會簡潔很多。 0x00. 前言 微博現(xiàn)在也是變得越來越臃腫,廣告越來越多,早已不再是微博了,這讓微博深度用戶的我感到十分焦灼。由于之前就嘗試寫過 Chrome 插件,...
摘要:以下示例將阻止所有對的請求。從存儲請求和阻止請求的對象中刪除當(dāng)前選項(xiàng)卡的屬性。收聽消息告知后臺進(jìn)程阻止的列表已被用戶更新。兩者都提供類似的功能和事件處理程序。 前言 當(dāng)我們?yōu)g覽網(wǎng)站時(shí),都會發(fā)送許多請求來獲取網(wǎng)頁內(nèi)容。這些請求中有些是重要的,而有些是我們不需要,因?yàn)樗鼈兛赡苁菑V告或建議等。在本文中,將創(chuàng)建一個(gè)有助于阻止和取消阻止所選URL的Chrome擴(kuò)展插件,讓你選擇你打開的網(wǎng)址及該打...
摘要:配置文件每一個(gè)擴(kuò)展都有一個(gè)格式的文件,叫。此消息發(fā)送后會觸發(fā)擴(kuò)展內(nèi)每個(gè)頁面的事件。和持續(xù)長時(shí)間的保持會話需要在和擴(kuò)展建立一個(gè)長時(shí)間存在的通道。內(nèi)容腳本發(fā)送消息到擴(kuò)展程序建立通道,并給通道命名利用通道發(fā)送一條消息。 這次的練習(xí)是做一個(gè)Chrome的擴(kuò)展,分享一下入門開發(fā)過程。因?yàn)樵谙鬟f那塊糾結(jié)了特別久,所以我會重點(diǎn)總結(jié)消息傳遞那塊。 這次做這個(gè)插件的功能很簡單,就是點(diǎn)擊按鈕后可以對當(dāng)...
閱讀 3647·2023-04-26 02:32
閱讀 3947·2021-11-23 10:05
閱讀 2304·2021-10-08 10:04
閱讀 2731·2021-09-22 16:06
閱讀 3626·2021-09-22 15:27
閱讀 776·2019-08-30 15:54
閱讀 1728·2019-08-30 13:50
閱讀 2713·2019-08-29 13:56