Extensión de Chrome cómo enviar datos desde la secuencia de comandos de contenido a popup.html

96

Sé que esto se ha preguntado en numerosas publicaciones, pero honestamente no las entiendo. Soy nuevo en JavaScript, extensiones de Chrome y todo y tengo esta tarea de clase. Entonces, necesito crear un complemento que cuente los objetos DOM en cualquier página determinada utilizando solicitudes de dominio cruzado. He podido lograr esto hasta ahora usando las API de extensión de Chrome. Ahora el problema es que necesito mostrar los datos en mi página popup.html del archivo contentScript.js. No sé cómo hacerlo. Intenté leer la documentación, pero al enviar mensajes en Chrome, simplemente no puedo entender qué hacer.

el siguiente es el código hasta ahora.

manifest.json

{
"manifest_version":2,

"name":"Dom Reader",
"description":"Counts Dom Objects",
"version":"1.0",

"page_action": {
    "default_icon":"icon.png",
    "default_title":"Dom Reader",
    "default_popup":"popup.html"
},

"background":{
    "scripts":["eventPage.js"],
    "persistent":false
},

"content_scripts":[
    {
        "matches":["http://pluralsight.com/training/Courses/*", "http://pluralsight.com/training/Authors/Details/*",                                          "https://www.youtube.com/user/*", "https://sites.google.com/site/*", "http://127.0.0.1:3667/popup.html"],
        "js":["domReader_cs.js","jquery-1.10.2.js"]
        //"css":["pluralsight_cs.css"]
    }
],

"permissions":[
    "tabs",
    "http://pluralsight.com/*",
    "http://youtube.com/*",
    "https://sites.google.com/*",
    "http://127.0.0.1:3667/*"
]

popup.html

<!doctype html>
<html>

    <title> Dom Reader </title>    
    <script src="jquery-1.10.2.js" type="text/javascript"></script>
    <script src="popup.js" type="text/javascript"></script>

<body>
    <H1> Dom Reader </H1>
    <input type="submit" id="readDom" value="Read DOM Objects" />

   <div id="domInfo">

    </div>
</body>
</html>

eventPage.js

var value1,value2,value3;

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.action == "show") {
    chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.pageAction.show(tabs[0].id);
    });
}

value1 = request.tElements;
});

popup.js

$(function (){
$('#readDom').click(function(){
chrome.tabs.query({active: true, currentWindow: true}, function (tabs){
    chrome.tabs.sendMessage(tabs[0].id, {action: "readDom"});

 });
});
});

contentScript

var totalElements;
var inputFields;
var buttonElement;

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse){
if(request.action == "readDom"){

    totalElements = $("*").length;
    inputFields = $("input").length;
    buttonElement = $("button").length;


}
})

chrome.runtime.sendMessage({ 
action: "show", 
tElements: totalElements, 
Ifields: inputFields, 
bElements: buttonElement 

});

Cualquier ayuda será apreciada y por favor evite cualquier novato que hice :)

Sumair Baloch
fuente

Respuestas:

173

Aunque definitivamente está en la dirección correcta (y en realidad bastante cerca del final), hay varias (imo) malas prácticas en su código (por ejemplo, inyectar una biblioteca completa (jquery) para una tarea tan trivial, declarar permisos innecesarios, hacer superfluo llamadas a métodos API, etc.).
No probé su código yo mismo, pero a partir de una descripción general rápida, creo que corregir lo siguiente podría resultar en una solución de trabajo (aunque no muy cercana a la óptima):

  1. En manifest.json : cambie el orden de los scripts de contenido, poniendo jquery primero. Según los documentos relevantes :

    "js" [...] La lista de archivos JavaScript que se inyectarán en páginas coincidentes. Estos se inyectan en el orden en que aparecen en esta matriz.

    (énfasis mío)

  2. En contentscript.js : Mover el chrome.runtime.sendMessage ({...}) del bloque en el interior de la onMessagedevolución de llamada oyente.


Dicho esto, aquí está mi enfoque propuesto:

Flujo de control:

  1. Se inyecta un guión de contenido en cada página que coincide con algunos criterios.
  2. Una vez inyectados, los scripts de contenido envían un mensaje a la página del evento (también conocida como página de fondo no persistente) y la página del evento adjunta una acción de página a la pestaña.
  3. Tan pronto como se carga la ventana emergente de acción de página, envía un mensaje al script de contenido, solicitando la información que necesita.
  4. El script de contenido procesa la solicitud y responde para que la ventana emergente de acción de página pueda mostrar la información.

Estructura de directorios:

          root-directory/
           |_____img
                 |_____icon19.png
                 |_____icon38.png
           |_____manifest.json
           |_____background.js
           |_____content.js
           |_____popup.js
           |_____popup.html

manifest.json:

{
  "manifest_version": 2,
  "name": "Test Extension",
  "version": "0.0",
  "offline_enabled": true,

  "background": {
    "persistent": false,
    "scripts": ["background.js"]
  },

  "content_scripts": [{
    "matches": ["*://*.stackoverflow.com/*"],
    "js": ["content.js"],
    "run_at": "document_idle",
    "all_frames": false
  }],

  "page_action": {
    "default_title": "Test Extension",
    //"default_icon": {
    //  "19": "img/icon19.png",
    //  "38": "img/icon38.png"
    //},
    "default_popup": "popup.html"
  }

  // No special permissions required...
  //"permissions": []
}

