Chrome扩展开发的简介与实战(1)

发布时间 2023-04-09 13:36:12作者: ministep88

tags: 学习

前言

本课的目的

使大家学会最基础的Chrome扩展开发

理念

以实战为主,概念为辅

本来我是想按部就班,条条框框的一步一步从概念,特点,等等讲起。左思右想觉得这样未免太过枯燥,如果大家失去了兴趣,讲得再有条理也是枉然。所以还是结合实战,能做出东西才是王道。

上课形式

  • 讲解部分: 我讲大家听
  • 实战部分: 我做一步,大家做一步,如有问题,当场解决,然后下一步

预想实战部分会较多,有条件的希望带好笔记本电脑,一起操作。

课件

我会公布上课资料的markdown版本在gist上,方便大家查阅。

课程安排

不定时,看我准备上课内容的进度而定。但有课会提前1-2周通知,不会仓促举行。

问答

不限于上课时段,平时只要有空,到位子上来问也是欢迎的。

第一课

大家有什么用的好的扩展介绍吗?

介绍几款个人觉得好用的扩展

Sexy Undo Close Tab

商店地址

功能

记录关闭过的tab,以便日后快速查找恢复。

OneTab Plus

商店地址

功能

把tab打包成tab组,以后可以快速打开这个组。

Clipboard History Pro

商店地址

延伸介绍(有兴趣有额外精力的)

tampermonkey

商店地址

官网

功能

使用已经开发好的脚本

简介

普通的扩展都是固定的功能,而tampermonkey却可以动态的加载别人写好的脚本 。可谓灵活多变,随心所欲。而且很多都是神作,有着不可思议的创意和功能。

优点

tampermonkey最大的特点就是跨平台,它在各个浏览器中都有发布,chrome, safari, firefox, opera, edge, dolphin, uc browser甚至在Android上也能使用。

缺点

如果可以称它为缺点的话,使用门槛稍高是我觉得唯一的不利因素。对于一个普通用户来说,如果从来没听说过tampermonkey的话,第一次听别人介绍你安装脚本,就必须先安装扩展,再去找脚本,步骤稍微一多便阻挡了很多用户。而如果你是老用户的话,就不会产生上述问题,遇到好用的脚本时,只会有相见恨晚的感觉。

举例介绍一款tampermonkey脚本

豆瓣资源下载大师

开发准备

编辑器

工欲善其事,必先利其器。我们需要一款好用的编辑器。

我们需要编辑器最好具备以下功能:

  • 可以打开文件夹(树状文件结构视图显示文件夹)
  • 关键字高亮

没有以上功能的,不推荐使用。真的不方便。

另外,我不反对使用IDE集成编辑环境,但一般就来说我们的工作比较轻量级,以下2款可能是最佳选择。

vscode

强力推荐

下载地址:https://code.visualstudio.com/

SublimeText3

推荐

下载地址:https://www.sublimetext.com/3

文档

chrome extention

主要是看官方的文档,最新,最权威

官方介绍

API查询

看不懂英文没关系,有中文的山寨文档,更新上可能跟不上,但大致能看。

中文文档

网页基础知识文档

因为我们可能需要随时查询html javascript css等知识,以下网站备选,第一个英文的,二三都是中文的。

www.w3schools.com

www.w3school.com.cn

www.w3cschool.cn

实战 1-1:在console中打印hello world

步骤

step1:创建专用的空目录

step2:在此目录下创建空文件manifest.json并键入以下内容

{
    "name": "helloworld 1",
    "version": "1.0",
    "description": "print helloworld in console",
    "background": {
        "scripts": ["background.js"]
    },
    "manifest_version": 2
}

step3: 根目录下创建background.js并键入以下内容

console.log("hello world")

step4:将文件夹导入到Chrome内

  • 打开chrome中的扩展程序
  • 打开开发模式
  • 加载已解压的扩展程序
  • 选定刚才创建的文件夹
  • 看到新扩展被导入了

step5:打开扩展的背景页面,确认输出内容

  • 点击背景页,看到【hello world】输出了

讲解

  • manifest.json是整个扩展的总说明书
  • background中申明的js会在初次加载扩展时运行一次
  • js中的console.log()方法,将来是我们的重要调试手段

