使用由弹出窗口注入的executeScript()函数将数据从弹出窗口传递给内容脚本。

12

我的Chrome扩展程序弹出窗口中有一个文本区和一个按钮。我希望用户在文本区中输入所需的文本。然后,一旦他们点击按钮,它将注入内容脚本以更改当前页面上具有<textarea class="comments"> 的字段的文本为用户在Chrome扩展程序弹出窗口中输入的文本。

我的问题是,如何从我的popup.html中获取<textarea> 中的文本,并将其从popup.js传递到内容脚本?

这是我目前拥有的:

popup.html:

<!doctype html>  
<html>  
    <head><title>activity</title></head>  
<body>  
    <button id="clickactivity3">N/A</button> 
    <textarea rows="4" cols="10" id="comments" placeholder="N/A Reason..."></textarea>
    <script src="popup.js"></script> 
</body>
</html>  

弹出窗口.js:

function injectTheScript3() {
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
        // query the active tab, which will be only one tab
        //and inject the script in it
        chrome.tabs.executeScript(tabs[0].id, {file: "content_script3.js"});
    });
}

document.getElementById('clickactivity3').addEventListener('click', injectTheScript3);

内容脚本3:

//returns a node list which is good
var objSelectComments = document.querySelectorAll('.comments'); 

//desired user input how?
function setCommentsValue(objSelectComments,){

    //This will be for loop to iterate among all the text fields on the page, and apply
    //  the text to each instance. 
    for (var i=0; i<objSelectComments.length; i++) {
        objSelectComments[i]= //user's desired text 
    }

您不能将DOM元素传递到或从内容脚本中。通过所有方法传递的所有信息都必须能够表示为JSON。DOM元素无法表示为JSON。 - Makyen
从你的问题中可以看出,你真正想问的是如何将用户输入的文本从弹出窗口传递到内容脚本,以便内容脚本可以使用它来填充<textarea>的内容。这真的是你想问的吗?顺便说一下:你的选择器最好写成'textarea.comments'。目前你获取的是所有具有该类的元素,而不仅仅是<textarea class="comments"> - Makyen
嗨Makyen,是的,这正是我想做的。将弹出窗口中的用户文本传递到填充当前选项卡页面的所有实例中。 - tiger_groove
您可能需要查看从弹出窗口获取变量,到内容脚本,其中建议您需要使用消息传递将一个变量传递给内容脚本。除此之外,在Chrome扩展程序中替换当前文本区域中的单词中提供的解决方案也可能有所帮助。 - Teyam
@Makyen,您知道如何实现这个吗? - tiger_groove
1个回答

25
有三种一般的方法可以实现这个:
  1. 使用 chrome.storage.local (MDN) 传递数据(在注入脚本之前设置)。
  2. 在注入脚本之前插入代码,设置一个带有数据的变量(可能存在安全问题,请参见详细讨论)。
  3. 使用 message passing (MDN) 在注入脚本后传递数据。

使用 chrome.storage.local(在执行脚本之前设置)

使用此方法可以保持您正在使用的注入执行范例,即注入执行函数然后退出。它也没有使用动态值构建执行代码的潜在安全问题,这是下面第二个选项中完成的。

从您的弹出式脚本开始:

  1. 使用 chrome.storage.local.set() (MDN) 存储数据。
  2. chrome.storage.local.set() 的回调中,调用 tabs.executeScript() (MDN)。
var updateTextTo = document.getElementById('comments').value;
chrome.storage.local.set({
    updateTextTo: updateTextTo
}, function () {
    chrome.tabs.executeScript({
        file: "content_script3.js"
    });
});

来自您的内容脚本:

  1. chrome.storage.local.get()MDN)读取数据
  2. 对DOM进行更改
  3. 使storage.local中的数据无效(例如,删除键:chrome.storage.local.remove()MDN))。
chrome.storage.local.get('updateTextTo', function (items) {
    assignTextToTextareas(items.updateTextTo);
    chrome.storage.local.remove('updateTextTo');
});
function assignTextToTextareas(newText){
    if (typeof newText === 'string') {
        Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
            el.value = newText;
        });
    }
}

请参见:注释1和2。

在脚本之前注入代码以设置变量

在执行您的脚本之前,您可以注入一些代码来设置内容脚本上下文中的变量,然后您的主要脚本可以使用该变量:

