chrome extension开发实战之QQ空间助手

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

TL;DRcss

  • 背景介绍
  • 需求分析
  • 知识储备
  • 文档推荐
  • 结构组成
  • 组件通讯
  • 开发调试
  • 打包发布
  • 问题解决

背景介绍

本文历时较久,2020年写了一半,写到迷茫,索性搁置,2021年翻出来继续写的;通过了半年多的时间再看,会有一些不同的理解,存在部分配图风格不一致.可是我会确保内容的正确和表达的一致.

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

需求分析

QQ空间小助手功能很简单,核心功能(其实也就这点功能)是批量删除qq空间的说说和留言.
实现上也很简单,只须要两个步骤.前端

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

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

根据上面这些需求,我理解大概操做流程是这样的,github

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

里面大概须要用到,组件,组件间通讯这些内容.chrome

可是由于是没有接触过这块,因此得搞清楚都须要有什么知识储备,那就先从这里开始介绍.json

知识储备

总体上,插件开发这个东西没那么难,须要的技术也比较简单.基本上就是前端那些知识:vim

  • JavaScript
  • HTML + CSS

外加一些软技能segmentfault

  • chrome devtools的使用和基本的调试能力
  • 文档阅读能力

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

若是你在开发以前有些不肯定的问题的话须要提早了解的话,也能够看这篇文档高频问题Q&A.

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

文档推荐

开发文档推荐看这份官方文档 加上一些 官方demo.
image.png

接触新的技术,文档确定是不可少的,可是网上各类教程 + 文档,鱼龙混杂,咱们到底应该看教程仍是看文档.
我我的理解是,无论教程仍是文档,均可以看,可是要优先看官方的文档和教程,看一手的资料,由于我看文档的过程当中发现,非官方的东西(非一手文档)信息传达不完整,甚至有纰漏.因此尽着官方的来,除非有很好的非官方教程或者其余的缘由.

也有一份非官方文档不错,可是不推荐直接看,建议结合官方文档做为对照来看,由于我在开发的时候发现这份文档有些内容和官方最新的文档不一致(好比对于browser actionpage action的定义).
image.png

另外,在实际的阅读过程当中,还会有些地方看文档看不明白的,这时候有针对的搜一些博客/教程就能够.好比此次组件通讯的地方有困惑,就找了些不错的文档看.

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

看完文档咱们大体会获得一些信息.下面就来说讲.

结构组成

UI组成

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

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

chrome-extensions#1.png

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

chrome-extensions#2.png

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

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

chrome-extensions#3.png
下面看看文件结构.

文件结构

文件结构比较能直观的反应组件的组成和功能.从文件结构来看。通常一个插件的文件结构长这样的(之因此说通常是由于不是每一个插件都能用到所有的功能).

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是和网页是一伙的,能够简单的理解为是咱们在网页那边的代理,帮咱们作些网页相关的操做.

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

chrome-extensions#4.png

到这里,你大概了解的一个插件的基本组成结构.了解完结构,咱们分来看看各部分的具体状况.

插件各部分功能

Icon(按钮)

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

他们的区别与使用场景以下

类型 支持功能 应用场景
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任务进程.
image.png

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"});
  }
);

整个通讯用一张图表示就是
chrome-extensions#5.png

加上通讯的部分,咱们大概清楚了,咱们在 popup中触发操做,而后 popup发送事件给 content_script, content_script开始获取请求参数,请求数据列表,而后遍历数据作删除操做.整个过程大概就这样,没用到 background.
chrome-extensions#5.png

开发调试

开发时候如何运行插件

在地址栏打开chrome://extensions/,而后开启右上角的开发者模式
image.png
而后点击加载已解压的拓展程序,在打开的文件选择框中选择你的目录就算安装你开发的插件了.

如何打开控制台

background / popup / content script的控制台都是独立的,能够经过下面的方式打开

  • 打开background的控制台
    chrome://extensions/页面,点击对应的插件背景页三个字便可打开background的控制台
    image.png
    image.png
  • 打开popup的控制台
    点击icon在弹出的popup页面上右键,而后点击检查,就能够打开popup的控制台.须要注意的是popup页面一旦关闭,控制台也会随之关闭.
    image.png
    image.png
  • 打开content script的控制台
    content script的控制台其实就是tab页的控制台,可是须要切换一下
    image.png

如何debug

插件的调试和普通前端开发的debug方式是同样的.

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

打包发布

咱们的插件开发完成以后能够选择打包成crx文件发布到chrome 网上应用店,上传到chrome是须要注册开发者的,注册帐号须要5$,注册以后能够发布多个chrome插件.

更多详情能够参考文档 建立和发布自定义 Chrome 应用和扩展程序

image.png
image.png
打包功能在加载已解压的拓展程序按钮的旁边,而后按照提示走就能够了
image.png
也能够直接打成压缩包放到网上让别人下载使用,以前能够本地安装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()]
        }]);
    });
});

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

这种状况要留意插件管理页面你的插件那里有没有报错提示,像这样
image.png
这里的报错必须点进去清除了,才能继续向下进行,否则就会出现操做没有响应的状况.
image.png

相关文章
相关标签/搜索