1. 程式人生 > >一篇文章教你順利入門和開發chrome擴充套件程式

一篇文章教你順利入門和開發chrome擴充套件程式

前言

關於chrome extension的開發經驗總結或說明文件等資料很多,很多人在寫,然而,我也是一員。但是,也許這篇文章,可能給你一些不一樣的感受。
前面部分大多數是一些基礎介紹,和別人的資料大同小異,但是用的是通俗的語言或者我自己理解來描述的,不是拷貝官方的描述,不然的話,你乾脆看官方文件就好啦,幹嘛還來我這裡折騰對吧,也許這些通俗的描述,更方便你理解(當然不排除也會有官方的話語)
後面部分多為一些我在專案中總結的方法,這部分就是在別人的資料可能看不到的地方了,當然,這些方法也許不通用,因為畢竟是基於我專案裡的,但是儘量總結一套方法出來。

廢話不多說,咱們開始吧...


WHAT

谷歌擴充套件(chrome extension),在認識之前,首先要明確一個觀念,這種擴充套件程式,實際上不是一個exe、app之類的程式,下載了本地開啟執行安裝,本質上,它就是一個網頁,寫的用的都是前端的語言,高檔點說是一個程式,通俗來講, 就是執行在瀏覽器上的一個網站,網頁。

我這種說法也許不對,不準確,不專業。但是起碼,能把小白開發擴充套件的心態,調整好點,實際上是一個不難的東西,就是在寫頁面而已。要知道,心態不好,後面就堅持不下去了。

最基本組成

這裡講的是開發一個擴充套件(外掛)最常用最基本的所需的東西,並不像官方說的那種分類。

  • manifest.json
  • background script
  • content script
  • popup

嚴格上來講主要是background script 、 content script 和 popup,畢竟他們都是貫穿在manifest裡的,把manifest寫出來,只是為了凸顯一下它的重要性

(一)manifest.json

一個外掛,必須都含有這個一個檔案——manifest.json,位於根目錄。顧名思義,這是一個擴充套件的組成清單,在這個清單裡能大約看到該外掛的一個“規則”。

羅列和簡單介紹一下一些常用的配置項,說之前,先看一個大致的檔案,首先感官感受一下先

