Estender o DevTools

Visão geral

Uma extensão do DevTools adiciona funcionalidade ao Chrome DevTools. Ela pode adicionar novos painéis e barras laterais da interface, interagir com a página inspecionada, receber informações sobre solicitações de rede e muito mais. Confira as extensões em destaque do DevTools. As extensões do DevTools têm acesso a um conjunto adicional de APIs de extensão específicas do DevTools:

Uma extensão do DevTools é estruturada como qualquer outra extensão: ela pode ter uma página de segundo plano, scripts de conteúdo e outros itens. Além disso, cada extensão do DevTools tem uma página do DevTools, que tem acesso às APIs do DevTools.

Diagrama de arquitetura mostrando a página do DevTools se comunicando com a
       janela inspecionada e a página em segundo plano. A página em segundo plano é mostrada
       comunicando-se com os scripts de conteúdo e acessando APIs de extensão.
       A página do DevTools tem acesso às APIs do DevTools, por exemplo, para criar painéis.

A página do DevTools

Uma instância da página do DevTools da extensão é criada sempre que uma janela do DevTools é aberta. A página do DevTools existe durante o ciclo de vida da janela do DevTools. A página do DevTools tem acesso às APIs do DevTools e a um conjunto limitado de APIs de extensão. Especificamente, a página do DevTools pode:

  • Criar e interagir com painéis usando as devtools.panels APIs.
  • Receber informações sobre a janela inspecionada e avaliar o código na janela inspecionada usando as devtools.inspectedWindow APIs.
  • Receber informações sobre solicitações de rede usando as devtools.network APIs.

A página do DevTools não pode usar a maioria das APIs de extensões diretamente. Ela tem acesso ao mesmo subconjunto das extension e runtime APIs que um script de conteúdo. Como um script de conteúdo, uma página do DevTools pode se comunicar com a página de segundo plano usando a passagem de mensagens. Para conferir um exemplo, consulte Como injetar um script de conteúdo.

Como 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",
  "minimum_chrome_version": "10.0",
  "devtools_page": "devtools.html",
  ...
}

Uma instância da devtools_page especificada no manifesto da extensão é criada para cada janela do DevTools aberta. A página pode adicionar outras páginas de extensão como painéis e barras laterais à janela do DevTools usando a devtools.panels API.

Os módulos de API chrome.devtools.* estão disponíveis apenas para as páginas carregadas na janela do DevTools. Scripts de conteúdo e outras páginas de extensão não têm essas APIs. Assim, as APIs estão disponíveis apenas durante o ciclo de vida da janela do DevTools.

Há também algumas APIs do DevTools que ainda são experimentais. Consulte chrome.experimental.* APIs para a lista de APIs experimentais e diretrizes sobre como usá-las.

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

Além dos elementos de interface de extensão comuns, 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. A aparência dos painéis da barra lateral pode não corresponder à imagem, dependendo da versão do Chrome que você está usando e de onde a janela do DevTools está ancorada.

Janela do DevTools mostrando o painel "Elementos" e o painel da barra lateral "Estilos".

Cada painel é um arquivo HTML próprio, que pode incluir outros recursos (JavaScript, CSS, imagens etc.). A criação de um painel básico é assim:

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

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

A criação de um painel da barra lateral básico para o painel "Elementos" é assim:

chrome.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 da mesma forma que ele aparece no console do DevTools. No entanto, setExpression permite mostrar elementos DOM e objetos JavaScript arbitrários, enquanto setObject só oferece suporte a objetos JSON.

Como se comunicar entre componentes de extensão

As seções a seguir descrevem alguns cenários típicos de comunicação entre os diferentes componentes de uma extensão do DevTools.

Como injetar um script de conteúdo

A página do DevTools não pode chamar tabs.executeScript diretamente. Para injetar um script de conteúdo de a página do DevTools, é necessário recuperar o ID da guia da janela inspecionada usando a propriedade inspectedWindow.tabId e enviar uma mensagem para a página de segundo plano. Na página de segundo plano, chame tabs.executeScript para injetar o script.

Os snippets de código a seguir mostram como injetar um script de conteúdo usando 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"
});

Código para a página de segundo plano:

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

Como avaliar o JavaScript na janela inspecionada

Você pode usar o inspectedWindow.eval método para executar o código JavaScript no contexto da página inspecionada. É possível invocar o método eval em uma página, painel ou painel da barra lateral do DevTools.

