请选择 进入手机版 | 继续访问电脑版

网络科技

    今日:454| 主题:266362
收藏本版 (1)
互联网、科技极客的综合动态。

[其他] Chrome 扩展程序开发

[复制链接]
枕梦 发表于 2016-10-15 20:56:31
260 8

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
Table of Contents generated withDocToc
  Chrome扩展程序开发

  十一在家无聊时开发了这个项目。其出发点是想通过chrome插件,来保存网页上选中的文本。后来就顺手把前后端都做了(Koa2 + React):
   chrome插件源码
   插件对应的前后端源码
  概述

  chrome扩展程序

  chrome扩展程序大家应该都很熟悉了,它可以通过脚本帮我们完成一些快速的操作。通过插件可以捕捉到网页内容、标签页、本地存储,或者用户的操作行为;它也可以在一定程度上改变浏览器的UI,例如页面上右键的菜单、浏览器右上角点击插件logo后的弹窗,或者浏览器新标签页
  开发缘由

  按照惯例,开发前多问问自己 why? how?
  why:
  
       
  • 我在平常看博文时,对于一些段落想进行摘抄或者备注,又懒得复制粘贴  
  how:
  
       
  • 一个chrome扩展程序,可以通过鼠标右键的菜单,或者键盘快捷键快速保存当前页面上选择的文本   
  • 如果没有选择文本,则保存网页链接   
  • 要有对应的后台服务,保存 user、cliper、page (后话,本文不涉及)   
  • 还要有对应的前端,以便浏览我的保存记录 (后话,本文不涉及)  
  先上个成果图:
   

Chrome 扩展程序开发

Chrome 扩展程序开发

   

Chrome 扩展程序开发

Chrome 扩展程序开发

   

Chrome 扩展程序开发

Chrome 扩展程序开发

  clip 有剪辑之意,因此项目命名为 cliper
  这两天终于安奈不住买了服务器,终于把网址部署了,也上线了chrome插件:
  
       
  • cliper   
  • cliper extension  
    manifest.json  

   在项目根目录下创建 manifest.json 文件,其中会涵盖扩展程序的基本信息,并指明需要的权限和资源文件
  1. {
  2.   // 以下为必写
  3.   "manifest_version": 2, // 必须为2,1号版本已弃用
  4.   "name": "cliper", // 扩展程序名称
  5.   "version": "0.01", // 版本号
  6.   // 以下为选填
  7.   // 推荐
  8.   "description": "描述",
  9.   "icons": {
  10.     "16": "icons/icon_16.png",
  11.     "48": "icons/icon_48.png",
  12.     "64": "icons/icon_64.png",
  13.     "128": "icons/icon_128.png"
  14.   },
  15.   "author": "ecmadao",
  16.   // 根据自己使用的权限填写
  17.   "permissions": [
  18.     // 例如
  19.     "tab",
  20.     "storage",
  21.     // 如果会在js中请求外域API或者资源,则要把外域链接加入
  22.     "http://localhost:5000/*"
  23.   ],
  24.   // options_page,指右键点击右上角里的插件logo时,弹出列表中的“选项”是否可点,以及在可以点击时,左键点击后打开的页面
  25.   "options_page": "view/options.html",
  26.   // browser_action,左键点击右上角插件logo时,弹出的popup框。不填此项则点击logo不会有用
  27.   "browser_action": {
  28.     "default_icon": {
  29.       "38": "icons/icon_38.png"
  30.     },
  31.     "default_popup": "view/popup.html", // popup页面,其实就是普通的html
  32.     "default_title" : "保存到cliper"
  33.   },
  34.   // background,后台执行的文件,一般只需要指定js即可。会在浏览器打开后全局范围内后台运行
  35.   "background": {
  36.     "scripts": ["js/vendor/jquery-3.1.1.min.js", "js/background.js"],
  37.     // persistent代表“是否持久”。如果是一个单纯的全局后台js,需要一直运行,则不需配置persistent(或者为true)。当配置为false时转变为事件js,依旧存在于后台,在需要时加载,空闲时卸载
  38.     "persistent": false
  39.   },
  40.   // content_scripts,在各个浏览器页面里运行的文件,可以获取到当前页面的上下文DOM
  41.   "content_scripts": [
  42.     {
  43.       // matches 匹配 content_scripts 可以在哪些页面运行
  44.       "matches" : ["http://*/*", "https://*/*"],
  45.       "js": ["js/vendor/jquery-3.1.1.min.js", "js/vendor/keyboard.min.js", "js/selection.js", "js/notification.js"],
  46.       "css": ["css/notification.css"]
  47.     }
  48.   ]
  49. }