{
	// 必須
	"manifest_version": 2,
	"name"
: "EOD品牌印象標註-qa", "version": "1.1.2", // 推薦 "default_locale": "en", "description": "外掛的描述", "icons": { "16": "img/icon.png", // 擴充套件程式頁面上的圖示 "32": "img/icon.png", // Windows計算機通常需要此大小。提供此選項可防止尺寸失真縮小48x48選項。 "48": "img/icon.png", // 顯示在擴充套件程式管理頁面上 "128": "img/icon.png" // 在安裝和Chrome Webstore中顯示 }, // 可選 "background": { "page": "background/background.html", "scripts": ["background.js"], // 推薦 "persistent": false }, "browser_action": { "default_icon": "img/icon.png", // 特定於工具欄的圖示,至少建議使用16x16和32x32尺寸,應為方形, // 不然會變形 "default_title": "懸浮在工具欄外掛圖示上時的tooltip內容", "default_popup": "hello.html" // 不允許內聯JavaScript。 }, "content_scripts": [ { "js": [ "inject.js" ], "matches": [ "http://*/*", "https://*/*" ], "run_at": "document_start" } ], "permissions": [ "contextMenus", "tabs", "http://*/*", "https://*/*" ], "web_accessible_resources": [ "dist/*", "dist/**/*" ] } 複製程式碼

上面有我寫的一些註釋,用於幫助大家更好的去理解。
那接下來開始說一下其中的配置項

icons

extension程式的圖示,可以有一個或多個。
48x48的圖示用在extensions的管理介面(chrome://extensions);
128x128 的圖示用在安裝extension程式的時候;
16x16 的圖示當作 extension 的頁面圖示,也可以顯示在資訊欄上。
圖示一般為PNG格式, 因為最好的透明度的支援,不過WebKit支援任何格式,包括BMP,GIF,ICO等
注意: 以上寫的圖示不是固定的。隨瀏覽器的環境的改變而變。如:安裝時彈出的對話方塊變小。

browser_action與page_action

前者擴充套件可以適用於任何頁面。後者擴充套件只能作用於某一頁面,當開啟該頁面時觸發該Google Chrome擴充套件,關閉頁面則Google Chrome擴充套件也隨之消失。

default_popup

在使用者點選擴充套件程式圖示時,都可以設定彈出一個popup頁面。而這個頁面中自然是可以有執行的js指令碼的(比如就叫popup.js)。它會在每次點選外掛圖示——popup頁面彈出時,重新載入。

這個小小的設定,也就是上面我把它分為在基本組成裡的popup

permissions

在background裡使用一些chrome api,需要授權才能使用,例如要使用chrome.tabs.xxx的api,就要在permissions引入“tabs”

web_accessible_resources

允許擴充套件外的頁面訪問的擴充套件內指定的資源。通俗來講就是,擴充套件是一個資料夾A的,別人的網站是一個資料夾B,B要看A的東西,需要獲得許可權,而寫在這個屬性下的檔案,就是授予了別人訪問的許可權。

(二)background script

background可以理解為外掛執行在瀏覽器中的一個後臺網站/指令碼,注意它是與當前瀏覽頁面無關的。
實際上這部分內容的配置情況也會寫在manifest裡,對應的是background配置項。單獨拿出來講,是彰顯它的分量很重,也是一個外掛常用的配置。從其中幾個配置項項去了解一下什麼是background script

page

可以理解為這個後臺網站的主頁,在這個主頁中,有引用的指令碼,其中一般都會有一個專門來管理外掛各種互動以及監聽瀏覽器行為的指令碼,一般都起名為background.js。這個主頁,不一定要求有。

scripts

這裡的指令碼其實跟寫在page裡html引入的指令碼目的一樣,個人的理解是,page的html在沒有的情況下,那麼指令碼就需要通過這個屬性引入了;
如果在存在page的情況下,一般在這裡引入的指令碼是專門為外掛服務的指令碼,而那些第三方指令碼如jquery還是在page裡引用比較好,或許這是一個眾人的“潛規則”吧

persistent

所謂的後臺指令碼,在chrome擴充套件中又分為兩類,分別運行於後臺頁面(background page)和事件頁面(event page)中。兩者區別在於,

前者(後臺頁面)持續執行,生存週期和瀏覽器相同,即從開啟瀏覽器到關閉瀏覽器期間,後臺指令碼一直在執行,一直佔據著記憶體等系統資源,persistent設為true;

後者(事件頁面)只在需要活動時活動,在完全不活動的狀態持續幾秒後,chrome將會終止其執行,從而釋放其佔據的系統資源,而在再次有事件需要後臺指令碼來處理時,重新載入它,persistent設為false。

保持後臺指令碼持久活動的唯一場合是擴充套件使用chrome.webRequest API來阻止或修改網路請求。webRequest API與非永續性後臺頁面不相容。

(三) content script

這部分指令碼,簡單來說是插入到網頁中的指令碼。它具有獨立而富有包容性。

所謂獨立,指它的工作空間,名稱空間,域等是獨立的,不會說跟插入到的頁面的某些函式和變數發生衝突;

所謂包容性,指外掛把自己的一些指令碼(content script)插入到符合條件的頁面裡,作為頁面的指令碼,因此與插入的頁面共享dom的,即用dom操作是針對插入的網頁的,在這些腳本里使用的window物件跟插入頁面的window是一樣的。主要用在訊息傳遞上(使用postMessage和onmessage)

實際上這部分內容的配置情況也會寫在manifest裡,對應的是content_scripts配置項。單獨拿出來講,是彰顯它的分量很重,也是一個外掛常用的配置。從其中幾個配置項項去了解一下什麼是content script

js

要插入到頁面裡的指令碼。例子很常見,例如在一個別人的網頁上,你要開啟你做的擴充套件,對別人的網頁做一些處理或者獲取一些資料等,那怎麼跟別人的頁面建立起聯絡呢?就是通過把js裡的這些指令碼嵌入都別人的網頁裡。

matches

必需。匹配規則組成的陣列,用來匹配頁面url的,符合條件的頁面將會插入js的指令碼。當然,有可以匹配的自然會有不匹配的——exclude_matches。匹配規則:

developer.chrome.com/extensions/…

上面的官方描述已經很清晰啦,我就不多說了。

run_at

js配置項裡的指令碼何時插入到頁面裡呢,這個配置項來控制插入時機。有三個選擇項:

  • document_start
  • document_end
  • document_idle(預設)
document_start

style樣式載入好,dom渲染完成和指令碼執行前

document_end

dom渲染完成後,即DOMContentLoaded後馬上執行

document_idle

在DOMContentLoaded 和 window load之間,具體是什麼時刻,要視頁面的複雜程度和載入時間,並針對頁面載入速度進行了優化。

popup

其實這部分,早就講過了,就是在manifest裡的browser_actionpage_action配置項裡設定的


基礎的通訊機制

上面講述了基本的組成部分,那麼這幾部分,他們要進行交流合作,把他們組織起來,才能成就一個漂亮的擴充套件。那麼這種交流,分為以下幾種說明:

  • content script與background的通訊
  • popup與background的通訊
  • popup與content script的通訊
  • 外掛iframe網站與插入網頁的通訊

最後一點,是額外說的,但是卻是很重要的。畢竟很多擴充套件,也是以iframe的形式呈現的。

(一)content script與background的通訊

content-script向background傳送訊息

在content-script端

使用

chrome.runtime.sendMessege(
    message,
    function(response) {…}
)
複製程式碼

就能向background傳送訊息了,第一個引數message為傳送的訊息(基礎資料型別),回撥函式裡的第一個引數為background接收訊息後返回的訊息(如有)

在background端

使用

chrome.runtime.onMessege.addListener(
    function(request, sender, sendResponse) {…}
)
複製程式碼

進行監聽發來的訊息,request表示發來的訊息,sendResponse是一個函式,用於對發來的訊息進行迴應,如 sendResponse('我已收到你的訊息:'+JSON.stringify(request));

這裡需要注意的是,預設情況下sendResponse函式的執行是同步的,如果在這個監聽訊息的處理函式的同步執行流程裡沒有發現sendResponse,則預設返回undefined,假設我們是要經過一個非同步處理之後才呼叫sendResponse,已經為時已晚了。因此,我們可能需要非同步執行sendResponse,這時我們在這個監聽函式裡的新增return true就能實現了。

還有,由於background監聽所有頁面上的content script上發來的訊息,如果多個頁面同時傳送同種訊息,background的onMessage只會處理最先收到的那個,其他的不了了之了。

background向content-script傳送訊息

我們發現,一個外掛裡只有一個background環境,而content-script有多個(一個頁面一個),那麼background怎麼向特定的content-script傳送訊息?

在background端

首先我們需要知道要向哪個content scripts傳送訊息,一般一個頁面一份content scripts,而一個頁面對應一個瀏覽器tab,每個tab都有自己的tabId,因此首先要獲取要傳送訊息的tab對應的tabId。

/**
 * 獲取當前選項卡id
 * @param callback - 獲取到id後要執行的回撥函式
 */
function getCurrentTabId(callback) {
    chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
        if (callback) {
            callback(tabs.length ? tabs[0].id: null);
        }
    });
}
複製程式碼