实战 1-2:用新Tab打开一个网页

{
    "name": "tab basic action 1",
    "version": "1.0",
    "description": "open a new tab",
    "background": {
        "scripts": ["background.js"]
    },
    "permissions": [
        "tabs"
    ],
    "manifest_version": 2
}
let openPage = {
    url: "https://www.google.com/doodles/rampo-edogawas-birthday",
}

chrome.tabs.create(openPage, function (tab) {
    console.log("the new tab is:")
    console.log(tab)
})

补充

安装开发专用的Chrome

这只是一个建议,并非必须。

在扩展的开发过程中,有可能会产生一些不便的问题。例如

  • 相同网站不同账号间切换,需要反复login,logout。
  • 扩展产生的效果,影响正常作业。

为了不搞乱现在正在使用的chrome,可以安装别的chrome版本。这个2个版本间相互独立,不会影响对方。

chrome的版本

  • 正式版
  • beta版
  • 开发版
  • canary版

如何选择

越往下进度越快,越是超前版本,但稳定性也是递减,越不怕折腾的人选越下方。

另外可以安装开源的chromium,其版本速度和canary相当,可能稍慢。

windows用户

可以安装以上任意两个版本,都不会影响对方

mac用户

正式版,beta版,开发版,同时只能启动一种。canary和Chromium则和别的版本独立。 即(正式版,beta版,开发版)三选一。(canary和Chromium)二选一或都装。

问答

Chrome扩展开发的简介与实战(2)

tags: 学习

优秀扩展介绍

crxMouse 商店地址

自定义扩展Icon的动作

按下Icon后,可以触发某些动作。或者打开一个弹出式的网页。

要追加这些操作,必须先申明,"Page Action" 或 "Browser Action"。 二者只能选一,不能都选。都不选就代表没有操作内容,这是允许的。

"Page Action" & "Browser Action"

两者差不多

都是指扩展的icon,差别不明显。但google对于两者的使用有一定的建议。具体参看两者各自的文档,有详细说明。

用一句话区别

其实两者的差别还是有点微妙,初期不宜展开。一定要用一句话区别的话,就是

只对少数网页敏感 --> Page Actions

大多数网页都敏感 --> Browser Actions

例如:

  1. 我不喜欢youtube网站的背景色,我希望自定义一下。因为这是对少数网页感兴趣,所以应该用"Page Action"。
  2. 只要发现网页上有图片,我希望一键下载所有图片。因为这是对大多数网页感兴趣,所以应该用"Browser Action"

实战 2-1: 更改kintone的背景色

首先,由于是对少数网页敏感,所以我们选用Page Action。

code

manifest.json

{
    "name": "sample 3: action page",
    "version": "1.0",
    "description": "change kintone background color",
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "page_action": {
        "default_icon": "icon.png",
        "default_title": "change color"
    },
    "permissions": ["declarativeContent", "activeTab"],
    "manifest_version": 2
}

background.js

chrome.runtime.onInstalled.addListener(function() {
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
        chrome.declarativeContent.onPageChanged.addRules([{
            conditions: [
                // When a page contains the 'cybozu.com/k/' url...
                new chrome.declarativeContent.PageStateMatcher({
                    pageUrl: { urlContains: 'cybozu.com/k/' },
                })
            ],
            // ... show the page action.
            actions: [new chrome.declarativeContent.ShowPageAction()]
        }]);
    });
});

chrome.pageAction.onClicked.addListener(function(tab) {
    chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="gray";document.getElementsByClassName("gaia-header-header")[0].style.backgroundColor="gray";'
    });
})

icon资源

icon

功能

  • 只有在含有 cybozu.com/k/ 的网页下才会点亮icon
  • 按下icon后,改变kintone背景色

讲解

权限基础

manifest.json中 "permissions": ["declarativeContent", "activeTab"], permissions: 表示我要申请权限。在chrome ext中很多动作都需要权限。没有权限是寸步难行。

申请权限时,我们遵循的原则是“够用即可”。只申请必要的权限,发现多余权限时要及时删除。

权限为什么不是越多越好?

