Chrome插件開(kāi)發(fā)總結(jié)

架構(gòu)

  • 總括:

    • Manifest:程序清單
    • Background:插件運(yùn)行環(huán)境/主程序
    • Pop up:彈出頁(yè)面
    • Content scripts:與瀏覽頁(yè)面交互的腳本
  • Manifest

    每一個(gè)擴(kuò)展都有一個(gè)JSON格式的manifest文件,叫manifest.json,相當(dāng)于程序清單,其中定義了插件的一些元數(shù)據(jù)等信息,示例如下:
{
  // 必須添加
  "manifest_version": 2, //固定的,定義manifest版本信息
  "name": "My Extension", //插件名稱
  "version": "1.0", //插件本身的版本

  // 推薦添加
  "default_locale": "en",
  "description": "This is my first extension.", //插件的描述
  "icons": {...},

  //browser_action和page_action只能添加一個(gè)
  "browser_action": { //瀏覽器級(jí)別行為,所有頁(yè)面均生效
        "default_icon": "cc.gif",//圖標(biāo)的圖片
        "default_title": "Hello CC", //鼠標(biāo)移到圖標(biāo)顯示的文字 
        "default_popup": "popup.html" //單擊圖標(biāo)后彈窗頁(yè)面
    }, 
  "page_action":{ //頁(yè)面級(jí)別的行為,只在特定頁(yè)面下生效 
        "default_icon":{
            "24":"icon_24.png",
            "38":"icon_38.png"
            },
        "default_popup": "popup.html",
        "default_title":"MyTitle"
    },

  // 可選
  "author": ...,
  "automation": ...,   
  
  //background script即插件運(yùn)行的環(huán)境
  "background":{
        "page":"background.html", //page和scripts只能設(shè)置一個(gè)   
        "persistent": false   
        
        //scripts定義一個(gè)腳本文件的數(shù)組,chrome會(huì)在擴(kuò)展啟動(dòng)時(shí)自動(dòng)創(chuàng)建一個(gè)包含所有指定腳本的頁(yè)面
        // "scripts": ["js/jquery-1.9.1.min.js","js/background.js"]
    },
  "background_page": ...,
  "chrome_settings_overrides": {...},
  "chrome_ui_overrides": {
    "bookmarks_ui": {
      "remove_bookmark_shortcut": true,
      "remove_button": true
    }
  },
  "chrome_url_overrides": {...},
  "commands": {...},
  "content_capabilities": ...,
  
  //定義對(duì)頁(yè)面內(nèi)容進(jìn)行操作的腳本
  "content_scripts": [{  
         "matches": ["http://*/*","https://*/*"],//只在這些站點(diǎn)下 content_scripts會(huì)運(yùn)行
         "js": ["js/jquery-1.9.1.min.js", "js/js.js"],   
         "run_at": "document_start",  //在document加載時(shí)執(zhí)行該腳本,如果不指定,則在document加載完成后執(zhí)行
    }] 
    
  "content_security_policy": "policyString",
  "converted_from_user_script": ...,
  "current_locale": ...,
  "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": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
  "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"
  },
  
  //數(shù)組,聲明插件所需要的權(quán)限
  "permissions": [ 
        "http://*/", 
        "bookmarks", 
        "tabs", 
        "history",
        "activeTab",
        "storage"
    ], 
  "platforms": ...,
  "plugins": [...],
  "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 Page

    • background是插件的運(yùn)行環(huán)境,姑且叫做主程序。一旦插件被啟用,chrome就會(huì)為該插件開(kāi)辟一個(gè)獨(dú)立運(yùn)行環(huán)境,用來(lái)執(zhí)行background script。若設(shè)置了scripts字段,瀏覽器的擴(kuò)展系統(tǒng)會(huì)自動(dòng)根據(jù)scripts字段指定的所有js文件自動(dòng)生成背景頁(yè)。也可以直接page字段,指定背景頁(yè)。兩者只能設(shè)置一個(gè)。
    • 一般情況下,我們會(huì)讓將擴(kuò)展的主要邏輯都放在 background 中比較便于管理。其它頁(yè)面可以通過(guò)消息傳遞的機(jī)制與 background 進(jìn)行通訊。理論上 content script 與 popup 之間也可以傳遞消息,但不建議這么做。
    • 包含background.html和background.js兩個(gè)文件,包括如下兩種:
      • Persistent background pages:一直開(kāi)啟
      • Event pages:需要時(shí)開(kāi)啟,可通過(guò)將persistent設(shè)置為false來(lái)設(shè)置
        • 開(kāi)啟時(shí)機(jī):
          • 首次安裝或更新完版本時(shí)
          • 觸發(fā)特定事件時(shí)
          • content script 向其發(fā)送消息時(shí)
          • 其他頁(yè)面(例如popup)調(diào)用runtime.getBackgroundPage時(shí)
        • 注冊(cè)到Manifest中:
        {
         "name": "My extension",
         ...
         "background": {
           "scripts": ["eventPage.js"],
           "persistent": false
          },
         ...
         }
  • UI Page

    • popup:

      • popup.html和popup.js:popup和background page在同一個(gè)運(yùn)行環(huán)境中,均在插件主程序中。也就是說(shuō)popup可以通過(guò)特定的方式調(diào)background里的方法。注:出于安全考慮,javascript必須與html分開(kāi)存放且寫(xiě)在html里的js無(wú)效。
    • option:

  • Content Scripts

    Content Scripts相當(dāng)于一個(gè)對(duì)當(dāng)前瀏覽頁(yè)面(符合matches定義的模式)的補(bǔ)充腳本,稱作內(nèi)容腳本,與popup不同,內(nèi)容腳本幾乎與主程序運(yùn)行環(huán)境相隔離、獨(dú)立。與主程序的交互只能通過(guò)發(fā)送消息的方式進(jìn)行。
    • CanDo:
      可以訪問(wèn)和修改單前瀏覽頁(yè)面的DOM,但不能訪問(wèn)該頁(yè)面腳本定義的變量和方法,并且不會(huì)和該頁(yè)面原有js的方法和變量沖突。
    • CanNot:
      • 不能使用chrome獨(dú)有API,除了以下幾點(diǎn):
        • extension(getURL,inIncognitoContext,lastError,onRequest,sendRequest)
        • i18n
        • runtime ( connect , getManifest , getURL , id , onConnect , onMessage , sendMessage )
        • storage
      • 不能使用插件里其他頁(yè)面定義的變量和方法
    • 向訪問(wèn)的頁(yè)面注入腳本,訪問(wèn)和修改DOM的方式:
      只有為要訪問(wèn)的頁(yè)面賦予權(quán)限才可以,需在Manifest中添加permissions節(jié)點(diǎn)。
      向頁(yè)面插入腳本的API:
    • 注入腳本:chrome.tabs.executeScript,如下:
    //將訪問(wèn)頁(yè)面的背景變?yōu)榧t色
    chrome.browserAction.onClicked.addListener(function(tab) {
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="red"'
      });
    });
  • 注入CSS:chrome.tabs.insertCSS
  • 注冊(cè)到Manifest中
    {
      "name": "My extension",
      ...
      "content_scripts": [
        {
          "matches": ["http://www.google.com/*"],//只有在符合該pattern的站點(diǎn)才會(huì)運(yùn)行
          "css": ["mystyles.css"],
          "js": ["jquery.js", "myscript.js"]
        }
      ],
      //為以下頁(yè)面賦予訪問(wèn)并向注入腳本的權(quán)限
      "permissions": [
        "tabs", "http://www.google.com/*",
        "activeTab"
      ],
      ...
    }
  • 消息傳遞:
    雖然內(nèi)容腳本的執(zhí)行環(huán)境和托管它們的頁(yè)面是彼此隔離的,但它們共享對(duì)頁(yè)面DOM的訪問(wèn)權(quán)限,如果頁(yè)面希望與內(nèi)容腳本(或通過(guò)內(nèi)容腳本進(jìn)行擴(kuò)展)進(jìn)行通信,那么它必須通過(guò)共享的DOM進(jìn)行。示例如下:
      //訪問(wèn)的頁(yè)面:
      document.getElementById("theButton").addEventListener("click",
    function() {
    window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
    }, false);
    
    //content_scripts腳本;
    var port = chrome.runtime.connect();
    
    window.addEventListener("message", function(event) {
    // We only accept messages from ourselves
    if (event.source != window)
      return;
    
    if (event.data.type && (event.data.type == "FROM_PAGE")) {
      console.log("Content script received: " + event.data.text);
      port.postMessage(event.data.text);
      }
    }, false);

