Extensión de Chrome: obtenga contenido DOM

116

Estoy intentando acceder al contenido DOM de activeTab desde mi ventana emergente. Aquí está mi manifiesto:

{
  "manifest_version": 2,

  "name": "Test",
  "description": "Test script",
  "version": "0.1",

  "permissions": [
    "activeTab",
    "https://api.domain.com/"
  ],

  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "Chrome Extension test",
    "default_popup": "index.html"
  }
}

Estoy realmente confundido si los scripts en segundo plano (páginas de eventos con persistencia: falso) o content_scripts son el camino a seguir. He leído toda la documentación y otras publicaciones de SO y todavía no tiene sentido para mí.

¿Alguien puede explicar por qué podría usar uno sobre el otro?

Aquí está el background.js que he estado probando:

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    // LOG THE CONTENTS HERE
    console.log(request.content);
  }
);

Y solo estoy ejecutando esto desde la consola emergente:

chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, { }, function(response) {
    console.log(response);
  });
});

Me estoy poniendo:

Port: Could not establish connection. Receiving end does not exist. 

ACTUALIZAR:

{
  "manifest_version": 2,

  "name": "test",
  "description": "test",
  "version": "0.1",

  "permissions": [
    "tabs",
    "activeTab",
    "https://api.domain.com/"
  ],

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],

  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "Test",
    "default_popup": "index.html"
  }
}

content.js

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.text && (request.text == "getDOM")) {
      sendResponse({ dom: document.body.innerHTML });
    }
  }
);

popup.html

chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, { action: "getDOM" }, function(response) {
    console.log(response);
  });
});

Cuando lo ejecuto, sigo recibiendo el mismo error:

undefined
Port: Could not establish connection. Receiving end does not exist. lastError:30
undefined
brandonhilkert
fuente

Respuestas:

184

Los términos "página de fondo", "ventana emergente", "secuencia de comandos de contenido" todavía te confunden; Sugiero encarecidamente una mirada más profunda a la documentación de extensiones de Google Chrome .

Con respecto a su pregunta si los scripts de contenido o las páginas de fondo son el camino a seguir:

Scripts de contenido : definitivamente, los
scripts de contenido son el único componente de una extensión que tiene acceso al DOM de la página web.

Página de fondo / ventana emergente : tal vez (probablemente como máximo 1 de las dos)
Es posible que deba hacer que el script de contenido pase el contenido DOM a una página de fondo o a la ventana emergente para su posterior procesamiento.


¡Permítanme repetir que recomiendo encarecidamente un estudio más detenido de la documentación disponible!
Dicho esto, aquí hay una extensión de muestra que recupera el contenido DOM en las páginas de StackOverflow y lo envía a la página de fondo, que a su vez lo imprime en la consola:

background.js:

// Regex-pattern to check URLs against. 
// It matches URLs like: http[s]://[...]stackoverflow.com[...]
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?stackoverflow\.com/;

// A function to use as callback
function doStuffWithDom(domContent) {
    console.log('I received the following DOM content:\n' + domContent);
}

// When the browser-action button is clicked...
chrome.browserAction.onClicked.addListener(function (tab) {
    // ...check the URL of the active tab against our pattern and...
    if (urlRegex.test(tab.url)) {
        // ...if it matches, send a message specifying a callback too
        chrome.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
    }
});

content.js:

// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
    // If the received message has the expected format...
    if (msg.text === 'report_back') {
        // Call the specified callback, passing
        // the web-page's DOM content as argument
        sendResponse(document.all[0].outerHTML);
    }
});

manifest.json:

{
  "manifest_version": 2,
  "name": "Test Extension",
  "version": "0.0",
  ...

  "background": {
    "persistent": false,
    "scripts": ["background.js"]
  },
  "content_scripts": [{
    "matches": ["*://*.stackoverflow.com/*"],
    "js": ["content.js"]
  }],
  "browser_action": {
    "default_title": "Test Extension"
  },

  "permissions": ["activeTab"]
}
gkalpak
fuente
6
@solvingPuzzles: chrome.runtime.sendMessageenvía mensajes a BackgroundPage y a Popups. chrome.tabs.sendMessageenvía mensajes a ContentScripts.
gkalpak
22
Votado en contra ya que esta respuesta no explica cómo obtener el DOM ACTUAL de la pestaña actual.
John Paul Barbagallo
2
@JohnPaulBarbagallo: La pregunta era sobre obtener el contenido DOM, no sobre cómo acceder / manipular el DOM real. Creo que mi respuesta hace eso (y otros parecen pensar de la misma manera). Si tiene una mejor solución, publíquela como respuesta. Si tiene un requisito diferente, publíquelo como una nueva pregunta. En cualquier caso, gracias por los comentarios :)
gkalpak
2
@zoltar: Se imprime en la consola de la página de fondo.
gkalpak
2
He copiado / pegado esta respuesta pero no puedo obtener ningún console.log desde el script de contenido. ¡ayuda por favor!
ClementWalter
72

No es necesario que utilice el paso de mensajes para obtener o modificar DOM. Usé en su chrome.tabs.executeScriptlugar. En mi ejemplo, estoy usando solo el permiso activeTab, por lo tanto, el script se ejecuta solo en la pestaña activa.

parte de manifest.json

"browser_action": {
    "default_title": "Test",
    "default_popup": "index.html"
},
"permissions": [
    "activeTab",
    "<all_urls>"
]

index.html

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <button id="test">TEST!</button>
    <script src="test.js"></script>
  </body>
</html>

test.js

document.getElementById("test").addEventListener('click', () => {
    console.log("Popup DOM fully loaded and parsed");

    function modifyDOM() {
        //You can play with your DOM here or check URL against your regex
        console.log('Tab script:');
        console.log(document.body);
        return document.body.innerHTML;
    }

    //We have permission to access the activeTab, so we can call chrome.tabs.executeScript:
    chrome.tabs.executeScript({
        code: '(' + modifyDOM + ')();' //argument here is a string but function.toString() returns function's code
    }, (results) => {
        //Here we have just the innerHTML and not DOM structure
        console.log('Popup script:')
        console.log(results[0]);
    });
});
Oskar
fuente
1
¡Funciona perfectamente! Gracias. No sé por qué, pero no pude hacer que la solución aceptada funcionara para mí.
goodfellow
Su declaración de que está utilizando solo activeTabpermiso es inexacta. Claramente está obteniendo <all_urls>además de activeTab.
Makyen
1
test.js es un script que ha incluido en el HTML de su página, por lo que no estoy seguro de que necesite ningún permiso.
Scott Baker
11

Para aquellos que probaron la respuesta de gkalpak y no funcionó,

Tenga en cuenta que Chrome agregará el script de contenido a una página necesaria solo cuando su extensión esté habilitada durante el inicio de Chrome y también es una buena idea reiniciar el navegador después de realizar estos cambios.

bxN5
fuente
1
Esto me salvó el día
Romain Derie