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

相關文章
相關標籤/搜索