内容脚本中的页面变量

问题描述 投票:29回答:8

有没有办法从谷歌Chrome内容脚本中检索页面的javascript变量?

javascript google-chrome google-chrome-extension
8个回答
64
投票

如果你真的需要,你可以在页面的DOM中插入一个<script>元素;将执行<script>元素中的代码,并且该代码可以访问窗口范围内的JavaScript变量。然后,您可以使用data-属性将其传回内容脚本并触发自定义事件。

听起来很尴尬?为什么是,它是,并且故意是因为serg引用的文档中的所有原因。但如果你真的真的需要这样做,那就可以做到。有关详细信息,请参阅herehere。还有祝你好运!


19
投票

我创建了一个小帮手方法,玩得开心:)

检索窗口的变量“lannister”,“always”,“pays”,“his”,“debts”,执行以下命令:

var windowVariables = retrieveWindowVariables(["lannister", "always", "pays", "his", "debts"]);
console.log(windowVariables.lannister);
console.log(windowVariables.always);

我的代码:

function retrieveWindowVariables(variables) {
    var ret = {};

    var scriptContent = "";
    for (var i = 0; i < variables.length; i++) {
        var currVariable = variables[i];
        scriptContent += "if (typeof " + currVariable + " !== 'undefined') $('body').attr('tmp_" + currVariable + "', " + currVariable + ");\n"
    }

    var script = document.createElement('script');
    script.id = 'tmpScript';
    script.appendChild(document.createTextNode(scriptContent));
    (document.body || document.head || document.documentElement).appendChild(script);

    for (var i = 0; i < variables.length; i++) {
        var currVariable = variables[i];
        ret[currVariable] = $("body").attr("tmp_" + currVariable);
        $("body").removeAttr("tmp_" + currVariable);
    }

    $("#tmpScript").remove();

    return ret;
}

请注意我使用jQuery ..您可以轻松地使用本机js“removeAttribute”和“removeChild”。


14
投票

使用Liran的解决方案,我正在为Objects添加一些修复,这是正确的解决方案:

function retrieveWindowVariables(variables) {
    var ret = {};

    var scriptContent = "";
    for (var i = 0; i < variables.length; i++) {
        var currVariable = variables[i];
        scriptContent += "if (typeof " + currVariable + " !== 'undefined') $('body').attr('tmp_" + currVariable + "', JSON.stringify(" + currVariable + "));\n"
    }

    var script = document.createElement('script');
    script.id = 'tmpScript';
    script.appendChild(document.createTextNode(scriptContent));
    (document.body || document.head || document.documentElement).appendChild(script);

    for (var i = 0; i < variables.length; i++) {
        var currVariable = variables[i];
        ret[currVariable] = $.parseJSON($("body").attr("tmp_" + currVariable));
        $("body").removeAttr("tmp_" + currVariable);
    }

     $("#tmpScript").remove();

    return ret;
}

2
投票

Chrome的文档为您提供了一个很好的起点:https://developer.chrome.com/extensions/content_scripts#host-page-communication

此方法允许您将全局页面变量提取到内容脚本。它还使用一个想法只接受您在握手时识别的传入消息。您也可以使用Math.random()进行握手,但我有一些乐趣。

说明

  1. 此方法创建脚本标记
  2. 它将函数propagateVariable字符串化,并将当前的handShake和目标变量名称传递给字符串以进行保存,因为该函数无法访问我们的内容脚本范围。
  3. 然后它将该脚本标记注入页面。
  4. 然后,我们在内容脚本中创建一个监听器,等待从页面回来以传回我们所追求的变量。
  5. 到目前为止,已注入的脚本已经到达页面。
  6. 注入的代码被包装在IIFE中,因此它自己运行将数据推送到监听器。
  7. 可选:监听器确保它具有正确的握手,并且我们可以信任数据的来源(它实际上并不安全,但它有助于在这种情况下创建标识符,这给了我们一定程度的信任)。

第1轮

v1.0

const globalToExtract = 'someVariableName';
const array = new Uint32Array(5);
const handShake = window.crypto.getRandomValues(array).toString();

function propagateVariable(handShake, variableName) {
  const message = { handShake };
  message[variableName] = window[variableName];
  window.postMessage(message, "*");
}

(function injectPropagator() {
  const script = `( ${propagateVariable.toString()} )('${handShake}', '${globalToExtract}');`
  const scriptTag = document.createElement('script');
  const scriptBody = document.createTextNode(script);
  
  scriptTag.id = 'chromeExtensionDataPropagator';
  scriptTag.appendChild(scriptBody);
  document.body.append(scriptTag);
})();

window.addEventListener("message", function({data}) {
  console.log("INCOMINGGGG!", data);
  // We only accept messages from ourselves
  if (data.handShake != handShake) return;

  console.log("Content script received: ", data);
}, false);

v1.1 With Promise!

function extractGlobal(variableName) {

  const array = new Uint32Array(5);
  const handShake = window.crypto.getRandomValues(array).toString();

  function propagateVariable(handShake, variableName) {
    const message = { handShake };
    message[variableName] = window[variableName];
    window.postMessage(message, "*");
  }

  (function injectPropagator() {
    const script = `( ${propagateVariable.toString()} )('${handShake}', '${variableName}');`
    const scriptTag = document.createElement('script');
    const scriptBody = document.createTextNode(script);

    scriptTag.id = 'chromeExtensionDataPropagator';
    scriptTag.appendChild(scriptBody);
    document.body.append(scriptTag);
  })();

  return new Promise(resolve => {
    window.addEventListener("message", function({data}) {
      // We only accept messages from ourselves
      if (data.handShake != handShake) return;
      resolve(data);
    }, false);
  });
}

extractGlobal('someVariableName').then(data => {
  // Do Work Here
});

