<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端拓展:如何開(kāi)發(fā)一個(gè) Chrome 插件?

          共 17993字,需瀏覽 36分鐘

           ·

          2021-03-02 15:52

          什么是瀏覽器插件?

          簡(jiǎn)單來(lái)說(shuō)瀏覽器插件,是瀏覽器上的一種工具,可以提供一些瀏覽器沒(méi)有的功能,幫你做一些有趣的事情。開(kāi)發(fā)者可以根據(jù)自己的喜歡,去實(shí)現(xiàn)一些功能。插件基于Web技術(shù)(html、css、js)構(gòu)建。

          舉個(gè)栗子??

          FeHelper.JSON插件


          功能:格式化JSON、編碼轉(zhuǎn)化、markdown、代碼壓縮等功能。

          二維碼生成器


          功能:可以根據(jù)當(dāng)前瀏覽的網(wǎng)頁(yè)地址,生成一個(gè)二維碼。

          SwitchyOmega Proxy


          功能:你懂的。

          Hello World

          manifest.json

          Chrome 瀏覽器插件沒(méi)有嚴(yán)格的文件結(jié)構(gòu)約束,只需要保證文件夾根目錄有 manifest.json 文件**,**該文件的內(nèi)容會(huì)概括插件所需的資源、權(quán)限等等。

          一個(gè)段簡(jiǎn)單的示例:

          {
              "manifest_version": 2, // 必填
              "name""my-plugin", // 必填
              "version""0.1.0" // 必填
          }

          manifest_version:代表了manifest文件的版本,瀏覽器會(huì)根據(jù)這個(gè)值去指定該版本擁有的功能。

          name:插件的名稱。

          version:插件版本。

          將manifest.json文件放到一個(gè)文件夾內(nèi)。

          chrome://extensions/

          在瀏覽器地址欄輸入chrome://extensions/打開(kāi)“拓展程序”頁(yè)面。

          注意:需要啟用右上角的 “開(kāi)發(fā)者模式” 才能加載已解壓的插件文件:


          加載已解壓的插件

          啟用之后點(diǎn)擊加載已解壓的拓展程序,選擇剛剛我們放入了manifest.json的文件夾,之后你會(huì)看到:

          新增了一個(gè)我們剛剛添加的插件,而且瀏覽器右上角也會(huì)有我們的一個(gè)圖標(biāo):

          此時(shí)已經(jīng)加載了一個(gè)插件了,但是這個(gè)插件除了占用瀏覽器的一個(gè)位置除外,沒(méi)有任何作用。

          如果沒(méi)有設(shè)置插件圖標(biāo),那么插件的第一個(gè)字符會(huì)成為插件的默認(rèn)icon。

          讓插件看起來(lái)更“插件”一點(diǎn)

          為了讓這個(gè)插件更“完善”一點(diǎn),我們給它加一個(gè)icon和描述,并且點(diǎn)擊出現(xiàn)一個(gè)popup頁(yè)面,popup 頁(yè)面一般用來(lái)承載臨時(shí)性的交互,且生命周期很短:?jiǎn)螕魣D標(biāo)打開(kāi)popup,焦點(diǎn)離開(kāi)又立即關(guān)閉,可以通過(guò)default_popup字段來(lái)定義。

          {
            .....

            "description""這是一段描述",
            // 插件管理頁(yè)面的icon
            "icons": {
              "84""./icon/ball.png"
            },
            // 瀏覽器右上角的圖標(biāo)和內(nèi)容
            "browser_action": {
              "default_icon""./icon/ball.png",
              "default_title""我的插件",
              "default_popup""./html/popup.html"
            }
          }

          此時(shí)我們的目錄結(jié)構(gòu)也變成了這樣:

          給popup.html加上內(nèi)容:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <title>my-plugin</title>
          </head>
          <body>
              <p style="width: 200px;text-align:center;">hello world!!</p>
          </body>
          </html>

          之后,我們點(diǎn)擊插件右下角的“刷新”按鈕:

          你會(huì)發(fā)現(xiàn)插件有了icon和描述:

          并且右上角的icon也變了,點(diǎn)擊一下,會(huì)彈出我們剛剛編寫(xiě)的popup.html頁(yè)面:

          現(xiàn)在,我們一個(gè)“完整”的插件就已經(jīng)做好了。

          manifest.json 配置介紹

          background

          {
              ...
              "background": {
                  // 提供一個(gè)頁(yè)面給background
                  "page""./html/background.html"
                  // 或者若干個(gè)js文件,后臺(tái)會(huì)默認(rèn)生成一個(gè)空白的html
                  "scripts": ["./js/background.js"]
              }
          }

          background配置項(xiàng),為插件的后臺(tái)常駐頁(yè)面,生命周期隨著瀏覽器的生命周期一樣,瀏覽器一啟動(dòng),后臺(tái)頁(yè)面就會(huì)開(kāi)始運(yùn)行,直到瀏覽器被關(guān)閉;或者在插件管理頁(yè)面,將該插件禁用了,后臺(tái)頁(yè)面也會(huì)停止運(yùn)行。

          另外,background擁有的權(quán)限比較高,幾乎可以調(diào)用所有的Chrome擴(kuò)展API(除了devtools),同時(shí)擁有直接跨域的能力。

          page:指定一個(gè)網(wǎng)頁(yè)為后臺(tái)頁(yè)面。

          scripts:指定若干個(gè)js文件,后臺(tái)會(huì)自動(dòng)生成一個(gè)html,并按順序調(diào)用這些js文件。

          注意:pagescripts 選項(xiàng)只能二選一,不然會(huì)報(bào)錯(cuò)。

          配置好之后,屬性插件,會(huì)出現(xiàn)一個(gè)背景頁(yè)選項(xiàng):

          我使用的是一個(gè)background.js文件:
          function _back() {
            console.log('background.js')
          }
          console.log('running...')

          點(diǎn)進(jìn)去看看里面裝的什么玩意:

          沒(méi)錯(cuò),是一個(gè)普通的后臺(tái)頁(yè)面,如果background.js和其他頁(yè)面有通信,則可以在這里進(jìn)行查看請(qǐng)求或者調(diào)試代碼。

          如果使用page選項(xiàng),打開(kāi)也是這個(gè)樣子。

          另外:由于background是一直在后臺(tái)運(yùn)行的,為了優(yōu)化性能,可以增加一個(gè)配置:

          {
              ...
              "background": {
                  ...
                  "persistent"false
              }
          }

          這樣,插件就會(huì)在被需要時(shí)加載,在空閑時(shí)被關(guān)閉。比如安裝、更新插件的時(shí)候,或者有其他頁(yè)面與background通信的時(shí)候才會(huì)被加載。

          content-scripts

          content-scripts能夠在合適的時(shí)機(jī)(頁(yè)面載入前、載入后、空閑時(shí))注入腳本,允許內(nèi)容腳本更改其JavaScript環(huán)境,而不與頁(yè)面或其他內(nèi)容腳本發(fā)生沖突。

          例如,原頁(yè)面有個(gè)按鈕,并且給按鈕添加了一個(gè)點(diǎn)擊事件:

          <html>
              <button id="mybutton">click me</button>
              <script>
                var greeting = "hello, ";
                var button = document.getElementById("mybutton");
                button.person_name = "Bob";
                button.addEventListener("click"function() {
                  alert(greeting + button.person_name + ".");
                }, false);
              </script>
            </html>

          在content-scripts中,加入以下代碼:

          var greeting = "hola, ";
          var button = document.getElementById("mybutton");
          button.person_name = "Roberto";
          button.addEventListener("click"function() {
          alert(greeting + button.person_name + ".");
          }, false);

          當(dāng)頁(yè)面運(yùn)行之后,腳本內(nèi)容也會(huì)在插件定義的時(shí)間運(yùn)行,當(dāng)頁(yè)面點(diǎn)擊按鈕時(shí),會(huì)出現(xiàn)兩次彈窗。

          content-scripts配置:

          {
              ...
              "content_scripts": [
                  {
                    // 在匹配的URL中運(yùn)行,<all_urls>表示所有的URL都會(huì)運(yùn)行。
                    "matches": ["<all_urls>"],
                    // 注入的js,會(huì)按順序運(yùn)行。
                    "js": ["./js/content.js"],
                    // css引入需謹(jǐn)慎,因?yàn)榭赡軙?huì)影響全局的樣式,同樣也能接收多個(gè)css文件,會(huì)按順序插入到頁(yè)面中
                    "css": ["./css/style.css"],
                    // 代碼注入的時(shí)機(jī),可選值: "document_start""document_end", or "document_idle",最后一個(gè)表示頁(yè)面空閑時(shí),默認(rèn)document_idle
                    "run_at""document_start"
                  },
                  {
                    "matches": ["https://www.baidu.com/"],
                    "js": ["./js/other.js"],
                    "run_at""document_start"
                  }
                ],
              ...
          }

          content.js代碼如下:

          console.log('hello, from content.js');

          other.js代碼如下:

          console.log('hello, from other.js...')

          更新插件,當(dāng)在 https://bytedance.feishu.cn/drive/home/運(yùn)行時(shí):

          因?yàn)椤?https://bytedance.feishu.cn/drive/home/】只匹配到了<all_urls>的規(guī)則,所以之后運(yùn)行content.js

          當(dāng)在https://www.baidu.com/運(yùn)行時(shí):

          同時(shí)命中了2個(gè)規(guī)則,所以content.js和other.js都會(huì)運(yùn)行,順序也是正確的。

          content-scripts 和原始頁(yè)面共享DOM,但是不共享JS,如要訪問(wèn)頁(yè)面JS(例如某個(gè)JS變量),只能通過(guò)inject-scripts來(lái)實(shí)現(xiàn)。content-scripts能夠訪問(wèn)的Chrome API的權(quán)限也比較低,只能訪問(wèn)以下四個(gè)API:

          • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
          • chrome.i18n
          • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
          • chrome.storage

          Inject-scripts

          inject-scripts 是通過(guò)DOM操作插入的JS代碼,通常在content-scripts只能操作DOM,但是卻無(wú)法訪問(wèn)頁(yè)面的JS,借助content-scripts可以操作DOM的能力,往頁(yè)面中插入JS文件,給頁(yè)面提供調(diào)用插件API的能力,以及和background通信的能力。

          在插入之前,需配置一下web可訪問(wèn)的資源,同時(shí)content-scripts的調(diào)用時(shí)機(jī)換成"document_end"或者"document_idle",不然會(huì)無(wú)法獲取DOM,導(dǎo)致插入失敗。在manifest.json中添加以下內(nèi)容:

          {
              ...
              "content_scripts": [
                  {
                    "matches": ["<all_urls>"],
                    "js": ["./js/content.js"],
                    "run_at""document_end"
                  },
                  ...
               ],
              "web_accessible_resources": ["js/inject.js"],
              ...
          }

          inject.js的內(nèi)容如下:

          function mockApi () {
            console.log('this is from inject.js')
          }

          content.js增加以下代碼:

          (function () {
            let path = 'js/inject.js';
            let script = document.createElement('script');
            script.setAttribute('type''text/javascript');
            // 注意,路徑需用Chrome API 生成,這個(gè)方法可以獲得插件的資源的真實(shí)路徑。
            // 類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
            script.src = chrome.extension.getURL(path);
            script.onload = function () {
              // 在執(zhí)行完代碼之后移除script標(biāo)簽
              this.parentNode.removeChild(this);
            }
            document.body.appendChild(script);
          })();

          更新插件后,頁(yè)面就可以訪問(wèn)inject.js的方法:

          permissions

          插件后臺(tái)有的操作需要配置相應(yīng)的權(quán)限,例如本地存儲(chǔ)、網(wǎng)絡(luò)請(qǐng)求、通知等等,示例:

          {
              ...
              "permissions": [
                  "contextMenus", // 右鍵菜單
                  "tabs", // 標(biāo)簽
                  "notifications", // 通知
                  "webRequest", // web請(qǐng)求
                  "webRequestBlocking",
                  "storage" // 插件本地存儲(chǔ)
              ],
              ...
          }

          完整的manifest配置

          官方文檔:https://developer.chrome.com/extensions/manifest

          通信

          popup和background通信

          popup可以通過(guò) chrome.extension.getBackgroundPage() API 直接獲取到background的上下文,從而調(diào)用background的方法來(lái)通信:

          // popup.js
          var backend = chrome.extension.getBackgroundPage();
          backend.test(); // 訪問(wèn)bbackground的函數(shù)

          background可以通過(guò)chrome.extension.getViews({type:'popup'}) 獲取到popup的上下文,前提是popup頁(yè)面是打開(kāi)的狀態(tài)下。

          let views = chrome.extension.getViews({type:'popup'});
          let popup = null
          if(views.length > 0) {
              popup = views[0];
              // 直接訪問(wèn)popup的函數(shù)
              popup.test();
          }

          這里需要注意一點(diǎn):

          在popup頁(yè)面,你如果想編寫(xiě)js,請(qǐng)將js編寫(xiě)在一個(gè)文件里面,然后引入進(jìn)來(lái),不然會(huì)報(bào)錯(cuò),這是因?yàn)镃hrome的安全政策規(guī)定的:https://developer.chrome.com/extensions/contentSecurityPolicy

          popup錯(cuò)誤示范:

          <!DOCTYPE html>
          <html lang="en">
          <head>
             ...
          </head>
          <body>
              <p style="width: 200px;text-align:center;">hello world!!</p>
              <script>
                  // 不能直接在里面寫(xiě)
              </script>
          </body>
          </html>

          正確姿勢(shì):

          <!DOCTYPE html>
          <html lang="en">
          <head>
              ...
          </head>
          <body>
              <p style="width: 200px;text-align:center;">hello world!!</p>
              <script src="../js/popup.js"></script>
          </body>
          </html>

          content-scripts和background通信

          content-scripts可以通過(guò) chrome.runtime.sendMessage(message) 給background發(fā)送消息:

          chrome.runtime.sendMessage('message content', (res) => {
              console.log('from background:', res)
          });

          background通過(guò)chrome.runtime.onMessage.addListener()監(jiān)聽(tīng)content-scripts發(fā)送的消息:

          chrome.runtime.onMessage.addListener(function(message, sender, callback) {
             console.log(mesasge); // meesage content
             callback && callback('yes this from background')
          });

          background主動(dòng)給content-scripts發(fā)消息,首先得查找要給哪個(gè)tab發(fā)消息,使用chrome.tabs.query 這個(gè)方法查找到tab,再使用chrome.tabs.sendMessage 方法給tab發(fā)消息:

          // {active: true, currentWindow: true} 表示查找當(dāng)前屏幕下的active狀態(tài)的tab;
          chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
              chrome.tabs.sendMessage(tabs[0].id, 'message content', (res) => {
                  console.log('from content:', res)
              });
          });

          content-scripts通過(guò)chrome.runtime.onMessage.addListener 去監(jiān)聽(tīng)事件:

          chrome.runtime.onMessage.addListener(function (message, sender, callback) {
              console.log(message, sender)
              callback && callback('yes this from content')
          });

          注意:

          1.消息內(nèi)容可以直接發(fā)送JSON格式的對(duì)象。

          2.popup和content的通信方式與上面一樣。

          3.如果popup和background都監(jiān)聽(tīng)了從content發(fā)來(lái)的消息,兩者都能收到監(jiān)聽(tīng)消息,但是callback只會(huì)觸發(fā)一次,被誰(shuí)觸發(fā)取決與誰(shuí)先發(fā)送。

          inject-scripts和content-scripts

          inject-scripts和content-scripts通信有兩種方法:

          1.window.postMessage發(fā)送,window.addEventListener接收

          2.還有一種是自定義的DOM事件;

          但是很少情況會(huì)是content-scripts去調(diào)inject-scripts,因?yàn)椋梢裕菦](méi)必要....content-scripts完全可以自己處理一些API的事件監(jiān)聽(tīng),況且inject-scripts也只是content-scripts生成并插入到DOM里面的,所以在content-scripts眼里,inject-scripts就是個(gè)弟弟...

          但是,很多用戶觸發(fā)的事件,需要通過(guò)inject-scripts告訴content-scripts,content-scripts再給background通信并且去做一些事情,然后再發(fā)消息告訴inject-scripts,從這個(gè)角度看:content-scripts就是一個(gè)inject-scripts的工具人!

          (扯平了,完美。)

          inject-scripts給content-scripts發(fā)消息:

          window.postMessage({"test"'你好!工具人!'}, '*');

          content-scripts接收消息:

          window.addEventListener("message"function(message) {
              console.log('來(lái)了老弟!', message.data);
          }, false);

          同樣的,content-scripts給inject-scripts發(fā)消息是一樣的。

          練練手:HTTP Header 插件

          實(shí)現(xiàn)一個(gè)HTTP Header 插件,可以實(shí)現(xiàn)動(dòng)態(tài)添加header,并且給網(wǎng)絡(luò)請(qǐng)求自動(dòng)加上header,header參數(shù)可以配置。

          示例圖:

          Background 功能設(shè)計(jì)

          background復(fù)制存儲(chǔ)、操作headers,對(duì)所有瀏覽器請(qǐng)求做一層攔截,并加上啟用的headers。

          注意:因?yàn)樯婕暗骄W(wǎng)絡(luò)請(qǐng)求,所以需在manifest.json中添加權(quán)限:

          {
              ...
              "permissions": [
                  "storage", // 本地存儲(chǔ)
                  "webRequest", // 網(wǎng)絡(luò)請(qǐng)求
                  "webRequestBlocking", // 網(wǎng)絡(luò)請(qǐng)求 阻塞式
                  "<all_urls>" // 匹配的URL
              ]
              ...
          }

          Background 功能偽代碼:

          // headers數(shù)據(jù)結(jié)構(gòu), 附帶默認(rèn)值;(可以改為本地存儲(chǔ))。
          const headers = [
              {
              key: 'Content-Type',
              value: 'application/x-www-form-urlencoded',
              enablefalse,
            },
            {
              key: 'Test-Header',
              value: '按F進(jìn)入坦克',
              enabletrue,
            },
          ];

          // 獲取、新增、刪除、啟用禁用
          function getHeaders () {
              return headers;
          }
          function addHeader (header) {
              headers.push(header);
          }
          function deleteHeader (index) {
              headers.splice(index, 1);
          }

          function toggleHeader(index) {
            headers[index].enable = !headers[index].enable;
          }
          ...

          // 請(qǐng)求攔截器
          // On install 在被安裝的時(shí)候去初始化
          chrome.runtime.onInstalled.addListener(function(){
              // 添加事件  
              chrome.webRequest.onBeforeSendHeaders.addListener(requestHandle, {
                  urls: ["<all_urls>"],// 攔截所有URL的請(qǐng)求
              },["blocking""requestHeaders"]); // 阻塞式
              console.log('load');
          });

          // 添加header
          function requestHandle(request) {
              let requestHeaders = request.requestHeaders;
              // 添加headers
              headers.forEach(item => {
                  if (item.enable) {
                      requestHeaders.push({
                          name: item.key,
                          value: item.value,
                      });
                  }
              });
              return {requestHeaders};
          }

          chrome.webRequest的生命周期:

          詳細(xì)參考:https://developer.chrome.com/extensions/webRequest

          popup 頁(yè)面設(shè)計(jì)

          popup頁(yè)面提供增加、刪除、啟用禁用功能接口,并且在每次打開(kāi)popup頁(yè)面的時(shí)候去background獲取最新的header數(shù)據(jù),展示在前臺(tái)。

          popup.js 功能偽代碼:

          // popup頁(yè)面被打開(kāi)時(shí),去后臺(tái)獲取最新header
          window.onload = function () {
              let backend = chrome.extension.getBackgroundPage();
              // 調(diào)用background方法,獲得headers
              let headers = backend.getHeaders();
              // 渲染header
              createElement(headers);
          }

          // 增加按鈕
          function addHeader() {
              let backend = chrome.extension.getBackgroundPage();
              let key = document.querySelector('.key');
              let value =  document.querySelector('.value');
              let header = {
                  key: key.value,
                  value: value.value,
                  enabletrue
                }
              // 調(diào)用background方法,新增headers
              backend.addHeader(header);
              createElement(header);
          }
          // 啟用禁用、刪除功能
          function toggleHeader(index) {
            let backend = chrome.extension.getBackgroundPage();
            backend.toggleHeader(index);
          }

          function delHeader(index) {
            let backend = chrome.extension.getBackgroundPage();
            backend.deleteHeader(index);
          }

          效果

          打開(kāi)popup,添加一個(gè)header:

          隨便打開(kāi)一個(gè)網(wǎng)頁(yè),打開(kāi)控制臺(tái)查看RequestHeaders:

          總結(jié)

          • 很多權(quán)限、功能需要在manifest.json配置。
          • content-scripts、popup、background、inject-scripts擁有的權(quán)限不一樣,通信方式也不一樣,理解各個(gè)腳本的特點(diǎn),組合使用。
          • 開(kāi)發(fā)調(diào)試可在后臺(tái)背景頁(yè)查看信息,popup、inject-scripts、content-scripts可直接審查元素調(diào)試。

          Chrome 插件還有很多功能這里沒(méi)有詳細(xì)介紹,例如devtools。感興趣的同學(xué)可以查閱下面的參考文檔。

          參考文檔

          官方文檔:https://developer.chrome.com/extensions

          參考博客:https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品操逼视频 | 国产精品国内自产拍 | 美女被暴草视频在线看 | 国产精品国产三级国芦专播精品人 | 久久综合国产视频 |