當知道了tabId後,就使用該api進行傳送訊息

chrome.tabs.sendMessage(tabId, message, function(response) {...});
複製程式碼

其中message為傳送的訊息,回撥函式的response為content scripts接收到訊息後的回傳訊息

在content scripts端

同樣是使用

chrome.runtime.onMessege.addListener(function(request, sender, sendResponse) {…})
複製程式碼

進行來自background發來訊息的監聽並回傳

(二)popup與background的通訊

一般地,popup與background的交流,常見於popup要獲取background裡的某些“東西”,當然我們可以使用上述的chrome.runtime.sendMessagechrome.runtime.onMessage的方式進行popup向background的交流,但是其實有更方便快捷的方式:

var bg = chrome.extension.getBackgroundPage();
bg.someMethod();    //someMethod()是background中的一個方法
複製程式碼

(三)popup與content script的通訊

這裡的通訊,實際上跟background與content script的方式是一樣的

(四)外掛iframe網站與插入網頁的通訊

其實這兩個的通訊,算不上是chrome extension開發裡的知識,它就是一個基礎的js知識——ifame與父窗體的通訊。

同域的情況下,可以通過DOM操作達到通訊的目的,如獲取dom元素,獲取值賦值之類的。
在父窗體裡,用window.contentWindow獲取到iframe的window物件
在iframe裡,用window.parent獲取到父窗體的window物件

而在跨域下,上述的方法是行不通的,網上也有各種方法解決,但是在外掛這塊裡,最方便的就是使用js的message機制了。
我這裡說的message機制,就是使用window物件的postMessage()onmessage