安全问题:
以下使用"'" + JSON.stringify().replace(/\\/g,'\\\\').replace(/'/g,"\\'") + "'"将数据编码为文本,当作代码解释之前,放入code字符串中。 .replace()方法需要用于A)在使用代码时正确解释文本为字符串,B)引用数据中存在的任何'。然后使用JSON.parse()将数据返回到内容脚本中的字符串。虽然此编码不是严格要求的,但这是一个好主意,因为您不知道要发送到内容脚本的值的内容。这个值很容易成为导致注入的代码损坏的东西(例如用户可能在输入的文本中使用'和/或")。如果您没有以某种方式转义该值,则存在安全漏洞,可能会导致执行任意代码。

从你的弹出脚本:

  1. 注入一个简单的代码片段,将数据存储在变量中。
  2. MDNchrome.tabs.executeScript() 回调函数中,调用 tabs.executeScript() 来注入你的脚本(注意:只要它们对于 runAt 具有相同的值,tabs.executeScript() 将按照你调用 tabs.executeScript() 的顺序执行脚本。因此,严格来说,等待小 code 的回调并不是必需的)。
var updateTextTo = document.getElementById('comments').value;
chrome.tabs.executeScript({
    code: "var newText = JSON.parse('"
          + JSON.stringify(updateTextTo).replace(/\\/g,'\\\\').replace(/'/g,"\\'") + "';"
}, function () {
    chrome.tabs.executeScript({
        file: "content_script3.js"
    });
});

从您的内容脚本:

  1. 使用存储在变量中的数据对DOM进行更改
if (typeof newText === 'string') {
    Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
        el.value = newText;
    });
}

请参见:注释1、2和3。

使用消息传递MDN)(在内容脚本注入后发送数据)

这需要您的内容脚本代码安装一个侦听器,以便接收弹出窗口或者后台脚本发送的消息(如果与UI的交互导致弹出窗口关闭)。这会稍微复杂一些。

从您的弹出窗口脚本中:

  1. 使用 tabs.query() (MDN) 确定活动选项卡。
  2. 调用 tabs.executeScript() (MDN)。
  3. tabs.executeScript() 的回调函数中,使用 tabs.sendMessage() (MDN)(需要知道 tabId)将数据作为消息发送。
var updateTextTo = document.getElementById('comments').value;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.executeScript(tabs[0].id, {
        file: "content_script3.js"
    }, function(){
        chrome.tabs.sendMessage(tabs[0].id,{
            updateTextTo: updateTextTo
        });
    });
});

来自您的内容脚本:

  1. 使用chrome.runtime.onMessage.addListener()MDN)添加监听器。
  2. 退出主要代码,使监听器保持活动状态。如果选择,您可以返回一个成功指示器。
  3. 收到带有数据的消息后:
    1. 对DOM进行更改
    2. 删除您的runtime.onMessage监听器

#3.2是可选的。您可以保持代码处于活动状态,等待另一个消息,但这将改变您使用的范例,使其成为加载代码并保持驻留状态以等待消息来启动操作的模式。

chrome.runtime.onMessage.addListener(assignTextToTextareas);
function assignTextToTextareas(message){
    newText = message.updateTextTo;
    if (typeof newText === 'string') {
        Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
            el.value = newText;
        });
    }
    chrome.runtime.onMessage.removeListener(assignTextToTextareas);  //optional
}

参见:注1和注2。


注意1:如果您不需要多次使用并且正在使用支持它的浏览器版本(Chrome >= version 45,Firefox >= 32),那么使用Array.from()是可以的。在Chrome和Firefox中,与从NodeList获取数组的其他方法相比,Array.from()速度较慢。为了更快速、更兼容地将其转换为数组,您可以使用这个答案中的asArray()代码。该答案提供的第二个版本的asArray()也更加健壮。

注意2:如果你愿意将你的代码限制在Chrome版本>=51或Firefox版本>=50,Chrome从v51开始为NodeLists提供了forEach()方法。因此,你不需要转换为数组。当然,如果你使用不同类型的循环,也不需要转换为数组。
注意3:虽然我以前在自己的代码中使用过这种方法(通过注入带有变量值的脚本),但当阅读这个答案时,我被提醒应该在这里包含它。

非常感谢您详细的回答!我很佩服您的专业知识,希望有朝一日能达到您的水平 :) - tiger_groove

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接