复制代码
综上,我们一共有三种资源文件,针对着三个运行环境:
  
       
  • browser_action
           
    • 控制logo点击后出现的弹窗,涵盖相关的html/js/css     
    • 在弹窗中,会进行登录/注册的操作,并将用户信息保存在本地储存中。已登录用户则展现基本信息   
       
  • background
           
    • 在后台持续运行,或者被事件唤醒后运行     
    • 右键菜单的点击和异步保存事件将在这里触发   
       
  • content_scripts
           
    • 当前浏览的页面里运行的文件,可以操作DOM     
    • 因此,我会在这个文件里监听用户的选择事件   
       
  注:
  
       
  • content_scripts 中如果没有 matches ,则扩展程序无法正常加载,也不能通过“加载未封装的扩展程序”来添加。如果你的 content_scripts 中有js可以针对所有页面运行,则填写 "matches" : ["http://*/*", "https://*/*"] 即可   
  • 推荐将 background 中的 persistent 设置为 false ,根据事件来运行后台js  
  不同运行环境JS的绳命周期

  如上所述,三种JS有着三种运行环境,它们的生命周期、可操作DOM/接口也不同
    content_scripts  

   content_scripts 会在每个标签页初始化加载的时候进行调用,关闭页面时卸载
   内容脚本,在每个标签页下运行。虽然它可以访问到页面DOM,但无法访问到这个里面里,其他JS文件创建的全局变量或者函数。也就是说,各个 content_scripts (以及外部JS文件)之间是相互独立的,只有:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]
复制代码
  js 所定义的一个Array里的各个JS可以相互影响。
    background  

   官方建议将后台js配置为 "persistent": false ,以便在需要时加载,再次进入空闲状态后卸载
   什么时候会让 background 的资源文件加载呢?
  
       
  • 应用程序第一次安装或者更新   
  • 监听某个事件触发(例如 chrome.runtime.onInstalled.addListener )   
  • 监听其他环境的JS文件发送消息(例如 chrome.runtime.onMessage.addListener )   
  • 扩展程序的其他资源文件调用了 runtime.getBackgroundPage  
    browser_action  

   browser_action 里的资源会在弹窗打开时初始化,关闭时卸载
   browser_action 里定义的JS/CSS运行环境仅限于popup,并且会在每次点开弹窗的时候初始化。但是它可以调用一些 chrome api ,以此来和其他js进行交互
  除此以外:
  
       
  • browser_action 的HTML文件里使用的JS,不能直接以 <script></script> 的形式行内写入HTML里,需要独立成JS文件再引入   
  • 如果有其他第三方依赖,比如 jQuery 等文件,也无法通过CDN引入,而需要保持资源文件到项目目录后再引入  
  不同运行环境JS之间的交互

  虽然运行环境和绳命周期都不相同,但幸运的是,chrome为我们提供了一些三种JS都通用的API,可以起到JS之间相互通讯的效果。
   chrome.runtime

   消息传递
  普通的消息传递

   通过 runtime 的 onMessage 、 sendMessage 等方法,可以在各个JS之间传递并监听消息。举个栗子:
   在 popup.js 中,我们让它初始化之后发送一个消息:
  1. chrome.runtime.sendMessage({
  2.   method: 'showAlert'
  3. }, function(response) {});
复制代码
  然后在 background.js 中,监听消息的接收,并进行处理:
  1. chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  2.   if (message.method === 'showAlert') {
  3.     alert('showAlert');
  4.   }
  5. });
复制代码
以上代码,会在每次打开插件弹窗的时候弹出一个Alert。
   chrome.runtime 的常用方法:
  1. // 获取当前扩展程序中正在运行的后台网页的 JavaScript window 对象
  2. chrome.runtime.getBackgroundPage(function (backgroundPage) {
  3.   // backgroundPage 即 window 对象
  4. });
  5. // 发送消息
  6. chrome.runtime.sendMessage(message, function(response) {
  7.   // response 代表消息回复,可以接受到通过 sendResponse 方法发送的消息回复
  8. });
  9. // 监听消息
  10. chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  11.   // message 就是你发送的 message
  12.   // sender 代表发送者,可以通过 sender.tab 判断消息是否是从内容脚本发出
  13.   // sendResponse 可以直接发送回复,如:
  14.   sendResponse({
  15.     method: 'response',
  16.     message: 'send a response'
  17.   });
  18. });
