chrome插件开发

参考文档:
入门:建立 Chrome 扩展程序 - Google Chrome 扩展程序开发文档(非官方中文版)
GitHub - sxei/chrome-plugin-demo: 《Chrome插件开发全攻略》配套完整Demo,欢迎clone体验

简介

年前因为公司一个项目接触了浏览器插件的开发,其实在这之前大学的时候在加密网盘项目中也接触过浏览器插件的开发,但是那时候只写了一个popup页面。
Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,Chrome浏览器扩展开发算是相当简单的,基本只要掌握HTML+CSS+Javascript,即可快速开发一个属于你的Chrome插件!它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包.

核心部分

  • manifest
    插件的入口。配置了整个插件所需要用到的东西,例如插件的名称和图标,插件的动作,和对应动作的执行的js文件等等。
    代码示例:Manifest File Format - Google Chrome

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    {
    //必选
    /*
    指定您的应用包要求的清单文件格式的版本。从 Chrome 18 开始,开发人员应该指定 2
    */
    "manifest_version": 2,
    "name":"我的应用名称",
    "version":"我的应用版本",

    //推荐
    /*
    清单文件-默认语言 指定_locales中的子目录,包含该应用默认字符串。
    对于含有 _locales 目录的应用来说这一属性是必需的,
    在没有 _locales 目录的应用中该属性不能存在
    */
    "default_locale":"en",

    /*
    这个描述在安装应用之后可以看见
    */
    "description":"关于应用的描述",

    /*一个或多个代表应用、应用或主题背景的图标*/
    "icons":{
    "16":"icon16.png",
    "48":"icon48.png"
    },

    /*
    选择某一个(或者无)
    browser_action(浏览器按钮)
    page_action(页面按钮)
    */

    // 如果有 browser_action, 即在 chrome toolbar 的右边添加了一个 icon
    "browser_action": {
    "default_icon": "advicedog.jpg",
    "default_title": "Google Mail", // tooltip, 光标停留在 icon 上时显示
    "default_popup": "popup.html" // 如果有 popup 的页面, 则用户点击图标就会渲染此 HTML 页面
    },


    // 如果并不是对每个网站页面都需要使用插件, 可以使用 page_action(页面按钮) 而不是 browser_action(浏览器按钮)
    // browser_action 应用更加广泛
    // 如果 page_action 并不应用在当前页面, 会显示灰色

    "page_action":{
    "default_icon": { // 可选
    "19": "images/icon19.png", // 可选
    "38": "images/icon38.png" // 可选
    },
    "default_title": "Google Mail", // 可选,在工具提示中显示
    "default_popup": "popup.html" // 可选
    },

    //可选
    "author":"开发者",
    "automation":"",


    /*
    后台网页
    1.应用通常需要有一个长时间运行的脚本来管理一些任务或状态,而后台网页就是为这一目的而设立。
    通常情况下,后台页面不需要任何 HTML 标记,这种情况下后台页面可以单独使用 JavaScript文件实现。
    后台页面将由应用系统生成,包含 scripts 属性中列出的每一个文件。

    2.page:如果您需要在您的后台页面中指定 HTML,您可以改用 page 属性:

    3.persistent:应用和应用通常需要长时间运行的脚本来管理某些任务或状态,这就是事件页面的作用。
    事件页面只在需要时加载,当事件页面不活动时就会卸载,以便释放内存和其他系统资源。
    如何得到事件页面 就是设置一个"persistent"键,如果没有设置,你将得到一个普通的后台页面。
    */
    "background":{
    "scripts":["background.js"],
    "page": "background.html",
    "persistent":false
    },


    /*
    内容脚本:其实就是向你想要的网页中插入一个脚本代码,执行你想要做的事情
    内容脚本是在网页的上下文中运行的 JavaScript 文件,
    它们可以通过标准的文档对象模型(DOM)来获取浏览器访问的网页详情,或者作出更改。

    1.run_at 可选。
    控制 js 中的 JavaScript 文件何时插入,
    可以为 "document_start"、
    "document_end" 或 "document_idle",默认为 "document_idle"。


    1.1如果是 "document_start",这些文件将在 css 中指定的文件之后,但是在所有其他 DOM 构造或脚本运行之前插入。

    1.2.如果是 "document_end",文件将在 DOM 完成之后立即插入,但是在加载子资源(如图像与框架)之前插入。

    1.3.如果是 "document_idle",浏览器将在 "document_end" 和刚发生 window.onload 事件这两个时刻之间选择合适的时候插入,
    具体的插入时间取决于文档的复杂程度以及加载文档所花的时间,并且浏览器会尽可能地为加快页面加载速度而优化。

    2.all_frames 可选。
    控制内容脚本运行在匹配页面的所有框架中还是仅在顶层框架中。 默认为 false,意味着仅在顶层框架中运行

    content_scripts还有一些其他不是很常用的属性
    */

    "content_scripts": [{
    "matches": ["https://*.pingan.com.cn/*"], //匹配的地址网页
    "exclude_matches":[],
    "js": ["jquery.js","ideacome.js"], //插入的js
    "css": ["mystyles.css"], //css改变样式
    "run_at":"document_idle",
    "all_frames": true //该匹配下面的所有窗口
    },{
    "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
    "js": ["js/show-image-content-size.js"] //可以针对不同的规则插入不同的内容
    }],

    // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
    "web_accessible_resources": [
    "images/*.png",
    "style/double-rainbow.css",
    "script/double-rainbow.js",
    "script/main.js",
    "templates/*"
    ],

    /**
    如果不是通过 chrome web store 自动更新插件

    我们希望扩展能自动升级,理由和让chrome自动升级一样:修改程序bug和安全漏洞 ,增加新功能,提升性能,改善体验。
    一个扩展的manifest文件里面必须指定一个"update_url"来执行升级检测。

    扩展可以托管在Chrome Web Store,也可以发布到极速浏览器应用开放平台上。
    如果托管在Chrome Web Store则update_url应该是:http://clients2.google.com/service/update2/crx
    **/
    "update_url": "https://clients2.google.com/service/update2/crx",

    // 插件主页,这个很重要,不要浪费了这个免费广告位
    "homepage_url": "https://www.baidu.com",

    /*
    扩展或app将使用的一组权限。每个权限是一列已知字符串列表中的一个,
    如geolocatioin或者一个匹配模式,来指定可以访问的一个或者多个主机。
    权限可以帮助限定危险,如果你的扩展或者app被攻击。
    一些权限在安装之前,会告知用户
    */
    "permissions":[
    "tabs", //Required if the extension uses the chrome.tabs or chrome.windows module.
    "bookmarks", //使用chrome.bookmarks模块来创建、组织和管理书签
    "http://www.blogger.com/",
    "http://*.google.com/",
    "unlimitedStorage", //提供了一个无限的HTML5配额来存储客户端数据,如数据库和本地存储文件。没有这个权限,扩展仅限于5 MB的本地存储
    "history" //历史记录的使用权限 chrome.history
    "notifications",//提示
    "cookies",//Required if the extension uses the chrome.cookies module.
    ],

    /**开发时为扩展指定的唯一标识值。
    注意:通常您并不需要直接使用这个值,而是在您的代码中使用相对路径或者chrome.extension.getURL()得到的绝对路径。
    这个值并不是开发时显式指定的,而是Chrome在安装.crx时辅助生成的。(开发时可以通过上传扩展或者手工打包生成crx文件)。 安装完crx,在Chrome的用户数据目录下的Default/Extensions/<extensionId>/<versionString>/manifest.json文件中,您可以看到这个扩展的key。**/

    key:'',

    "commands": {
    // commands API 用来添加快捷键
    // 需要在 background page 上添加监听器绑定 handler
    "toggle-feature-foo": {
    "suggested_key": {
    "default": "Ctrl+Shift+Y",
    "mac": "Command+Shift+Y"
    },
    "description": "Toggle feature foo",
    "global": true
    // 当 chrome 没有 focus 时也可以生效的快捷键
    // 仅限 Ctrl+Shift+[0..9]
    },
    "_execute_browser_action": {
    "suggested_key": {
    "windows": "Ctrl+Shift+Y",
    "mac": "Command+Shift+Y",
    "chromeos": "Ctrl+Shift+U",
    "linux": "Ctrl+Shift+J"
    }
    },
    "_execute_page_action": {
    "suggested_key": {
    "default": "Ctrl+Shift+E",
    "windows": "Alt+Shift+P",
    "mac": "Alt+Shift+P"
    }
    },
    ...
    },
    "content_capabilities": ...,
    "optional_permissions": ["tabs"], // 其他需要的 permission, 在使用 chrome.permissions API 时用到, 并非安装插件时需要

    "short_name": "Short Name", // 插件名字简写

    "storage": {
    "managed_schema": "schema.json"
    }, // 使用 storage.managed api 的话, 需要一个 schema 文件指定存储字段类型等, 类似定义数据库表的 column

    //还有很多其他的配置
    }
  • background
    字面意思,后台,可以包含一个html后台页面(可以不用)和后台执行的js脚本,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。
    background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。
    1.配置文件
    可以通过page指定一张网页,也可以通过scripts直接指定一个JS,Chrome会自动为这个JS生成一个默认的网页

    1
    2
    3
    4
    5
    6
    7
    8
    9
      {
    // 会一直常驻的后台JS或后台页面
    "background":
    {
    // 2种指定方式,如果指定JS,那么会自动生成一个背景页
    "page": "background.html"
    //"scripts": ["js/background.js"]
    },
    }

    2.运行环境(调试)
    f110b974.png

    1f025365.png

  • event-pages
    前面提到background的生命周期很长,但是长时间的挂载后台会消耗性能,Google又弄一个event-pages,在配置文件上,它与background的唯一区别就是多了一个persistent参数:

    1
    2
    3
    4
    5
    6
    7
    {
    "background":
    {
    "scripts": ["event-page.js"],
    "persistent": false
    },
    }

    它的生命周期是:在被需要时加载,在空闲时被关闭,什么叫被需要时呢?比如第一次安装、插件更新、有content-script向它发送消息,等等。

  • popup
    点击browser_action或者page_action图标时打开的一个小窗口网页,实际上是一个html页面(safari13后不再是html页面),同样可以执行js脚本,但是弹窗关闭,脚本生命周期结束。
    popup的页面可以在配置文件中通过default_popup指定,也可以在js中通过setPopup()方法指定,可以用该方法执行页面的跳转,但这种方式的页面跳转会有延迟,需要页面重新加载才会跳转,另一种跳转方法是“window.location.href=”popup.html””
    在权限上,它和background非常类似,它们之间最大的不同是生命周期的不同。值得注意的是:popup中可以直接通过chrome.extension.getBackgroundPage()获取background的window对象,实现在popup中可以直接调用background中的方法。

    1.运行环境(调试)
    a9ef4bb9.png

    ecd7615a.png

  • content-scripts
    页面内容脚本,就是通过插件向一个页面注入脚本,使得页面在加载前或者加载后等阶段执行脚本命令(虽然名为script,其实还可以包括css的)。最常见的操作:广告屏蔽、页面CSS定制,等等。
    content-scripts和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过injected js来实现。content-scripts不能访问绝大部分chrome.xxx.api,除了下面这4种:

    1. chrome.extension(getURL, inIncognitoContext, lastError,onRequest,sendRequest)
    2. chrome.i18n
    3. chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
    4. chrome.storage

    运行(调试)
    浏览器F12开发者工具中进行调试即可。

  • injected-script
    前面有提到,content-srcript不共享JS,如要访问页面JS(例如某个JS变量),只能通过injected js来实现。这是因为content-script有一个很大的“缺陷”,也就是无法访问页面中的JS,虽然它可以操作DOM,但是DOM却不能调用它,也就是无法在DOM中通过绑定事件的方式调用content-script中的代码(包括直接写onclick和addEventListener2种方式都不行),但是,“在页面上添加一个按钮并调用插件的扩展API”是一个很常见的需求,那该怎么办呢?
    通过content-script向页面注入inject-js示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      function injectCustomJs(jsPath)
    {
    jsPath = jsPath || 'js/inject.js';
    var temp = document.createElement('script');
    temp.setAttribute('type', 'text/javascript');
    // 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
    temp.src = chrome.extension.getURL(jsPath);
    temp.onload = function()
    {
    // 放在页面不好看,执行完后移除掉
    this.parentNode.removeChild(this);
    };
    document.body.appendChild(temp);

    }

    manifest.json

    1
    2
    3
    4
    {
    // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
    "web_accessible_resources": ["js/inject.js"],
    }

