javascript - 从浏览器扩展调用网页 JavaScript 方法

标签 javascript jquery cross-domain browser-plugin firefox-addon-webextensions

我正在使用 webExtensions 开发 firefox 扩展程序这将帮助我简化以下场景的工作。

我必须单击网站上大约 50-60 个按钮来更新任务状态。单击此按钮后,网页将调用网页的 updTask(id) JavaScript 函数,然后该函数会调用 Web 服务来更新任务。

我无法使用以下代码从我的内容脚本执行此操作:

manifest.json:

"permissions": [
    "activeTab",
    "cross-domain-content": ["http://workdomain.com/","http://workdomain.org/","http://www.workdomain.com/","http://www.workdomain.org/"]
  ]

内容脚本代码:

function taskUpdate(request, sender, sendResponse) {
  console.log(request.start + 'inside task update');
  updateTask(45878);
  chrome.runtime.onMessage.removeListener(taskUpdate);
}

function updateTask(id) {
  //TODO: code to get all buttons and task id's
  updTask(id);  // Not working
}

插件脚本:

document.addEventListener("click", function(e) {
  if (e.target.classList.contains("startButton")) {

    chrome.tabs.executeScript(null, {
      file: "/content_scripts/taskUpdate.js"
    });

    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      chrome.tabs.sendMessage(tabs[0].id, {start: "start"});
    });
    return;
  }
  else if (e.target.classList.contains("clear")) {
    chrome.tabs.reload();
    window.close();
    return;
  }
});

有人能指出我正确的方向吗?我在这里缺少什么?

最佳答案

您的内容脚本与页面脚本(网页中已存在的脚本)处于不同的上下文/范围中。您的内容脚本具有比授予页面脚本更高的权限。将内容脚本与页面脚本分开是浏览器扩展的正常架构,这样做是出于安全原因。

由于您的内容脚本与页面脚本处于不同的上下文中,因此您无法直接从内容脚本访问页面脚本中定义的函数和变量。您可以通过几种不同的方式访问页面上下文中的信息。这样做的跨浏览器方法是使代码的某些特定部分在页面上下文中执行。我发现最方便且跨浏览器兼容的方法是创建并插入 <script>元素到包含您要执行的代码的页面 DOM 中。

你可以这样做:

function updateTask(id) {
    let newScript = document.createElement('script');
    newScript.innerHTML='updTask(' + id + ');';
    document.head.appendChild(newScript);
    //newScript.remove(); //Can be removed, if desired.
}

添加的脚本在页面上下文中运行,因为它现在是 <script> DOM 中的元素。浏览器识别出 <script>添加元素并在插入它的脚本不再处理时对其进行评估(执行包含的代码)。对于添加到 DOM 的任何其他元素,它基本上执行相同的操作。因为它是页面的一部分,所以内部的代码在页面脚本上下文/范围中运行。

将数据返回到内容脚本

可以通过多种方式在页面上下文中运行的代码与内容脚本上下文中的代码之间进行通信。我首选的方法是使用 CustomEvent s。我在this answer的第一部分描述了原因。 。通常,我至少使用一种自定义事件类型从页面上下文到内容脚本上下文进行通信,并使用另一种自定义事件类型从内容脚本上下文到页面上下文进行通信。您可以使用任意多个 CustomEvent类型如你所愿。我经常使用多个事件,每个事件传达不同的事物,而不是使用单个事件类型来解析几种不同类型的消息。

从内容脚本在页面上下文中执行的通用代码

维护要在页面上下文中执行的代码的最简单方法是将其编写为内容脚本中的函数,然后将该函数注入(inject)到页面上下文中。下面是一些通用代码,可以在将参数传递给您在页面上下文中执行的函数时执行此操作:

这个实用函数,executeInPage() ,将在页面上下文中执行一个函数并将任何提供的参数传递给该函数。参数必须是 Object , Array , function , RegExp , Date ,和/或其他 primitives ( Boolean null undefined Number String ,但不是 Symbol )。

/* executeInPage takes a function defined in this context, converts it to a string
 *  and inserts it into the page context inside a <script>. It is placed in an IIFE and
 *  passed all of the additional parameters passed to executeInPage.
 *  Parameters:
 *    func          The function which you desire to execute in the page. 
 *    leaveInPage   If this does not evaluate to a truthy value, then the <script> is
 *                    immediately removed from the page after insertion. Immediately
 *                    removing the script can normally be done. In some corner cases,
 *                    it's desirable for the script to remain in the page. However,
 *                    even for asynchronous functionality it's usually not necessary, as
 *                    the context containing the code will be kept with any references
 *                    (e.g. the reference to a callback function).
 *    id            If this is a non-blank string, it is used as the ID for the <script>
 *    All additional parameters   are passed to the function executing in the page.
 *                    This is done by converting them to JavaScript code-text and back.
 *                    All such parameters must be Object, Array, functions, RegExp,
 *                    Date, and/or other primitives (Boolean, null, undefined, Number,
 *                    String, but not Symbol). Circular references are not supported.
 *                    If you need to communicate DOM elements, you will need to
 *                    pass selectors, or other descriptors of them (e.g. temporarily
 *                    assign them a unique class), or otherwise communicate them to the
 *                    script (e.g. you could dispatch a custom event once the script is
 *                    inserted into the page context).
 */