复制代码
  需要注意的是,即便你在多个JS中注册了消息监听 onMessage.addListener ,也只有一个监听者能收到通过 runtime.sendMessage 发送出去的消息。如果需要不同的监听者分别监听消息,则需要使用 chrome.tab API来指定消息接收对象
  举个栗子:
   上文说过,需要在 content_scripts 中监听选择事件,获取选择的文本,而对于右键菜单的点击则是在 background 中监听的。那么需要把选择的文本作为消息,发送给 background ,在 background 完成异步保存。
  1. // content_scripts 中获取选择,并发送消息
  2. // js/selection.js
  3. // 获取选择的文本
  4. function getSelectedText() {
  5.   if (window.getSelection) {
  6.     return window.getSelection().toString();
  7.   } else if (document.getSelection) {
  8.     return document.getSelection();
  9.   } else if (document.selection) {
  10.     return document.selection.createRange().text;
  11.   }
  12. }
  13. // 组建信息
  14. function getSelectionMessage() {
  15.   var text = getSelectedText();
  16.   var title = document.title;
  17.   var url = window.location.href;
  18.   var data = {
  19.     text: text,
  20.     title: title,
  21.     url: url
  22.   };
  23.   var message = {
  24.     method: 'get_selection',
  25.     data: data
  26.   }
  27.   return message;
  28. }
  29. // 发送消息
  30. function sendSelectionMessage(message) {
  31.   chrome.runtime.sendMessage(message, function(response) {});
  32. }
  33. // 监听鼠标松开的事件,只有在右键点击时,才会去获取文本
  34. window.onmouseup = function(e) {
  35.   if (!e.button === 2) {
  36.     return;
  37.   }
  38.   var message = getSelectionMessage();
  39.   sendSelectionMessage(message);
  40. };
复制代码
  1. // background 中接收消息,监听右键菜单的点击,并异步保存数据
  2. // js/background.js
  3. // 创建一个全局对象,来保存接收到的消息值
  4. var selectionObj = null;
  5. // 首先要创建菜单
  6. chrome.runtime.onInstalled.addListener(function() {
  7.   chrome.contextMenus.create({
  8.     type: 'normal',
  9.     title: 'save selection',
  10.     id: 'save_selection',
  11.     // 有选择才会出现
  12.     contexts: ['selection']
  13.   });
  14. });
  15. // 监听菜单的点击
  16. chrome.contextMenus.onClicked.addListener(function(menuItem) {
  17.   if (menuItem.menuItemId === "save_selection") {
  18.     addCliper();
  19.   }
  20. });
  21. // 消息监听,接收从 content_scripts 传递来的消息,并保存在一个全局对象中
  22. chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  23.   if (message.method === 'get_selection') {
  24.     selectionObj = message.data;
  25.   }
  26. });
  27. // 异步保存
  28. function addCliper() {
  29.   $.ajax({
  30.     // ...
  31.   });
  32. }
复制代码
长链接

   通过 chrome.runtime.connect (或者 chrome.tabs.connect )可以建立起不同类型JS之间的长链接。
  信息的发送者需要制定独特的信息类型,发送并监听信息:
  1. var port = chrome.runtime.connect({type: "connection"});
  2. port.postMessage({
  3.   method: "add",
  4.   datas: [1, 2, 3]
  5. });
  6. port.onMessage.addListener(function(msg) {
  7.   if (msg.method === "answer") {
  8.     console.log(msg.data);
  9.   }
  10. });
复制代码
而接受者则要注册监听,并判断消息的类型:
  1. chrome.runtime.onConnect.addListener(function(port) {
  2.   console.assert(port.type == "connection");
  3.   port.onMessage.addListener(function(msg) {
  4.     if (msg.method == "add") {
  5.       var result = msg.datas.reduce(function(previousValue, currentValue, index, array){
  6.       return previousValue + currentValue;
  7.   });
  8.       port.postMessage({
  9.         method: "answer",
  10.         data: result
  11.       });
  12.     }
  13.   });
  14. });
