代码之家  ›  专栏  ›  技术社区  ›  icecub

未定义函数或拒绝访问Greasemonkey中的对象的权限

  •  3
  • icecub  · 技术社区  · 7 年前

    我正在开发一个Greasemonkey脚本,它在聊天系统(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未定义”。因此,根据前面提到的问题中提供的其他adivse,我尝试:

    unsafeWindow.showMsg = function(){
        console.log('showMsg triggered');
    }
    

    然而,这让我想起:

    错误:拒绝访问对象的权限

    奇怪的是,似乎只有在创建div元素并使用 innerHTML 将按钮插入该分区。当实际创建这样的按钮时:

    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;
    }
    
    1 回复  |  直到 7 年前
        1
  •  7
  •   JRI    7 年前

    您的用户脚本是“ content script “,它在Greasemonkey扩展(或其他用户脚本管理器)的上下文中运行,并可以访问Greasemonkey API。它被称为内容脚本,因为它可以访问网页的内容,即DOM。

    从网页的HTML调用的脚本是“页面脚本”,在单独的上下文中运行。他们无法直接访问内容脚本的范围,包括您的用户脚本。

    在这两个示例中 showMsg() 函数仅存在于内容脚本的范围内。在工作示例中,将匿名函数附加到按钮的 onclick 处理程序在同一范围内执行,其中 showMsg() 可见。匿名函数的引用附加到 <button> 节点,创建 closure 这使得它即使在主用户脚本完成执行后也会一直存在于内存中。闭包还意味着函数可以访问定义函数时在范围内的相同对象,包括 showMsg() (和GM API对象)。

    innerHTML 其母公司的 <div> 在页面脚本范围中。这意味着 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 ,但如果您使用的是Greasemonkey v4或更高版本,和/或Firefox v57或更高版本,这可能是由于Firefox处理扩展的方式最近发生了变化。看见 MDN Greasemonkey blog 寻找更多线索。

    如果您确实需要向网页本身添加功能,一种相对简单可靠的方法是注入 <script> 节点进入DOM。请注意,插入的函数将无法访问userscript范围中的任何内容。有 more documentation MDN描述了与页面脚本交互的更复杂方式,但其中一些只适用于Firefox。