Rozszerzanie narzędzi deweloperskich

Opis

Rozszerzenie Narzędzia deweloperskie dodaje funkcje do Narzędzi deweloperskich w Chrome. Może dodawać nowe panele interfejsu i paski boczne, wchodzić w interakcje ze sprawdzaną stroną, uzyskiwać informacje o żądaniach sieciowych itp. Zobacz polecane rozszerzenia Narzędzi deweloperskich. Rozszerzenia w Narzędziach deweloperskich mają dostęp do dodatkowego zestawu interfejsów API rozszerzeń związanych z Narzędziami deweloperskimi:

Rozszerzenie w Narzędziach deweloperskich ma strukturę tak samo jak każde inne rozszerzenie: może zawierać stronę w tle, skrypty treści i inne elementy. Oprócz tego każde rozszerzenie w Narzędziach deweloperskich ma stronę DevTools z dostępem do interfejsów DevTools API.

Diagram architektury przedstawiający stronę Narzędzi deweloperskich komunikujących się ze sprawdzanym oknem i stroną w tle. Wyświetlana jest strona w tle, która komunikuje się ze skryptami treści i uzyskuje dostęp do interfejsów API rozszerzeń.
       Strona Narzędzia deweloperskie ma dostęp do interfejsów API Narzędzi deweloperskich, na przykład do tworzenia paneli.

Strona DevTools

Za każdym razem, gdy otwiera się okno Narzędzi deweloperskich, tworzona jest instancja strony Narzędzi deweloperskich dla danego rozszerzenia. Strona Narzędzia deweloperskie istnieje przez cały okres istnienia okna Narzędzi deweloperskich. Strona DevTools ma dostęp do interfejsów API DevTools i ograniczonego zestawu interfejsów API rozszerzeń. Na stronie Narzędzi deweloperskich możesz:

  • Tworzenie paneli i interakcja z nimi za pomocą interfejsów API devtools.panels.
  • Uzyskaj informacje o sprawdzanym oknie i oceniaj kod w sprawdzanym oknie za pomocą interfejsów API devtools.inspectedWindow.
  • Uzyskuj informacje o żądaniach sieciowych za pomocą interfejsów API devtools.network.

Na stronie Narzędzi deweloperskich nie można bezpośrednio korzystać z większości interfejsów API rozszerzeń. Ma dostęp do tego samego podzbioru interfejsów API extension i runtime, do którego ma dostęp skrypt treści. Podobnie jak skrypt zawartości, strona w Narzędziach deweloperskich może komunikować się ze stroną w tle za pomocą funkcji przekazywania wiadomości. Przykład znajdziesz w artykule Wstawianie skryptu treści.

Tworzenie rozszerzenia w Narzędziach deweloperskich

Aby utworzyć stronę w Narzędziach deweloperskich dla rozszerzenia, dodaj pole devtools_page w pliku manifestu rozszerzenia:

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

Dla każdego otwartego okna Narzędzi deweloperskich tworzona jest instancja devtools_page określona w pliku manifestu rozszerzenia. Strona może dodawać inne strony rozszerzeń w postaci paneli i pasków bocznych do okna Narzędzi deweloperskich za pomocą interfejsu API devtools.panels.

Moduły interfejsu API chrome.devtools.* są dostępne tylko dla stron wczytywanych w oknie Narzędzi deweloperskich. Skrypty treści i inne strony rozszerzeń nie mają tych interfejsów API. Dlatego interfejsy API są dostępne tylko przez cały okres ważności okna Narzędzi deweloperskich.

Istnieją również interfejsy API Narzędzi deweloperskich, które wciąż znajdują się w fazie eksperymentalnej. Więcej informacji znajdziesz na stronie chrome.experimental.* interfejsów API, gdzie znajdziesz listę eksperymentalnych interfejsów API oraz wskazówki dotyczące ich używania.

Elementy interfejsu Narzędzi deweloperskich: panele i panele na pasku bocznym

Oprócz typowych elementów interfejsu rozszerzeń, takich jak działania przeglądarki, menu kontekstowe czy wyskakujące okienka, rozszerzenie DevTools może dodawać do okna Narzędzi deweloperskich elementy interfejsu:

  • Panel to karta najwyższego poziomu, taka jak Panele Elementy, Źródła i Sieć.
  • Panel boczny to dodatkowy interfejs powiązany z panelem. Panele Style, Style obliczone i Detektory zdarzeń w panelu Elementy to przykłady paneli paska bocznego. (Pamiętaj, że panele paska bocznego mogą się różnić od obrazu w zależności od używanej wersji Chrome i tego, gdzie zadokowane jest okno Narzędzi deweloperskich).

