chrome扩展程序初体验

常被称为chrome插件,虽然名称定语为chrome,但是chrome扩展程序除了在chrome浏览器上使用,理论上还可以运行在wekit内核的浏览器上(毕竟chrome已经和webkit分道扬镳开始走Blink内核了)

chrome扩展主要是 增强浏览器的功能 ,能定制个人专属的浏览器不是很有意思:smirk:

用chrome扩展可以对页面原有DOM进行修改,比如本例是对大搜车全体开发人员的语雀个人中心进行扩展,在原页面的基础上将开发人员的日常动态集中在一个页面中,包括gitlab的git热力图。直接重写整个页面的成本过大,所以就用chrome扩展对原页面进行二次修改,并能避免第三方接口跨域的问题。

上述情景包括Chrome扩展的开发,嵌入的iframe页面的开发,下面主要写的是chrome开发总结,适合chrome开发新手。

入门须知:

chrome扩展程序的主要组成:

  • manifest.json // 说明整个程序的配置,必选 (具体各配置项参考 官方配置API文档
  • background.js // 后台脚本,默认持续运行在后台的脚本
  • content.js // 内容脚本,注入到匹配吻合页面的脚本
  • popup.html // 扩展图标点击查看的视图窗

脚本类型

注入到原网页上下文的脚本

与原页面自带脚本同等的脚本,可以访问原页面脚本中的所有变量,这种脚本只能使用网页的通用API,无法使用chrome扩展提供的API,使用时需要在mainfest.js中配置web-accessible-resources,表明扩展程序的某些脚本可以从网页访问

"web_accessible_resources": [
    "js/sideTag.js"
]

重新载入,不属于扩展程序的脚本

content脚本

注入到匹配的当前页面中的脚本,但是并不能完成融入到当前页面的脚本中,而是运行在一个被隔离的环境中,能共享当前页面的DOM对象,但无法获取当前页面的全部数据,例如当前页面脚本中的变量数据,在这种脚本中可以使用部分chrome扩展的API,可以用来解决跨域问题(后文详解),在mainfest中配置content

"content_scripts": [{
    "matches": ["*://souche.yuque.com/*"],
    "js": ["modifyDom.js"],
    "run_at": "document_end"
}]

重新载入,不完全属于扩展程序的脚本

background脚本

运行在后台的脚本,与当前浏览页面无关,但是这类脚本可以保持持续运行或者活动时运行,持续运行在浏览器页面打开到关闭的时间里,活动状态运行为一种非持续运行后台脚本,可以在不活动状态释放占用的资源,提高性能;后台脚本可以使用全部的chrome扩展API,在mainfest中配置background

"background": {
    "scripts": ["background.js"],
    "persistent": false 
    // true表明持续运行,false非持续运行
}

在非持续运行时,每次页面活动会触发重新载入,持续运行时加载一次,完全属于扩展程序的脚本

popup脚本

也就是我们常见的在点击扩展图标时弹出的视图窗口,它和background脚本一样可以使用全部的chrome扩展API,一般会用来用户授权,但是最好在用户授权之后将授权信息保存在本地,不然每次打开都需要用户授权就很不友好了(这次没实现popup,只能说说我所了解的),在mainfest中配置popup

"browser_action" : {
    "default_title" : "搜车KM",
    "default_popup" : "popup.html",
    "default_icon" : {
      "16" : "KM.png"
    }
}

用户每次点击图标都会重新载入脚本,完全属于扩展程序的脚本

再往下分析一下实践中一定会碰到的通信和跨域问题,毕竟这些坑都踩了–

通信问题

注入脚本与原页面脚本:

可以从注入脚本中获取到原页面脚本的变量,通过window.addEventListener和 window.postMessage来实现,例如获取window对象中的特殊属性,:chestnut:

// 注入脚本 sideTag.js
window.postMessage({ "sLogin": window.appData.me.login }, '*');  
// 内容脚本 modifyDom.js
var sLogin = '';  
window.addEventListener("message", function (event){  
if (event.data.sLogin) {  
        sLogin = event.data.sLogin
    }
}, false);

之前为了获取原页面的window.appData数据,用了一种很ZZ的方法——模版字符串,能实现,但是代码看起来真的累啊。。。来个对比吧 -_-|

// 内容脚本使用模版字符串code // 内容脚本引用code // 注入脚本code

PS: 同一个扩展程序的不同注入脚本是运行在同一个上下文的,注意变量不要重复声明

内容脚本与“完全属于扩展程序脚本”的通信:

这两者之前是可以互通消息的,内容脚本可以调用chrome.runtime的API,后者可以调用chrome.*的全部API,只是两者作为发送方时发送的方式不一样

// 内容脚本 modifyDom.js
chrome.runtime.sendMessage(  
    {
        type: 'get',
        url: 'https://skm.souche.com/user/' + urlParam // 需要请求的url
    },
    response => {
        // ...
    }
)
​
// 后台脚本 background.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {  
  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
    // ...
  });
});

接收方的方法一致,:chestnut:

// 后台脚本 background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {  
  if (request.type == 'get') {
    fetch(request.url)
      .then(response => {
        if (response.status === 200) return response.text()
        else if (response.status === 401) {
          return Promise.resolve(response.status)
        }
      })
      .then(text => {
        sendResponse(text)
      })
      .catch(error => console.error('error', error))
    return true // 异步
  }
})

“完全属于扩展程序脚本”之间的通信:

他们之间函数是可以直接互相访问的,并且可以互相访问对方的DOM元素;所以他们之间事实上不存在什么复杂的通信

跨域问题

跨域感觉是使用chrome扩展的大收益,毕竟有些第三方API不允许服务端直接跨域请求啊。。。

在扩展脚本中使用XHR发起跨域请求,第三方域名需要在mainfest的permissions中配置一下 ​

"permissions": [
    "*://souche.yuque.com/*",
    "*://skm.souche.com/*",
    "*://git.souche-inc.com/*",
    "*://f2e-assets.souche.com/*"
]

BUT,前不久chrome扩展已经不支持内容脚本中xhr的跨域请求了,开发中的我不得已换了跨域的方法,也就是前面通信中提到的chrome.runtime的调用,在后台脚本中处理内容脚本的外部服务器请求,(此处自行回看上文代码)再将处理后的数据发送给内容脚本,前端同学生活不易啊,说不定一觉起来你的代码就跑不动了。。。

emmm… 介于这个扩展没有发布,只能给大家看一下前后对比图了:sweat_smile:

第一次写chrome扩展,总结的肯定不全面,大佬们多多指导。