权限越多,意味着能做的事越多,可能会产生以下后果

  • 需要用户对你更高的信任度,如果在用户看来,你申请的权限和扩展的功能不匹配,用户会认为此扩展有可疑行径,从而拒绝安装。
  • 更多权限也意味着发行时google更多的审查。也许你就过不了审核这一关了。

权限:declarativeContent

申明你的Page Action对于那些网页的url或网页中有某些特征(css特征等)的才会激活。

权限:activeTab

申明你对当前活动的tab具有操作权限。

在本例中,按下icon时改变背景色需要此项权限。

background中的操作

chrome.runtime.onInstalled.addListener(function() { // 安装时
    chrome.declarativeContent.onPageChanged.removeRules(undefined,  function() { // 删除规则
        chrome.declarativeContent.onPageChanged.addRules(...) // 增加规则
        }
    })
})
chrome.pageAction.onClicked.addListener(function(tab) { // 点击icon执行
    chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="gray";document.getElementsByClassName("gaia-header-header")[0].style.backgroundColor="gray";'
    }); // 这里也可以是file来指定js,css文件
})

tips:找到画面元素的html代码的方法

  • 找到开发者工具或者右键-->检查 (windows快捷键为F12)
  • 点击选择元素按钮 (windows快捷键为ctrl+shift+c)
  • 将鼠标放在你想查看的元素上

HomeWork

更改整体搜索框的背景色

Chrome扩展开发的简介与实战(3)

tags: 学习

优秀扩展介绍

save to pocket 商店地址

tips

多看google官方的sample

学习开发扩展,除了查阅Api之外,还有一个很重要的方法就是研究官方提供的sample。

有时候为了实现一个功能费了半天劲,查了各种网上的问题。最后解决是解决了,但是后来发现在sample中早就有很好的方案了。如果前期就浏览过一遍sample的话,很多问题都可以迎刃而解。

官方sample虽然都是些很简单功能的演示,但是其中却有着能体现从设计者初衷的逻辑在内。如果了解了这些逻辑,后期会少走很多弯路。

content script

上一讲,我们的实战中,改变了网页的一些元素。我们用的方法是先在background里注册了icon button按下去的事件,然后在触发里面写如何改变页面的元素。

chrome.pageAction.onClicked.addListener(function(tab) { // 点击icon执行
    chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="gray";document.getElementsByClassName("gaia-header-header")[0].style.backgroundColor="gray";'
    }); // 这里也可以是file来指定js,css文件
})

这里我们其实已经用到了content script,只是用到了它的一种方式:程序式

今天要介绍的是另外一种方式: 申明式 详见谷歌官方文档

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["http://*.nytimes.com/*"],
     "css": ["myStyles.css"],
     "js": ["contentScript.js"]
   }
 ]
}

其实从底层逻辑上说,这两者没什么差别,即使有差别也是写法上的。随着后面的学习这一点会渐渐显露出来,现在不必理会。

特性

content script的工作原理是在页面加载完后,对页面上的DOM进行加工。有一点比较特殊,他有自己的作用域。(作用域将在后面章节做详细介绍)它既不属于页面自身所带js所在的域,也不属于扩展的background域。

content script可以使用所有的js自带功能,但是只能使用一部分的chrome提供的api,如果想使用全部的api,就得把消息传给background,在background中实现。这就牵涉到消息传递机制(也在稍后介绍)。

实战 3-1

去除 Announcement

manifest.json

{
    "description": "invisible kintone announcement",
    "manifest_version": 2,
    "name": "sample 3-1 content script",
    "content_scripts": [{
        "matches": [
            "https://*.cybozu.com/k/*",
            "https://*.cybozu.cn/k/*"
        ],
        "js": [
            "inject.js"
        ]
    }],                     
    "version": "1.0"
}

inject.js

window.onload = function () {
    let ann = this.document.getElementsByClassName("ocean-portal-announcement")
    console.log(ann)
    if (ann.length > 0) {
        setTimeout(() => {
            ann[0].style.display = "none"
        }, 1000)
    }
}

获取页面元素的方法

这些内容属于js范畴,不是chrome extension范围的知识。但是由于比较常用,在这里还是做一些介绍。将来也起到一个查阅的作用。 可以不必强行掌握,在运用的同时边查边用,效果更好。

了解DOM

DOM (Document Object Model)

MDN上的解释如下

文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。

