我正在尝试创建一个执行以下操作的Firefox插件:
我使用的是Firefox 48版的Windows,我无法使用它。有人可以指出我做错了吗。
这是我的内容脚本:
// setup message channel between this script and background script
var msgPort = chrome.runtime.connect({name:"msgPort"});
// fires when background script sends a message
msgPort.onMessage.addListener(function(msg) {
console.log(msg.txt);
});
// sends a message to background script when page is clicked
document.body.addEventListener("click", function() {
msgPort.postMessage({txt: "page clicked"});
});
这是我的后台脚本:
var msgPort;
var tmp;
// fires when message port connects to this background script
function connected(prt)
{
msgPort = prt;
msgPort.postMessage({txt: "message channel established"});
msgPort.onMessage.addListener(gotMessage);
}
// fires when content script sends a message
frunction gotMessage(msg)
{
// store the message
chrome.storage.local.set({message : msg.txt});
// read the stored message back again
chrome.storage.local.get("message", function(item){
tmp = item;
});
}
// send the saved message to the content script when the add-on button is clicked
chrome.browserAction.onClicked.addListener(function() {
msgPort.postMessage({txt: "saved message: "+tmp});
});
chrome.runtime.onConnect.addListener(connected);
这是我的清单:
{
"name": "test",
"manifest_version": 2,
"version": "1.0",
"permissions": ["activeTab","storage"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Go"
},
"applications": {
"gecko": {
"id": "test@example.com",
"strict_min_version": "48.0a1"
}
}
}
答案 0 :(得分:3)
虽然您的代码中可能存在其他问题,但有一个问题是您没有考虑到chrome.storage.local.set
是异步的事实。
正如您的代码所示,您在chrome.storage.local.set
请求存储数据后立即执行对chrome.storage.local.get
的调用,而无需等待实际存储数据。因此,数据可能尚未可用。
您的gotMessage(msg)
功能应该更像:
function gotMessage(msg)
{
// store the message
chrome.storage.local.set({message : msg.txt}, function(){
// read the stored message back again
chrome.storage.local.get("message", function(item){
tmp = item;
//Indicate that the data is available
//Only do this if you desire.
//chrome.browserAction.setTitle({title:'Send data to content script'});
});
});
}
注意:您的问题中还存在语法错误,其中frunction gotMessage(msg)
应为function gotMessage(msg)
。
storage.local
可用于内容脚本:
您目前正在内容脚本和后台脚本之间来回传递消息,以便在后台脚本中set()
和get()
内容storage.local
。我假设你这样做是为了测试runtime.connect
,browser_action
等。另一种可能性是你不知道你可以get
和set
storage.local
1}}来自您的内容脚本。
还有其他一些问题。大多数问题都是由于用户需要使用 的一些复杂的操作序列才能使其工作。所需要的一些原因是由于WebExtensions / Firefox的一些特点。:
about:addons
( Ctrl - Shift - A , Cmd - Shift - OSX上的 A ,&#34;删除&#34;附加组件。about:debugging
重新加载临时加载项。runtime.connect()
。如果没有第一个 runtime.onConnect
侦听器,则无法连接。因此,内容脚本已经在后台脚本之后加载。browser_action
按钮如果不知道你需要完成这个序列,就很难让它做你想做的事。
在弄清楚发生了什么的困难中,很大一部分就是你有大量的状态信息只包含在代码中。没有迹象表明用户关于内容脚本或后台脚本所处的状态。为了帮助可视化每个脚本中发生的事情,我已经向{{1}添加了大量调用。更好地说明脚本中发生的事情。
我已经对代码进行了重大修改:
现在在浏览器控制台中生成的输出是:
console.log
的manifest.json :
1471884197092 addons.xpi WARN Addon with ID demo-runtime.connect-and-storage.local@example.com already installed, older version will be disabled
content: Content script injected.
content: Making message port available for connection
alert() is not supported in background windows; please use console.log instead.
Open the Browser Console.
background: In background script.
background: listening for a connection
content: page clicked
content: Sending message failed: not yet connected
content: Retrying connection for message: Object { type: "page clicked" }
content: Making message port available for connection
background: Port connected: sending confirmation message: Object { type: "message channel established" }
content: Received message: type: message channel established
content: Sending pending message Object { type: "page clicked" }
content: Sending message Object { type: "page clicked" }
background: Received message: Object { type: "page clicked" }
background: Got data from storage.local.get: Object { message: "page clicked" }
background: Button clicked sending message: Object { type: "saved message", data: "page clicked" }
content: Received message: type: saved message
message: data: page clicked
background.js :
{
"name": "Demo runtime.connect and storage.local",
"manifest_version": 2,
"version": "0.1",
"permissions": ["activeTab","storage"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Data not yet available",
"browser_style": true
},
"applications": {
"gecko": {
"id": "demo-runtime.connect-and-storage.local@example.com",
"strict_min_version": "48.0a1"
}
}
}
content.js :
//* For testing, open the Browser Console
try{
//Alert is not supported in Firefox. This forces the Browser Console open.
//This abuse of a misfeature works in FF49.0b+, not in FF48
alert('Open the Browser Console.');
}catch(e){
//alert throws an error in Firefox versions below 49
console.log('Alert threw an error. Probably Firefox version below 49.');
}
//*
console.log('background: In background script.');
var msgPort;
var dataFromStorage;
// Fires when message port connects to this background script
function connected(prt) {
msgPort = prt;
msgPort.onMessage.addListener(gotMessage); //This should be done first
let message = {type: "message channel established"};
console.log('background: Port connected: sending confirmation message:', message);
msgPort.postMessage(message);
}
//Fires when content script sends a message
//Syntax error this line (misspelled function)
function gotMessage(msg) {
console.log('background: Received message:', msg);
// store the message
chrome.storage.local.set({message : msg.type}, function(){
// read the stored message back again
chrome.storage.local.get("message", function(item){
console.log('background: Got data from storage.local.get:',item);
//You were setting tmp (now dataFromStorage) to the item object, not the
// message key which you requested.
/*
for(x in item){
console.log('background: property of item:',x);
}
//*/
dataFromStorage = item.message;
//Indicate to the user that the data is available
chrome.browserAction.setTitle({title:'Send data to content script'});
});
});
}
// send the saved message to the content script when the add-on button is clicked
chrome.browserAction.onClicked.addListener(function() {
//msgPort not defined unless user has clicked on page
if(msgPort) {
let message = {type: "saved message",data:dataFromStorage};
console.log('background: Button clicked sending message:', message);
msgPort.postMessage(message);
} else {
console.log('background: No message port available (yet).');
}
});
//Be open to establishing a connection. This must be done prior to the
// chrome.runtime.connect elsewhere in your code.
chrome.runtime.onConnect.addListener(connected);
console.log('background: Listening for a connection');
注意:强>
这适用于演示或学习,但在生产扩展中使用效果不佳。代码中没有任何内容可以说明有多个标签。
您正在点击console.log('\tcontent: Content script injected.');
var isConnected=false;
var retryConnectionTimerId=-1; //In case we want to cancel it
var retryConnectionCount=0;
var messageBeingRetried=null;
//setup message channel between this script and background script
var msgPort;
function messageListener(msg){
//Using a closure for this function is a bad idea. This should be a named
// function defined at the global scope so we can remove it as a
// listener if the background script sends a message to disconnect.
// You need to be able to disable any active content scripts if the
// add-on is disabled/removed. This is a policy from Mozilla. However,
// for WebExtensions it is not yet possible due to the current lack of the
// runtime.onSuspend event.
if(typeof msg === 'object' && msg.hasOwnProperty('type')){
//Should look specifically for the message indicating connection.
console.log('\tcontent: Received message: type:', msg.type
,(msg.hasOwnProperty('data') ? '\n\t\t\t Message: data:':'')
,(msg.hasOwnProperty('data') ? msg.data : '')
);
if(msg.type === 'disableAddon'){
//Allow for the background script to disable the add-on.
disableThisScript('Received disableAddon message');
}
if(isConnected && msg.type === 'message channel established'){
//We are in a content script that is left over from a previous load
// of this add-on. Or, at least that is the most likely thing
// while testing. This probably needs to change for a real add-on.
// This is here because reloading the temporary add-on does not
// auto-disable any content scripts.
disableThisScript('Received second channel established message');
return;
}//else
isConnected=true; //Any correctly formatted message received indicates connection
//Immediately send a message that was pending (being retried).
// Note: This only immediately sends the message which was most recently attempted
// to send via sendMessage, not all messages which might be waiting in timers.
// Any others will be sent when their timers expire.
sendPendingMessageIfPending();
}else{
console.log('\tcontent: Received message without a "type":', msg);
}
}
function receiveDisconnect(){
//The port was disconnected
disableThisScript('Port disconnected');
isConnected=false;
}
function makePortAvailableForConnection(){
console.log('\tcontent: Making message port available for connection');
if(msgPort && typeof msgPort.disconnect === 'function'){
//Call disconnect(), if we have already tried to have a connection
msgPort.disconnect();
}
//Try to make a connection. Only works if ocConnect listener
// is already established.
msgPort = chrome.runtime.connect({name:"msgPort"});
//Fires when background script sends a message
msgPort.onMessage.addListener(messageListener);
msgPort.onDisconnect.addListener(receiveDisconnect);
//Can not use runtime.onConnect to detect that we are connected.
// It only fires if some other script is trying to connect
// (using chrome.runtime.connect or chrome.tabs.connect)
// to this script (or generally). It does not fire when the connection
// is initiated by this script.
chrome.runtime.onConnect.addListener(portConnected); //Does not fire
}
function portConnected(){
//This event does not fire when the connection is initiated,
// chrome.runtime.connect() from this script.
// It is left in this code as an example and to demonstrate that the event does
// not fire.
console.log('\tcontent: Received onConnect event');
isConnected=true;
}
// sends a message to background script when page is clicked
function sendClickMessage() {
console.log('\tcontent: Page clicked');
sendMessage({type: "page clicked"});
chrome.storage.local.get("message", function(item){
console.log('content: Got data from storage.local.get:',item);
});
}
function clearPendingMessage(){
window.clearTimeout(retryConnectionTimerId);
messageBeingRetried=null;
}
function sendPendingMessageIfPending() {
//Pending messages should really be implemented as a queue with each message
// being retried X times and then sent once a connection is made. Right now
// this works for a single message. Any other messages which were pending
// are only pending for the retry period and then they are forgotten.
if(messageBeingRetried !== null && retryConnectionTimerId){
let message = messageBeingRetried;
clearPendingMessage();
console.log('\tcontent: Going to send pending message', message);
sendMessage(message);
}
}
function retryingMessage(message) {
retryConnectionTimerId=-1;
messageBeingRetried=null;
sendMessage(message);
}
function sendMessage(message) {
if(isConnected){
try{
console.log('\tcontent: Sending message', message);
msgPort.postMessage(message);
retryConnectionCount=0;
}catch(e){
if(e.message.indexOf('disconnected port') > -1){
console.log('\tcontent: Sending message failed: disconnected port');
if(isConnected){
console.log('\tcontent: Had connection, but lost it.'
+ ' Likely add-on reloaded. So, disable.');
disableThisScript('Add-on likely reloaded.');
}else{
retryConnection(message);
}
}else{
console.log('\tcontent: Sending message failed: Unknown error', e);
}
}
}else{
console.log('\tcontent: Sending message failed: not yet connected');
retryConnection(message);
}
}
function retryConnection(message){
if(retryConnectionCount>=5){
//Limit the number of times we retry the connection.
// If the connection is not made by now, it probably won't be
// made at all. Don't fill up the console with a lot of
// messages that might make it harder to see what is happening.
retryConnectionCount=0; //Allow more retries upon another click event.
//The current message is forgotten. It is now just discarded.
return;
}
console.log('\tcontent: Retrying connection for message:', message);
makePortAvailableForConnection();
//Try sending the message after a timeout.
// This will result in the repeated attempts to
// connect and send the message.
messageBeingRetried=message;
retryConnectionTimerId = window.setTimeout(retryingMessage,500,message);
retryConnectionCount++;
}
function disableThisScript(reason){
console.log('\tcontent: Disable the content script:', reason);
//Gracefully disable everything previously set up.
msgPort.onMessage.removeListener(messageListener);
msgPort.onDisconnect.removeListener(receiveDisconnect);
try{
msgPort.disconnect();
}catch(e){
//most likely the port was already disconnected
}
chrome.runtime.onConnect.removeListener(portConnected);
document.body.removeEventListener("click", sendClickMessage);
isConnected=false;
}
//Making the connection available will silently fail if there is not already a
// onConnect listener in the background script. In the case of a "temporary"
// add-on upon load or reload, the content script is run first and
// no connection is made.
makePortAvailableForConnection();
document.body.addEventListener("click", sendClickMessage);
按钮向内容脚本发送消息。这意味着您需要能够仅将后台脚本中的消息显式发送到用户单击browser_action
按钮时活动窗口中显示的选项卡中的内容脚本。例如,用户可能在一个页面中,单击内容,然后切换到另一个选项卡,然后单击browser_action
按钮。在这种情况下,应将消息发送到当前活动的选项卡,而不是已切换到的选项卡(进行连接)。
虽然您可以跟踪在收到连接或单击消息时处于活动状态的选项卡(这可能因为实际建立了连接,或者基于点击事件(假设是用户输入)发送了消息) ),使用tabs.connect()
可能更好,它允许您仅与特定选项卡建立连接。了解哪个runtime.Port
与每个标签对应,您可以确保仅向点击browser_action
按钮时处于活动状态的标签发送消息。您需要保留一个包含由选项卡ID索引的连接端口的数组或对象。