function executeInPage(functionToRunInPage, leaveInPage, id) {
    //Execute a function in the page context.
    // Any additional arguments passed to this function are passed into the page to the
    // functionToRunInPage.
    // Such arguments must be JSON-ifiable (also Date, Function, and RegExp) (prototypes
    // are not copied).
    // Using () => doesn't set arguments, so can't use it to define this function.
    // This has to be done without jQuery, as jQuery creates the script
    // within this context, not the page context, which results in
    // permission denied to run the function.
    function convertToText(args) {
        //This uses the fact that the arguments are converted to text which is
        //  interpreted within a <script>. That means we can create other types of
        //  objects by recreating their normal JavaScript representation.
        //  It's actually easier to do this without JSON.strigify() for the whole
        //  Object/Array.
        var asText = '';
        var level = 0;
        function lineSeparator(adj, isntLast) {
            level += adj - ((typeof isntLast === 'undefined' || isntLast) ? 0 : 1);
            asText += (isntLast ? ',' : '') +'\n'+ (new Array(level * 2 + 1)).join('');
        }
        function recurseObject(obj) {
            if (Array.isArray(obj)) {
                asText += '[';
                lineSeparator(1);
                obj.forEach(function(value, index, array) {
                    recurseObject(value);
                    lineSeparator(0, index !== array.length - 1);
                });
                asText += ']';
            } else if (obj === null) {
                asText +='null';
            //undefined
            } else if (obj === void(0)) {
                asText +='void(0)';
            //Special cases for Number
            } else if (Number.isNaN(obj)) {
                asText +='Number.NaN';
            } else if (obj === 1/0) {
                asText +='1/0';
            } else if (obj === 1/-0) {
                asText +='1/-0';
            //function
            } else if (obj instanceof RegExp || typeof obj === 'function') {
                asText +=  obj.toString();
            } else if (obj instanceof Date) {
                asText += 'new Date("' + obj.toJSON() + '")';
            } else if (typeof obj === 'object') {
                asText += '{';
                lineSeparator(1);
                Object.keys(obj).forEach(function(prop, index, array) {
                    asText += JSON.stringify(prop) + ': ';
                    recurseObject(obj[prop]);
                    lineSeparator(0, index !== array.length - 1);
                });
                asText += '}';
            } else if (['boolean', 'number', 'string'].indexOf(typeof obj) > -1) {
                asText += JSON.stringify(obj);
            } else {
                console.log('Didn\'t handle: typeof obj:', typeof obj, '::  obj:', obj);
            }
        }
        recurseObject(args);
        return asText;
    }
    var newScript = document.createElement('script');
    if(typeof id === 'string' && id) {
        newScript.id = id;
    }
    var args = [];
    //using .slice(), or other Array methods, on arguments prevents optimization
    for(var index=3;index<arguments.length;index++){
        args.push(arguments[index]);
    }
    newScript.textContent = '(' + functionToRunInPage.toString() + ').apply(null,'
                            + convertToText(args) + ");";
    (document.head || document.documentElement).appendChild(newScript);
    if(!leaveInPage) {
        //Synchronous scripts are executed immediately and can be immediately removed.
        //Scripts with asynchronous functionality of any type must remain in the page
        //  until complete.
        document.head.removeChild(newScript);
    }
    return newScript;
};

使用 excuteInPage() :

function logInPageContext(arg0,arg1,arg2,arg3){
    console.log('arg0:', arg0);
    console.log('arg1:', arg1);
    console.log('arg2:', arg2);
    console.log('arg3:', arg3);
}

executeInPage(logInPageContext, false, '', 'This', 'is', 'a', 'test');


/* executeInPage takes a function defined in this context, converts it to a string
 *  and inserts it into the page context inside a <script>. It is placed in an IIFE and
 *  passed all of the additional parameters passed to executeInPage.
 *  Parameters:
 *    func          The function which you desire to execute in the page. 
 *    leaveInPage   If this does not evaluate to a truthy value, then the <script> is
 *                    immediately removed from the page after insertion. Immediately
 *                    removing the script can normally be done. In some corner cases,
 *                    it's desirable for the script to remain in the page. However,
 *                    even for asynchronous functionality it's usually not necessary, as
 *                    the context containing the code will be kept with any references
 *                    (e.g. the reference to a callback function).
 *    id            If this is a non-blank string, it is used as the ID for the <script>
 *    All additional parameters   are passed to the function executing in the page.
 *                    This is done by converting them to JavaScript code-text and back.
 *                    All such parameters must be Object, Array, functions, RegExp,
 *                    Date, and/or other primitives (Boolean, null, undefined, Number,
 *                    String, but not Symbol). Circular references are not supported.
 *                    If you need to communicate DOM elements, you will need to
 *                    pass selectors, or other descriptors of them (e.g. temporarily
 *                    assign them a unique class), or otherwise communicate them to the
 *                    script (e.g. you could dispatch a custom event once the script is
 *                    inserted into the page context).
 */