上述解释不理解也没关系,一般只要理解为节点或元素即可。

然后我们需要去操作DOM

javascript原生方法

https://www.w3schools.com/js/js_htmldom.asp

  • getElementById()
  • getElementsByTagName()
  • getElementsByClassName()
  • getElementsByName()

getElementById()

<!DOCTYPE html>
<html>
<body>

<h2>Finding HTML Elements by Id</h2>

<p id="intro">Hello World!</p>
<p>This example demonstrates the <b>getElementsById</b> method.</p>

<p id="demo"></p>

<script>
var myElement = document.getElementById("intro");
document.getElementById("demo").innerHTML = 
"The text from the intro paragraph is " + myElement.innerHTML;
</script>

</body>
</html>

getElementByTagName()

<!DOCTYPE html>
<html>
<body>

<h2>Finding HTML Elements by Tag Name</h2>

<p>Hello World!</p>
<p>This example demonstrates the <b>getElementsByTagName</b> method.</p>

<p id="demo"></p>

<script>
var x = document.getElementsByTagName("p");
document.getElementById("demo").innerHTML = 
'The text in first paragraph (index 0) is: ' + x[0].innerHTML;
</script>

</body>
</html>

getElementsByClassName()

<!DOCTYPE html>
<html>
<body>

<h2>Finding HTML Elements by Class Name</h2>

<p>Hello World!</p>

<p class="intro">The DOM is very useful.</p>
<p class="intro">This example demonstrates the <b>getElementsByClassName</b> method.</p>

<p id="demo"></p>

<script>
var x = document.getElementsByClassName("intro");
document.getElementById("demo").innerHTML = 
'The first paragraph (index 0) with class="intro": ' + x[0].innerHTML;
</script>

</body>
</html>

getElementsByName()

<!DOCTYPE html>
<html>
<body>

First Name: <input name="fname" type="text" value="Michael"><br>
First Name: <input name="fname" type="text" value="Doug">

<p>Click the button to get the tag name of the first element in the document that has a name attribute with the value "fname".</p>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
  var x = document.getElementsByName("fname")[0].tagName;
  document.getElementById("demo").innerHTML = x;
}
</script>

</body>
</html>

querySelectorAll()

<!DOCTYPE html>
<html>
<body>

<h2>Finding HTML Elements by Query Selector</h2>

<p>Hello World!</p>

<p class="intro">The DOM is very useful.</p>
<p class="intro">This example demonstrates the <b>querySelectorAll</b> method.</p>

<p id="demo"></p>

<script>
var x = document.querySelectorAll("p.intro");
document.getElementById("demo").innerHTML = 
'The first paragraph (index 0) with class="intro": ' + x[0].innerHTML;
</script>

</body>
</html>

css selector

教材

选择符
类型 符号
tag none
id #
class .
group ,
运算符
类型 符号
descendant(后裔) space(空格)
child(儿女 隔代无效) >
adjacent sibling(邻居 紧挨) +
general sibling(邻居 同一层) ~

jquery

我们也可以用jquery库提供的方法来选择元素

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>find demo</title>
  <style>
  span {
    color: blue;
  }
  </style>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
</head>
<body>
 
<p><span>Hello</span>, how are you?</p>
<p>Me? I'm <span>good</span>.</p>
<div>Did you <span>eat</span> yet?</div>
 
<script>
var spans = $( "span" );
$( "p" ).find( spans ).css( "color", "red" );
</script>
 
</body>
</html>

Chrome扩展开发的简介与实战(4)

tags: 学习

优秀扩展介绍

simpread

存储

localstorage

html 5所支持的一种存储方式。用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。

localstorage在这里分2种:

  • 页面原生的localstorage
  • chrome ext的background中的localstorage

页面原生的localstorage

页面原生的localstorage一般是网页制作者在编写网页js的时候使用。作用范围是以网站域名为单位。我们开发ext的时候如果想使用也是可以的,不过并不是在之前介绍的content script中注入,还需要用一种特殊方式注入到原生的页面中。现在我们还未讲到作用域的问题。所以在这里先不展开,大家只要先记得有这么一件事情即可。

background中的localstorage

作用范围是全局,不同于原生,是跨网站的。

content script中不能直接访问

localstorage通用的用法

