chrome.tabs.executeScript(...)时Chrome扩展程序的跨源问题

时间:2016-12-05 18:27:09

标签: javascript google-chrome-extension

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

我决定将userScript变成Chrome扩展程序,我想添加运行不同版本脚本的选项。

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

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

然而,对脚本的肉功能normalExecution()的调用似乎被跨源约束阻止了?

我看到以下错误:

  

未捕获的ReferenceError:未定义normalExecution

     

未捕获的DOMException:使用origin阻止了一个帧   " https://website.com"从访问跨源框架

以下是我的 popup.html popup.js 代码。

总之,HTML代码有3个div作为按钮。目前,我已将它们全部设置为具有应执行normalExecution()中的代码的click-action-handler,但我得到上述错误。

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>

2 个答案:

答案 0 :(得分:1)

您目前正在尝试在 popup.js 中定义的内容脚本code中执行某项功能。因此,它未在内容脚本上下文/范围中定义。因此,当您尝试使用chrome.tabs.executeScript()

在内容脚本上下文/范围中执行ReferenceError时会出现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>

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

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

我的回答:Replacing a lot of text in browser's addon显示了仅在文本节点上执行.replace()的一种方法。以下仅更改文本节点的附加示例在某些方面更复杂,并且在这种情况下以其他方式不太复杂。但是,有人会将文本节点限制为仅包含在特定标记中的文本节点(在示例中为<p>标记)。在以下各项中,文本节点将替换为<span>,其中包含其他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()。这些示例不太复杂,因为您希望影响<span>元素的内容。因此,您需要考虑您有多个<spans>的情况,其中一些可能是其他人的后代。上述示例都没有处理这种情况,因为所有这些都处理了可能无法选择后代的情况(页面上的所有文本或<p>元素中的文本)。

内容脚本中的其他低效率

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

  • 您目前正在使用regexIndexOf()来测试字符串中RegExp的不匹配。您当前没有使用regexIndexOf()的附加功能来提供起始索引。对于您使用的功能,有多种现有方法:String.prototype.search()RegExp.prototype.test()String.prototype.match()
  • 您正在使用document.getElementsByTagName("span");,然后在"textblock"中检测到className的存在。您可以使用querySelectorAll()执行此操作:

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

    但是,更有可能的情况是,您实际上是在尝试检测<span>是否具有完全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) {}
    }
})();

答案 1 :(得分:0)

我不认为它是关于交叉起源的限制。 当您执行"normalExecution();"时,它实际上将此字符串注入页面并在页面的上下文中执行它。由于未在页面上定义normalExecution(而是在popup.js处定义),因此您获得ReferenceError。 你需要做这样的事情

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