Chrome開(kāi)發(fā)工具 擴(kuò)展 DevTools

2018-03-01 18:51 更新

擴(kuò)展 DevTools

總覽

一個(gè) DevTools 插件能增加功能到 Chrome DevTools 中來(lái).它能夠增加新的 UI 面板和側(cè)邊欄,能與被檢查的頁(yè)面進(jìn)行通信,能獲得關(guān)于網(wǎng)絡(luò)請(qǐng)求的信息,以及其他的功能。詳見(jiàn)顯式的 DevTools 插件。DevTools 插件能夠訪問(wèn)一組額外特定 DevTools 擴(kuò)展 API:

一個(gè) DevTools 插件的結(jié)構(gòu)像其他插件一樣 : 它可以有一個(gè)后臺(tái)頁(yè)面,內(nèi)容腳本和其他主體。此外,每個(gè) DevTools 插件有一個(gè) DevTools 頁(yè)面,能訪問(wèn)到它的 DevTools API。

devtools-extension.png

DevTools 頁(yè)面

一個(gè)插件 DevTools 頁(yè)面的實(shí)例每次隨著 DevTools 窗口打開(kāi)而被創(chuàng)建。DevTools 頁(yè)面在 DevTools 窗口生命周期內(nèi)存在。DevTools 頁(yè)面能訪問(wèn) DevTools API 和有限的一組擴(kuò)展 API。具體來(lái)說(shuō),DevTools 頁(yè)面可以:

DevTools 頁(yè)面不能直接使用大多數(shù)的擴(kuò)展 API。它可以訪問(wèn)擴(kuò)展并運(yùn)行著的 API 子集,因?yàn)檫@些 API 的內(nèi)容腳本可以被訪問(wèn)到。像一個(gè)內(nèi)容腳本一樣,一個(gè) DevTools 頁(yè)面可以使用 消息傳遞(Message Passing)與后臺(tái)頁(yè)面交互,詳見(jiàn)注入內(nèi)容腳本 Message Passing。

創(chuàng)建一個(gè) DevTools 插件

為你的插件創(chuàng)建一個(gè) DevTools 頁(yè)面,在插件的注冊(cè)清單文件中添加 devtools_page域:

{
  "name": ...
  "version": "1.0",
  "minimum_chrome_version": "10.0",
  "devtools_page": "devtools.html",
  ...
}

每個(gè) DevTools 窗口被打開(kāi)時(shí),在插件清單中指定的 devtools_page 的實(shí)例都會(huì)被創(chuàng)建。該頁(yè)面可以使用 devtools.panels API 添加其它擴(kuò)展程序的網(wǎng)頁(yè),作為面板和側(cè)邊欄到 DevTools 窗口。

devtools_page 域必須指向一個(gè) HTML 頁(yè)面。這與 background 域不同, background 域用來(lái)具體一個(gè)后臺(tái)頁(yè)面,能讓你直接指定 JavaScript 文件。

chrome.develop.* API 模型只能在載入了 DevTools 窗口的頁(yè)面使用。內(nèi)容腳本和其他擴(kuò)展頁(yè)面并沒(méi)有這些 API 。因此,這些 API 只有在 DevTools 窗口生命周期內(nèi)才能使用。

也有一些 DevTools 的 API 仍然是在測(cè)試狀態(tài)。請(qǐng)參閱 chrome.experimental.* API,例舉了用于測(cè)試的 API 和如何使用它們的指南。

DevTools 界面元素:面板和側(cè)邊欄窗格

除了常用擴(kuò)展 UI 元素,如瀏覽器的行為,文本菜單和彈出窗口,一個(gè) DevTools 插件可以添加 UI 元素到 DevTools 窗口:

  • 面板是一個(gè)頂級(jí)標(biāo)簽,像元素(Elements),源(Sources)和網(wǎng)絡(luò)(Network)板。
  • 側(cè)邊欄窗格 顯示補(bǔ)充 UI 相關(guān)的面板。固有樣式,設(shè)定的樣式以及元素 (Elements) 面板上的事件監(jiān)聽(tīng)器窗格的都是側(cè)邊欄窗格的實(shí)例。目前你的插件只能在元素 (Elements) 面板加側(cè)邊欄窗格。 (請(qǐng)注意,側(cè)邊欄面板的外觀可能與圖像不匹配,這取決于你正在使用 Chrome 瀏覽器的版本和其中 DevTools 窗口??康奈恢?。)

devtools-extension-ui.png

