1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        chrome extension 開(kāi)發(fā)實(shí)戰(zhàn)之 QQ 空間助手

        共 13617字,需瀏覽 28分鐘

         ·

        2021-03-02 13:19

        作者:user name
        來(lái)源:SegmentFault 思否社區(qū)




        TL;DR


        • 背景介紹
        • 需求分析
        • 知識(shí)儲(chǔ)備
        • 文檔推薦
        • 結(jié)構(gòu)組成
        • 組件通信
        • 開(kāi)發(fā)調(diào)試
        • 打包發(fā)布
        • 問(wèn)題解決




        背景介紹


        本文歷時(shí)較久,2020年寫了一半,寫到迷茫,索性擱置,2021年翻出來(lái)繼續(xù)寫的;經(jīng)過(guò)了半年多的時(shí)間再看,會(huì)有一些不一樣的理解,存在部分配圖風(fēng)格不一致.但是我會(huì)確保內(nèi)容的正確和表達(dá)的一致.

        2020年的某一天我把之前寫的QQ空間批量刪除說(shuō)說(shuō)&留言的腳本寫了個(gè)chrome插件版,名字也改成了QQ空間小助手.
        QQ 空間小助手https://github.com/aqiongbei/qzone_helper

        因?yàn)橐彩堑谝淮螌懖寮?所以記錄這個(gè)過(guò)程,分享開(kāi)發(fā)過(guò)程和期間遇到的問(wèn)題,并做一些關(guān)鍵點(diǎn)的梳理.

        所以,這并不是一篇大而全的chrome extension開(kāi)發(fā)教程,這是一篇以QQ空間助手為實(shí)戰(zhàn)來(lái)切入插件開(kāi)發(fā)但是側(cè)重講chrome extension開(kāi)發(fā)的文章.(注:文中有大量鏈接,建議點(diǎn)擊閱讀原文進(jìn)行閱讀~)



        需求分析


        QQ空間小助手功能很簡(jiǎn)單,核心功能(其實(shí)也就這點(diǎn)功能)是批量刪除qq空間的說(shuō)說(shuō)和留言.

        實(shí)現(xiàn)上也很簡(jiǎn)單,只需要兩個(gè)步驟.

        1. 獲取說(shuō)說(shuō)或者留言列表
        2. 依次遍歷id刪除說(shuō)說(shuō)或者留言

        這里面的困難一個(gè)在于找到獲取&刪除 留言和說(shuō)說(shuō) 所需要的參數(shù)(這個(gè)我們不展開(kāi)介紹),另外一個(gè)困難是這些功能怎么在chrome extension中實(shí)現(xiàn).

        根據(jù)上面這些需求,我理解大概操作流程是這樣的,

        點(diǎn)擊icon,或者點(diǎn)擊icon彈出來(lái)的頁(yè)面然后就開(kāi)始獲取列表,然后遍歷列表開(kāi)始刪說(shuō)說(shuō).

        里面大概需要用到,組件,組件間通信這些內(nèi)容.

        但是因?yàn)槭菦](méi)有接觸過(guò)這塊,所以得搞清楚都需要有什么知識(shí)儲(chǔ)備,那就先從這里開(kāi)始介紹.



        知識(shí)儲(chǔ)備


        整體上,插件開(kāi)發(fā)這個(gè)東西沒(méi)那么難,需要的技術(shù)也比較簡(jiǎn)單.基本上就是前端那些知識(shí):

        • JavaScript
        • HTML + CSS

        外加一些軟技能

        • chrome devtools的使用和基本的調(diào)試能力
        • 文檔閱讀能力

        基本上做過(guò)項(xiàng)目的前端都能hold住.

        如果你在開(kāi)發(fā)之前有些不確定的問(wèn)題的話需要提前了解的話,也可以看這篇文檔高頻問(wèn)題Q&A.

        介紹完了知識(shí)儲(chǔ)備,我們就得看文檔了.



        文檔推薦


        開(kāi)發(fā)文檔推薦看這份官方文檔 加上一些 官方demo.
        官方文檔:
        https://developer.chrome.com/extensions/getstarted
        官方demo:
        https://github.com/GoogleChrome/chrome-extensions-samples



        接觸新的技術(shù),文檔肯定是不可少的,但是網(wǎng)上各種教程 + 文檔,魚(yú)龍混雜,我們到底應(yīng)該看教程還是看文檔.

        我個(gè)人理解是,不管教程還是文檔,都可以看,但是要優(yōu)先看官方的文檔和教程,看一手的資料,因?yàn)槲铱次臋n的過(guò)程中發(fā)現(xiàn),非官方的東西(非一手文檔)信息傳達(dá)不完整,甚至有紕漏.所以盡著官方的來(lái),除非有很好的非官方教程或者其他的原因.

        也有一份非官方文檔不錯(cuò),但是不推薦直接看,建議結(jié)合官方文檔作為對(duì)照來(lái)看,因?yàn)槲以陂_(kāi)發(fā)的時(shí)候發(fā)現(xiàn)這份文檔有些內(nèi)容和官方最新的文檔不一致(比如對(duì)于browser action和page action的定義).
        非官方文檔:
        https://crxdoc-zh.appspot.com/extensions/getstarted


        另外,在實(shí)際的閱讀過(guò)程中,還會(huì)有些地方看文檔看不明白的,這時(shí)候有針對(duì)的搜一些博客/教程就可以.比如這次組件通信的地方有困惑,就找了些不錯(cuò)的文檔看.

        • 一篇文章教你順利入門和開(kāi)發(fā)chrome擴(kuò)展程序(插件)(文章中的代碼有筆誤,不要直接拿來(lái)用)
        • Chrome插件中 popup,background,contantscript消息傳遞機(jī)制

        還有一些別的文章,都整理下來(lái)可以參考.

        • 官方文檔(推薦) | 官方Demo
        • 推薦的非官方文檔
        • 插件通信相關(guān)輔助
          • 一篇文章教你順利入門和開(kāi)發(fā)chrome擴(kuò)展程序(插件)
          • Chrome插件中 popup,background,contantscript消息傳遞機(jī)制
        • 其他
          • Chrome插件開(kāi)發(fā)全攻略
          • 調(diào)試相關(guān) | Debugging extensions
          • 打包發(fā)布相關(guān) | 創(chuàng)建和發(fā)布自定義 Chrome 應(yīng)用和擴(kuò)展程序

        看完文檔我們大致會(huì)得到一些信息.下面就來(lái)講講.



        結(jié)構(gòu)組成


        UI組成


        在這一部分你只需要了解一個(gè)插件會(huì)由哪些部分組成就可以了,具體功能后面介紹.

        正常情況下我們看到的瀏覽器和插件大概長(zhǎng)這樣


        當(dāng)我們鼠標(biāo)點(diǎn)擊插件icon之后就變成了這樣


        從上面的圖上來(lái)看,主要由 icon(任務(wù)欄的圖標(biāo)) 和 點(diǎn)擊之后的彈窗兩個(gè)大件組成,部分插件還會(huì)有一個(gè)專門的用于配置的頁(yè)面(這里沒(méi)用到,就不介紹了).


        另外,在UI之外,還會(huì)background和content_script存在.

        再加上這兩個(gè)重要的概念之后整個(gè)結(jié)構(gòu)就是這樣的


        下面看看文件結(jié)構(gòu).

        文件結(jié)構(gòu)


        文件結(jié)構(gòu)比較能直觀的反應(yīng)組件的組成和功能.從文件結(jié)構(gòu)來(lái)看。一般一個(gè)插件的文件結(jié)構(gòu)長(zhǎng)這樣的(之所以說(shuō)一般是因?yàn)椴皇敲總€(gè)插件都能用到全部的功能).

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

        其中各個(gè)文件的功能如下:

        • manifest.json是我們最先需要了解的部分,這個(gè)文件的功能類似于package.json文件,這里定義插件的配置信息,這些信息小到插件名稱,圖標(biāo),版本號(hào)等描述信息,大到你需要申請(qǐng)的權(quán)限和各種引入的資源,入口文件等重要配置。所以這里應(yīng)該是我們最先需要了解的部分。
        • qq_icon.png是最不值得我們理解的部分,這就是個(gè)圖標(biāo)文件而已。
        • popup.html是比較直觀的一個(gè)文件,這個(gè)就是你點(diǎn)擊插件圖標(biāo)之后彈出來(lái)的界面,那個(gè)界面是html寫的,同樣的,對(duì)應(yīng)的popup.js用來(lái)處理popup頁(yè)面的交互和popup模塊和別的模塊之間的通信。
        • background.js和content_script.js是核心.其中,background.js可以理解為項(xiàng)目的app.js文件,而content_script.js是和網(wǎng)頁(yè)是一伙的,可以簡(jiǎn)單的理解為是我們?cè)诰W(wǎng)頁(yè)那邊的代理,幫我們做些網(wǎng)頁(yè)相關(guān)的操作.

        加上這些結(jié)構(gòu)和文件之間的對(duì)應(yīng)關(guān)系是這樣的


        到這里,你大概了解的一個(gè)插件的基本組成結(jié)構(gòu).了解完結(jié)構(gòu),我們分來(lái)看看各部分的具體情況.



        插件各部分功能


        Icon(按鈕)


        一般來(lái)說(shuō),Icon是我們插件的功能入口,也可以顯示一些徽標(biāo).

        在chrome插件中,這種icon按鈕是分兩種的:

        • page action
        • browser action

        他們的區(qū)別與使用場(chǎng)景如下


        你可能有點(diǎn)困惑,我了解了這個(gè)區(qū)別了,但是有什么用嗎?有用,在定義manifest.json的時(shí)候會(huì)用到.page action對(duì)應(yīng)的定義字段是page_action,browser action對(duì)應(yīng)的定義字段是browser_action.

        pupop(彈出頁(yè)面)


        • pupop這個(gè)頁(yè)面主要承載簡(jiǎn)單的配置和信息展示功能.

        • 這就是個(gè)普通的html文件,里面寫你的css和js邏輯.

        • 需要注意的是,這里的js只能操作pupop里面的DOM.



        回到我們的需求,我們可以在popup頁(yè)面放置一些用于觸發(fā)操作的按鈕.比如刪除說(shuō)說(shuō)按鈕

        manifest.json


        這個(gè)文件很重要,但是一上來(lái)就放這個(gè)文件,讓人有點(diǎn)摸不著頭腦,所以,放在這里.
        但是這個(gè)文件沒(méi)必要每個(gè)字段都清楚啥意思,搞清楚你用到的就行了.

        {
          // Require
          "manifest_version": 2, // 不同的manifest版本會(huì)有不同的功能
          "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對(duì)應(yīng)的配置
            // 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對(duì)應(yīng)的配置
          "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"], // 需要申請(qǐng)的權(quán)限
          "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


        這基本可以理解為是一個(gè)常駐后臺(tái)的js文件,在里面可以處理一些事件監(jiān)聽(tīng).比如監(jiān)聽(tīng)頁(yè)面初始化的事件(chrome.runtime.onInstalled),監(jiān)聽(tīng)通信事件(chrome.runtime.onMessage).

        另外,從background發(fā)出去的請(qǐng)求可以跨域.

        在V3的實(shí)現(xiàn)中,background引入了service worker的概念.

        在chrome的設(shè)置-更多工具-任務(wù)管理器里面可以看到我們的background任務(wù)進(jìn)程.


        content_script.js


        content_script文件是和瀏覽器打開(kāi)的頁(yè)面一塊加載的,可以操作打開(kāi)頁(yè)面的DOM.

        例如,點(diǎn)擊插件的圖標(biāo),然后頁(yè)面改變顏色.這種情況是不能在background.js中直接改變頁(yè)面顏色的,而是需要通過(guò)事件發(fā)送消息到content_script.js中,通過(guò)content_script來(lái)操作頁(yè)面DOM.

        了解完這些我們大概知道了,我們獲取發(fā)請(qǐng)求的哪些參數(shù)包括發(fā)請(qǐng)求都可以在content_script中實(shí)現(xiàn).因?yàn)橹挥兄惪梢圆僮黜?yè)面的DOM.




        組件通信


        上面介紹完了各個(gè)組成部分,下面介紹這些部分之間的通信.

        chrome extension的通信是通過(guò)事件來(lái)進(jìn)行的,通信的內(nèi)容是有效的JSON對(duì)象.共有三種通信方式:

        • Simple one-time requests (就像短連接)
        • Long-lived connections (就像長(zhǎng)連接)
        • Cross-extension messaging (多個(gè)插件間通信)

        我們這里只展開(kāi)這次用到的Simple one-time requests.
        插件內(nèi)的通信分為這幾種:

        • 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這么發(fā)送事件

        chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
          console.log(response.farewell);
        });

        1. content script發(fā)送的事件popup和background都可以收到,所以再發(fā)送的時(shí)候需要加上發(fā)給誰(shuí)的標(biāo)識(shí),然后收到消息的時(shí)候處理
        2. popup => background的通信其實(shí)也可以通過(guò)chrome.extension.getBackgroundPage()來(lái)獲取到background的所有方法,直接調(diào)用

        background => content script/popup => content script這么發(fā)送事件

        chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
          chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
            console.log(response.farewell);
          });
        });

        1. 因?yàn)榭赡艽蜷_(kāi)了多個(gè)同一個(gè)url的tab,所以需要區(qū)分tab.
        2. 通過(guò)chrome.tabs.sendMessage發(fā)送的事件只有指定頁(yè)面的content可以收到

        而對(duì)于事件的監(jiān)聽(tīng)處理是一樣的

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

        整個(gè)通信用一張圖表示就是


        加上通信的部分,我們大概清楚了,我們?cè)趐opup中觸發(fā)操作,然后popup發(fā)送事件給content_script,content_script開(kāi)始獲取請(qǐng)求參數(shù),請(qǐng)求數(shù)據(jù)列表,然后遍歷數(shù)據(jù)做刪除操作.整個(gè)過(guò)程大概就這樣,沒(méi)用到background.






        開(kāi)發(fā)調(diào)試


        開(kāi)發(fā)時(shí)候如何運(yùn)行插件


        在地址欄打開(kāi)chrome://extensions/,然后開(kāi)啟右上角的開(kāi)發(fā)者模式


        然后點(diǎn)擊加載已解壓的拓展程序,在打開(kāi)的文件選擇框中選擇你的目錄就算安裝你開(kāi)發(fā)的插件了.

        如何打開(kāi)控制臺(tái)


        background / popup / content script的控制臺(tái)都是獨(dú)立的,可以通過(guò)下面的方式打開(kāi)

        • 打開(kāi)background的控制臺(tái)
          在chrome://extensions/頁(yè)面,點(diǎn)擊對(duì)應(yīng)的插件背景頁(yè)三個(gè)字即可打開(kāi)background的控制臺(tái)




        • 打開(kāi)popup的控制臺(tái)
          點(diǎn)擊icon在彈出的popup頁(yè)面上右鍵,然后點(diǎn)擊檢查,就可以打開(kāi)popup的控制臺(tái).需要注意的是popup頁(yè)面一旦關(guān)閉,控制臺(tái)也會(huì)隨之關(guān)閉.



        • 打開(kāi)content script的控制臺(tái)
          content script的控制臺(tái)其實(shí)就是tab頁(yè)的控制臺(tái),但是需要切換一下


        如何debug


        插件的調(diào)試和普通前端開(kāi)發(fā)的debug方式是一樣的.

        更多詳情可以參考文檔:Debugging extensions

        https://developer.chrome.com/extensions/tut_debugging




        打包發(fā)布


        我們的插件開(kāi)發(fā)完成之后可以選擇打包成crx文件發(fā)布到chrome 網(wǎng)上應(yīng)用店,上傳到chrome是需要注冊(cè)開(kāi)發(fā)者的,注冊(cè)賬號(hào)需要5$,注冊(cè)之后可以發(fā)布多個(gè)chrome插件.

        更多詳情可以參考文檔創(chuàng)建和發(fā)布自定義 Chrome 應(yīng)用和擴(kuò)展程序





        打包功能在加載已解壓的拓展程序按鈕的旁邊,然后按照提示走就可以了



        也可以直接打成壓縮包放到網(wǎng)上讓別人下載使用,之前可以本地安裝crx文件,現(xiàn)在不支持了,都是通過(guò)加載已解壓的拓展程序在本地使用.



        問(wèn)題解決


        chrome.tabs.query得到空的tabs


        開(kāi)發(fā)的過(guò)程中發(fā)現(xiàn)有的時(shí)候chrome.tabs.query得到的結(jié)果為[],后來(lái)發(fā)現(xiàn)這是chrome的一個(gè)bug,在2015年就存在了,一直沒(méi)有修復(fù).解決方法如下:

        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.');
                        }
                    });
                }
            });
        }

        更多相關(guān)討論見(jiàn): Why doesn't chrome.tabs.query() return the tab's URL when called using RequireJS in a Chrome extension?
        https://stackoverflow.com/a/34214430

        讓插件只在特定頁(yè)面可用功能實(shí)現(xiàn)


        需求是在某些頁(yè)面插件的icon才亮起來(lái),點(diǎn)擊icon才會(huì)展示popup頁(yè)面.

        這個(gè)功能的實(shí)現(xiàn)思路比較多,這里講兩種

        • 一種是通過(guò)監(jiān)聽(tīng)conect事件,在事件的處理方法中setIcon和初始化對(duì)應(yīng)的popup頁(yè)面.Vimium是這么做的,可以看這里.

        • 還有一些利用chrome的onPageChanged事件的規(guī)則來(lái)實(shí)現(xiàn),這種處理只有在符合規(guī)則的時(shí)候才展示popup頁(yè)面.



        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()]
                }]);
            });
        });

        操作插件(點(diǎn)擊或者別的操作)沒(méi)有響應(yīng)


        這種情況要留意插件管理頁(yè)面你的插件那里有沒(méi)有報(bào)錯(cuò)提示,像這樣


        這里的報(bào)錯(cuò)必須點(diǎn)進(jìn)去清除了,才能繼續(xù)向下進(jìn)行,不然就會(huì)出現(xiàn)操作沒(méi)有響應(yīng)的情況.




        點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開(kāi)更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

        - END -


        瀏覽 80
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            在办公室把我添高潮了电影 | 久久精品大香蕉 | 精品国产一区二区三区四区精华 | freesexvideos喷水 | 看全色黄大色黄大片美女 | 欧美美女爱爱视频 | 韩日无码| 午夜五月天网站 | 国产suv一区二区三区 | 久久婷婷五月综合伊人 |