localStorage.config = "abc"

localstorage里存的东西是纯字符串,这点很重要,开发中有人经常忘记这点。

由于在js中我们经常操作的对象是json object,而存localstorage时,业界通用的做法也是存json object。所以存取的时候,各要在字符串json object做转换。

虽然你可以存任何形式的字符串,但还是建议存json object

//存
localStorage.config = JSON.stringify({
            name: "Tom",
            age: 13,
            gender: "male"
        })
//取
let config = JSON.parse(localStorage.config)

sessionstorage

和localstorage一样都是html 5标准所支持的存储方式。不同的是,这是临时保存窗口或标签的数据,窗口和标签关闭后,会删除这些数据。

由于在实际开发中,我并没有用到过sessionstorage,可能是因为我开发的东西都是需要长期保存。个人经验这个在chrome extension开发中并不算常用。这里就先略过了。如有必要使用,网上随时都可查到资料。

chrome.storage

该API已经过优化,可以满足扩展程序的特定存储需求。 它提供与localStorage API相同的存储功能,但有以下主要区别:

  • 用户数据可以自动与Chrome同步(使用storage.sync)同步。
  • 扩展程序的content script可以直接访问用户数据,而无需后台页面。
  • 即使使用拆分隐身行为,也可以保留用户的扩展名设置。
  • 它与批量读取和写入操作异步,因此比阻塞和串行localStorage API更快。
  • 用户数据可以存储为对象(localStorage API以字符串形式存储数据)。
  • 可以读取管理员为扩展配置的企业策略(使用具有模式的storage.managed)。

官方文档

indexedDB

要点:

  • 存储复杂数据建议使用。
  • 类型上属于NoSQL的数据库(非关系型)。
  • 操作异步

内容相对较多,且知识点游离于chrome ext开发本身。计划后期有机会再介绍。

一篇个人认为写的不错的文档

Web Sql

并不是HTML5规范的一部分,但主流浏览器都支持。使用SQL语言操作,简单方便。

缺点是,主流浏览器厂商都宣称将来会不支持。所以如果是长久打算,建议放弃。 如果一定要用,也不是不可以。

课堂练习 4-1

要求:

使用chrome.storage存储bozuman上的个人基本情报 地址:https://bozuman.cybozu.com/settings/profile 额外:使用sync功能,使得另外一台浏览器上有相同的信息。

提示

  1. 在manifest里申明对"https://*.cybozu.com/settings/profile"敏感
  2. 申请权限storage
  3. 在注入的js中获取节点使用方法querySelectorAll()
  4. css选择子可以用chrome的开发工具获得
  5. 把获取到的情报用json组织起来
  6. 存盘并打印到console

Chrome扩展开发的简介与实战(5)

tags: 学习

优秀扩展介绍

Imagus

主要功能

图片跟随鼠标放大。 ###评价 一页上有很多小图,一个个点开特别麻烦的时候特别好用。不过也不是所有的小图都能放大。这跟网页的写法有关,倒不能说是这款扩展的锅。

Chrome扩展中的网页

概述

在Chrome扩展的开发中,有时候我们需要自己作成网页。那什么哪些地方需要作成呢?有哪些注意点?下面就这方面展开介绍。

分类

我把Chrome扩展的网页分成以下几种:

  • popup
  • option
  • other

popup

顾名思义,popup就是弹出的页面。在这里特指,按了扩展的按钮后所弹出显示的网页。

网页本身的制作和一般意义上的没有什么区别。只是具有一个特殊功能,就是当扩展按钮按下时,它会显示。为了实现这个功能,我们要在manifest里这样写:

manifest.json

{
    "description": "html in chrome ext",
    "manifest_version": 2,
    "name": "sample 5-1: html",
    "version": "1.0",
    "browser_action": {
        "default_title": "many htmls",
        "default_popup": "popup.html"
    }
}

在这里,"browser_action"或是"page_action"二者必选其一,只有这样才能让popup起作用。 但在这里,还有一些区别: browser_action:只要申明了,按下按钮马上就能弹出网页。 page_action:申明后,需要在js中编程,注明对哪些网页敏感。然后使用时在特定网页下,按下才会弹出网页。 我们在第二课中讲过,browser_action和page_action最主要的区别在于,前者对所有网页敏感,后者对特定网页敏感。所以这就是造成区别的主要原因。