Okno narzędzi deweloperskich z widocznym panelem Elementy i panelem na pasku bocznym Style.

Każdy panel to własny plik HTML, który może zawierać inne zasoby (JavaScript, CSS, obrazy itd.). Tworzenie podstawowego panelu wygląda tak:

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

Kod JavaScript wykonywany w panelu lub panelu paska bocznego ma dostęp do tych samych interfejsów API co strona Narzędzi deweloperskich.

Tworzenie podstawowego panelu paska bocznego w panelu Elementy wygląda tak:

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

Zawartość panelu paska bocznego można wyświetlić na kilka sposobów:

  • Treść HTML. Wywołaj setPage, aby określić stronę HTML wyświetlaną w panelu.
  • Dane JSON. Przekaż obiekt JSON do setObject.
  • wyrażenie JavaScript. Przekaż wyrażenie do funkcji setExpression. Narzędzia deweloperskie oceniają wyrażenie w kontekście sprawdzanej strony i wyświetlają zwracaną wartość.

Zarówno w przypadku setObject, jak i setExpression panel wyświetla wartość, która będzie widoczna w konsoli Narzędzi deweloperskich. Metoda setExpression umożliwia jednak wyświetlanie elementów DOM i dowolnych obiektów JavaScript, a setObject obsługuje tylko obiekty JSON.

Komunikacja między komponentami rozszerzeń

W kolejnych sekcjach opisano kilka typowych scenariuszy komunikacji między różnymi komponentami rozszerzenia z Narzędzi deweloperskich.

Wstawianie skryptu treści

Strona Narzędzi deweloperskich nie może wywoływać bezpośrednio narzędzia tabs.executeScript. Aby wstrzyknąć skrypt treści ze strony Narzędzia deweloperskie, musisz pobrać identyfikator karty sprawdzanego okna za pomocą właściwości inspectedWindow.tabId i wysłać wiadomość na stronę w tle. Na stronie w tle wywołaj tabs.executeScript, aby wstrzyknąć skrypt.

Poniższe fragmenty kodu pokazują, jak wstrzyknąć skrypt treści za pomocą polecenia executeScript.

// 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"
});

Kod strony w tle:

// 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.addListener(function() {
         devToolsConnection.onMessage.removeListener(devToolsListener);
    });
});

Ocena kodu JavaScript w sprawdzanym oknie

Metoda inspectedWindow.eval pozwala uruchamiać kod JavaScript w kontekście sprawdzanej strony. Metodę eval możesz wywołać ze strony Narzędzia deweloperskie, panelu lub panelu paska bocznego.

Domyślnie wyrażenie jest sprawdzane w kontekście głównej ramki strony. Być może znasz już funkcje interfejsu wiersza poleceń w Narzędziach deweloperskich, takie jak inspekcja elementów (inspect(elem)), włamanie na funkcje (debug(fn)), kopiowanie do schowka (copy()) i inne. inspectedWindow.eval() korzysta z tego samego kontekstu wykonywania skryptu i tych samych opcji, co kod wpisywany w konsoli Narzędzi deweloperskich, co umożliwia dostęp do tych interfejsów API w ramach oceny. Na przykład SOAK używa go do badania elementu:

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

Możesz też użyć opcji useContentScriptContext: true dla atrybutu inspectedWindow.eval(), aby ocenić wyrażenie w tym samym kontekście co skrypty treści. Wywołanie eval za pomocą polecenia useContentScriptContext: true nie tworzy kontekstu skryptu treści, dlatego przed wywołaniem funkcji eval musisz wczytać skrypt kontekstu. Można to zrobić, wywołując executeScript lub określając skrypt treści w pliku manifest.json.

Gdy pojawi się kontekst skryptu, możesz użyć tej opcji, aby wstawić dodatkowe skrypty treści.

Metoda eval jest skuteczna, gdy jest używana w odpowiednim kontekście, a nieprawidłowo niebezpieczna. Jeśli nie potrzebujesz dostępu do kontekstu JavaScriptu sprawdzanej strony, użyj metody tabs.executeScript. Szczegółowe uwagi i porównanie obu metod znajdziesz w artykule inspectedWindow.

Przekazywanie wybranego elementu do skryptu treści

Skrypt treści nie ma bezpośredniego dostępu do obecnie wybranego elementu. Jednak każdy kod wykonywany w inspectedWindow.eval ma dostęp do konsoli Narzędzi deweloperskich i interfejsów API wiersza poleceń. Na przykład w ocenionym kodzie możesz użyć polecenia $0, aby uzyskać dostęp do wybranego elementu.

