Chrome Extensionss && Chrome Debug

杜俊逸
2023-12-01

简介

  • Chrome插件的本质就是一个由 manifest.json 文件和插件所需要的图片,css,html,js资源组成的一个web页面,只是和传统的web页面不同的,它是以chrome浏览器为宿主运行的一个web程序。
  • Chrome插件可以与Web页面交互,也可以通过content script或cross-origin XMLHttpRequests与服务器交互。还可以访问浏览器提供的内部功能,例如标签或书签等。同时也可以以browser action或page action的形式在浏览器界面上展现出来。
  • 工具栏所显示的电脑管家的插件就是采用了browser action,而在地址栏最后的那个T型图标(广告终结者)则是采用了page action和content script注入到页面中的。每个插件最多可以有一个browser action或page action。当插件的图标是否显示出来是取决于单个的页面时,应当选择page action;当其它情况时可以选择browser action。
  • 上面第一副图是原图,第二幅图则是采用了content script来改变了页面的内容。content script可以很轻松地给页面注入脚本。这样就可以实现个性化的操作了。
  • 下面是一个简单的manifest.json(manifest.json文件格式需为utf-8):
{
    "name": "我的第一个Chrome插件",
    "version": "1.0.0",
    "manifest_version": 2,
    "icons": {
        "48": "logo_avatar.png"
    }
}

小例子 来自官网

  • manifest.json
{
    "name": "我的第一个Chrome插件",
    "version": "1.0.1",
    "manifest_version": 2,
    "description": "我的第一个Chrome插件",
    "permissions": [
        "https://secure.flickr.com/"
    ],
    "icons": {
        "48": "logo_avatar.png"
    },
    "browser_action": {
      "default_icon": "icon.png",
      "default_popup": "popup.html"
    }
}
  • popup.html
<!doctype html>
<html>
<head>
<title>Getting Started Extension's Popup</title>
<style>
body {
  min-width: 357px;
  overflow-x: hidden;
}
img {
  margin: 5px;
  border: 2px solid black;
  vertical-align: middle;
  width: 75px;
  height: 75px;
}
</style>
<script src="popup.js"></script>
</head>
<body>
</body>
</html>
  • popup.js
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
 * Global variable containing the query we'd like to pass to Flickr. In this
 * case, kittens!
 *
 * @type {string}
 */
var QUERY = 'kittens';
var kittenGenerator = {
  /**
   * Flickr URL that will give us lots and lots of whatever we're looking for.
   *
   * See http://www.flickr.com/services/api/flickr.photos.search.html for
   * details about the construction of this URL.
   *
   * @type {string}
   * @private
   */
  searchOnFlickr_: 'https://secure.flickr.com/services/rest/?' +
    'method=flickr.photos.search&' +
    'api_key=90485e931f687a9b9c2a66bf58a3861a&' +
    'text=' + encodeURIComponent(QUERY) + '&' +
    'safe_search=1&' +
    'content_type=1&' +
    'sort=interestingness-desc&' +
    'per_page=20',
  /**
   * Sends an XHR GET request to grab photos of lots and lots of kittens. The
   * XHR's 'onload' event is hooks up to the 'showPhotos_' method.
   *
   * @public
   */
  requestKittens: function() {
    var req = new XMLHttpRequest();
    req.open("GET", this.searchOnFlickr_, true);
    req.onload = this.showPhotos_.bind(this);
    req.send(null);
  },
  /**
   * Handle the 'onload' event of our kitten XHR request, generated in
   * 'requestKittens', by generating 'img' elements, and stuffing them into
   * the document for display.
   *
   * @param {ProgressEvent} e The XHR ProgressEvent.
   * @private
   */
  showPhotos_: function (e) {
    var kittens = e.target.responseXML.querySelectorAll('photo');
    for (var i = 0; i < kittens.length; i++) {
      var img = document.createElement('img');
      img.src = this.constructKittenURL_(kittens[i]);
      img.setAttribute('alt', kittens[i].getAttribute('title'));
      document.body.appendChild(img);
    }
  },
  /**
   * Given a photo, construct a URL using the method outlined at
   * http://www.flickr.com/services/api/misc.urlKittenl
   *
   * @param {DOMElement} A kitten.
   * @return {string} The kitten's URL.
   * @private
   */
  constructKittenURL_: function (photo) {
    return "http://farm" + photo.getAttribute("farm") +
      ".static.flickr.com/" + photo.getAttribute("server") +
      "/" + photo.getAttribute("id") +
      "_" + photo.getAttribute("secret") +
      "_s.jpg";
  }
};
// Run our kitten generation script as soon as the document's DOM is ready.
document.addEventListener('DOMContentLoaded', function () {
  kittenGenerator.requestKittens();
});

