未定义函数或拒绝访问Gresemonkey中的对象
我正在开发一个Gresemonkey脚本,它将一个按钮插入到允许您发送默认消息的聊天系统(Gitter)中。(不是垃圾邮件。这是供管理员发送的消息,如行为准则)。
假设我注入了一个按钮:
<button onclick='setTimeout( function() { showMsg() }, 10 );'>Send default message</button>
按照此问题的建议:Javascript: Function is defined, but Error says.. Function is not found ! (Strange)使用setTimeout()
,因为GM中定义的函数在主窗口中不可用。
出于测试目的,该函数是一个简单的控制台日志:
function showMsg(){
console.log('showMsg triggered');
}
问题是这仍然会返回"showMsg is not fined"。因此,按照前面提到的问题中提供的其他方法,我尝试了:
unsafeWindow.showMsg = function(){
console.log('showMsg triggered');
}
这将返回我:
奇怪的是,这似乎只有在创建一个div元素并使用错误:拒绝访问对象的权限
innerHTML
将按钮注入该div时才会发生。在实际创建这样的按钮时:
var btn = document.createElement('button');
btn.onclick = function(){ showMsg(); };
它按预期工作。
一些可能有助于找到答案的其他信息:
Gitter Chat使用IFRAME作为聊天消息和消息输入字段。IFrame位于同一服务器上,因此不存在相同的原始策略问题。
按钮被注入主体。以下是我如何尝试注入它们的两个示例。
(工作版)
var iframe, iframeDoc;
var chatContainer;
var chatInput, chatText;
var msgMenu;
$(document).ready(function() {
iframe = document.getElementById('content-frame');
iframe.onload = function(){
iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
chatContainer = iframeDoc.getElementById('chat-container');
chatInput = iframeDoc.getElementById('chat-input');
chatText = iframeDoc.getElementById('chat-input-textarea');
loadButton();
}
});
function loadButton(){
var div = document.createElement('div');
var btn = document.createElement('button');
var btnText = document.createTextNode('Send default message');
// Lots of CSS here that ads absolute positioning
// and makes the div float in the middle of the screen
btn.onclick = function(){ showMsg(); };
btn.appendChild(btnText);
div.appendChild(btn);
div.setAttribute("id", "msgMenu");
document.body.appendChild(div);
msgMenu = document.getElementById('msgMenu');
return false;
}
function showMsg(){
console.log('showMsg triggered');
}
将loadButton函数更改为此函数时,会导致上述问题:
function loadButton(){
var div = document.createElement('div');
div.innerHTML = "<button onclick='setTimeout( function() { showMsg() }, 10 );'>Send default message</button>";
div.setAttribute("id", "msgMenu");
document.body.appendChild(div);
msgMenu = document.getElementById('msgMenu');
return false;
}
解决方案
您的用户脚本是一个"content script",它在Gresemonkey扩展(或其他用户脚本管理器)的上下文中运行,并且可以访问Gresemonkey API。它之所以称为内容脚本,是因为它可以访问网页的内容,即DOM。
从网页的HTML调用的脚本是"页面脚本",并在单独的上下文中运行。他们无法直接访问内容脚本的范围,包括您的用户脚本。 在您的两个示例中,showMsg()
函数仅存在于内容脚本的作用域中。在工作示例中,将匿名函数附加到按钮的onclick
处理程序的代码在相同的作用域中执行,其中showMsg()
是可见的。对匿名函数的引用被附加到<button>
节点,创建了一个closure,即使在主用户脚本执行完之后,它也会保存在内存中。闭包还意味着该函数可以访问定义该函数时在作用域中的相同对象,包括showMsg()
(以及GM API对象)。
在第二个示例中,按钮是通过计算其父<div>
在页面脚本作用域中的innerHTML
创建的。这意味着showMsg()
在单击处理程序附加到按钮时不可见,从而在调用处理程序时显示"未定义"错误。
问题不在于您使用innerHTML
注入按钮,而在于您使用它附加了点击处理程序。此代码也可以工作,使用innerHTML
创建按钮,但从内容脚本作用域附加处理程序:
function loadButton(){
var div = document.createElement('div');
div.innerHTML = "<button id='myButton'>Send default message</button>";
document.body.appendChild(div);
msgMenu = document.getElementById('msgMenu');
document.getElementById('myButton').addEventListener('click', showMsg);
return false;
}
我不能确切地解释为什么您会收到unsafeWindow
的错误消息,但如果您使用的是Gresemonkey v4或更高版本,和/或Firefox V57或更高版本,这很可能是因为Firefox处理扩展的方式最近发生了变化。有关更多线索,请参阅MDN和Greasemonkey blog。
如果您确实需要向Web页面本身添加函数,则相对简单和可靠的方法是将<script>
节点注入DOM。请注意,插入的函数将无法访问用户脚本作用域中的任何内容。MDN上有more documentation描述了更复杂的与页面脚本交互的方式,但其中一些只适用于Firefox。
相关文章