ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # Google Chrome扩展简介 Google Chrome扩展是一种软件,以增强Chrome浏览器的功能,它是**以chrome浏览器为宿主运行**的一个web程序。 Google Chrome扩展使用HTML、JavaScript、CSS和图片等Web技术开发。 ## 扩展(Extension)与插件(Plugin) Google Chrome扩展与Google Chrome插件不同。 1. Google Chrome扩展无需了解浏览器的源代码,用JS开发。 2. Google Chrome插件是更底层的浏览器功能扩展,需要深入掌握浏览器的源代码,用C/C++开发。 这意味着 Plugin 以Native Code运行,在性能上要优于 Extension,适合执行**计算密集型**工作。不过,以 Native Code 运行,使得 Plugin 在安全上面临更大挑战。 ## 两种类型的 Plugin Chromium最初支持两种类型的 Plugin:NPAPI Plugin 和 PPAPI Plugin。 1. NPAPI 全称是Netscape Plugin Application Programming interface。 2. PPAPI 全称是Pepper Plugin Application Programming Interface。 从表面上看,两者的区别在于使用了不同的API规范。其中,NPAPI来自于Mozilla,而PPAPI来自于Google。但实际上,**PPAPI与另外一种称为Native Client(NaCl)的技术是紧密联系在一起的**。 [NPAPI 为什么会被 Chrome 禁用?受影响的网站有什么普遍性?](https://www.zhihu.com/question/31227185?rf=30953196) # Chrome扩展的基本组成 至少包括一个 `manifest.json` 和一个 `js 文件` * **manifest.json 是扩展的调度中心,用于声明各种资源。该文件采用JSON格式定义** * js 文件中定义要执行的操作 Google Chrome扩展,通常还可以包括图标、页面和CSS等资源 * 图标通常是`19px*19px`的PNG文件 * 页面通常是HTML文件,用于定义显示给用户的窗口,如popup页面或options页面等 ## Manifest 示例: ```js { "manifest_version": 2, "name": "我的时钟", "version": "1.0", "description": "我的第一个Chrome扩展", "icons": { "16": "images/icon16.png", "48": "images/icon48.png", "128": "images/icon128.png" }, "browser_action": { "default_icon": { "19": "images/icon19.png", "38": "images/icon38.png" }, "default_title": "我的时钟", "default_popup": "popup.html" } ``` `manifest.json`文件,就是一个json格式标准的文件。 1. name **必须**,用来标识扩展的简短纯文本。这个文字将出现在安装对话框,扩展管理界面,和store里面 2. version **必须**,定义了扩展的版本,用一个到4个数字来表示,中间用点隔开。 3. manifest_version **必须**,表示manifest文件自身格式的版本号。从Chrome 18开始,必须指定版本号为数字`2` 4. description 定义了插件的详细描述信息 5. app 对象定义了要打开的URL地址 6. iocns 对象定义了几种不同尺寸的图标的地址 7. requirements 对象定义了需要用到资源权限 8. browser_action为设置扩展在浏览器右上角的一些动作,比如鼠标悬停展示的标题default_title、鼠标点击展示的页面default_popup等。 9. 文件中出现了两个icons的key,第一个为在浏览器扩展页面上展示的图标,第二个default_icons为在浏览器右上角展示的图标。 10. 我们需要新建一个images文件夹,并放入一些图标文件。 # 开发&测试你的扩展 还好,有一种方法可以测试您的扩展, 而不必将其发布到 Chrome 的 web 商店。在 chrome 浏览器的地址栏中, 只需键入: ~~~ chrome://extensions ~~~ 勾选 **Developer mode**,然后点击**Load unpacked extension...**按钮。然后选择存放你开发扩展文件的工作目录。 ![](https://img.kancloud.cn/bd/c1/bdc103efd6ba471525c3c8ddd6359407_600x86.png) 记住:每次修改了代码要重新加载扩展。 # Chrome 扩展架构 如下图示,一个Chrome 扩展的架构: ![the architecture](https://img.kancloud.cn/56/71/5671155ac49515bcb2949bf95b825a89_600x223.png) ## Background Pages 每个扩展都有一个不可见的后台页面由浏览器运行,每个扩展都有一个由浏览器运行的不可见的后台页面。 有两种类型——**持久的后台页面**和**事件页面**。 第一种,会一直保持在后台活动的状态。 第二种,只有在需要时才会活动。 Google鼓励开发人员使用事件页面,因为这样可以节省内存并提高浏览器的整体性能。但是,要知道这也是你应该将主逻辑和初始化的地方。 通常,背景页面/背景脚本 在扩展的其他部分之间扮演桥梁的角色。 在`manifest.json`文件中可以如此定义`background`: ~~~ "background": { "scripts": ["background.js"], "persistent": false/true } ~~~ 正如您可能已经猜到的,如果 `persistent` 属性是 `false` ,那么您将使用**事件页面**。否则,您将使用一个持久的后台页面。 顾名思义,可以理解为背景页面脚本,或者直接解释为后台脚本。background 用来处理插件本身的一些逻辑,比如插件加载时需要执行的处理,运行中需要统一维护的数据等等,background 只会在插件加载的时候运行一次,你可以在这个过程中让它绑定一些运行中的事件。 ## Content Script 如果您需要访问当前页面的DOM,那么您就需要用到内容脚本 content script。该脚本代码是在当前web页面的上下文中运行的,这意味着它将在每次页面刷新时执行。要添加这样的脚本,请使用以下语法 ``` "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], "js": ["content.js"] } ] ``` 请记住,`matches`的值决定了您的脚本会对哪个页面有效。请阅读有关[匹配模式](https://developer.chrome.com/extensions/match_patterns.html)的更多信息。 content script 跟页面 page 共用同一份页面的 dom,也就是说 content script 可以直接去访问或修改当前页面的 dom,但是注意了,它们只是共享了 dom 的访问,js 处理本身却是在两个不同的沙盒中运行的,所以并不能互调各自的js代码。 如下处理是在当前页面中插入一个div节点: ``` var element = document.body.firstChild; var div = document.createElement("div"); document.body.insertBefore(div, element); ``` 那么在 content script 中能跟 background 交互吗?当然。首先在 content script 中可以通过`chrome.extension.sendRequest`给 background 发送消息请求,同时可以通过`chrome.extension.onRequest.addListener`来监听从 background 发送来的消息。 content script 除了跟 background 可以交互,跟 web page 本身也可以有信息交互。一方面 content script 可以直接访问 page 的 dom,同时还可以通过 dom 的 Event 来跟页面进行交互。 ## 用户界面 有几种方法可以构建你的扩展的 UI。以下是最受欢迎的四种。 ### Browser Action 大多数开发人员使用`browser_action`属性来构建他们的插件。一旦你设置好了,一个代表你的扩展名的图标就会被放在地址栏的右边。然后,用户可以单击图标并打开一个实际上是由您控制的HTML内容的弹出窗口。 ![](https://img.kancloud.cn/69/2d/692d99383555b2936ed6b686bc95042f_471x145.png) `manifest.json`文件中对`browser action`进行配置。 ``` "browser_action": { "default_icon": { "19": "icons/19x19.png", "38": "icons/38x38.png" }, "default_title": "That's the tool tip", "default_popup": "popup.html" } ``` `default_title`是一个小工具提示,当用户鼠标悬置在您的图标上时显示。`default_popup`实际上是在弹出窗口中加载的名为`popup.html`的HTML文件。还有一个徽章,你可以把它放在你的图标上。您可以在后台脚本中执行该操作。例如 ~~~ chrome.browserAction.setBadgeText({text: "yeah"}); ~~~ 在`browser action`中通过background对象可以直接调用background中定义的方法或对象,如下所示,假设在`background.js`中定义了`testBG`函数,那么在popup.html中可以这样访问: ~~~ var bg = chrome.extension.getBackgroundPage(); bg.testBG(); ~~~ ### Page Action `page_action` 属性类似于browser action,但是它会在地址栏中显示图标,所以一般会跟当前访问的URL地址进行交互。 ![](https://img.kancloud.cn/4b/fc/4bfc1c96b80b282374fc4057eeb47f85_600x79.png) 有趣的是,您的图标最初是隐藏的,因此您可以决定何时显示它。例如,在上面的图片中,RSS 图标只有在当前页面包含了 RSS 提要的链接时才会显示。如果你需要一直看到你的图标,直接使用 `browser_action` 是很好的 使用`page action`功能,需要在`manifest.json`中定义如下属性: ``` "page_action": { "default_icon": { "19": "images/icon19.png", "38": "images/icon38.png" }, "default_title": "Google Mail", "default_popup": "popup.html" } ``` 与 browser action 的图标不同,页面动作的图标没有 badges。 我们可以把对 page aciton 的设置和处理放在 background page 中,从而直接在 background 中通过 `chrome.pageAction` 来设置page action,比如如下代码实现了当所访问URL中有 rss 字符串时就显示page action的 icon 这样的功能: ``` chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (tab.url.indexOf("rss") > -1) { chrome.pageAction.show(tabId); } }); ``` ### DeveloperTools 我经常使用开发者工具,很高兴Chrome提供了一种方法来为这些工具添加新的标签。首先要做的是添加一个HTML页面,当面板打开时,它将被加载。 ~~~ "devtools_page": "devtools.html" ~~~ 不需要在页面内放置任何HTML,除了在JavaScript文件的链接,这将创建标签: ~~~ <script src="devtools.js"></script>; ~~~ 然后在 `devtools.js` 文件中包含以下代码: ```js chrome.devtools.panels.create( "TheNameOfYourExtension", "img/icon16.png", "index.html", function() { } ); ``` 现在,上面的代码会添加一个新的名为`TheNameOfYourExtension` 的标签,一旦你点击它,浏览器就会在DeveloperTools里面加载该 `index.html`。 ### Omnibox `omnibox`是在Chrome的地址栏中显示的关键字。例如,如果您将以下属性添加到您的manifest中: ```js "omnibox": { "keyword" : "yeah" } ``` 然后在 background 脚本中添加以下代码: ```js chrome.omnibox.onInputChanged.addListener(function(text, suggest) { suggest([ {content: text + " one", description: "the first one"}, {content: text + " number two", description: "the second entry"} ]); }); chrome.omnibox.onInputEntered.addListener(function(text) { alert('You just typed "' + text + '"'); }); ``` 你应该能够在地址栏里输入`yeah`。然后你会看到这样: ![](https://img.kancloud.cn/42/41/4241bd72e93d5e1e6df89fb7b8125433_600x63.png) 按下 tab 键会产生以下屏幕: ![](https://img.kancloud.cn/25/0a/250a1976b8c9e16e96975f6331c6f5f2_600x111.png) 当然是使用了 `chrome.omnibox` API,您可以捕获用户的输入并对该输入作出反应。 ## APIs 你可以在扩展中做很多不同的事情。例如,您可以访问用户的书签或历史记录。你可以移动,创建标签,甚至调整主窗口的大小。我强烈建议查阅[文档](https://developer.chrome.com/extensions/api_index.html),以便更好地了解如何完成这些任务。 您应该知道的是,并不是所有的 APIs 都可以在扩展的每个部分中使用。例如,您的内容脚本不能访问`chrome.devtools.panels`或您的开发者工具标签中的脚本不能读取页面的DOM。所以,你肯定想知道为什么有些东西不起作用: ### 消息传送 正如我上面提到的,您并不总是能够访问您想要使用的API。如果是这样,那么您应该使用消息传递。有两种类型的消息传递—— 一次性请求和长时间连接。 #### [兼容性](https://stackoverflow.com/questions/15718066/chrome-runtime-sendmessage-not-working-as-expected) `chrome.runtime.sendMessage / onMessage`(还有`connect`也是相似的)都是在 Chrome 26 开始引入的。 如果你想兼容 Chrome 20 - 25 版本,那么请使用 `chrome.extension.sendMessage`。 最好的兼容 `chrome.runtime` 方法如下示例: ```js if (!chrome.runtime) { // Chrome 20-21 chrome.runtime = chrome.extension; } else if(!chrome.runtime.onMessage) { // Chrome 22-25 chrome.runtime.onMessage = chrome.extension.onMessage; chrome.runtime.sendMessage = chrome.extension.sendMessage; chrome.runtime.onConnect = chrome.extension.onConnect; chrome.runtime.connect = chrome.extension.connect; } ``` #### 一次性请求 这种类型的通信只发生一次。也就是说,你发送一个信息,等待一个回复。例如,您可以在 background 脚本中放置以下代码: ```js chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { switch(request.type) { case "dom-loaded": alert(request.data.myProperty); break; } return true; }); ``` 然后在你的 content 脚本中使用下面的代码: ```js window.addEventListener("load", function() { chrome.runtime.sendMessage({ type: "dom-loaded", data: { myProperty: "value" } }); }, true); ``` 这就是如何获取关于当前页面DOM的信息并在background 脚本中使用它。如果不这样 ,background 脚本通常无法访问这些数据。 #### 长时间连接 如果需要持久的通信通道,请使用这种类型的消息传递。在您的 content 脚本中,放置以下代码: ```js var port = chrome.runtime.connect({name: "my-channel"}); port.postMessage({myProperty: "value"}); port.onMessage.addListener(function(msg) { // do some stuff here }); ``` 然后在background 脚本中,使用这个: ```js chrome.runtime.onConnect.addListener(function(port) { if(port.name == "my-channel"){ port.onMessage.addListener(function(msg) { // do some stuff here }); } }); ``` ### 覆盖页面 覆盖页面是定制浏览器的一种很好的方式。你也可以替代Chrome中的一些默认的页面。例如,您可以创建自己的历史页面。要做到这一点,需要添加以下代码片段: ```js "chrome_url_overrides" : { "<page to override>;": "custom.html" } ``` `<page to override>`的值可以为:`bookmarks`,`history` 和 `newtab`。创建自己的新的标签页是很酷的。 # 一个扩展示例 为了总结这篇文章,我决定创建一个简单的示例,这样您就可以更好地理解整个所示图片。这个示例扩展使用了我上面所描述的大部分内容,只是为当前页面中的所有`div`设置了一个`#F00`的背景颜色。您可以[下载源代码](https://github.com/tutsplus/developing-google-chrome-extensions)。 ## Manifest 文件 我们当然还是先从 manifest 文件开始: ```js { "name": "BrowserExtension", "version": "0.0.1", "manifest_version": 2, "description" : "Description ...", "icons": { "16": "icons/16x16.png", "48": "icons/48x48.png", "128": "icons/128x128.png" }, "omnibox": { "keyword" : "yeah" }, "browser_action": { "default_icon": { "19": "icons/19x19.png", "38": "icons/38x38.png" }, "default_title": "That's the tool tip", "default_popup": "browseraction/popup.html" }, "background": { "scripts": ["background.js"], "persistent": false }, "chrome_url_overrides" : { "newtab": "newtab/newtab.html" }, "content_scripts": [{ "matches": ["http://*/*", "https://*/*"], "js": ["content.js"] }], "devtools_page": "devtools/devtools.html" } ``` 请记住,您可以将您的文件组织到文件夹中。另外,请注意该 `version` 属性。每次你想要把你的扩展上传到网络商店时,你都应该更新这个属性。 ## Background 脚本 ```js // omnibox chrome.omnibox.onInputChanged.addListener(function(text, suggest) { suggest([ {content: "color-divs", description: "Make everything red"} ]); }); chrome.omnibox.onInputEntered.addListener(function(text) { if(text == "color-divs") colorDivs(); }); // listening for an event / one-time requests // coming from the popup chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { switch(request.type) { case "color-divs": colorDivs(); break; } return true; }); // listening for an event / long-lived connections // coming from devtools chrome.runtime.onConnect.addListener(function (port) { port.onMessage.addListener(function (message) { switch(port.name) { case "color-divs-port": colorDivs(); break; } }); }); // send a message to the content script var colorDivs = function() { chrome.tabs.getSelected(null, function(tab){ chrome.tabs.sendMessage(tab.id, {type: "colors-div", color: "#F00"}); // setting a badge chrome.browserAction.setBadgeText({text: "red!"}); }); } ``` 前几行从 omnibox 中获得用户的操作。在此之后,我设置了一个一次性的请求侦听器,它将接受来自 browser action 图标的消息。 接下来一个代码片段是与devtools选项卡进行的长时间连接(这并不是绝对必要的,我只是为了这篇教程才这么做的)。通过使用这些侦听器,我可以从用户那里获得输入,并将其发送到 content 脚本,其脚本可以访问DOM元素。这里的关键是首先选择我想要操作的选项卡,然后向它发送一条消息。最后,我在扩展图标上添加了一个 badge 。 ## Browser Action 从我们的`popup.html`文件开始: ```html // popup.html <script type="text/javascript" src="popup.js"></script> <div style="width:200px"> <button id="button">Color all the divs</button> </div> ``` 然后新建 `popup.js` 文件: ```js // popup.js window.onload = function() { document.getElementById("button").onclick = function() { chrome.runtime.sendMessage({ type: "color-divs" }); } } ``` 弹出框包含一个按钮,一旦用户点击它,它就会向后台脚本发送一条消息。 ## DeveloperTools ```js window.onload = function() { var port = chrome.runtime.connect({ name: "color-divs-port" }); document.getElementById("button").onclick = function() { port.postMessage({ type: "color-divs"}); } } ``` 对于开发者工具,我们在这里做的几乎和在弹出窗口中做的一样,唯一的区别是我使用了一个长时间的连接。 ## Content 脚本 ```js chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { switch(message.type) { case "colors-div": var divs = document.querySelectorAll("div"); if(divs.length === 0) { alert("There are no any divs in the page."); } else { for(var i=0; i&lt;divs.length; i++) { divs[i].style.backgroundColor = message.color; } } break; } }); ``` 这个content 脚本监听消息,选择当前页面上的所有`div`,并更改它们的背景颜色。注意我将监听器连接到的对象,在 content 脚本中是[`chrome.runtime.onMessage`](https://chajian.baidu.com/developer/extensions/runtime.html#method-sendMessage)。 ## 定制新的标签页(`New Tab`) 这个扩展所做的最后一件事是定制`新的`标签页。只需将 `newtab` 属性指向`newtab/newtab.html`文件,我们就可以轻松地做到这一点: ```js "chrome_url_overrides" : { "newtab": "newtab/newtab.html" } ``` 请记住,您不能创建默认的新选项卡页面的副本。这个特性的想法是添加一个完全不同的功能。以下是谷歌的说法: > 不要试图模拟默认的新选项卡页面。创建一个稍微修改过的默认新选项卡页面的api——包括顶部页面、最近关闭的页面、提示、主题背景图像等等——还不存在。在做到这一点之前,你最好试着去做一些完全不同的事情。 # 调试 为谷歌 Chrome 写一个扩展并不总是一件容易的事,你可能会遇到一些问题。好处是,您仍然可以使用控制台输出变量,以帮助调试。你可以随意添加`console.log`到您的 background 或 content 脚本。然而,在开发人员工具的上下文中运行的脚本,这不会起作用,在这种情况下,您可能会考虑使用 `alert` 方法,因为它在任何地方都是有效的。 # 总结 在我看来,Chrome是最好的浏览器之一。Google的开发人员通过让我们有能力在HTML、CSS和JavaScript中创建扩展,使得创建扩展变得相对容易。 是的,虽然有一些棘手的部分,但是总的来说,我们还是能够产生有价值的插件。请记住,本文并没有涵盖与开发Chrome扩展相关的所有内容。还有其他一些有用的东西,比如上下文菜单、选项页面和通知。对于我没有涉及的主题,请参阅[文档](https://developer.chrome.com/extensions/)以获得更详细的信息。 # 参考 [Developing Google Chrome Extensions](https://code.tutsplus.com/tutorials/developing-google-chrome-extensions--net-33076)