Por padrão, a expressão é avaliada no contexto do frame principal da página. Agora, talvez você já conheça os recursos da API de linha de comando do DevTools, como inspeção de elementos (inspect(elem)), interrupção em funções (debug(fn)), cópia para a área de transferência (copy()) e muito mais. inspectedWindow.eval() usa o mesmo contexto e opções de execução de script que o código digitado no console do DevTools, o que permite o acesso a essas APIs na avaliação. Por exemplo, SOAK usa isso para inspecionar um elemento:

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

Como alternativa, use a opção useContentScriptContext: true para inspectedWindow.eval() para avaliar a expressão no mesmo contexto que os scripts de conteúdo. Chamar eval com useContentScriptContext: true não cria um contexto de script de conteúdo. Portanto, é necessário carregar um script de contexto antes de chamar eval, chamando executeScript ou especificando um script de conteúdo no arquivo manifest.json.

Depois que o contexto do script de conteúdo existir, você poderá usar essa opção para injetar outros scripts de conteúdo.

O método eval é útil quando usado no contexto certo e perigoso quando usado de forma inadequada. Use o tabs.executeScript método se não precisar de acesso ao contexto JavaScript da página inspecionada. Para conferir avisos detalhados e uma comparação dos dois métodos, consulte inspectedWindow.

Como transmitir o elemento selecionado para um script de conteúdo

O script de conteúdo não tem acesso direto ao elemento selecionado no momento. No entanto, qualquer código que você execute usando inspectedWindow.eval tem acesso ao console do DevTools e às APIs de linha de comando. Por exemplo, no código avaliado, você pode usar $0 para acessar o elemento selecionado.

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

  • Crie um método no script de conteúdo que receba o elemento selecionado como argumento.
  • Chame o método na página do DevTools usando inspectedWindow.eval com a useContentScriptContext: true opção.

O código no script de conteúdo pode ser parecido com este:

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

Invoque o método na página do DevTools desta forma:

chrome.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 ela possa acessar o método setSelectedElement.

Como receber a window de um painel de referência

Para postMessage de um painel do DevTools, você precisa de uma referência ao objeto window. Receba a janela do iframe de um painel no manipulador de eventos panel.onShown:

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

Como enviar mensagens de scripts de conteúdo para a página do DevTools

O envio de mensagens entre a página do DevTools e os scripts de conteúdo é indireto, por meio da página de segundo plano.

Ao enviar uma mensagem para um script de conteúdo, a página de segundo plano pode usar o tabs.sendMessage método, que direciona uma mensagem para os scripts de conteúdo em uma guia específica, conforme mostrado em Como injetar um script de conteúdo.

Ao enviar uma mensagem de um script de conteúdo, não há um método pronto para entregar uma mensagem à instância correta da página do DevTools associada à guia atual. Como solução alternativa, você pode fazer com que a página do DevTools estabeleça uma conexão de longa duração com a página de segundo plano e que a página de segundo plano mantenha um mapa de IDs de guias para conexões, para que ela possa rotear cada mensagem para a conexão correta.

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

A página do DevTools (ou painel ou painel da barra lateral) estabelece a conexão desta forma:

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

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

Como enviar mensagens de scripts injetados para a página do DevTools

Embora a solução acima funcione para scripts de conteúdo, o código injetado diretamente na página (por exemplo, anexando uma tag <script> ou usando inspectedWindow.eval) exige uma estratégia diferente. Nesse contexto, runtime.sendMessage não vai transmitir mensagens para o script de segundo plano conforme o esperado.

Como solução alternativa, você pode combinar o script injetado com um script de conteúdo que atua como intermediário. Para transmitir mensagens ao script de conteúdo, use a window.postMessage API. Confira um exemplo, supondo o script de 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
  if (typeof message !== 'object' || message === null ||
      !message.source === 'my-devtools-extension') {
    return;
  }

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

Sua mensagem agora vai fluir do script injetado para o script de conteúdo, para o script de segundo plano e, por fim, para a página do DevTools.

Você também pode considerar duas técnicas alternativas de transmissão de mensagens aqui.

Como detectar quando o DevTools é aberto e fechado

Se a extensão precisar rastrear se a janela do DevTools está aberta, adicione um onConnect listener à página de segundo plano e chame a conexão da página do DevTools. Como cada guia pode ter sua própria janela do DevTools aberta, você pode receber vários eventos de conexão. Para rastrear se alguma janela do DevTools está aberta, é necessário contar os eventos de conexão e desconexão, conforme mostrado abaixo:

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

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

// devtools.js

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

Exemplos de extensão do DevTools

Navegue pela origem destes exemplos de extensão do DevTools:

Mais informações

Para informações sobre as APIs padrão que as extensões podem usar, consulte chrome.* 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 APIs do DevTools em Amostras.