以上是开发一个插件的几个核心部分,除此之外,还有一些通信交互、api使用、权限等问题:

消息通信

injected-script content-script popup-js background-js
injected-script - window.postMessage - -
content-script window.postMessage - chrome.runtime.sendMessage chrome.runtime.connect chrome.runtime.sendMessage chrome.runtime.connect
popup-js - chrome.tabs.sendMessage chrome.tabs.connect - chrome.extension. getBackgroundPage()
background-js - chrome.tabs.sendMessage chrome.tabs.connect chrome.extension.getViews -
devtools-js chrome.devtools. inspectedWindow.eval - chrome.runtime.sendMessage chrome.runtime.sendMessage
  • popup与background通信
    1.上文说过,popup可以直接调用background中的JS方法,也可以直接访问background的DOM:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     // background.js
    function test()
    {
    alert('我是background!');
    }

    // popup.js
    var bg = chrome.extension.getBackgroundPage();
    bg.test(); // 访问bg的函数
    alert(bg.document.body.innerHTML); // 访问bg的DOM

    2.background访问popup如下(前提是popup已经打开):

    1
    2
    3
    4
    var views = chrome.extension.getViews({type:'popup'});
    if(views.length > 0) {
    console.log(views[0].location.href);
    }
  • popup或者bg与content通信

    1. background.js或者popup.js向content-script

    2. background.js或者popup.js发送

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function sendMessageToContentScript(message, callback)
      {
      chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
      {
      chrome.tabs.sendMessage(tabs[0].id, message, function(response)
      {
      if(callback) callback(response);
      });
      });
      }
      sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
      {
      console.log('来自content的回复:'+response);
      });
    3. content-script.js接收

      1
      2
      3
      4
      5
      6
      chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
      {
      // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
      if(request.cmd == 'test') alert(request.value);
      sendResponse('我收到了你的消息!');
      });

      网上一些用法是chrome.extension.onMessage,貌似没有区别。

    4. content-script发消息给后台

    5. content-script.js发送

      1
      2
      3
      chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主动发消息给后台!'}, function(response) {
      console.log('收到来自后台的回复:' + response);
      });
    6. background.js 或者 popup.js接收

      1
      2
      3
      4
      5
      6
      7
      // 监听来自content-script的消息
      chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
      {
      console.log('收到来自content-script的消息:');
      console.log(request, sender, sendResponse);
      sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request));
      });

    注:
    1).content_scripts向popup主动发消息的前提是popup必须打开!否则需要利用background作中转;
    2).如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效;

  • injected-script与content-script通信
    content-script与injected-script之间唯一共享的是页面的DOM。有2种方法可以实现二者通讯:

    1. 通过window.postMessage和window.addEventListener来实现二者消息通讯:
    • injected-script:

      1
      window.postMessage({"test": '你好!'}, '*');
    • content script:

      1
      2
      3
      4
      window.addEventListener("message", function(e)
      {
      console.log(e.data);
      }, false);
    1. 通过自定义DOM事件来实现:
    • injected-script:

      1
      2
      3
      4
      5
      6
      7
      8
      var customEvent = document.createEvent('Event');
      customEvent.initEvent('myCustomEvent', true, true);
      function fireCustomEvent(data) {
      hiddenDiv =document.getElementById('myCustomEventDiv');
      hiddenDiv.innerText = data
      hiddenDiv.dispatchEvent(customEvent);
      }
      fireCustomEvent('你好,我是普通JS!');
    • content script:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var hiddenDiv =
      document.getElementById('myCustomEventDiv');
      if(!hiddenDiv) {
      hiddenDiv = document.createElement('div');
      hiddenDiv.style.display = 'none';
      document.body.appendChild(hiddenDiv);
      }
      hiddenDiv.addEventListener('myCustomEvent', function() {
      var eventData =
      document.getElementById('myCustomEventDiv').innerText;
      console.log('收到自定义事件消息:' + eventData);
      });

      长连接和短连接

      chrome插件中有2种通信方式,一个是短连接(chrome.tabs.sendMessage和chrome.runtime.sendMessage),一个是长连接(chrome.tabs.connect和chrome.runtime.connect),短链接上面已经给出示例,这里对长链接给出示例:

  • popup.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      getCurrentTabId((tabId) => {
    var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
    port.postMessage({question: '你是谁啊?'});
    port.onMessage.addListener(function(msg) {
    alert('收到消息:'+msg.answer);
    if(msg.answer && msg.answer.startsWith('我是'))
    {
    port.postMessage({question: '哦,原来是你啊!'});
    }
    });
    });
  • content-script.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      // 监听长连接
    chrome.runtime.onConnect.addListener(function(port) {
    console.log(port);
    if(port.name == 'test-connect') {
    port.onMessage.addListener(function(msg) {
    console.log('收到长连接消息:', msg);
    if(msg.question == '你是谁啊?') port.postMessage({answer: '我是你爸!'});
    });
    }
    });