讲解

  • manifest.json - 所有插件都要有这个文件,这是插件的配置文件,可看作插件的“入口”。
  • icon.png - 小图标,推荐使用19x19的半透明png图片,更好的做法是同时提供一张38x38的半透明的png图片作为大图标,在我后面提供的例子中,我就是那么干的。
  • popup.html - 就是你所看到的那个阿猫阿狗的弹出页面。
  • popup.js - 阿猫阿狗页面所引用的javascript文件。
  • 这里千万千万注意了,我当初没仔细看popup.html里有一小段注释,这一小段注释说:出于安全考虑,javascript必须与html分开存放。而我想嘛,一个小测试程序,没必要分开吧,直接写一起不就行了吗?结果javascript死活执行不了,我翻来覆去找不到原因,还以为弹出的小窗口不支持javascript,在网上搜索了半天又没有结果,最后才发现是这个原因,浪费了许多时间,这个事情也一定程度上说明了:细节决定成败。

Page Action

  • “permissions”:很重要的东西,即允许插件做哪些事情,访问哪些站点,假如一个插件的”permissions”里写有“http://*.hacker.com/”,那么这个插件就被允许访hacker.com上的所有内容,包括可能会把你的一些个人信息提交给hacker.com,危险性不言而喻,查看一个插件能访问那些站点的方法是:在chrome的地址栏里输入“chrome://extensions/”(注意:这个页面我们之后要频繁用到,请收藏一下),然后点对应插件的旁边的那个“权限”
  • “default_popup”:用来指定点击小图标后弹出的小窗口中默认显示的是哪个html,这个弹出的小窗口就叫做“popup”。
  • “browser_action”:这是一个浏览器级的动作,也就是说,不管你现在在访问哪个页面,那个小按钮总是显示出来,而我们的插件如果仅仅是针对某些页面的话,就不适合用这个”browser_action”了。下面我们来弄一个只有访问博客园(www.cnblogs.com)才会出现的小按钮。
  • 这个插件只有4个文件,其中两个还是图标,那就只剩下一个必须的manifest.json和一个background.js了。
  • mainifest.json:
{
     "manifest_version": 2,
     "name": "cnblogs.com viewer",
     "version": "0.0.1",
     "background": { "scripts": ["background.js"] },
     "permissions": ["tabs"],
     "page_action": {
          "default_icon": {
               "19": "cnblogs_19.png",
               "38": "cnblogs_38.png"
          },
          "default_title": "cnblogs.com article information"
     }
}
  • 注意:这里是“page_action”而不是“browser_action”属性了。
  • “permissions”属性里的“tabs”是必须的,否则下面的js不能获取到tab里的url,而这个url是我们判断是否要把小图标show出来的依据。background是什么概念?这是一个很重要的东西,可以把它认为是chrome插件的主程序,理解这个很关键,一旦插件被启用(有些插件对所有页面都启用,有些则只对某些页面启用),chrome就给插件开辟了一个独立的javascript运行环境(又称作运行上下文),用来跑你指定的background script,在这个例子中,也就是background.js。
  • background.js