插件頁(yè)面間通訊:

插件各個(gè)部分之間的通訊有如下兩種模式:

  • 簡(jiǎn)單的單次請(qǐng)求

    • 內(nèi)容腳本到主程序:
chrome.extension.sendMessage({hello: "Cissy"}, function(response) {
    console.log(response.farewell);
});
  • 主程序到內(nèi)容腳本
chrome.tabs.query({active:true}, function(tab) {
    chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
        console.log(response.farewell);
    });
});
  • 接收消息
    chrome.extension.sendMessage()向擴(kuò)展內(nèi)的其它監(jiān)聽(tīng)者發(fā)送一條消息。此消息發(fā)送后會(huì)觸發(fā)擴(kuò)展內(nèi)每個(gè)頁(yè)面的chrome.extension.onMessage()事件。
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"});
});
  • 長(zhǎng)連接

    • background 和 popup
      background和popup運(yùn)行在同一個(gè)進(jìn)程中,都是運(yùn)行在主程序中的,所以background 和 popup 之間可以直接相互調(diào)用對(duì)方的方法,不需要消息傳遞。
      • popup調(diào)用background中變量或方法
var bg = chrome.extension.getBackgroundPage();//獲取background頁(yè)面
console.log(bg.a);//調(diào)用background的變量或方法。