然后我们来制作网页 popup.html

<!doctype html>
<html>

<head>
    <meta charset="utf-8" />
    <title>my popup html</title>
</head>

<body>
    <h1>我是popup弹出网页</h1>
</body>

</html>

弹出网页的大小是根据你的内容而定的,你画多大就显示多大。没有规定尺寸的话,就按刚刚好能包住来显示。

option

为选项页面而特制的网页。制作上也是没什么特殊之处,只是需要申明这是选项网页就行。 icon上右键点击会有一个option的选项,点进去就会显示。

manifest.json

{
    "options_page": "option.html"
}

option.html

<!doctype html>
<html>

<head>
    <meta charset="utf-8" />
    <title>my option html</title>
</head>

<body>
    <h1>我是option</h1>
</body>

</html>

option网页一般用来保存用户的偏好设置等。一般不用来做其他事情。

other

除popup和option之外的自定义网页。

和popup和option的唯一区别是没有chrome扩展所给的入口。打开的方法是用户输入确切的URL或者是从popup,option的超链接跳转。

tips

  • 内联js将不会被执行,要以文件的形式独立出来。官方文档
  • js的库不能引用网上资源,要下载下来再引用。(可以通过放松政策来开通)官方文档

课堂练习

要求

结合上节课的存储内容,把一项用户的输入内容存储起来

  1. 在option页面上设置输入框
  2. 把用户输入的内容存入chrome.storage

提示

  1. 结合上节课存储的部分
  2. option.html有可能是类似这样的结构:
<!doctype html>
<html>

<head>
    <meta charset="utf-8" />
    <title>my option html</title>
</head>

<body>
    <h1>我是option</h1>
    <span>请输入:</span>
    <input id="myinput" type="text">
    <button id="mybutton">save</button><br>
    <button id="getbutton">get value</button>
    <input id="getinput" type="text">
    <script src="option.js"></script>
</body>

</html>
  1. option.js的逻辑提示:
// getElementById("myinput")
// getElementById("mybutton")
// myButton.onclick = function (...)

// getElementById("getbutton")
// getElementById("getinput")
// getButton.onclick = function (...)

Chrome扩展开发的简介与实战(6)

tags: 学习

如何对原生的JS进行注入

需求

在开发过程中,有时候需要对原生的网页中js加以利用。譬如有这些用途:

  • 利用已有函数做一些操作,或者获取变量的值
  • 利用原生js所注册的事件,以达到在相应的时机做某些操作
  • 其他等等

利用content script注入

我们之前学习了一些content script的知识,知道它是运行在一个孤立的世界中,如果网页的作者写的js文件,我们是无法在content script中加以利用的。

不过我们还是有方法的。可以通过一种方法来注入原生网页。然后再利用html 5的消息传递机制。

例子

manifest.json

{
    "description": "origin js inject",
    "manifest_version": 2,
    "name": "sample 6-1: inject 2",
    "version": "1.0",
    "content_scripts": [{
        "matches": [
            "https://*.cybozu.com/k/*",
            "https://*.cybozu.cn/k/*"
        ],
        "js": [
            "contentScript.js"
        ]
    }],
    "web_accessible_resources": [
        "inject.js"
    ]
}

contentScript.js

let myScript = document.createElement('script')
myScript.src = chrome.extension.getURL('inject.js');
(document.head || document.documentElement).appendChild(myScript)
window.onmessage = function (event) {
    console.log(event)
    console.log(event.data.msg)
}

inject.js

kintone.events.on('app.record.index.show', function (event) {
    window.postMessage({
        "msg": "レコード一覧画面が表示されました!"
    }, document.URL)
})

解说

首先,我们在contentScript.js中调取了外部js文件injent.js并把它加入到了HTML的DOM中。

现在injent.js已经成为原生js的一部分了。 所以现在我们就可以使用kintone所准备的方法了。

我们的思路是:当一览打开时,会触发上述方法,在此时,发一条消息,然后再用content script里的window.onmessage来接受这条消息