一般外掛展現都是在別人的網站上,因此沒辦法直接在別人的網站上新增postMessageonmessage的程式碼。這時候,重任就落在了外掛的content script身上了(之前說了他們共用DOM)。由於content script是自己編寫的,所以可以“為所欲為”了

iframe向父窗體傳送訊息

在iframe端

假設iframe類名為extension-iframe,這裡設定類名而不是id名的初衷是,我們不能保證設定的名稱原本的網站會不會已經存在,設定類名能共存。傳送訊息使用
window.parent.postMessage(message, '*');
其中message為傳送的訊息

在父窗體端

由於一個頁面,可能有來自頁面本身的postMessage來的訊息,也有可能來自該頁面其他chrome extension傳送來的訊息,因此用onmessage來監聽,要做好區分來源,這裡使用以下方法

window.addEventListener('message', function (event, a, b) {
    // 如果沒訊息就退出
    if (!event.data) {
        return;
    }
    var iframes = document.getElementsByClassName('extension-iframe');
    var extensionIframe = null; // 存外掛iframe節點物件
    var correctSource = false;  // 是否來源正確
    // 找出真正的外掛生成的iframe
    for (var i = 0; i < iframes.length; i++) {
        if (iframes[i].contentWindow && (event.source === iframes[i].contentWindow)) {
            correctSource = true;
            extensionIframe = iframes[i];
            break;
        }
    }
    // 如果來源不是來自外掛的,就退出
    if (!correctSource) {
        return;
    }
}, false);
複製程式碼

這裡也不能百分百區分好是不是來自自己extension的訊息,或許真的那麼倒黴剛好有一個跟自己extension同類名的iframe也發了一個訊息過來。因此還可以加多一層保障,在iframe傳送訊息的內容上做手腳,例如加個from,然後在這邊判斷一下等。當然,這樣也不能百分百確定,只能說保障更上一層樓了。
如果大家有好的點子,請務必告訴鄙人!受教受教!

父窗體向iframe傳送訊息

在父窗體端

使用 extensionIframe.contentWindow.postMessage(message, '*');
其中extensionIframe為外掛的iframe節點物件,message為傳送的訊息,例如

{from: 'content-script', other: xxx}
複製程式碼

在iframe端

使用

window.addEventListener('message', function (event, a, b) {
    let result = event.data;
    if (result && (result.from === 'content-script') && (event.source === window.parent)) {...}
});
複製程式碼

在這裡,在傳送訊息裡增加了個from屬性,進而進一步判斷是不是來自父窗體自己外掛的content script

外掛內容傳送ajax請求,我的一套“土辦法”

我們知道,在進行ajax請求,是有可能遇到跨域的。例如我的專案就是在任何一個頁面插入iframe網站,然後有些操作就需要發請求了,這樣必然存在跨域問題。
然而,如果開發外掛還要開發者想辦法解決跨域問題,那chrome extension就太遜了,而且,跨不跨域,還不是瀏覽器自己的主意,是瀏覽器本身的安全策略。

所以,chrome extension為了保證自己的優越性,允許在自己的程式裡面,實現跨域請求,那完全的chrome extension程式,無非就是在background裡了。

因此,外掛要實現一些ajax請求,都得通通搬到background裡實現。這個事情,本身不是什麼重大發現。接下來要說的是,我利用這個特性,按照某個規則,實現一套方便的請求流程。

這裡以一個ifame網站嵌入到別人頁面的這類形式的chrome extension為例子。

在外掛生成的iframe網站裡

首先在這個外掛網站中,有一些按鈕操作本身是要觸發某些ajax請求的,但是由於上述原因,不能直接在外掛網站裡發請求,而是先向父視窗傳送訊息,利用postMessage。例如

window.parent.postMessage({
    from: 'extension-iframe',
    type: 'loadTable',
    data: {
        pageIndex: 1,
        pageSize: 10,
        sortProp: '',
        sortOrder: 0
    }
}, '*');
複製程式碼

by the way,這裡用window.parent.postMessage是為了解決iframe跨域通訊問題,當然如果是確保同域的情況下,其實可以直接用DOM操作告訴父視窗一些訊息。

言歸正傳,在postMessage第一個引數物件裡

屬性名 描述
from 標記這條訊息來自哪裡
type 操作的名稱,如傳送該message的操作目的是為了載入表格
data 傳送請求的data

