乐趣区

关于chrome:chrome-extension开发实战之QQ空间助手

原文地址: https://segmentfault.com/a/11…
转载请在文首注明起源

TL;DR

  • 背景介绍
  • 需要剖析
  • 常识储备
  • 文档举荐
  • 构造组成
  • 组件通信
  • 开发调试
  • 打包公布
  • 问题解决

背景介绍

本文历时较久,2020 年写了一半, 写到迷茫, 索性搁置,2021 年翻出来持续写的; 通过了半年多的工夫再看, 会有一些不一样的了解, 存在局部配图格调不统一. 然而我会确保内容的正确和表白的统一.

2020 年的某一天我把之前写的 QQ 空间批量删除说说 & 留言的脚本写了个 chrome 插件版, 名字也改成了 QQ 空间小助手.
因为也是第一次写插件, 所以记录这个过程, 分享开发过程和期间遇到的问题, 并做一些关键点的梳理.
所以,这并不是一篇大而全的 chrome extension 开发教程, 这是一篇以 QQ 空间助手为实战来切入插件开发然而偏重讲 chrome extension 开发的文章.

需要剖析

QQ 空间小助手性能很简略, 外围性能 (其实也就这点性能) 是批量删除 qq 空间的说说和留言.
实现上也很简略, 只须要两个步骤.

  1. 获取说说或者留言列表
  2. 顺次遍历 id 删除说说或者留言

这外面的艰难一个在于找到获取 & 删除 留言和说说 所须要的参数 (这个咱们不开展介绍),
另外一个艰难是这些性能怎么在 chrome extension 中实现.

依据下面这些需要, 我了解大略操作流程是这样的,

点击 icon, 或者点击icon 弹出来的页面而后就开始获取列表, 而后遍历列表开始删说说.

外面大略须要用到, 组件, 组件间通信这些内容.

然而因为是没有接触过这块, 所以得搞清楚都须要有什么常识储备, 那就先从这里开始介绍.

常识储备

整体上, 插件开发这个货色没那么难, 须要的技术也比较简单. 基本上就是前端那些常识:

  • JavaScript
  • HTML + CSS

外加一些软技能

  • chrome devtools的应用和根本的调试能力
  • 文档浏览能力

基本上做过我的项目的前端都能 hold 住.

如果你在开发之前有些不确定的问题的话须要提前理解的话, 也能够看这篇文档高频问题 Q &A.

介绍完了常识储备, 咱们就得看文档了.

文档举荐

开发文档举荐看这份官网文档 加上一些 官网 demo.

接触新的技术, 文档必定是不可少的, 然而网上各种教程 + 文档, 泥沙俱下, 咱们到底应该看教程还是看文档.
我集体了解是, 不论教程还是文档, 都能够看, 然而要优先看官网的文档和教程, 看一手的材料, 因为我看文档的过程中发现, 非官方的货色 (非一手文档) 信息传播不残缺, 甚至有纰漏. 所以尽着官网的来, 除非有很好的非官方教程或者其余的起因.

也有一份非官方文档不错, 然而不举荐间接看, 倡议联合官网文档作为对照来看, 因为我在开发的时候发现这份文档有些内容和官网最新的文档不统一 (比方对于browser actionpage action的定义).

另外, 在理论的浏览过程中, 还会有些中央看文档看不明确的, 这时候有针对的搜一些博客 / 教程就能够. 比方这次组件通信的中央有困惑, 就找了些不错的文档看.

  • 一篇文章教你顺利入门和开发 chrome 扩大程序(插件)(文章中的代码有笔误, 不要间接拿来用)
  • Chrome 插件中 popup,background,contantscript 消息传递机制

还有一些别的文章, 都整顿下来能够参考.

  • 官网文档(举荐) | 官网 Demo
  • 举荐的非官方文档
  • 插件通信相干辅助

    • 一篇文章教你顺利入门和开发 chrome 扩大程序(插件)
    • Chrome 插件中 popup,background,contantscript 消息传递机制
  • 其余

    • Chrome 插件开发全攻略
    • 调试相干 | Debugging extensions
    • 打包公布相干 | 创立和公布自定义 Chrome 利用和扩大程序