function getDomainFromUrl(url){
     var host = "null";
     if(typeof url == "undefined" || null == url)
          url = window.location.href;
     var regex = /.*\:\/\/([^\/]*).*/;
     var match = url.match(regex);
     if(typeof match != "undefined" && null != match)
          host = match[1];
     return host;
}
function checkForValidUrl(tabId, changeInfo, tab) {
     if(getDomainFromUrl(tab.url).toLowerCase()=="www.cnblogs.com"){
          chrome.pageAction.show(tabId);
     }
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);
  • 代码中,我们使用了一个正则表达式去匹配url,获取出其中的domain部分,如果domain部分是“www.cnblogs.com”的话,就把小图标show出来,效果如下:
  • 会在地址栏中右边出现一个标志,当然了,你现在点那个小图标的话,是没有任何反应的,我没有像官方提供的那个例子那样提供了popup。OK,现在是时候描述下chrome插件的结构了

Chrome插件结构

  • manifest.json作为插件的配置文件,同时可以看作程序的“入口”,因为它指定了显示什么图标,background script有哪些文件,content script又有哪些文件,pop up的页面是什么,等等。
  • 什么是popup,什么是background script,相信大家都清楚了,那什么是content script呢?content script就是我们要注入到页面中的脚本,插件允许我们往网页中注入脚本,这是一个多么让人有想象力的功能,其功能之强大无需多解释,总的来说,就是让我们全面干预页面的内容!也许你马上会想到,这可能带来很大的安全隐患,没错,有些恶意插件会窃取你的页面信息,而有些有漏洞的插件则可能让你遭受跨站脚本注入(XSS)的攻击;另一个可能你会想到的问题是:往页面中注入自己的脚本,难道不会跟页面原本的脚本发生冲突吗?能想到这点说明你真的很厉害,如果我们的注入脚本和页面原本的脚本处于同一个运行环境中,确实会发生冲突,所以,Chrome是另外开辟了一个独立的运行空间,供我们的Content Script使用的,Content Script能访问DOM的内容,但却不能访问页面原本的脚本(我是说直接访问不行),反之,页面原本的脚本也不能直接访问Content Script。在图中,浅红色的背景块代表Content Script的运行环境,而浅蓝色的背景块代表页面运行环境,另外插件的运行环境我用浅绿色表示,注意,这是三个不同的运行环境,调试的时候你会充分体会到它们的不同。
  • 那么,Content Script会在什么时候运行呢?默认情况下,是在网页加载完了和页面脚本执行完了,页面转入空闲的情况下(Document Idle),但这个是可以改变的,详情可参考https://developer.chrome.com/extensions/content_scripts.html,查看其中的“run_at”。
  • 由于处于不同的运行环境中,Content Script和Background Script不能直接互相访问,那它们之间如何通信?通过Message!这个之后的代码中会有。

Chrome 调试

  • 其实Chrome直接支持javascript的调试,拥有了Chrome,就相当于拥有了一个强大的javascript调试器了
  • F12 代开源代码
  • 我们这次需要关心的有“Elements”、“Sources”和“Console”这三个标签。Elements是用来做DOM分析的,功能有点类似Firebug,帮助我们分析页面的内容;而Sources,是我们用来调试javascript的;Console则是我们的Log的输出窗口,也是一个调试利器。
  • 下面的调试过程,见第三个链接

调试Content Script

  • 如我提供的这个例子,可在Sources的“Copntent Scripts”下看到“content_script.js”然后设断点,执行到断点处时,Chrome会停住,鼠标放上去,你可以观察到上面的值

调试Background

  • 由于background和content script并不在同一个运行环境中,因此上面的方法是看不到Background的javascript的。要调试Background,还需要打开插件页,也就是“chrome://extensions”。点对应的插件的“generated background page.html”,就出现了调试窗口,接下来的操作就跟前面的类似了
  • 至于你看到ID,“aajnhhjiia……”这一长串东西,这是chrome自动安排的一个ID。