复制代码
chrome.tabs

   要使用这个API则需要先在 manifest.json 中注册:
  1. "permissions": [
  2.   "tabs",
  3.   // ...
  4. ]
复制代码
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]0
复制代码
举个栗子:
   在 background.js 中,我们获取到当前Tab,并发送消息:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]1
复制代码
  然后在 content_scripts 中,进行消息监听:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]2
复制代码
chrome.storage

   chrome.storage 是一个基于 localStorage 的本地储存,但chrome对其进行了IO的优化,可以储存对象形式的数据,也不会因为浏览器完全关闭而清空。
   同样,使用这个API需要先在 manifest.json 中注册:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]3
复制代码
  chrome.storage 有两种形式, chrome.storage.sync 和 chrome.storage.local :
   chrome.storage.local 是基于本地的储存,而 chrome.storage.sync 会先判断当前用户是否登录了google账户,如果登录,则会将储存的数据通过google服务自动同步,否则,会使用 chrome.storage.local 仅进行本地储存
  注:因为储存区没有加密,所以不应该储存用户的敏感信息
  API:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]4
复制代码
举栗子:
   我们在 browser_action 完成了用户的登录/注册操作,将部分用户信息储存在 storage 中。每次初始化时,都会检查是否有储存,没有的话则需要用户登录,成功后再添加:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]5
复制代码
  而在其他环境的JS里,我们可以监听 storage 的变化:
  1. "content_scripts": [
  2.   {
  3.     "js": [...]
  4.   }
  5. ]6
复制代码
大体上,我们目前为止理清了三种环境下JS的不同,以及他们交流和储存的方式。除此以外,还有popup弹窗、右键菜单的创建和使用。其实使用这些知识就足够做出一个简单的chrome扩展了。
  正式发布

  其实我觉得整个过程中最蛋疼的一步就是把插件正式发布到chrome商店了。
  
       
  • 首先,你要在 开发者信息中心 进行登记,缴费5刀。这一步可以参照 如何成为一名Chrome应用开发者 一文来通过验证和支付。但需要注意的是,我在尝试时使用的账户为中国google账户,因此完全无法支付,直到重新注册了一个香港账户才搞定   
  • 之后,要填写一系列的发布信息。google对icon和banner的尺寸要求的相当严格。。这一步可以参考 Google Chrome 应用商店上传扩展程序 一文  
   最后终于搞定,线上可见: cliper extension
  学习资源

  
       
  • 建立 Chrome 扩展程序   
  • Chrome插件(Extensions)开发攻略   
  • 如何成为一名Chrome应用开发者   
  • chrome扩展的开发  
   注:本文源码位于 github仓库:cliper-chrome ,线上产品见: cliper 和 cliper extension
  下一步?

  
       
  • 插件功能丰富化   
  • 插件可在网页上高亮展示标记的文本   
  • 用 es6 + babel 重构   
  • 需要使用框架吗?  



上一篇:聚集众多资深玩家,play玩具控欲打造成玩具界的豆瓣社区? | 每日黑马 ...
下一篇:Samsung Galaxy C9 spotted on TENAA, C9 Pro photos leaked
重生的风信子 发表于 2016-10-15 22:26:52
在撸一遍。。。
回复 支持 反对

使用道具 举报

metone 发表于 2016-10-15 22:38:18
我对楼主的敬仰犹如滔滔江水绵延不绝!
回复 支持 反对

使用道具 举报

蒋锐 发表于 2016-10-15 23:22:49
楼下的不要小看我,我可不是吃素的。
回复 支持 反对

使用道具 举报

nbmvbmnv 发表于 2016-10-16 01:45:52
锻炼肌肉,防止挨揍!  
回复 支持 反对

使用道具 举报

代悦 发表于 2016-11-12 17:00:53
楼主这么可爱,你造么?
回复 支持 反对

使用道具 举报

龙飞 发表于 2016-11-21 06:06:26
为保住菊花,这个一定得回复!
回复 支持 反对

使用道具 举报

nikon100 发表于 2016-11-21 06:48:03
坐沙发喽,楼主给赏钱不?
回复 支持 反对

使用道具 举报

贱客灬殇情 发表于 2016-11-21 12:02:12
过去的事情可以不忘记,但一定要放下。
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表