看完文档咱们大抵会失去一些信息. 上面就来讲讲.

构造组成

UI 组成

在这一部分你只须要理解一个插件会由哪些局部组成就能够了, 具体性能前面介绍.

失常状况下咱们看到的浏览器和插件大略长这样

当咱们鼠标点击插件 icon 之后就变成了这样

从下面的图上来看, 次要由 icon(任务栏的图标) 和 点击之后的弹窗两个大件组成, 局部插件还会有一个专门的用于配置的页面(这里没用到, 就不介绍了).

另外, 在 UI 之外, 还会 backgroundcontent_script存在.
再加上这两个重要的概念之后整个构造就是这样的


上面看看文件构造.

文件构造

文件构造比拟能直观的反馈组件的组成和性能. 从文件构造来看。个别一个插件的文件构造长这样的(之所以说个别是因为不是每个插件都能用到全副的性能).

qzone_helper_extension
│  background.js
│  manifest.json
│  popup.html
│  popup.js
│  qq_icon.png
└─ content_script.js

其中各个文件的性能如下:

  • manifest.json是咱们最先须要理解的局部, 这个文件的性能相似于 package.json 文件,这里定义插件的配置信息,这些信息小到插件名称,图标,版本号等形容信息, 大到你须要申请的权限和各种引入的资源,入口文件等重要配置。所以这里应该是咱们最先须要理解的局部。
  • qq_icon.png是最不值得咱们了解的局部,这就是个图标文件而已。
  • popup.html是比拟直观的一个文件,这个就是你点击插件图标之后弹出来的界面,那个界面是 html 写的, 同样的, 对应的 popup.js 用来解决 popup 页面的交互和 popup 模块和别的模块之间的通信。
  • background.jscontent_script.js 是外围. 其中,background.js能够了解为我的项目的 app.js 文件, 而 content_script.js 是和网页是一伙的, 能够简略的了解为是咱们在网页那边的代理, 帮咱们做些网页相干的操作.

加上这些构造和文件之间的对应关系是这样的

到这里, 你大略理解的一个插件的根本组成构造. 理解完构造, 咱们分来看看各局部的具体情况.

插件各局部性能

Icon(按钮)

一般来说,Icon是咱们插件的性能入口, 也能够显示一些徽标.
在 chrome 插件中, 这种 icon 按钮是分两种 的:

  • page action
  • browser action

他们的区别与应用场景如下

类型 反对性能 利用场景
page action 不能应用徽章(就是揭示你有多少未读音讯的红点和数字) 非应用在所有页面的状况, 比方我这次开发的 QQ 空间小助手就只是利用在 user.qzone.qq.com 的一个插件, 应用的就是 page action
browser action 领有残缺的性能(tooltip / popup / badges 对于这些性能不再赘述) 对所有页面都能够应用的插件, 比方 adblock / vimium 这种

你可能有点困惑, 我理解了这个区别了, 然而有什么用吗? 有用, 在定义 manifest.json 的时候会用到.page action对应的定义字段是 page_action,browser action 对应的定义字段是browser_action.

pupop(弹出页面)

pupop这个页面次要承载简略的配置和信息展现性能.
这就是个一般的 html 文件, 外面写你的 cssjs逻辑.
须要留神的是, 这里的 js 只能操作 pupop 外面的DOM.

回到咱们的需要, 咱们能够在 popup 页面搁置一些用于触发操作的按钮. 比方删除说说按钮

manifest.json

这个文件很重要, 然而一上来就放这个文件, 让人有点摸不着头脑, 所以, 放在这里.

然而这个文件没必要每个字段都分明啥意思, 搞清楚你用到的就行了.

