Estender DevTools

As extensões do DevTools adicionam recursos ao Chrome DevTools acessando APIs de extensão específicas do DevTools em uma página do DevTools adicionada à extensão.

Diagrama de arquitetura mostrando a página do DevTools se comunicando com a
         janela inspecionada e o service worker. O service worker é mostrado
         se comunicando com os scripts de conteúdo e acessando as APIs de extensão.
         A página DevTools tem acesso às APIs do DevTools, por exemplo, para criar painéis.
Arquitetura da extensão DevTools.

As APIs de extensão específicas do DevTools incluem o seguinte:

A página do DevTools

Quando uma janela do DevTools é aberta, uma extensão do DevTools cria uma instância da página do DevTools que existe enquanto a janela está aberta. Essa página tem acesso às APIs do DevTools e de extensão e pode fazer o seguinte:

A página do DevTools pode acessar diretamente as APIs de extensões. Isso inclui a capacidade de se comunicar com o service worker usando transmissão de mensagens.

Criar uma extensão do DevTools

Para criar uma página do DevTools para sua extensão, adicione o campo devtools_page no manifesto da extensão:

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

O campo devtools_page precisa apontar para uma página HTML. Como a página do DevTools precisa ser local para sua extensão, recomendamos especificá-la usando um URL relativo.

Os membros da API browser.devtools só estão disponíveis para as páginas carregadas na janela do DevTools enquanto ela está aberta. Scripts de conteúdo e outras páginas de extensão não têm acesso a essas APIs.

O namespace do navegador e as extensões do DevTools

Nas versões 152 e mais recentes do Chrome, as extensões com uma página do DevTools podem usar o namespace browser.

Em versões do Chrome anteriores à 152, o namespace browser foi desativado para extensões que declaram devtools_page. A recusa se aplicava a toda a extensão, não apenas à página DevTools, mas a todos os contextos de script em que as APIs de extensão são executadas.

O motivo foi uma falha de compatibilidade com webextension-polyfill. As APIs browser.devtools.* anteriores ao Chrome 152 eram apenas de callback. Elas não retornavam Promises de forma nativa. Por isso, as extensões do DevTools geralmente dependiam do polyfill para encapsulá-las. O polyfill pula o encapsulamento sempre que browser é definido, presumindo que o host já fez o trabalho. Se o Chrome tivesse ativado browser para essas extensões, o polyfill não faria nada e as chamadas de browser.devtools.* parariam de retornar promessas. Manter o browser desativado fez com que o polyfill continuasse encapsulando.

A mesma desativação também desativou as outras mudanças na API de mensagens do Chrome 148 para essas extensões, incluindo Respostas de promessa em runtime.onMessage. A restrição foi removida quando as APIs do DevTools passaram a oferecer suporte nativo a Promises.

Elementos da interface do DevTools: painéis e painéis da barra lateral

Além dos elementos comuns da interface da extensão, como ações do navegador, menus de contexto e pop-ups, uma extensão do DevTools pode adicionar elementos de interface à janela do DevTools:

  • Um painel é uma guia de nível superior, como os painéis "Elementos", "Fontes" e "Rede".
  • Um painel da barra lateral apresenta uma interface complementar relacionada a um painel. Os painéis "Estilos", "Estilos computados" e "Listeners de eventos" no painel "Elementos" são exemplos de painéis da barra lateral. Dependendo da versão do Chrome que você está usando e de onde a janela do DevTools está ancorada, os painéis da barra lateral podem ter a aparência da imagem de exemplo a seguir:
Janela do DevTools mostrando o painel "Elementos" e o painel lateral "Estilos".
Janela do DevTools mostrando o painel "Elementos" e o painel da barra lateral "Estilos".

Cada painel é um arquivo HTML independente, que pode incluir outros recursos (JavaScript, CSS, imagens etc.). Para criar um painel básico, use o seguinte código:

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

O JavaScript executado em um painel ou painel lateral tem acesso às mesmas APIs que a página do DevTools.

Para criar um painel de barra lateral básico, use o seguinte código:

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

Há várias maneiras de mostrar conteúdo em um painel da barra lateral:

  • Conteúdo HTML: chame setPage() para especificar uma página HTML a ser mostrada no painel.
  • Dados JSON: transmita um objeto JSON para setObject().
  • Expressão JavaScript: transmita uma expressão para setExpression(). O DevTools avalia a expressão no contexto da página inspecionada e mostra o valor de retorno.

Para setObject() e setExpression(), o painel mostra o valor como ele apareceria no console do DevTools. No entanto, o setExpression() permite mostrar elementos DOM e objetos JavaScript arbitrários, enquanto o setObject() só aceita objetos JSON.

Comunicar entre componentes de extensão

As seções a seguir descrevem algumas maneiras úteis de permitir que os componentes de extensão do DevTools se comuniquem entre si.

Injetar um script de conteúdo

Para injetar um script de conteúdo, use scripting.executeScript():

// DevTools page -- devtools.js
browser.scripting.executeScript({
  target: {
    tabId: browser.devtools.inspectedWindow.tabId
  },
  files: ["content_script.js"]
});