第二轮 - 阶级和承诺

v2.0

如果使用es模块,我建议将类放入自己的文件并将其导出为默认值。然后它变成:

ExtractPageVariable('someGlobalPageVariable').data.then(pageVar => {
  // Do work here 💪
});

class ExtractPageVariable {
  constructor(variableName) {
    this._variableName = variableName;
    this._handShake = this._generateHandshake();
    this._inject();
    this._data = this._listen();
  }

  get data() {
    return this._data;
  }

  // Private

  _generateHandshake() {
    const array = new Uint32Array(5);
    return window.crypto.getRandomValues(array).toString();
  }

  _inject() {
    function propagateVariable(handShake, variableName) {
      const message = { handShake };
      message[variableName] = window[variableName];
      window.postMessage(message, "*");
    }

    const script = `( ${propagateVariable.toString()} )('${this._handShake}', '${this._variableName}');`
    const scriptTag = document.createElement('script');
    const scriptBody = document.createTextNode(script);

    scriptTag.id = 'chromeExtensionDataPropagator';
    scriptTag.appendChild(scriptBody);
    document.body.append(scriptTag);
  }

  _listen() {
    return new Promise(resolve => {
      window.addEventListener("message", ({data}) => {
        // We only accept messages from ourselves
        if (data.handShake != this._handShake) return;
        resolve(data);
      }, false);
    })
  }
}

const windowData = new ExtractPageVariable('somePageVariable').data;
windowData.then(console.log);
windowData.then(data => {
   // Do work here
});

1
投票

我实际上使用本地Storage API解决了这个问题。注意:要使用它,我们的内容脚本应该能够读取localStorage。在manifest.json文件中,只需添加“storage”字符串:

"permissions": [...,"storage"]

劫持功能存在于内容脚本中:

function hijack(callback) {
    "use strict";
    var code = function() {
      //We have access to topframe - no longer a contentscript          
      var ourLocalStorageObject = {
        globalVar: window.globalVar,
        globalVar2: window.globalVar2
      };
      var dataString = JSON.stringify(ourLocalStorageObject);
      localStorage.setItem("ourLocalStorageObject", dataString);
    };
    var script = document.createElement('script');
    script.textContent = '(' + code + ')()';
    (document.head||document.documentElement).appendChild(script);
    script.parentNode.removeChild(script);
    callback();
  }

现在我们可以从内容脚本调用

document.addEventListener("DOMContentLoaded", function(event) { 
    hijack(callback);
});

或者如果你在你的内容中使用jQuery,就像我一样:

$(document).ready(function() { 
    hijack(callback);
});

提取内容:

function callback() {
    var localStorageString = localStorage.getItem("ourLocalStorageObject");
    var ourLocalStorageObject= JSON.parse(localStorageString);

    console.log("I can see now on content script", ourLocalStorageObject);
    //(optional cleanup):
    localStorage.removeItem("ourLocalStorageObject");
}

这可以多次调用,因此如果您的页面更改元素或内部代码,您可以添加事件侦听器以使用新数据更新扩展。

编辑:我添加了回调,因此您可以确保您的数据不会无效(我自己有这个问题)


1
投票

如果您知道要访问哪些变量,则可以创建一个快速自定义内容脚本来检索其值。

popup.js

chrome.tabs.executeScript(null, {code: 'var name = "property"'}, function() {
    chrome.tabs.executeScript(null, {file: "retrieveValue.js"}, function(ret) {
        for (var i = 0; i < ret.length; i++) {
            console.log(ret[i]); //prints out each returned element in the array
        }
    });
});

retrieveValue.js

function returnValues() {
    return document.getElementById("element")[name];
    //return any variables you need to retrieve
}
returnValues();

您可以修改代码以返回数组或其他对象。


0
投票

没有。

内容脚本在称为孤立世界的特殊环境中执行。他们可以访问注入页面的DOM,但不能访问页面创建的任何JavaScript变量或函数。它将每个内容脚本视为在其运行的页面上没有执行其他JavaScript。反过来也是如此:页面上运行的JavaScript无法调用任何函数或访问内容脚本定义的任何变量。

隔离世界允许每个内容脚本对其JavaScript环境进行更改,而不必担心与页面或其他内容脚本冲突。例如,内容脚本可以包含JQuery v1,页面可以包含JQuery v2,它们不会相互冲突。

孤立世界的另一个重要好处是它们将页面上的JavaScript与扩展中的JavaScript完全分开。这使我们能够为不能从网页访问的内容脚本提供额外的功能,而无需担心网页访问它。


0
投票

正如在其他答案中部分解释的那样,页面中的JS变量与Chrome扩展内容脚本隔离。通常,无法访问它们。

但是,如果在页面中注入JavaScript标记,则可以访问其中定义的任何变量。

我使用实用程序函数在页面中注入我的脚本:

/**
 * inject - Inject some javascript in order to expose JS variables to our content JavaScript
 * @param {string} source - the JS source code to execute
 * Example: inject('(' + myFunction.toString() + ')()');
 */
function inject(source) {
  const j = document.createElement('script'),
    f = document.getElementsByTagName('script')[0];
  j.textContent = source;
  f.parentNode.insertBefore(j, f);
  f.parentNode.removeChild(j);
}

然后你可以这样做:

function getJSvar(whichVar) {
   document.body.setAttribute('data-'+whichVar,whichVar);
}
inject('(' + getJSvar.toString() + ')("somePageVariable")');

var pageVar = document.body.getAttribute('data-somePageVariable');

请注意,如果变量是复杂数据类型(对象,数组...),则需要将值存储为getJSvar()中的JSON字符串,并将JSON.parse存储在内容脚本中。

© www.soinside.com 2019 - 2024. All rights reserved.