web-dev-qa-db-ja.com

Chrome Extension:コンテンツスクリプトでのページ変数の取得

Google Chrome Content ScriptからページのJavaScript変数を取得する方法はありますか?

31
esqew

本当に必要な場合は、<script>要素をページのDOMに挿入できます。 <script>要素内のコードが実行され、そのコードはウィンドウのスコープでJavaScript変数にアクセスできます。次に、data-属性を使用してカスタムスクリプトを起動し、それらをコンテンツスクリプトに返信できます。

不自然に聞こえますか?なぜそうなのか、そうであり、sergが引用したドキュメントのすべての理由から意図的にそうなのです。しかし、本当に、本当にそれを行う必要がある場合は、それを行うことができます。詳細は here および here を参照してください。そして成功を祈る!

64
npdoty

私は少しヘルパーメソッドを作成しました、楽しんでください:)

ウィンドウの変数"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"を簡単に使用できます。

20
Liran Brimer

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;
}
14
Taras Kosteskyi

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プロミス付き!

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

ラウンド2-クラスと約束

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
});
2
CTS_AE

アクセスする変数がわかっている場合は、カスタムコンテンツスクリプトをすばやく作成して、それらの値を取得できます。

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();

コードを変更して、配列またはその他のオブジェクトを返すことができます。

1
victor

私は実際にlocalStorge APIを使用してそれを回避しました。注:これを使用するには、コンテンツスクリプトがlocalStorageを読み取ることができる必要があります。 manifest.jsonファイルで、「ストレージ」文字列を追加するだけです。

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

Hijack関数は、コンテンツスクリプトに含まれています。

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

これでcontentscriptから呼び出すことができます

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
Elia Grady

番号。

コンテンツスクリプトは、孤立した世界と呼ばれる特別な環境で実行されます。挿入されたページのDOMにアクセスできますが、ページによって作成されたJavaScript変数や関数にはアクセスできません。実行されているページで他のJavaScriptが実行されていないかのように、各コンテンツスクリプトを調べます。同じことが逆にも言えます。ページ上で実行されているJavaScriptは、関数を呼び出したり、コンテンツスクリプトで定義された変数にアクセスしたりすることはできません。

分離された世界では、ページや他のコンテンツスクリプトとの競合を心配することなく、各コンテンツスクリプトがJavaScript環境を変更できます。たとえば、コンテンツスクリプトにJQuery v1を含め、ページにJQuery v2を含めることができます。これらは互いに競合しません。

孤立した世界のもう1つの重要な利点は、ページ上のJavaScriptを拡張機能のJavaScriptから完全に分離することです。これにより、Webページにアクセスすることを心配せずに、Webページからアクセスできないコンテンツスクリプトに追加の機能を提供できます。

0
serg

他の回答で部分的に説明したように、ページの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');

変数が複合データ型(オブジェクト、配列...)の場合、値をJSON文字列としてgetJSvar()に保存し、JSON.parseでコンテンツスクリプトに戻す必要があることに注意してください。

0
SHamel