É possível recuperar o ID da guia da janela inspecionada usando a propriedade inspectedWindow.tabId.

Se um script de conteúdo já tiver sido injetado, use as APIs de mensagens para se comunicar com ele.

Avaliar JavaScript na janela inspecionada

Use o método inspectedWindow.eval() para executar código JavaScript no contexto da página inspecionada. É possível invocar o método eval() em uma página, um painel ou um painel lateral do DevTools.

Por padrão, a expressão é avaliada no contexto do frame principal da página. O inspectedWindow.eval() usa o mesmo contexto e opções de execução de script que o código inserido no console do DevTools, o que permite o acesso aos recursos da API de utilitários do console do DevTools ao usar o eval(). Por exemplo, use-o para inspecionar o primeiro elemento de script na seção <head> do documento HTML:

browser.devtools.inspectedWindow.eval(
  "inspect($$('head script')[0])",
  function(result, isException) { }
);

Você também pode definir o useContentScriptContext como true ao chamar inspectedWindow.eval() para avaliar a expressão no mesmo contexto dos scripts de conteúdo. Para usar essa opção, use uma declaração de script de conteúdo estático antes de chamar eval(), seja chamando executeScript() ou especificando um script de conteúdo no arquivo manifest.json. Depois que o contexto do script de conteúdo for carregado, você também poderá usar essa opção para injetar outros scripts de conteúdo.

Transmitir o elemento selecionado para um script de conteúdo

O script de conteúdo não tem acesso direto ao elemento selecionado. No entanto, qualquer código executado usando inspectedWindow.eval() tem acesso ao console do DevTools e às APIs de utilitários do console. Por exemplo, no código avaliado, é possível usar $0 para acessar o elemento selecionado.

Para transmitir o elemento selecionado a um script de conteúdo:

  1. Crie um método no script de conteúdo que use o elemento selecionado como argumento.

    function setSelectedElement(el) {
        // do something with the selected element
    }
    
  2. Chame o método na página do DevTools usando inspectedWindow.eval() com a opção useContentScriptContext: true.

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

A opção useContentScriptContext: true especifica que a expressão precisa ser avaliada no mesmo contexto que os scripts de conteúdo, para que possa acessar o método setSelectedElement.

Receber o window de um painel de referência

Para chamar postMessage() em um painel das ferramentas de desenvolvimento, você precisa de uma referência ao objeto window. Receba uma janela de iframe de um painel do manipulador de eventos panel.onShown:

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

Enviar mensagens de scripts injetados para a página do DevTools

O código injetado diretamente na página sem um script de conteúdo, incluindo a anexação de uma tag <script> ou a chamada de inspectedWindow.eval(), não pode enviar mensagens para a página do DevTools usando runtime.sendMessage(). Em vez disso, recomendamos combinar o script injetado com um script de conteúdo que possa atuar como intermediário e usar o método window.postMessage(). O exemplo a seguir usa o script em segundo plano da seção anterior:

// 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. Note that this is not foolproof
  // and the page can easily spoof messages if it wants to.
  if (typeof message !== 'object' || message === null ||
      message.source !== 'my-devtools-extension') {
    return;
  }

  browser.runtime.sendMessage(message);
});

Outras técnicas alternativas de transmissão de mensagens podem ser encontradas no GitHub.

Detectar quando o DevTools é aberto e fechado

Para rastrear se a janela do DevTools está aberta, adicione um listener onConnect ao service worker e chame connect() na página do DevTools. Como cada guia pode ter uma janela do DevTools aberta, você pode receber vários eventos de conexão. Para acompanhar se alguma janela do DevTools está aberta, conte os eventos de conexão e desconexão, conforme mostrado no exemplo a seguir:

// background.js
var openCount = 0;
browser.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.");
          }
      });
    }
});

A página do DevTools cria uma conexão assim:

// devtools.js

// Create a connection to the service worker
const serviceWorkerConnection = browser.runtime.connect({
    name: "devtools-page"
});

// Send a periodic heartbeat to keep the port open.
setInterval(() => {
  port.postMessage("heartbeat");
}, 15000);

Exemplos de extensões do DevTools

Os exemplos nesta página vêm das seguintes páginas:

  • Extensão Polymer Devtools: usa muitos helpers em execução na página do host para consultar o estado DOM/JS e enviar de volta ao painel personalizado.
  • Extensão React DevTools: usa um submódulo do renderizador para reutilizar componentes da interface do DevTools.
  • Ember Inspector: núcleo de extensão compartilhada com adaptadores para Chrome e Firefox.
  • Coquette-inspect: uma extensão limpa baseada em React com um agente de depuração injetado na página do host.
  • As extensões de exemplo têm mais extensões úteis para instalar, testar e aprender.

Mais informações

Para informações sobre as APIs padrão que as extensões podem usar, consulte browser.* APIs e APIs da Web.

Envie seu feedback. Seus comentários e sugestões nos ajudam a melhorar as APIs.

Exemplos

Você pode encontrar exemplos que usam as APIs do DevTools em Exemplos.