每個(gè)面板都是其自身的 HTML 文件,可以包括其它資源(JavaScript,CSS,圖片,等等)。像這樣創(chuàng)建一個(gè)基本的面板:

chrome.devtools.panels.create("My Panel",
    "MyPanelIcon.png",
    "Panel.html",
    function(panel) {
      // code invoked on panel creation
    }
);

在面板上或者在側(cè)邊欄窗格中執(zhí)行的 JavaScript 對(duì)象能訪問(wèn) DevTools 頁(yè)面有權(quán)訪問(wèn)的 API。

如下,為元素面板創(chuàng)建一個(gè)基礎(chǔ)的側(cè)邊窗格,像這樣:

chrome.devtools.panels.elements.createSidebarPane("My Sidebar",
    function(sidebar) {
        // sidebar initialization code here
        sidebar.setObject({ some_data: "Some data to show" });
});

有幾種方法來(lái)顯示一個(gè)側(cè)邊欄窗格中的內(nèi)容:

  • 利用HTML 文檔。調(diào)用 setPage 指定一個(gè) HTML 頁(yè)面在窗格中顯示。

  • 利用 JSON 數(shù)據(jù)。傳遞一個(gè)JSON 對(duì)象給 setObject方法。

  • 利用 JavaScript 表達(dá)式。傳遞一個(gè)表達(dá)式給 setExpression方法。 DevTools 在被檢查頁(yè)面的文檔中的執(zhí)行表達(dá),并輸出該返回值。

對(duì)于這兩種方法 setObjectsetExpression,當(dāng)他們它輸入進(jìn) DevTools 控制臺(tái)后,窗格會(huì)輸出該值,但是,setExpression可以顯示 DOM 元素和任意 JavaScript 對(duì)象,而 setObject 只支持 JSON 對(duì)象。

插件組件之間的通信

下面的部分描述了 DevTools 插件的不同組件之間通信的一些典型場(chǎng)景。

注入腳本內(nèi)容

該 DevTools 頁(yè)不能直接調(diào)用tabs.executeScript。為了從 DevTools 頁(yè)面注入內(nèi)容腳本,必須使用inspectedWindow.tabId屬性檢索的檢查窗口選項(xiàng)卡的 ID ,并且發(fā)送一個(gè)消息到后臺(tái)頁(yè)面。在后臺(tái)頁(yè)面,調(diào)用 tabs.executeScript 注入腳本。

如果內(nèi)容腳本已經(jīng)被注入,你可以使用 eval方法來(lái)添加其他內(nèi)容腳本。請(qǐng)參見(jiàn)傳遞選定元素為內(nèi)容腳本(Passing the Selected Element to a Content Script ) 以獲取更多信息。

下面的代碼片段展示了如何使用 executeScript 注入一個(gè)腳本內(nèi)容:

// DevTools page -- devtools.js
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
    // Handle responses from the background page, if any
});
// Relay the tab ID to the background page
chrome.runtime.sendMessage({
    tabId: chrome.devtools.inspectedWindow.tabId,
    scriptToInject: "content_script.js"
});

后臺(tái)頁(yè)面的代碼:

// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
    // assign the listener function to a variable so we can remove it later
    var devToolsListener = function(message, sender, sendResponse) {
        // Inject a content script into the identified tab
        chrome.tabs.executeScript(message.tabId,
            { file: message.scriptToInject });
    }
    // add the listener
    devToolsConnection.onMessage.addListener(devToolsListener);
    devToolsConnection.onDisconnect(function() {
         devToolsConnection.onMessage.removeListener(devToolsListener);
    });
}

在檢查窗口測(cè)試 JavaScript 代碼

你可以使用 inspectedWindow.eval 方法在檢查頁(yè)面的上下文中執(zhí)行 JavaScript 代碼。然后你可以在DevTools頁(yè),面板或側(cè)邊欄窗格中調(diào)用 eval 方法。

默認(rèn)情況下,表達(dá)式在頁(yè)面的主框架文檔中被計(jì)算?,F(xiàn)在,你可能熟悉 DevTools 命令行API(commandline API) 功能像元素檢查(inspect(elem)), 函數(shù)中斷(debug(fn)),復(fù)制內(nèi)容到剪貼板(copy()) ,或許更多。

inspectedWindow.eval() 使用相同的腳本執(zhí)行上下文,在 DevTools 控制臺(tái)的選項(xiàng)輸入代碼,它允許使在測(cè)試范圍內(nèi)訪問(wèn)這些 API。例如,SOAK 使用它來(lái)檢測(cè)一個(gè)元素:

    chrome.devtools.inspectedWindow.eval(
      "inspect($$('head script[data-soak=main]')[0])",
      function(result, isException) { }
    );