{
  // Require
  "manifest_version": 2, // 不同的 manifest 版本会有不同的性能
  "name": "My Extension",
  "version": "versionString",

  // Recommended
  "default_locale": "en",
  "description": "A plain text description",
  "icons": {...}, // icon 文件门路

  // Pick one (or none)
  "browser_action": {...},
  "page_action": {...},

  // Optional
  "action": ...,
  "author": ...,
  "automation": ...,
  "background": { // background 对应的配置
    // Recommended
    "persistent": false,
    // Optional
    "service_worker":
  },
  "chrome_settings_overrides": {...},
  "chrome_ui_overrides": {
    "bookmarks_ui": {
      "remove_bookmark_shortcut": true,
      "remove_button": true
    }
  },
  "chrome_url_overrides": {...},
  "commands": {...},
  "content_capabilities": ...,
  "content_scripts": [{...}], // content_script 对应的配置
  "content_security_policy": "policyString",
  "converted_from_user_script": ...,
  "current_locale": ...,
  "declarative_net_request": ...,
  "devtools_page": "devtools.html",
  "event_rules": [{...}],
  "externally_connectable": {"matches": ["*://*.example.com/*"]
  },
  "file_browser_handlers": [...],
  "file_system_provider_capabilities": {
    "configurable": true,
    "multiple_mounts": true,
    "source": "network"
  },
  "homepage_url": "http://path/to/homepage",
  "import": [{"id": "aaaa"}],
  "incognito": "spanning, split, or not_allowed",
  "input_components": ...,
  "key": "publicKey",
  "minimum_chrome_version": "versionString",
  "nacl_modules": [...],
  "oauth2": ...,
  "offline_enabled": true,
  "omnibox": {"keyword": "aString"},
  "optional_permissions": ["tabs"],
  "options_page": "options.html",
  "options_ui": {
    "chrome_style": true,
    "page": "options.html"
  },
  "permissions": ["tabs"], // 须要申请的权限
  "platforms": ...,
  "replacement_web_app": ...,
  "requirements": {...},
  "sandbox": [...],
  "short_name": "Short Name",
  "signature": ...,
  "spellcheck": ...,
  "storage": {"managed_schema": "schema.json"},
  "system_indicator": ...,
  "tts_engine": {...},
  "update_url": "http://path/to/updateInfo.xml",
  "version_name": "aString",
  "web_accessible_resources": [...]
}
background.js

这根本能够了解为是一个常驻后盾的 js 文件, 在外面能够解决一些事件监听. 比方监听页面初始化的事件 (chrome.runtime.onInstalled), 监听通信事件(chrome.runtime.onMessage).
另外, 从 background 收回去的申请能够跨域.

在 V3 的实现中,background 引入了 service worker 的概念.

在 chrome 的 设置 - 更多工具 - 工作管理器 外面能够看到咱们的 background 工作过程.

content_script.js

content_script文件是和浏览器关上的页面一块加载的, 能够操作关上页面的 DOM.
例如, 点击插件的图标, 而后页面扭转色彩. 这种状况是不能在 background.js 中间接扭转页面色彩的, 而是须要通过事件发送音讯到 content_script.js 中, 通过 content_script 来操作页面DOM.

理解完这些咱们大略晓得了, 咱们获取发申请的哪些参数包含发申请都能够在 content_script 中实现. 因为只有之类能够操作页面的 DOM.

组件通信

下面介绍完了各个组成部分, 上面介绍这些局部之间的通信.
chrome extension的通信是通过事件来进行的, 通信的内容是无效的 JSON 对象. 共有三种通信形式:

  • Simple one-time requests (就像短连贯)
  • Long-lived connections (就像长连贯)
  • Cross-extension messaging (多个插件间通信)

咱们这里只开展这次用到的 Simple one-time requests.
插件内的通信分为这几种:

  • content script => background
  • content script => popup
  • background => content script
  • background => popup
  • popup => content script
  • popup => background

content script => background/content script => popup/background => popup/popup => background这么发送事件

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {console.log(response.farewell);
});
  1. content script发送的事件 popupbackground都能够收到, 所以再发送的时候须要加上发给谁的标识, 而后收到音讯的时候解决
  2. popup => background的通信其实也能够通过 chrome.extension.getBackgroundPage() 来获取到 background 的所有办法, 间接调用

