【问题标题】:Cross origin issue with Chrome extension when chrome.tabs.executeScript(...)chrome.tabs.executeScript(...) 时 Chrome 扩展的跨源问题
【发布时间】:2017-04-20 05:41:41
【问题描述】:

我有一个在公司网站上运行的用户脚本,用于从页面中删除某些信息(基于 JavaScript 正则表达式替换)。

我决定将 userScript 制作成 Chrome 扩展程序,并且我想添加选项以运行不同风格的脚本。

我的第一个目标是让脚本在用户按下 popup.html 中的一个选项时运行。

目前,我的chrome.tabs.executeScript() 调用尝试访问该文档并用它的bodyHTML 减去被替换的正则表达式替换它的bodyHTML。

但是,对脚本的肉函数 normalExecution() 的调用似乎被跨域约束阻止了?

我看到以下错误:

Uncaught ReferenceError: normalExecution 未定义

Uncaught DOMException: Blocked a frame with origin “https://website.com”访问跨域框架

下面是我的 popup.htmlpopup.js 代码。

总的来说,HTML 代码有 3 个用作按钮的 div。目前,我已将它们全部设置为具有应执行 normalExecution() 中的代码的单击操作处理程序,但出现上述错误。

Popup.js

function click(e) {
  chrome.tabs.executeScript(null, {code:"normalExecution();"}, null);
  window.close();
}

var normalExecution = function(){
    console.log('normal execution engaged');
    var allSpans = document.getElementsByTagName("span");
    for(var i = 0; i < allSpans.length; i++) {
        try{
            if (allSpans[i].className.indexOf("textblock") > -1) {
                //check if the ">" is part of switch output, if not then procede
                if(allSpans[i].innerHTML.regexIndexOf(/sw.&gt;.*/g) < 0){
                    //console.log('true');
                    allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/&gt;.*/g, '    ---- Removed ----    ');
                    allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/(-----).*/g, '    ---- Removed Dashes (beta) ----    ');
                }
                else{
                    console.log('switch CLI output detected');
                }
            }
        }catch(e){}
    }
};


document.addEventListener('DOMContentLoaded', function () {
  console.log('adding event listener'); 
  var divs = document.querySelectorAll('div');
  for(var i = 0; i < divs.length; i++){
    divs[i].addEventListener('click', click);  
  }
});


String.prototype.regexIndexOf = function(regex, startpos) {
    var indexOf = this.substring(startpos || 0).search(regex);
    return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
};

popup.html [仅正文]:

<body>
<div id="Disabled">Disabled</div>
<div id="Normal">Normal</div>
<div id="Super-power">Super-power</div>

【问题讨论】:

  • 我建议您阅读Chrome extension architecture overview。它具有整体架构信息,应该有助于您了解通常如何完成/组织事情。
  • 您可以尝试在弹出窗口中添加一个事件监听器并将该函数用作回调。

标签: javascript google-chrome-extension


【解决方案1】:

您当前正尝试在您在 popup.js 中定义的内容脚本code 中执行一个函数。因此,它没有在内容脚本上下文/范围中定义。因此,当您尝试使用 chrome.tabs.executeScript() 在内容脚本上下文/范围内执行它时,您会得到一个 ReferenceError

没有理由在您的popup.js 中定义normalExecution。您没有在那里使用它,并且似乎无意在那里使用它。最好将此内容脚本移出到单独的文件中,然后注入该文件:

popup.js

function click(e) {
  chrome.tabs.executeScript({file:"/contentScript.js"});
  window.close();
}

document.addEventListener('DOMContentLoaded', function () {
  console.log('adding event listener'); 
  var divs = document.querySelectorAll('div');
  for(var i = 0; i < divs.length; i++){
    divs[i].addEventListener('click', click);  
  }
});

contentScript.js

(function() {
    //Don't change the prototype of a built in type just to use it _once_.
    //  Changing the prototype of a built in type is, generally, not a good idea. Sometimes,
    //  it is the right thing to do, but there are potential issues. Without learning what
    //  those issues are, it is better to avoid changing the prototype, particularly if
    //  you are only using it once.
    //String.prototype.regexIndexOf = function(regex, startpos) {
    function regexIndexOf(str, regex, startpos) {
        var indexOf = str.substring(startpos || 0).search(regex);
        return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
    };

    console.log('normal execution engaged');
    var allSpans = document.getElementsByTagName("span");
    for(var i = 0; i < allSpans.length; i++) {
        try {
            if (allSpans[i].className.indexOf("textblock") > -1) {
                //check if the ">" is part of switch output, if not then proceed
                if(regexIndexOf(allSpans[i].innerHTML,/sw.&gt;.*/g) < 0){
                    //console.log('true');
                    allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/&gt;.*/g
                        , '    ---- Removed ----    ');
                    allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/(-----).*/g
                        , '    ---- Removed Dashes (beta) ----    ');
                } else {
                    console.log('switch CLI output detected');
                }
            }
        } catch(e) {}
    }
})();

popup.html

<body>
<div id="Disabled">Disabled</div>
<div id="Normal">Normal</div>
<div id="Super-power">Super-power</div>

innerHTML 上使用.replace() 通常是个坏主意

innerHTML 属性上使用.replace() 并将其分配回innerHTML 具有多种潜在的负面影响。这些包括通过更改实际 HTML 元素中包含的值而不是文本中的值来潜在地破坏 HTML 代码;并通过删除正在侦听后代元素的事件处理程序来破坏现有的 JavaScript。您应该只将此类更改应用于文本节点。如果您可以控制要更改的 HTML,则对 innerHTML 属性执行 .replace() 可能是合理的。如果您无法控制页面(以及其他扩展程序中)使用的所有 HTML 和 JavaScript,那么您很可能会遇到问题。

