Chrome插件開發總結

架構

  • 總括:

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

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

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

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

  // 可選
  "author": ...,
  "automation": ...,   
  
  //background script即插件運行的環境
  "background":{
        "page":"background.html", //page和scripts只能設置一個   
        "persistent": false   
        
        //scripts定義一個腳本文件的數組,chrome會在擴展啟動時自動創建一個包含所有指定腳本的頁面
        // "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": ...,
  
  //定義對頁面內容進行操作的腳本
  "content_scripts": [{  
         "matches": ["http://*/*","https://*/*"],//只在這些站點下 content_scripts會運行
         "js": ["js/jquery-1.9.1.min.js", "js/js.js"],   
         "run_at": "document_start",  //在document加載時執行該腳本,如果不指定,則在document加載完成后執行
    }] 
    
  "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"
  },
  
  //數組,聲明插件所需要的權限
  "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是插件的運行環境,姑且叫做主程序。一旦插件被啟用,chrome就會為該插件開辟一個獨立運行環境,用來執行background script。若設置了scripts字段,瀏覽器的擴展系統會自動根據scripts字段指定的所有js文件自動生成背景頁。也可以直接page字段,指定背景頁。兩者只能設置一個
    • 一般情況下,我們會讓將擴展的主要邏輯都放在 background 中比較便于管理。其它頁面可以通過消息傳遞的機制與 background 進行通訊。理論上 content script 與 popup 之間也可以傳遞消息,但不建議這么做。
    • 包含background.html和background.js兩個文件,包括如下兩種:
      • Persistent background pages:一直開啟
      • Event pages:需要時開啟,可通過將persistent設置為false來設置
        • 開啟時機:
          • 首次安裝或更新完版本時
          • 觸發特定事件時
          • content script 向其發送消息時
          • 其他頁面(例如popup)調用runtime.getBackgroundPage
        • 注冊到Manifest中:
        {
         "name": "My extension",
         ...
         "background": {
           "scripts": ["eventPage.js"],
           "persistent": false
          },
         ...
         }
  • UI Page

    • popup:

      • popup.html和popup.js:popup和background page在同一個運行環境中,均在插件主程序中。也就是說popup可以通過特定的方式調background里的方法。注:出于安全考慮,javascript必須與html分開存放且寫在html里的js無效。
    • option:

  • Content Scripts

    Content Scripts相當于一個對當前瀏覽頁面(符合matches定義的模式)的補充腳本,稱作內容腳本,與popup不同,內容腳本幾乎與主程序運行環境相隔離、獨立。與主程序的交互只能通過發送消息的方式進行。
    • CanDo:
      可以訪問和修改單前瀏覽頁面的DOM,但不能訪問該頁面腳本定義的變量和方法,并且不會和該頁面原有js的方法和變量沖突。
    • CanNot:
      • 不能使用chrome獨有API,除了以下幾點:
        • extension(getURL,inIncognitoContext,lastError,onRequest,sendRequest)
        • i18n
        • runtime ( connect , getManifest , getURL , id , onConnect , onMessage , sendMessage )
        • storage
      • 不能使用插件里其他頁面定義的變量和方法
    • 向訪問的頁面注入腳本,訪問和修改DOM的方式:
      只有為要訪問的頁面賦予權限才可以,需在Manifest中添加permissions節點。
      向頁面插入腳本的API:
    • 注入腳本:chrome.tabs.executeScript,如下:
    //將訪問頁面的背景變為紅色
    chrome.browserAction.onClicked.addListener(function(tab) {
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="red"'
      });
    });
  • 注入CSS:chrome.tabs.insertCSS
  • 注冊到Manifest中
    {
      "name": "My extension",
      ...
      "content_scripts": [
        {
          "matches": ["http://www.google.com/*"],//只有在符合該pattern的站點才會運行
          "css": ["mystyles.css"],
          "js": ["jquery.js", "myscript.js"]
        }
      ],
      //為以下頁面賦予訪問并向注入腳本的權限
      "permissions": [
        "tabs", "http://www.google.com/*",
        "activeTab"
      ],
      ...
    }
  • 消息傳遞:
    雖然內容腳本的執行環境和托管它們的頁面是彼此隔離的,但它們共享對頁面DOM的訪問權限,如果頁面希望與內容腳本(或通過內容腳本進行擴展)進行通信,那么它必須通過共享的DOM進行。示例如下:
      //訪問的頁面:
      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);

插件頁面間通訊:

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

  • 簡單的單次請求

    • 內容腳本到主程序:
chrome.extension.sendMessage({hello: "Cissy"}, function(response) {
    console.log(response.farewell);
});
  • 主程序到內容腳本
chrome.tabs.query({active:true}, function(tab) {
    chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
        console.log(response.farewell);
    });
});
  • 接收消息
    chrome.extension.sendMessage()向擴展內的其它監聽者發送一條消息。此消息發送后會觸發擴展內每個頁面的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"});
});
  • 長連接

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

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

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

數據保存及隱身模式

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

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

調試

插件各部分的調試方式各不相同,具體如下:

  • Content script:直接F12打開開發者工具,在Sources欄下的Content scripts即可調試
  • background:在chrome的擴展程序設置頁面chrome://extention 下找到相應的插件,點擊檢查視圖即可調試
  • popup:右擊工具欄上插件的圖標,選擇審查彈出內容即可調試

Chrome API

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

  • 同步方法vs異步方法
    例:
    同步方法:返回string的chrome.runtime.getURL()方法
    異步方法:
   //獲取當前頁面,并修改頁面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():向訪問頁面注入腳本

最后附上一個我自己用的兩個小demo,一個可以自動調整簡書文章頁面的寬度,一個可以修改頁面樣式:
簡書調寬:https://github.com/rascalquan/JianShuNoteWidth

簡書調寬.png

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

Paste_Image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容