或者,使用 inspectedWindow.eval()useContentScriptContext:true 選項(xiàng),以計(jì)算在和內(nèi)容腳本相同的上下文內(nèi)容中的表達(dá)式。在 useContentScriptContext:true 域調(diào)用 eval 不會(huì)創(chuàng)建內(nèi)容腳本的環(huán)境,所以你必須在調(diào)用 evel 之前,載入內(nèi)容腳本,或者通過(guò)在 manifest.json 文件中指定內(nèi)容腳本來(lái)調(diào)用執(zhí)行腳本(executeScript)。

一旦上下文文腳本內(nèi)容環(huán)境存在,你可以使用此選項(xiàng)來(lái)注入額外的內(nèi)容腳本。

eval方法是強(qiáng)大的當(dāng)它在正確的應(yīng)用情景中使用的時(shí)候,但,如果沒(méi)有被正確使用,它同樣也是危險(xiǎn)的。如果你不需要獲取檢查頁(yè)的 JavaScript 的內(nèi)容,使用 tabs.executeScript 方法。有關(guān)詳細(xì)的注意事項(xiàng)和兩種方法的比較,請(qǐng)參閱 inspectedWindow。

傳遞選定元素到內(nèi)容腳本

內(nèi)容腳本不能直接訪問(wèn)當(dāng)前選中的元素。但是,任何使用 inspectedWindow.eval 來(lái)執(zhí)行的代碼都可以在 DevTools 控制臺(tái)和命令行的 API 中使用。例如,在測(cè)試代碼時(shí),你可以使用 $0 訪問(wèn)當(dāng)前被選定的元素。

要傳遞選中的元素到內(nèi)容腳本,可以如下完成:

  • 在內(nèi)容腳本中,創(chuàng)建一個(gè)函數(shù),將選定參數(shù)作為這個(gè)函數(shù)的參數(shù)。
  • 在 DevTools 頁(yè)面中使用在useContentScriptContext:true的選項(xiàng)中的inspectedWindow.eval來(lái)該函數(shù)方法。

在內(nèi)容腳本中你的函數(shù)代碼可能是這個(gè)樣子:

function setSelectedElement(el) {
    // do something with the selected element
}

在 DevTools 頁(yè)面調(diào)用這個(gè)方法,像這樣:

chrome.devtools.inspectedWindow.eval("setSelectedElement($0)",
    { useContentScriptContext: true });

useContentScriptContext:true 選項(xiàng)限定的是表達(dá)必須在相同的上下文中的內(nèi)容腳本中進(jìn)行計(jì)算,所以它可以使用setSelectedElement方法。

獲得一個(gè)參考板的窗口

從 devtools 面板 postMessage ,你需要它的 window對(duì)象的一個(gè)參考。獲取面板的 iframe 窗口從該 panel.onShown 事件處理程序;

onShown.addListener(function callback)
extensionPanel.onShown.addListener(function (extPanelWindow) {
    extPanelWindow instanceof Window; // true
    extPanelWindow.postMessage( // …
});

從內(nèi)容腳本傳遞信息到 DevTools 頁(yè)面

在 DevTools 頁(yè)面和內(nèi)容腳本之間傳遞消息并不是直接的,而是通過(guò)后臺(tái)頁(yè)面。

當(dāng)將消息發(fā)送到內(nèi)容腳本,后臺(tái)頁(yè)面可以使用 tabs.sendMessage 方法,該方法在指定的選項(xiàng)卡中發(fā)送消息到內(nèi)容腳本,就如同注入一個(gè)內(nèi)容腳本。

當(dāng)從內(nèi)容腳本發(fā)送消息出來(lái),也沒(méi)有現(xiàn)成的方法來(lái)傳遞消息到與當(dāng)前選項(xiàng)卡相關(guān)聯(lián)的確切的 DevTools 頁(yè)面的實(shí)例。作為一種變通方法,你可以讓 DevTools 頁(yè)面與后臺(tái)頁(yè)面建立長(zhǎng)生命周期的連接,并讓后臺(tái)頁(yè)持有 ID 選項(xiàng)卡到連接的映射,這樣它可以路由的每條消息到正確連接處。

// background.js
var connections = {};
chrome.runtime.onConnect.addListener(function (port) {
    var extensionListener = function (message, sender, sendResponse) {
        // The original connection event doesn't include the tab ID of the
        // DevTools page, so we need to send it explicitly.
        if (message.name == "init") {
          connections[message.tabId] = port;
          return;
        }
    // other message handling
    }
    // Listen to messages sent from the DevTools page
    port.onMessage.addListener(extensionListener);
    port.onDisconnect.addListener(function(port) {
        port.onMessage.removeListener(extensionListener);
        var tabs = Object.keys(connections);
        for (var i=0, len=tabs.length; i < len; i++) {
          if (connections[tabs[i]] == port) {
            delete connections[tabs[i]]
            break;
          }
        }
    });
});
// Receive message from content script and relay to the devTools page for the
// current tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // Messages from content scripts should have sender.tab set
    if (sender.tab) {
      var tabId = sender.tab.id;
      if (tabId in connections) {
        connections[tabId].postMessage(request);
      } else {
        console.log("Tab not found in connection list.");
      }
    } else {
      console.log("sender.tab not defined.");
    }
    return true;
});