我对@9​​87654321@ 的回答显示了一种仅在文本节点上执行.replace() 的方法。以下仅更改文本节点的附加示例在某些方面更复杂,而在其他方面则比本例所需的复杂。但是,一个显示将文本节点限制为仅包含在特定标记中的那些节点(在示例中,&lt;p&gt; 标记)。在以下每一项中,文本节点都被替换为 &lt;span&gt;,其中包括额外的 HTML(此处似乎不需要):Replace each word in webpage's paragraphs with a button containing that textChange matching words in a webpage's text to buttonsHighlight a word of text on the page using .replace()。这些示例没有必要那么复杂,因为您希望影响&lt;span&gt; 元素的内容。因此,您需要考虑有多个 &lt;spans&gt; 的情况,其中一些可能是其他人的后代。上述示例都没有处理这种情况,因为它们都处理了不可能选择后代的情况(页面上的所有文本,或&lt;p&gt; 元素中的文本)。

内容脚本效率低下

您的内容脚本(normalExecution()regexIndexOf())中还有其他内容可以更有效地完成。下面解决的是:

  • 您目前正在使用regexIndexOf() 来测试字符串中正则表达式的不匹配。您当前没有使用 regexIndexOf() 的新增功能来提供起始索引。对于您正在使用的功能,已有多种方法:String.prototype.search()RegExp.prototype.test()String.prototype.match()
  • 您正在使用document.getElementsByTagName("span");,然后在className 中检测到"textblock" 的存在。您可以使用querySelectorAll() 一步完成:

    document.querySelectorAll("span[class*=textblock]");
    

    但是,更有可能的情况是您实际上是在尝试检测&lt;span&gt; 是否有一个恰好是textblock 的类。在这种情况下,你会想要使用:

    document.querySelectorAll("span.textblock");
    

contentScript.js

(function() {
    console.log('normal execution engaged');
    //You may actually want the selector "span.textblock", which matches spans with the
    //  class "textblock" (e.g. <span class="foo textblock someOtherClass">).  However, the
    //   following matches your current code which checks to see that the className contains
    //  "textblock" somewhere (e.g. <span class="footextblockbar someOtherClass">).
    var allSpans = document.querySelectorAll("span[class*=textblock]");
    for(var i = 0; i < allSpans.length; i++) {
        try {
            //check if the ">" is part of switch output, if not then proceed
            //This does not account for the possibility of having the &gt both after 
            //  a 'sw.' and by itself.
            //It is unclear if you are really wanting /sw.&gt/ or /sw\.&gt/ here (i.e. is 
            //  the "." intended to match any character, or an actual "."). Which
            //  is really desired will depend on the text you are trying to not change.
            if(allSpans[i].innerHTML.search(/sw.&gt;.*/g) < 0 ){
                //console.log('true');
                allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/&gt;.*/g
                    , '    ---- Removed ----    ');
                allSpans[i].innerHTML = allSpans[i].innerHTML.replace(/(-----).*/g
                    , '    ---- Removed Dashes (beta) ----    ');
            } else {
                console.log('switch CLI output detected');
            }
        } catch(e) {}
    }
})();

【讨论】:

  • 谢谢,这很有帮助,我开始了解弹出窗口、contentScript 等之间的交互(和范围)。今晚晚些时候我会尝试一下 :)
  • @RenderedNonsense,我很高兴它有帮助。我添加了一个附加部分,它为您在normalExecution() 中所做的一些事情提供了替代方案。这些问题在文本和代码 cmets 中都有讨论。如果您在今晚晚些时候尝试时遇到问题,请随时发表评论来描述问题。
  • 非常感谢。我是一名 CS / 网络安全专业的学生,​​在 Java 方面拥有最丰富的经验。学习网络编程是最近的一个副项目,我非常感谢你的帮助。我的 javascript 目前是令人尴尬的 hacky。你的建议让我成为一个更好的程序员:)
  • 盯着这个问题看了好几个小时...我偶然发现了 iframe 并用 -- "all_frames": true 解决了它
  • @RenderedNonsense,是的,如果不是 manifest.json 中的 all_framestabs.executeScript() 中的 allFrames:true,则仅在顶部框架中注入。但是,这些选项非常不同。 manifest.json 中的all_frames 表示只有当 URL 匹配时,才会在创建/加载框架时在匹配的框架中进行注入,即使该框架不是顶级的框架。 allFrames:true for tabs.executeScript() 表示在执行tabs.executeScript() 时存在的指定框架和所有子框架将被注入内容脚本。
【解决方案2】:

我认为这与跨源限制无关。 当您执行"normalExecution();" 时,它实际上会将此字符串注入页面并在页面的上下文中执行它。由于normalExecution 没有在页面中定义(而是在您的popup.js 中定义),所以您会得到ReferenceError。 你需要做这样的事情

chrome.tabs.executeScript(null, {code: "(" + normalExecuton + ")();"}, null);

【讨论】:

  • 这是有道理的。我按照你说的做了,但现在我在与 popup.js(而不是网页)关联的控制台中收到“未捕获的 ReferenceError:normalExecution 未定义”错误。我认为这是因为该函数未在网页范围内声明。我怎样才能解决这个问题?传文件?传递整个方法的代码?
  • @renderednonsense 显然 normalExecution 需要在您调用 chrome.tabs.executeScript 的地方可以访问。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 1970-01-01
  • 2016-08-28
  • 2018-11-24
  • 1970-01-01
  • 2019-11-01
  • 1970-01-01
相关资源
最近更新 更多