background => content script/popup => content script这么发送事件

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {console.log(response.farewell);
  });
});
  1. 因为可能关上了多个同一个 url 的 tab, 所以须要辨别 tab.
  2. 通过 chrome.tabs.sendMessage 发送的事件只有指定页面的 content 能够收到

而对于事件的监听解决是一样的

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  }
);

整个通信用一张图示意就是

加上通信的局部, 咱们大略分明了, 咱们在 popup 中触发操作, 而后 popup 发送事件给 content_script,content_script 开始获取申请参数, 申请数据列表, 而后遍历数据做删除操作. 整个过程大略就这样, 没用到background.

开发调试

开发时候如何运行插件

在地址栏关上 chrome://extensions/, 而后开启右上角的 开发者模式

而后点击 加载已解压的拓展程序, 在关上的文件抉择框中抉择你的目录就算装置你开发的插件了.

如何关上控制台

background / popup / content script的控制台都是独立的, 能够通过上面的形式关上

  • 关上 background 的控制台
    chrome://extensions/ 页面, 点击对应的插件 背景页 三个字即可关上 background 的控制台

  • 关上 popup 的控制台
    点击 icon 在弹出的 popup 页面 上右键, 而后点击查看, 就能够关上 popup 的控制台. 须要留神的是 popup 页面一旦敞开, 控制台也会随之敞开.

  • 关上 content script 的控制台
    content script 的控制台其实就是 tab 页的控制台, 然而须要切换一下

如何 debug

插件的调试和一般前端开发的 debug 形式是一样的.

更多详情能够参考文档:Debugging extensions

打包公布

咱们的插件开发实现之后能够抉择打包成 crx 文件公布到 chrome 网上利用店, 上传到 chrome 是须要注册开发者的, 注册账号须要 5$, 注册之后能够公布多个 chrome 插件.

更多详情能够参考文档创立和公布自定义 Chrome 利用和扩大程序



打包性能在 加载已解压的拓展程序 按钮的旁边, 而后依照提醒走就能够了

也能够间接打成压缩包放到网上让他人下载应用, 之前能够本地装置 crx 文件, 当初不反对了, 都是通过 加载已解压的拓展程序 在本地应用.

问题解决

chrome.tabs.query 失去空的 tabs

开发的过程中发现有的时候 chrome.tabs.query 失去的后果为[], 起初发现这是 chrome 的一个 bug, 在 2015 年就存在了, 始终没有修复. 解决办法如下:

var activeTabId;

chrome.tabs.onActivated.addListener(function(activeInfo) {activeTabId = activeInfo.tabId;});

// https://bugs.chromium.org/p/chromium/issues/detail?id=462939
function getActiveTab(callback) {chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {let tab = tabs[0];
        if (tab) {callback(tab.id);
        } else {chrome.tabs.get(activeTabId, function (tab) {if (tab) {callback(tab.id);
                } else {console.log('No active tab identified.');
                }
            });
        }
    });
}

更多相干探讨见: Why doesn’t chrome.tabs.query() return the tab’s URL when called using RequireJS in a Chrome extension?

让插件只在特定页面可用性能实现

需要是在某些页面插件的 icon 才亮起来, 点击 icon 才会展现 popup 页面.
这个性能的实现思路比拟多, 这里讲两种
一种是通过监听 conect 事件, 在事件的解决办法中 setIcon 和初始化对应的 popup 页面.
Vimium 是这么做的, 能够看这里.
还有一些利用 chromeonPageChanged事件的规定来实现, 这种解决只有在合乎规定的时候才展现 popup 页面.

chrome.runtime.onInstalled.addListener(function() {chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
        chrome.declarativeContent.onPageChanged.addRules([{
            conditions: [
                new chrome.declarativeContent.PageStateMatcher({
                    pageUrl: {hostContains: 'qzone.qq.com'}
                })
            ],
            actions: [new chrome.declarativeContent.ShowPageAction()]
        }]);
    });
});

操作插件 (点击或者别的操作) 没有响应

这种状况要注意插件治理页面你的插件那里有没有报错提醒, 像这样

这里的报错必须点进去革除了, 能力持续向下进行, 不然就会呈现操作没有响应的状况.

退出移动版