调试Popup

  • 虽然Popup和Background是处于同一运行环境中,但在刚才的Background的调试窗口中是看不到Popup的代码的

一些问题

  • 也许有时候你会发觉调试器不是很灵,至少我用下来感觉如此,比如你可能发现断点设不了,或者断点不起作用,或者看不到你自己的javascript文件。我的方法是在插件页中,把对应的插件的“已启用”这个复选框去掉,再重新勾上,然后再点一下“重新加载(Ctrl+R)”,通常能解决问题。当然了,还有些很古怪的问题,还不好重现,总体的解决思路就是重新载入一下,实在不行的话重启浏览器,或者清除浏览器缓存什么的,再试试看。

自己写的一个Chrome Extensions

  • 要把 script 和 html 分开放
<html>
<head>
<script type="text/javascript">
    document.getElementById("text1").focus();
    function open_win()
    {
        var add = document.getElementById("address").value
        if (add == "jcsdn") {window.open("http://blog.csdn.net/u014015972")}
        else if (add == "dcsdn") {window.open("http://blog.csdn.net/banana_baba")}
        else if (add == "jcsdnjava") {window.open("http://blog.csdn.net/u014015972/article/category/3193881")}
        else if (add == "jcsdncpp") {window.open("http://blog.csdn.net/u014015972/article/category/3193883")}
        else if (add == "jcsdnpy") {window.open("http://blog.csdn.net/u014015972/article/category/3193879")}
        else if (add == "jgithub") {window.open("https://github.com/coder352")}
        else if (add == "jgitosc") {window.open("http://git.oschina.net/jiaruipeng")}
        else if (add == "bdy") {window.open("http://pan.baidu.com/disk/home")}
        else {document.write("No Record")}
    }
</script>
</head>
<body>
<form id='form1' defaultfocus="text1">
<input type='text' id='address' onkeydown='if(event.keyCode==13){open_win();}' />
</form>
<table frame='hsides'>
<tr><td>jcsdn: </td><td>jrp csdn</td></tr>
<tr><td>dcsdn: </td><td>dr csdn</td></tr>
<tr><td>jcsdnjava: </td><td></td></tr>
<tr><td>jcsdncpp: </td><td></td></tr>
<tr><td>jcsdnpy: </td><td></td></tr>
<tr><td>jgithub: </td><td></td></tr>
<tr><td>jgitosc: </td><td></td></tr>
<tr><td>bdy: </td><td></td></tr>
</table>
</body>
</html>

比较坑的是 Chrome Extension don’t allow you to have inline JavaScript (documentation).

  • You are going to have to do something similar to this.
  • 就是不让用 onclick、onkeydown 这些函数,只能加监听函数了
Assign an ID to the link (<a onClick=hellYeah("xxx")> becomes <a id="link">), and use addEventListener to bind the event. Put the following in your popup.js file:
document.addEventListener('DOMContentLoaded', function() {
    var link = document.getElementById('link');
    // onClick's logic below:
    link.addEventListener('click', function() {
        hellYeah('xxx');
    });
});

如何优雅的调试

  • chrome://extensions 转到扩展的界面,找到你的扩展的ID,例如下面
  • chrome-extension://giajmoheipkbmimhmakeklbidkepennb/popup.html 进行调试

如何查看其他插件的源码

  • chrome://version 找到Chrome插件安装的本机目录 Profile Path: /home/coder352/.config/google-chrome/Default
  • 然后找到extension目录,所有插件和数据都在这里,可以随便参考其他插件源码

不要把JS代码写在html文件里

  • 出于安全考虑,入口html文件中的JS代码只能通过script标签引用外部脚本文件,内嵌的JS代码会失效的。

注意国际化

  • webstore面向的是全球用户,你辛辛苦苦写的小工具肯定不想只限于国内用户吧,所以在你的项目里面加上_locales文件夹,写代码的时候时刻考虑到如何才能更好地支持国际化。
 类似资料:

相关阅读

相关文章

相关问答