background.js:

chrome.runtime.onMessage.addListener((msg, sender) => {
  // First, validate the message's structure.
  if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
    // Enable the page-action for the requesting tab.
    chrome.pageAction.show(sender.tab.id);
  }
});

content.js:

// Inform the background page that 
// this tab should have a page-action.
chrome.runtime.sendMessage({
  from: 'content',
  subject: 'showPageAction',
});

// Listen for messages from the popup.
chrome.runtime.onMessage.addListener((msg, sender, response) => {
  // First, validate the message's structure.
  if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
    // Collect the necessary data. 
    // (For your specific requirements `document.querySelectorAll(...)`
    //  should be equivalent to jquery's `$(...)`.)
    var domInfo = {
      total: document.querySelectorAll('*').length,
      inputs: document.querySelectorAll('input').length,
      buttons: document.querySelectorAll('button').length,
    };

    // Directly respond to the sender (popup), 
    // through the specified callback.
    response(domInfo);
  }
});

popup.js:

// Update the relevant fields with the new data.
const setDOMInfo = info => {
  document.getElementById('total').textContent = info.total;
  document.getElementById('inputs').textContent = info.inputs;
  document.getElementById('buttons').textContent = info.buttons;
};

// Once the DOM is ready...
window.addEventListener('DOMContentLoaded', () => {
  // ...query for the active tab...
  chrome.tabs.query({
    active: true,
    currentWindow: true
  }, tabs => {
    // ...and send a request for the DOM info...
    chrome.tabs.sendMessage(
        tabs[0].id,
        {from: 'popup', subject: 'DOMInfo'},
        // ...also specifying a callback to be called 
        //    from the receiving end (content script).
        setDOMInfo);
  });
});

popup.html:

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="popup.js"></script>
  </head>
  <body>
    <h3 style="font-weight:bold; text-align:center;">DOM Info</h3>
    <table border="1" cellpadding="3" style="border-collapse:collapse;">
      <tr>
        <td nowrap>Total number of elements:</td>
        <td align="right"><span id="total">N/A</span></td>
      </tr>
      <tr>
        <td nowrap>Number of input elements:</td>
        <td align="right"><span id="inputs">N/A</span></td>
      </tr>
      <tr>
        <td nowrap>Number of button elements:</td>
        <td align="right"><span id="buttons">N/A</span></td>
      </tr>
    </table>
  </body>
</html>
gkalpak
fuente
Guauu ! gracias hermano, comprobando tu enfoque ahora siento cuántos problemas he creado con mi código. Muchas gracias, esto funciona como un encanto. :)
Sumair Baloch
hola, lo siento, no he estado en por algún tiempo, solo revisé tu comentario. No puedo aceptar ninguna respuesta. dice que necesita 15 puntos de reputación para hacerlo.
Sumair Baloch
¿Le importaría ayudarme a resolver un problema muy similar? <a href=" stackoverflow.com/questions/34467627/…> Mi oportunidad actual es más o menos tu código de esta respuesta. Pero todavía no puedo hacer que funcione y parece que no puedo encontrar ninguna buena ayuda al respecto.
wuno
por qué usó chrome.tabs.sendMessageen popupjs y chrome.runtime.onMessage.addListeneren content.js por qué .tabspara popupjs y .runtimeen content.js
Habib Kazemi
2
@hkm: Porque así es como envías y recibes mensajes. Todo está en los documentos .
gkalpak
7

Puede usar localStorage para eso. Puede almacenar cualquier dato en un formato de tabla hash en la memoria del navegador y luego acceder a él en cualquier momento. No estoy seguro de si podemos acceder a localStorage desde el script de contenido (estaba bloqueado antes), intente hacerlo usted mismo. Aquí se explica cómo hacerlo a través de su página de fondo (primero paso los datos del script de contenido a la página de fondo, luego los guardo en localStorage):

en contentScript.js:

chrome.runtime.sendMessage({
  total_elements: totalElements // or whatever you want to send
});

en eventPage.js (su página de fondo):

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse){
       localStorage["total_elements"] = request.total_elements;
    }
);

Luego, puede acceder a esa variable en popup.js con localStorage ["total_elements"].

Tal vez pueda acceder a localStorage directamente desde el script de contenido en los navegadores modernos. Entonces no necesita pasar los datos a través de su página de fondo.

Buena lectura sobre localStorage: http://diveintohtml5.info/storage.html

gthacoder
fuente
1
Por favor, no promueva el uso de chrome.extension.onRequest / sendRequest en desuso (que por cierto no cargan una página de fondo no persistente antes de ejecutarse). Utilice chrome.runtime. * En su lugar.
gkalpak
1
@ExpertSystem Por favor, si ve información tan desactualizada, sugiera / haga una edición.
Xan
3
@Xan: Entonces, eres el sucesor en [Google-Chrome-Extension] :) No me gusta editar las publicaciones de las personas (lo encuentro demasiado intrusivo). Además, con tantos tutoriales y ejemplos obsoletos (al menos en ese entonces), me pareció mejor tener un comentario explícito sobre por qué es malo usarlo .extension.xxx, en lugar de simplemente mostrar la forma correcta (que algunas personas podrían considerar como un " igualmente bien alternativa "). Supongo que es más una cuestión de estilo. ¡Sigan con el buen trabajo!
gkalpak