存储

本地存储分为两种,chrome.storage和html5的localStorage

  • localStorage
    localStorage的两种形式:sessionStorage 和 localStorage 分别可以看成临时和永久存储:后者永久留在硬盘上,除非你在程序里remove掉,或者手动去删掉;而前者,对在chrome中而言,本地存的数据的生存时间就等于相应标签的生存时间。
    localStorage 有 setItem, getItem, removeItem, key, clear 这5个方法:

    1
    2
    3
    4
    5
    localStorage.setItem(“fresh”,“vfresh.org”); //设置一个键值
    localStorage.getItem(“fresh”); //获取一个键值
    localStorage.key(0); //获取指定下标的键的名称(如同Array)
    localStorage.removeItem(“fresh”); //删除一个键值
    localStorage.clear(); //清空storage

    注意:
    虽然所有脚本都可以访问 localStorage 接口,但访问时仍然是基于当前的域的。也就是说 content script 们和 background script 是无法通过 localStorage 访问到同一块本地数据的!(前者域是当前浏览页面站点,后者是扩展程序自己的域“chrome://extension-id/”

  • chrome.storage
    官方文档chrome.storage - Google Chrome
    API 为扩展程序的存储需要而特别优化,它提供了与 localStorage API 相同的能力,但是具有如下几个重要的区别:

    1. 用户数据可以通过 Chrome 浏览器的同步功能自动同步(使用 storage.sync)。
    2. 您的扩展程序的内容脚本可以直接访问用户数据,而不需要后台页面。
    3. 即使使用分离式隐身行为,用户的扩展程序设置也会保留。
    4. 异步的,并且能够进行大量的读写操作,因此比阻塞和串行化的 localStorage API 更快。
    5. 用户数据可以存储为对象(localStorage API 以字符串方式存储数据)。
    6. 可以读取管理员为扩展程序配置的企业策略(使用 storage.managed 和架构)。

    但无论哪种方式,随着插件卸载,通过插件存储的数据都会被删除。

    webRequest

    官方文档chrome.webRequest - Google Chrome
    在看到插件的这一块接口时,不禁感叹插件的强大,在request的前中后均可以对其进行操作。使用该功能需要在manifest中配置权限"webRequest", // web请求"webRequestBlocking", // 阻塞式web请求

  • manifest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //manifest.json
    {
    // 权限申请
    "permissions":
    [
    "webRequest", // web请求
    "webRequestBlocking", // 阻塞式web请求
    "storage", // 插件本地存储
    "http://*/*", // 可以通过executeScript或者insertCSS访问的网站
    "https://*/*" // 可以通过executeScript或者insertCSS访问的网站
    ],
    }
  • background.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var showImage;
    chrome.storage.sync.get({showImage: true}, function(items) {
    showImage = items.showImage;
    });
    // web请求监听,最后一个参数表示阻塞式,需单独声明权限:webRequestBlocking
    chrome.webRequest.onBeforeRequest.addListener(details => {
    // cancel 表示取消本次请求
    if(!showImage && details.type == 'image') return {cancel: true};
    // 简单的音视频检测
    // 大部分网站视频的type并不是media,且视频做了防下载处理,所以这里仅仅是为了演示效果,无实际意义
    if(details.type == 'media') {
    chrome.notifications.create(null, {
    type: 'basic',
    iconUrl: 'img/icon.png',
    title: '检测到音视频',
    message: '音视频地址:' + details.url,
    });
    }
    }, {urls: ["<all_urls>"]}, ["blocking"]);
    //blocking的意思是阻塞request,直到回调函数返回结果,从而能改变request是取消,还是重定向还是其他。

权限

官方文档chrome.permissions - Google Chrome
权限在前面manifest里面已经体现了,插件和Android类似,需要使用哪些权限,需要在配置文件里申请。我们可以看到插件的权限是非常大的,可以读取你的页面输入的数据(明文数据),可以读取你存储的数据,可以任意写入,可以重定向request……

1
2
3
4
5
6
7
8
9
10
11
"permissions":
[
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 插件本地存储
"http://*/*", // 可以通过executeScript或者insertCSS访问的网站
"https://*/*" // 可以通过executeScript或者insertCSS访问的网站
],

我们在安装外部插件的时候,要先看看他所获取的哪些权限,是不是有些是这个插件本不需要的。

mac下插件安装路径

/Users/xxx/Library/Application Support/Google/Chrome/Default/Extensions

chrome插件转Firefox插件以及safari插件

这也是在项目中遇到的需求。
浏览器扩展 - Mozilla 产品与私有技术 | MDN这是firefox的官方插件开发文档,点开一个简单示例,我们会发现和chrome的插件开发模式是基本一致。细微差别可以通过终端提示信息进行修改。
对于Safari,差别相对较大。safari12及以前的版本,插件的模式和chrome类似,是一个独立插件安装包,安装到浏览器即可,但是api和chrome不兼容,官方开发文档:About Safari Extensions
但是Safari12之后的版本,插件作了改变,不再是一个可以直接用js和html实现的独立安装包,而是一个本地应用,应用包含了插件,也就是安装一个包含插件的本地应用,插件就默认安装了。因此需要使用objective-c等语言进行开发,而且版本12中的popover(也就是chrome重的popup)窗口,由原来的html页面变成了一个webview。相对没有ios开发基础的人来说,难度变大了。官方文档Building a Safari App Extension | Apple Developer Documentation,但是感觉并不能对开发有啥帮助。

小结

chrome插件除了以上功能外,还有很多其他的,比如通知、devtools扩展、右键菜单等等,除此,还有插件的打包、安装、以及发布等,这些网上可查找内容较多,不再赘述。项目结束有几个月了,才写下文档,开发过程中遇到一些问题,解决了之后,记忆已经有些模糊了。这篇文章大多参照GitHub - sxei/chrome-plugin-demo: 《Chrome插件开发全攻略》配套完整Demo,欢迎clone体验,结合了自己的使用体验。下一篇将会介绍浏览器插件的安全。

chrome插件安全

待续……