在外掛的content script裡

監聽發來的訊息,這裡①標註的程式碼為前面說過的區分來源,這裡重點放在②部分的程式碼

window.addEventListener('message', function (event, a, b) {
    var responseData = event.data;
    if (!event.data) {
        return;
    }
    // 來自外掛內嵌網站的訊息
    if (responseData.from === 'extension-iframe') {
        // ① 判斷是否自己外掛的iframe
        var iframes = document.getElementsByClassName('extension-iframe');
        var extensionIframe = null;
        var correctSource = false;
        for (var i = 0; i < iframes.length; i++) {
            if (iframes[i].contentWindow && (event.source === iframes[i].contentWindow)) {
                correctSource = true;
                extensionIframe = iframes[i];
                break;
            }
        }
        if (!correctSource) {
            return;
        }
        // ② 載入表格、提交資訊、獲取未標註url條數
        // 該陣列為iframe傳來各個操作的名稱,對應發來的訊息的type屬性
        var operators = ['loadTable', 'submit', 'getNonMarkedCount', 'getUrl'];
        // 如果跟操作匹配上了,就轉發給background
        if (operators.indexOf(responseData.type) !== -1) {
            chrome.runtime.sendMessage({
                type: responseData.type,
                data: responseData.data
            },function (response) {
                // 返回請求後的資料給iframe網站
                extensionIframe.contentWindow.postMessage({
                    from: 'extension-content-script',
                    type:  responseData.type,
                    response: response
                }, '*');
            });
        }
    }
}, false);
複製程式碼

在外掛的background script裡

監聽剛轉發過來的訊息

// 這是所有請求組成物件
var httpService = {
    loadTable: function (config) {
        return eodHttp.get('/brandimageservice/perspective/mark', config);
    },
    submit: function (config) {
        return eodHttp.post('/brandimageservice/perspective/mark', config);
    },
    ...
};

// 監聽剛轉發過來的訊息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // 該陣列為iframe傳來各個操作的名稱,對應發來的訊息的type屬性
    var operators = ['loadTable', 'submit', 'getNonMarkedCount', 'getUrl'];
    if (operators.indexOf(request.type) !== -1) {
        // 這裡的type剛好與請求的屬性名一致
        httpService[request.purpose](request.data).then(res => {
            // 把請求的結果回傳給content script
            sendResponse(res);
        }).catch(e => {
            // 這裡做了請求攔截,如果不是canceled的請求報錯,則把報錯資訊也回傳給content script
            (e.status !== -1) && (sendResponse(e.data));
        });
    }
    // 此處return true是為了把sendResponse作為非同步處理。
    return true;
});
複製程式碼

再返回到外掛生成的iframe網站裡

這次繞回到這個ifame裡,最終的請求的資料還是會流回這裡。

 window.addEventListener('message', function (event, a, b) {
    let result = event.data;
    if (result && (result.from === 'extension-content-script') && (event.source === window.parent)) {
        // 以下為請求返回內容
        let res = result.response;
        // 載入表格資料
        if (result.type === 'loadTable') {...}
    }
});
複製程式碼

這樣,最終在iframe裡獲取到的請求資料還是跟之前我們平常開發調介面的情況是一樣的。

總結

整個流程是 iframe -> content script -> background -> content script -> iframe
以background為中分線,前半截為傳送請求,後半截為獲取請求資料。這裡巧妙的用法就是“type”這個欄位由始至終都一直存在,都代表一樣的意思。這樣的寫法的好處是,所有請求操作都可以共用這麼一個流程,改一下type區分一下操作即可。

檢測chrome extension是否已經安裝

有一些chrome extension,可能不單單是通過點選瀏覽器工具欄上的外掛圖示來啟用外掛,也有一些需求是通過點選網站上某個按鈕來啟用外掛(如自家的系統),那麼這時候第一步需要的是,檢測瀏覽器是否安裝了要求的chrome extension,如果沒有,進行提示等等。

在國內資料中進行搜尋,往往會看到很多條教用navigator物件來查詢安裝的外掛,可能是我太弱雞了,我發現並不能用來檢測到自己新增的chrome extension。於是我只能另尋他法了,如果有大神知道如何用navigator物件來判斷,麻煩指導一下。

思路