Aby przekazać wybrany element do skryptu dotyczącego treści:

  • Utwórz w skrypcie treści metodę, która przyjmie wybrany element jako argument.
  • Wywołaj tę metodę na stronie Narzędzi deweloperskich za pomocą narzędzia inspectedWindow.eval z opcją useContentScriptContext: true.

Kod w skrypcie treści może wyglądać mniej więcej tak:

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

Wywołaj tę metodę na stronie Narzędzia deweloperskie w ten sposób:

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

Opcja useContentScriptContext: true wskazuje, że wyrażenie musi być oceniane w tym samym kontekście co skrypty treści, aby miało dostęp do metody setSelectedElement.

Pobieram window panelu referencyjnego

Aby wykonać żądanie postMessage z panelu narzędzi deweloperskich, potrzebujesz odwołania do jego obiektu window. Pobierz okno iframe panelu z modułu obsługi zdarzeń panel.onShown:

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

Komunikaty ze skryptów dotyczących treści na stronę Narzędzi deweloperskich

Komunikacja między stroną Narzędzi deweloperskich a skryptami zawartości jest pośrednia przez stronę w tle.

Gdy wysyłasz wiadomość do skryptu dotyczącego treści, strona w tle może użyć metody tabs.sendMessage, która kieruje wiadomość do skryptów treści na określonej karcie – tak jak to opisano w sekcji Wstawianie skryptu treści.

Podczas wysyłania wiadomości ze skryptu dotyczącego treści nie ma gotowej metody przekazania wiadomości do właściwej instancji strony Narzędzi deweloperskich powiązanej z bieżącą kartą. Aby obejść ten problem, możesz nawiązać długotrwałe połączenie ze stroną w tle na stronie Narzędzi deweloperskich, a na stronie w tle przechowywać mapę identyfikatorów kart z połączeniami, dzięki czemu poszczególne komunikaty będą kierowane do odpowiedniego połączenia.

// 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;
});

Strona Narzędzia deweloperskie (lub panel lub panel na pasku bocznym) nawiązuje połączenie w ten sposób:

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

backgroundPageConnection.postMessage({
    name: 'init',
    tabId: chrome.devtools.inspectedWindow.tabId
});

Komunikaty z wstrzykniętych skryptów na stronę Narzędzi deweloperskich

Powyższe rozwiązanie sprawdza się w przypadku skryptów treści, ale kod wstawiony bezpośrednio na stronie (np. przez dołączenie tagu <script> lub za pomocą inspectedWindow.eval) wymaga innej strategii. W tym kontekście runtime.sendMessage nie przekazuje wiadomości do skryptu w tle zgodnie z oczekiwaniami.

Aby obejść ten problem, możesz połączyć wstrzyknięty skrypt ze skryptem treści, który pełni rolę pośrednika. Aby przekazywać komunikaty do skryptu treści, możesz użyć interfejsu API window.postMessage. Oto przykład, w oparciu o skrypt tła z poprzedniej sekcji:

// 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);
});

Wiadomość będzie teraz przepływać ze wstrzykniętego skryptu do skryptu treści, do skryptu działającego w tle, a na koniec na stronę Narzędzi deweloperskich.

Rozważ też 2 alternatywne techniki przekazywania wiadomości.

Wykrywanie otwierania i zamykania Narzędzi deweloperskich

Jeśli rozszerzenie ma śledzić, czy okno Narzędzi deweloperskich jest otwarte, do strony w tle możesz dodać odbiornik onConnect i wywołać connect ze strony Narzędzi deweloperskich. Każda karta może mieć własne okno Narzędzi deweloperskich, więc możesz otrzymywać wiele zdarzeń połączenia. Aby sprawdzić, czy jest otwarte jakieś okno Narzędzi deweloperskich, musisz zliczyć zdarzenia połączenia i rozłączenia w ten sposób:

// 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.");
          }
      });
    }
});

Strona Narzędzia deweloperskie tworzy takie połączenie:

// devtools.js

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

Przykłady rozszerzeń Narzędzi deweloperskich

Przejrzyj źródło tych przykładów rozszerzeń z Narzędzi deweloperskich:

Więcej informacji

Informacje o standardowych interfejsach API, z których mogą korzystać rozszerzenia, znajdziesz na stronie chrome*. interfejsy API i internetowe interfejsy API,

Prześlij nam opinię! Twoje uwagi i sugestie pomogą nam ulepszyć interfejsy API.

Przykłady

Przykłady korzystania z interfejsów API Narzędzi deweloperskich znajdziesz w artykule Przykłady.