DevTools 頁(yè)面(面板或側(cè)邊欄窗格)像這樣建立連接:

// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "panel"
});
backgroundPageConnection.postMessage({
    name: 'init',
    tabId: chrome.devtools.inspectedWindow.tabId
});

從注入腳本到 DevTools 頁(yè)通信

雖然上述的解決方案適用于內(nèi)容腳本,即直接注入頁(yè)面代碼(例如通過(guò)附加一個(gè) script 標(biāo)簽或通過(guò) inspectedWindow.eval)需要一個(gè)不同的策略。在這方面,runtime.sendMessage 不會(huì)如預(yù)期一樣向后臺(tái)腳本傳遞消息。

作為一種變通方法,你可以將內(nèi)容腳本和注入腳本結(jié)合起來(lái)。將消息傳遞給內(nèi)容腳本,你可以使用 API window.postMessage。這里有一個(gè)例子,假設(shè)后臺(tái)腳本是上一節(jié)中的:

// injected-script.js
window.postMessage({
  greeting: 'hello there!',
  source: 'my-devtools-extension'
}, '*');
// content-script.js
window.addEventListener('message', function(event) {
  // Only accept messages from the same frame
  if (event.source !== window) {
    return;
  }
  var message = event.data;
  // Only accept messages that we know are ours
  if (typeof message !== 'object' || message === null ||
      !message.source === 'my-devtools-extension') {
    return;
  }
  chrome.runtime.sendMessage(message);
});

你的信息現(xiàn)在將從注入腳本,傳遞到內(nèi)容腳本,再傳遞到后臺(tái)腳本,最后傳到 DevTools 頁(yè)。

你也可以在這里參考兩種備選消息傳遞技術(shù)。

檢測(cè) DevTools 打開(kāi)和關(guān)閉狀態(tài)

如果你的插件需要跟蹤 DevTools 窗口是否打開(kāi),你可以添加一個(gè) onConnect 的監(jiān)聽(tīng)器到后臺(tái)頁(yè)面中,并在 DevTools 頁(yè)調(diào)用 connect 方法。由于每個(gè)標(biāo)簽可以讓它自己的 DevTools 窗口打開(kāi),你可能會(huì)收到多個(gè)連接的事件。要跟蹤 DevTools 窗口何時(shí)打開(kāi),你需要計(jì)算連接事件和斷開(kāi)事件,如下所示:

// background.js
var openCount = 0;
chrome.runtime.onConnect.addListener(function (port) {
    if (port.name == "devtools-page") {
      if (openCount == 0) {
        alert("DevTools window opening.");
      }
      openCount++;
      port.onDisconnect.addListener(function(port) {
          openCount--;
          if (openCount == 0) {
            alert("Last DevTools window closing.");
          }
      });
    }
});

在 DevTools 頁(yè)面建立連接,如下:

// devtools.js

// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
    name: "devtools-page"
});

DevTools 插件的例子

瀏覽這些 DevTools 示例源代碼:

  • Polymer Devtools Extension- 使用在主機(jī)頁(yè)運(yùn)行的許多助手,來(lái)查詢并獲取 DOM/ JS 狀態(tài),發(fā)送回自定義面板。
  • React DevTools Extension - 使用 Bink 的一個(gè)子模塊來(lái)重用 DevTools UI 組件。
  • Ember Inspector - 與 Chrome 和 Firefox 上的適配器,共享插件核心。
  • Coquette-inspect - 一個(gè)干凈的基于 rect 的插件,調(diào)試器被注入到 host 頁(yè)面。
  • 我們的 DevTools Extension GallerySample Extensions是值得安裝,試用,并從學(xué)習(xí)更多的應(yīng)用程序。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)