如果安裝了某個外掛,那麼該外掛的content script就會插入到頁面上(沒有content script的除外,但是一般沒有content script的外掛往往也沒有以上這樣的需求),因此判斷是否安裝了該外掛,就變為判斷content script是否插入到頁面上。

方法一

在content script裡寫這麼一個邏輯:往插入頁面生成一個html元素標記,如<div class="extension-flag"><div>。然後在插入頁面獲取這個元素,如果獲取到了,就證明content script存在了(不存在也就沒有這個元素了),就證明已經安裝了。

缺點: 建立的這個元素,一定要夠“特別”,越能確保其獨一無二越能證明是來自外掛的。什麼意思?假設剛好頁面也有一個類名跟建立的一樣的,那就要做進一步區分這到底是不是來自該外掛的了。

方法二

通過message機制,在合適的時機裡,頁面用postMessage傳送訊息給window,content script監聽window訊息,判斷如果是要求檢查是否安裝的訊息,則再用postMessage告知,只要收到這個訊息,就證明已經安裝了。

缺點:由於傳送訊息和接受訊息再到傳送訊息,這個過程是非同步的。所以要處理好何時傳送檢測的時機問題。

安裝的一些注意事項

安裝手段一般是有兩種的,一種在谷歌商店上進行線上安裝,一種是下載安裝包離線安裝。線上安裝沒什麼好說的,那麼說一下離線安裝。

離線安裝的關鍵是,你提供的下載包是什麼?

開發者在開發擴充套件的時候,往往是直接安裝在本地上的擴充套件所在資料夾。在chrome://extensions上開啟開發者模式,點選“載入已解壓的擴充套件程式”。這時候會發現第一次開啟瀏覽器的時候會老是提示你這個擴充套件不安全之類的。當然我們提供給使用者下載的安裝包肯定不能是這個了。

一開始我傻不拉幾的直接壓縮自己的開發的擴充套件所在資料夾,然後發到伺服器上給使用者下載,結果呢,使用者下載了,然後把壓縮包拖動到chrome://extension裡,發現chrome不允許安裝,說什麼基於安全什麼的。也就是我這個擴充套件可能不安全不給我裝。

後來才知道,不應該提供這種壓縮包,而是在自己釋出擴充套件的開發者資訊中心裡,把已釋出的擴充套件下載下來提供給使用者用才可以。

也許...只有我那麼傻吧

最後

最後的最後,我說一下小細節的注意項吧,稍微不留神,可能就這樣傻傻地寫下了bug了...

擴充套件之間很容易相互影響

怎麼理解?在通訊部分我講過,在傳遞訊息的時候,在訊息裡,我有用type欄位來標明傳遞的內容型別。在開發完擴充套件的時候,發現有些同事的電腦可以正常使用有些卻不行,後來除錯程式碼發現,在postMessage函式裡的type引數給別的擴充套件改造過了,受到了影響。

為什麼會這樣呢?原因是擴充套件都是通過嵌入自己的指令碼到別人的網頁裡,因此在一個網頁裡的程式碼,特別是傳遞訊息機制裡,更容易受到牽連。

程式碼除錯

對於content script的除錯,平常我們開啟F12選擇到source選項的時候,一般都會顯示在"page"下,其實可以看到,還有個content script的選擇,裡邊的就是各個擴充套件的內容指令碼了。

對於backgroud script的除錯,就在去到chrome://extensions頁下,找到對應的擴充套件,然後點選背景檢視,就可以看到backgroud script進行除錯了,而且,還能在控制檯呼叫chrome api呢。以及,請求也可以在這裡看到。

資訊傳遞的非同步性

在擴充套件中,會用到很多訊息傳遞,如上述的postMessage和chrome.runtime.sendMessage等之類的,大家一定要有一個觀念,他們的交流並不是同步,不是說我發了一個訊息過去,就馬上收到然後做接下來的處理。

所以我們寫邏輯的時候一定要注意,這種非同步性,會對你的邏輯處理產生什麼效果。特別是也要考慮到content script的插入時機是否對這些通訊產生一定影響,如content script都沒有準備好,就發了一些訊息,然後就沒有聲響了。


關於chrome extensions的基本介紹和開發思路就介紹到這裡,後續會有一篇文章專門來闡述一下,我專案中遇到的需求,遇到的問題以及對應的解決方案。

感興趣的可以關注一下,感覺文章寫得對你有幫助的話,請點下贊。

轉載請標註出處謝謝,寫文章不易。