function executeInPage(functionToRunInPage, leaveInPage, id) {
    //Execute a function in the page context.
    // Any additional arguments passed to this function are passed into the page to the
    // functionToRunInPage.
    // Such arguments must be JSON-ifiable (also Date, Function, and RegExp) (prototypes
    // are not copied).
    // Using () => doesn't set arguments, so can't use it to define this function.
    // This has to be done without jQuery, as jQuery creates the script
    // within this context, not the page context, which results in
    // permission denied to run the function.
    function convertToText(args) {
        //This uses the fact that the arguments are converted to text which is
        //  interpreted within a <script>. That means we can create other types of
        //  objects by recreating their normal JavaScript representation.
        //  It's actually easier to do this without JSON.strigify() for the whole
        //  Object/Array.
        var asText = '';
        var level = 0;
        function lineSeparator(adj, isntLast) {
            level += adj - ((typeof isntLast === 'undefined' || isntLast) ? 0 : 1);
            asText += (isntLast ? ',' : '') +'\n'+ (new Array(level * 2 + 1)).join('');
        }
        function recurseObject(obj) {
            if (Array.isArray(obj)) {
                asText += '[';
                lineSeparator(1);
                obj.forEach(function(value, index, array) {
                    recurseObject(value);
                    lineSeparator(0, index !== array.length - 1);
                });
                asText += ']';
            } else if (obj === null) {
                asText +='null';
            //undefined
            } else if (obj === void(0)) {
                asText +='void(0)';
            //Special cases for Number
            } else if (Number.isNaN(obj)) {
                asText +='Number.NaN';
            } else if (obj === 1/0) {
                asText +='1/0';
            } else if (obj === 1/-0) {
                asText +='1/-0';
            //function
            } else if (obj instanceof RegExp || typeof obj === 'function') {
                asText +=  obj.toString();
            } else if (obj instanceof Date) {
                asText += 'new Date("' + obj.toJSON() + '")';
            } else if (typeof obj === 'object') {
                asText += '{';
                lineSeparator(1);
                Object.keys(obj).forEach(function(prop, index, array) {
                    asText += JSON.stringify(prop) + ': ';
                    recurseObject(obj[prop]);
                    lineSeparator(0, index !== array.length - 1);
                });
                asText += '}';
            } else if (['boolean', 'number', 'string'].indexOf(typeof obj) > -1) {
                asText += JSON.stringify(obj);
            } else {
                console.log('Didn\'t handle: typeof obj:', typeof obj, '::  obj:', obj);
            }
        }
        recurseObject(args);
        return asText;
    }
    var newScript = document.createElement('script');
    if(typeof id === 'string' && id) {
        newScript.id = id;
    }
    var args = [];
    //using .slice(), or other Array methods, on arguments prevents optimization
    for(var index=3;index<arguments.length;index++){
        args.push(arguments[index]);
    }
    newScript.textContent = '(' + functionToRunInPage.toString() + ').apply(null,'
                            + convertToText(args) + ");";
    (document.head || document.documentElement).appendChild(newScript);
    if(!leaveInPage) {
        //Synchronous scripts are executed immediately and can be immediately removed.
        //Scripts with asynchronous functionality of any type must remain in the page
        //  until complete.
        document.head.removeChild(newScript);
    }
    return newScript;
};

<小时/>

此答案的文本大部分取 self 的其他答案:this onethis one .

关于javascript - 从浏览器扩展调用网页 JavaScript 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40572065/

相关文章:

javascript + mysql 实时更改

javascript - 如何存储信息以正确显示构成单个游戏角色/对象/NPC 的多个图像?

javascript - ReactJS 不能使用组合组件

javascript - 在 Three.js 中的 CSS3DObject 中创建跨源 HTML iframe 会出错

javascript - 使用echart为条形图(yAxis)提供多种颜色

javascript - 在 Laravel 5 中使用 JQuery 从 href 中提取值时出错

javascript - 无法识别 Mozilla Firefox 中的右键单击事件

ruby-on-rails-3 - 导轨 : "head :ok" interpreted as "ajax:error"

reactjs - IE 11 未在 API 调用中传递授权 header

file-upload - 从浏览器将文件上传到另一个域的最佳方法是什么?