注: 使用chrome.extension.getBackgroundPage()時(shí)要確保此時(shí)的backgroundpage在運(yùn)行,否則會(huì)返回null,解決方案:
- 將manifest的background的persistent屬性值設(shè)置為true,確保backgroundpage處于常開(kāi)狀態(tài)(我使用的該方法);

"background": {
        "scripts": [
            "background.js"
        ],
        "persistent": true
    }
  - 使用異步的 chrome.runtime.getBackgroundPage代替(該方法沒(méi)測(cè)試)
- background調(diào)用popup中變量或方法
  background是一個(gè)運(yùn)行在擴(kuò)展進(jìn)程中的HTML頁(yè)面。它在你的擴(kuò)展的整個(gè)生命周期都存在,而popup是在你點(diǎn)擊了圖標(biāo)之后才存在,所以,在獲取popup變量時(shí),請(qǐng)確認(rèn)popup已打開(kāi)。
var pop = chrome.extension.getViews({type:'popup'});//獲取popup頁(yè)面
console.log(pop[0].b);//調(diào)用第一個(gè)popup的變量或方法。
  • background 和 content
    持續(xù)長(zhǎng)時(shí)間的保持會(huì)話需要在content script和擴(kuò)展建立一個(gè)長(zhǎng)時(shí)間存在的通道。當(dāng)建立連接,兩端都有一個(gè)runtime.Port 對(duì)象通過(guò)這個(gè)連接發(fā)送和接收消息。
    • 內(nèi)容腳本發(fā)送消息到擴(kuò)展程序
var bac = chrome.extension.connect({name: "ConToBg"});//建立通道,并給通道命名
bac.postMessage({hello: "Cissy"});//利用通道發(fā)送一條消息。
- 擴(kuò)展程序發(fā)送消息到內(nèi)容腳本
var cab = chrome.tabs.connect(tabId, {name: "BgToCon"});//建立通道,指定tabId,并命名
    cab.postMessage({ hello: "Cissy"});//利用通道發(fā)送一條消息。
- 接收消息
接收消息為了處理正在等待的連接,需要用chrome.extension.onConnect 事件監(jiān)聽(tīng)器,對(duì)于content script或者擴(kuò)展頁(yè)面,這個(gè)方法都是一樣的
chrome.extension.onConnect.addListener(function(bac) {//監(jiān)聽(tīng)是否連接,bac為Port對(duì)象
    bac.onMessage.addListener(function(msg) {//監(jiān)聽(tīng)是否收到消息,msg為消息對(duì)象
        console.log(msg.hello);
    })
})

數(shù)據(jù)保存及隱身模式

可以使用HTML5的localStorage或保存到服務(wù)器,但是,無(wú)論何種方式,盡量保證在隱身模式的情況下不要保存任何數(shù)據(jù),或僅將數(shù)據(jù)保存在內(nèi)存中,如下:

function saveTabData(tab, data) {
  if (tab.incognito) {
    chrome.runtime.getBackgroundPage(function(bgPage) {
      bgPage[tab.url] = data;      // 匿名模式,保存為當(dāng)前頁(yè)面的數(shù)據(jù),只在內(nèi)存中
    });
  } else {
    localStorage[tab.url] = data;  // 保存到本地
  }
}

調(diào)試

插件各部分的調(diào)試方式各不相同,具體如下:

  • Content script:直接F12打開(kāi)開(kāi)發(fā)者工具,在Sources欄下的Content scripts即可調(diào)試
  • background:在chrome的擴(kuò)展程序設(shè)置頁(yè)面chrome://extention 下找到相應(yīng)的插件,點(diǎn)擊檢查視圖即可調(diào)試
  • popup:右擊工具欄上插件的圖標(biāo),選擇審查彈出內(nèi)容即可調(diào)試

Chrome API

除了web本身的API以外,Chrome插件還支持一些獨(dú)有的API可供使用

  • 同步方法vs異步方法
    例:
    同步方法:返回string的chrome.runtime.getURL()方法
    異步方法:
   //獲取當(dāng)前頁(yè)面,并修改頁(yè)面url
   chrome.tabs.query({'active': true}, function(tabs) {
   chrome.tabs.update(tabs[0].id, {url: newUrl});
   });
  • 常用API:
    • chrome.extension.getURL():獲取插件文件的URL,
      例:chrome.extension.getURL("images/myimage.png");獲取文件myimage.png的URL
    • chrome.tabs.executeScript():向訪問(wèn)頁(yè)面注入腳本

最后附上一個(gè)我自己用的兩個(gè)小demo,一個(gè)可以自動(dòng)調(diào)整簡(jiǎn)書(shū)文章頁(yè)面的寬度,一個(gè)可以修改頁(yè)面樣式:
簡(jiǎn)書(shū)調(diào)寬:https://github.com/rascalquan/JianShuNoteWidth

簡(jiǎn)書(shū)調(diào)寬.png

CSS修改器:https://github.com/rascalquan/ChangePageStyle

Paste_Image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容