这里需要稍微提一下window.postMessage方法,这是html 5标准中所提供的方法,它可以跨窗口或者叫做跨tab地传递消息。值得留意的是,这个消息会向浏览器所有的窗口发送,别的网页上如果想获取这条消息是完全可能的。而且,我们也可能收到别的不相关网页所发送的信息。所以,我们在发送消息时可以加上一些特殊识别字段,在接受端来判断这条消息来源。这样可以避免误接受了别人的消息。

另外,需要认识到的是,不是所有的网页正好提供了例子中的类似"一览打开了的时候"这样的注册好了的方法。在实际运用中,你想要达到的目的未必能很好地达成,因为原生js作者不是为了让你注入而写的。所以,如何利用好原生js,还仰仗对原生js有着怎样深刻的了解。

课堂练习

目标

基础:kintone详细画面打开后,把所有的详细内容打印在console上。

提高:把详细内容传回content script,并存储到chrome.storage

提示

kintone中文文档:记录显示事件

  • 以课堂的例子为基础结构
  • 因为要求是详细画面的打开,所以kintone的事件名称需要替换
  • 根据kintone的文档,找到代表详细内容的变量并打印

Chrome扩展开发的简介与实战(7)

tags: 学习

图表版

存储的共享和消息传递

有三个区域并不是直接互通的,他们是

  • background
  • content script
  • origin web page(原生网页)

他们之间可以互通信息,但有一定的规则。

存储的共享

background内的共享自己的localstorage

backgound范畴的项目如果运用localstorage储存东西,他们都会存到一个地方。

以下这些地址前面的部分都相同。

chrome-extension://ccfbnhhlemoobofbfakkgpggmaocldlk/_generated_background_page.html

chrome-extension://ccfbnhhlemoobofbfakkgpggmaocldlk/option.html

chrome-extension://ccfbnhhlemoobofbfakkgpggmaocldlk/popup.html

localstorage

只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。

background和contentscript共享chrome.storage

前面已经讲过,chromestorage是改造过的localstorage,它在扩展内都是唯一的。

contentscript和origin web page共享自己的localstorage

假设我们对bozuman进行了注入,不管是原生的网页还是注入好的contentscript他们所属的地址都是: https://bozuman.cybozu.com/ 所以他们注定使用同一份localstorage

消息传递

background发消息给contentscript

background->contentscript:chrome.tabs.sendMessage()
note right of contentscript:chrome.runtime.onMessage.addListener()
contentscript->background:sendResponse()
note left of background:handle response

contentscript发消息给background

contentscript->background:chrome.runtime.sendMessage()
note right of background:chrome.runtime.onMessage.addListener()
background->contentscript:sendResponse()
note left of contentscript:handle response

originwebpage发消息给contentscript

originwebpage->contentscript:window.postMessage()
note right of contentscript:window.onmessage

研究

  1. contentscript传消息给originwebpage证实可以
  2. originwebpage发消息给background证实不行。

可以直接传递消息与否示意图

background->contentscript:可
contentscript->origin web page:可
origin web page->contentscript:可
contentscript->background:可
origin web page-->background:不可
background-->origin web page:不可

不可直接传递的,可通过contentscript间接传递。

闹钟

https://developer.chrome.com/extensions/alarms

创建闹钟

// 描述何时应触发闹钟。
// 初始时间必须由when或delayInMinutes指定(但不能同时指定)。
// 如果设置了periodInMinutes,则闹钟将在初始事件后每隔periodInMinutes分钟重复一次。
// 如果没有为重复警报设置when或delayInMinutes,则将periodInMinutes用作delayInMinutes的默认值。
let alarmInfo = {
    // when: Date.now(),  
    delayInMinutes: 10,
    periodInMinutes: 1440
}
// 创建闹钟
chrome.alarms.create("myAlarm", object alarmInfo)

设定闹钟行为

chrome.alarms.onAlarm.addListener(function(alarm) {
    let delayms = getrandom(7)
    if (alarm.name === "myalarm") {
       // do something
    }
})

练习

基础: 刷新扩展1分钟后,在background打印hello world

提高: 打开kintone某app一条记录1分钟后,把此记录详细内容存到background的localstorage上。(不是chrome.storage)

 

[chrome_ext_practice](https://gist.github.com/forestsheep/c2447c06ca6e185f61ce6035f681daf1)