这篇文章是介绍一个chrome浏览器插件的实现过程,主要包括:javascript
浏览器插件,是基于浏览器的原有功能,另外增长新功能的工具。它不是独立的软件,须要依附于相应的浏览器才能发挥做用。目前主流的浏览器都容许使用插件,以加强浏览器的功能,让你的浏览器的功能更加多样化。 开发浏览器插件,其实就是相似于开发一个web应用,都是由HTML+JS+CSS构成,本文将介绍一个图片采集功能插件的实现。该插件主要用于采集网页图片并存储到服务端。css
本文实现的chrome浏览器插件的目录以下图所示:html
根目录下有一个manifest.json文件,里面提供了整个插件的功能和配置文件清单,很是重要,是插件应用必不可少的文件且必须放置在根目录。其中manifest_version、name、version这3个是必不可少的, description和icons是推荐选项。java
{ "manifest_version": 2, // 清单文件的版本,这个必须写,并且必须是2 "name": "图片采集", // 插件的名称 "version": "1.0.0", // 插件的版本 "description": "这是一个图片采集插件", // 插件描述 "icons": { // 插件图标 "16": "static/img/icon.png" "48": "static/img/icon.png" "128": "static/img/icon.png" }, } 复制代码
template目录则用来放置页面模板,经常使用的有popup.html和background.html。jquery
popup.html是窗口网页,点击插件图标时弹出,焦点离开窗口网页就马上关闭,用于和用户交互。在图片采集插件中提供了”预览所有图片“和”展现采集按钮“两个按钮供用户操做。web
经过manifest的default_popup字段来指定pupup页面:ajax
{ "browser_action": { "default_icon": "static/img/icon.png", "default_title": "", "default_popup": "template/popup.html" }, } 复制代码
background.html是后台页面,是一个常驻页面,它的生命周期是插件中全部类型页面中最长的,随着浏览器的打开而打开,随着浏览器的关闭而关闭,因此一般把须要一直运行的、启动就运行的、全局的代码放置在background.html里面,能够理解为插件运行在浏览器中的一个后台脚本。好比有时候,插件须要和服务端交互进行数据同步等操做,这种操做是用户无感知的,就能够放在后台页面来运行这部分的逻辑。chrome
经过manifest的background字段来指定background页面:json
{ // 2种指定方式,若是指定JS,那么会自动生成一个背景页 "page": "template/background.html" // "scripts": ["static/js/background.js"] } 复制代码
static目录放置静态文件包括图片、js脚本、css样式。浏览器
icon.png为插件图标。
content-script.js是插件注入到web页面的js脚本,经过使用标准的DOM,它能够读取web页面的细节或者修改页面DOM结构,web页面和插件的通讯也能够经过它来实现。在图片采集插件中,主要用来操做网页拿到图片。
popup.js为popup.html的js脚本。在图片采集插件中,主要用户收集用户交互,通知content-script.js去操做网页采集图片。
background.js为background.html的js 脚本。在图片采集插件中,主要用于存储采集到的图片到服务端。
content-script.css为插件注入到web页面的css样式。
conten-script可经过manifest配置的方式注入:
{ "content_scripts": [{ "matches": ["<all_urls>"], // <all_urls> 表示匹配全部地址 "js": ["static/js/jquery-1.8.3.js", "static/js/content-script.js"], // 多个js顺序注入 "css": ["static/css/content-script.css"], //css注入 "run_at": "document_end" // 代码注入的时间,可选值: document_start, document_end, or document_idle,最后一个表示页面空闲时,默认document_idle }] } 复制代码
popup.js和background.js都运行在插件的上下文中, 由于是运行在同一个线程中,因此它们之间的通讯相对比较简单,页面之间能够直接相互调用方法来传递信息。 好比chrome.extension.getViews()方法能够返回属于你的插件的每一个活动页面的窗口对象列表,而chrome.extension.getBackgroundPage()方法能够返回background页。
// background.js var views = chrome.extension.getViews({type:'popup'}); // 返回popup对象 if(views.length > 0) { console.log(views[0].location.href); } function test(){ console.log('我是background'); } // popup.js var bg = chrome.extension.getBackgroundPage(); bg.test(); // 访问background的函数 console.log(bg.document.body.innerHTML); // 访问background的DOM 复制代码
content-script.js是嵌入在web页面的脚本,因此它实际是运行在web页面的上下文中,与插件上下文是彻底隔离的,没办法像插件上下文相关页面那样能够相互调用方法来实现通讯,它须要借助通讯通道来辅助通讯。在图片采集插件中content-script.js接收来自popup.js的消息去采集网页图片,并发消息给background.js存储图片。
popup.js或者background.js向content-script.js主动发消息:
function sendMsgToContentScript(message, callback){ chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ chrome.tabs.sendMessage(tabs[0].id, message, function(response){ if(callback) callback(response); }) }) } sendMsgToContentScript({type: 'popMsg', value: '你好, 我是popup'}, function(response){ console.log('来自content的回复:' + response); }) 复制代码
content-script.js接收消息:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){ if(request.type === 'popMsg'){ console.log('收到来自popup的消息:' + request.value); } sendResponse('我是content-script,已收到你的消息'); }) 复制代码
content-script.js主动发消息给popup.js或者background.js:
chrome.runtime.sendMessage({type: 'contentMsg', value: '你好,我是content-script'},function(response){ console.log('收到来自后台的回复:'+ response); }) 复制代码
popup.js和background.js接收消息:
chrome.runtime.onMessage.addListener(function(request, sender, sendResonse){ console.log() if(request.type === 'contentMsg'){ console.log('收到来自content-script的消息:' + request.value); } sendResonse('我是后台,已收到你的消息。'); }) 复制代码
须要注意的是content-script.js向popup.js主动发消息的前提是popup弹窗页面必须是打开的,不然须要利用background.js做中转。
一、打开插件管理页面。经过浏览器菜单进入插件管理页面,也能够直接在地址栏输入chrome://extensions访问。
二、勾选开发者模式。勾选后便可以直接加载插件应用文件夹,不然只能安装.crx格式的压缩文件,没法及时同步插件更新。
三、插件更新。开发过程当中代码有任何改动都须要从新加载插件,点击插件的更新按钮便可,以防万一最好能够把页面也刷新一下。
content-script.js是运行在web页面的脚本,打开web页面的开发者工具就能够进行调试了。
因为background.js和content-script.js不是运行在同一个上下文中,所以web页面的调试窗口是看不到background.js的。调试background.js须要打开插件调试窗口。在插件管理页面点击你的插件的“查看视图template/background.html”,就会出现插件调试窗口了,接下来的操做就和普通web页面调试同样了。
虽然popup.js和background.js是处于同一个上下文中,可是想要看到popup.js,还须要多一步操做“点击审查弹出内容”才能够:
popup窗口网页提供了用户操做界面,主要包含了如下功能:
<!-- popup.html --> <div> <div id="preViewAllImg"> <img src="https://s10.mogucdn.com/mlcdn/c45406/190219_4949lfk7le758fei1825i6dkd4g9i_40x42.png" />预览所有已加载图片 </div> <div class="collect_btn_wrap" id="showCollectBtn"> <div class="collect_btn"> <div class="show_collect_check"><img class="collect_check_img" src="" /></div>页面显示收藏按钮 </div> </div> </div> <script type="text/javascript" src="../static/js/jquery-1.8.3.js"></script> <script type="text/javascript" src="../static/js/popup.js"></script> <!-- popup.js --> $(function () { function sendMsgToContentScript(message, callback){ chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ chrome.tabs.sendMessage(tabs[0].id, message, function(response){ if(callback) callback(response); }) }) } // 一键收集所有图片 $('#preViewAllImg').click(e => { sendMsgToContentScript({type: 'REQUEST_PREVIEW_ALL_IMG', title: '我是popup, 请采集全部已加载图片并进行预览'}, function(response){ console.log('我是popup, 来自content的回复:' + response); }); }); // 是否显示采集按钮 $('#showCollectBtn').click(() => { var src = $('.collect_check_img').attr('src'); var status = src === ''; // status: true 显示 $('.collect_check_img')[0].src = src === '' ? 'https://s10.mogucdn.com/mlcdn/c45406/190219_0728g95i8bkl3i08jic6lhjhh7gae_24x18.png' : ''; // 向content-script发送消息显示or隐藏单个商品的收藏按钮 sendMsgToContentScript({type: 'REQUEST_SWITCH_COLLECT_BTN', title: `我是popup, 请${status?'显示':'隐藏'}采集按钮`, status}, function(response){ console.log('我是popup, 来自content的回复:' + response); }); }); }) 复制代码
content-script会监听来自popup.js的消息,根据消息通知操做web页面的DOM,执行读取图片连接、添加图片采集按钮、采集图片等操做,并发送消息给background通知其存储采集到的图片连接。
<!-- content-script.js --> chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if(request.type === 'REQUEST_PREVIEW_ALL_IMG'){ console.log('我是content-script,收到来自popup的消息:' + request.title); sendResponse('我是content-script, 已收到你的预览所有图片的消息'); PreviewAllImg(); }else if(request.type === 'REQUEST_SWITCH_COLLECT_BTN'){ console.log('我是content-script,收到来自popup的消息:' + request.title); sendResponse(`我是content-script, 已收到你${request.status ? '显示':'隐藏'}采集按钮的消息`); if(request.status){ ShowCollectBtn(); }else{ ClearCollectBtn(); } }else if(request.type === 'COLLECT_RESULT'){ console.log('我是content-script,收到来自background的消息:' + request.title); sendResponse(`我是content-script, 已收到你的${request.title}的消息`); } }); // 预览全部已加载图片 function PreviewAllImg(){ GetAllAttrList('img', ['src', 'data-src']).then((res) => { ShowImgPanel(res); }) } // 展现采集按钮 function ShowCollectBtn(){ $('img').each((index, item) => { let src = $(item).attr('src') || $(item).attr('data-src'); $($(item).parent()).css('position', 'relative'); $($(item).parent()).find('.collect_img_btn').remove(); $($(item).parent()).append('<div class="collect_img_btn" data-src="'+src+'">采集</div>'); }); $('.collect_img_btn').click((e) => { e.stopPropagation(); e.preventDefault(); let src = $(e.target).data('src'); chrome.runtime.sendMessage({type: 'SEND_IMG', src: src},function(response){ console.log('我是content-script, 收到来自后台的回复:' + response); }) }); } // 清除采集按钮 function ClearCollectBtn(){ $('.collect_img_btn').remove(); } // 展现预览图片对话框 function ShowImgPanel(list){ var panelDOM = $('<div id="collect_img_panel">' + '<div class="collect_img_panel_close">x</div>' + '<div class="collect_img_panel_content">x</div>' + '</div>'); $('body').append(panelDOM); $('body').append('<div id="collect_img_panel_mask"></div>'); let $item = ''; $.each(list, function(index, item) { $item = $item + '<div class="collect_img_panel_item">' + '<div class="collect_img_panel_item_img" hljs-string">')"></div>' + '<div class="collect_img_panel_item_mask"></div>' + '<div class="collect_img_panel_item_btn" data-src="'+ item+'">采集图片</div>' + '</div>'; }); $('.collect_img_panel_content').html($item); $('.collect_img_panel_item_btn').click((e)=>{ let src = $(e.target).data('src'); chrome.runtime.sendMessage({type: 'SEND_IMG', src: src},function(response){ console.log('我是content-script, 收到来自后台的回复:' + response); }) }); $(".collect_img_panel_close").click(function() { $('#collect_img_panel').remove(); $('#collect_img_panel_mask').remove(); }); } // 根据标签和属性采集全部符合条件的对象 function GetAllAttrList(obj, attrArr){ return new Promise((resolve) => { let list = []; $(obj).each((index, item) => { GetAttrContent(item, attrArr).then(res => { list.push(res); if(index === $(obj).length - 1){ resolve(list); } }); }); }); } // 获取对象的属性内容 function GetAttrContent(obj, attrArr){ return new Promise((resolve) => { $.each(attrArr, (attrIndex, attrItem) => { let attrContent = $(obj).attr(attrItem); if(attrContent){ resolve(attrContent); } }) }); } 复制代码
background后台页面监听来自content-script的消息,将采集到的图片存储到服务端。
<!-- background.html --> <div> <script type="text/javascript" src="../static/js/jquery-1.8.3.js"></script> <script type="text/javascript" src="../static/js/background.js"></script> </div> <!--background.js--> chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if(request.type === 'SEND_IMG'){ alert('我是background,采集到图片连接:' + request.src); sendResponse('正在采集图片'); requestStoreImg(request.src); } }); // 存储图片 function requestStoreImg(src) { $.ajax({ type: 'get', url: 'https:/www.mogu.com/*/store', data: { url: src }, success: function(res) { if (res.status && res.status.code && res.status.code === 1001) { sendMsgToContentScript({type: 'COLLECT_RESULT', title: '图片采集成功'}, function(response){ alert('我是background,来自content的回复:' + response); }); } else { sendMsgToContentScript({type: 'COLLECT_RESULT', title: '图片采集失败'}, function(response){ alert('我是background,来自content的回复:' + response); }); } }, error: function(res){ sendMsgToContentScript({type: 'COLLECT_RESULT', title: '接口异常采集失败'}, function(response){ alert('我是background,来自content的回复:' + response); }); } }) } function sendMsgToContentScript(message, callback){ chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ chrome.tabs.sendMessage(tabs[0].id, message, function(response){ if(callback) callback(response); }) }) } 复制代码
开发完成,经过插件页面的“打包扩展程序”打包生成.crx包。
申请一个Google帐号登陆开发者信息中心,按照要求填完全部的信息就能够发布了。