如何在Firefox Web扩展中使用cloneInto?

时间:2017-10-10 15:11:34

标签: javascript firefox-webextensions

我正在尝试编写一个Web扩展,我需要将对象从内容脚本传递到页面脚本。除其他外,我查看了https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Sharing_content_script_objects_with_page_scriptshttps://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.cloneInto

我想使用那里描述的cloneInto函数。有一个代码示例如下所示:

// this happens in the content script
var messenger = {
  notify: function(message) {
    browser.runtime.sendMessage({
      content: "Object method call: " + message
    });
  }
};

window.wrappedJSObject.messenger = cloneInto(
  messenger,
  window,
  {cloneFunctions: true});

然后在页面脚本中,这个:

window.messenger.notify("Message from the page script!");

当我将它粘贴到我自己的扩展中时,当它到达页面脚本中的上一行时会报告错误:TypeError:window.messenger未定义。

我无法弄清楚如何解决这个问题。有什么想法吗?或者是否有更好的方法从内容脚本与页面脚本共享对象?

2 个答案:

答案 0 :(得分:1)

我找到了另一种解决方案。我没有解决如何使用cloneInto的问题,但我现在能够通过使用sendMessage和创建onMessage监听器将对象传递给我的页面脚本。

内容脚本:

  browser.runtime.sendMessage({theBlobs: myBlobObjects});

页面脚本:

browser.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  var blobs = request.theBlobs;
  for (var i = blobs.length - 1; i >= 0; i--) {
    // do clever things with the blobs
  }
}

这种方法的一个优点是它不使用特定于Firefox的功能。

https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/sendMessage https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage

答案 1 :(得分:0)

我尽量避免使用cloneInto()exportFunction()

函数cloneInto()exportFunction()仅限Firefox。 WebExtensions的一个显着优点是能够编写在多个浏览器中工作的扩展。因此,我倾向于尽可能避免使用这些功能。

我会做的是使用我写的实用函数executeInPage()来复制ObjectArrayfunctionRegExpDate,和/或其他primitivesBooleannullundefinedNumberString,但不是{{ 3}})进入页面。另一个简单函数inPageAssignToWindowProperty()可以分配传递给全局范围中的变量的值。

这比cloneInto()更有限,因为executeInPage()目前不支持:SymbolBlobFile,{{3 }},FileListArrayBufferArrayBufferViewImageData。目前不支持这些,因为我没有必要将它们传递到页面上下文中。



var messenger = {
    notify: function(message) {
        browser.runtime.sendMessage({
            content: "Object method call: " + message
        });
    }
};

function inPageAssignToWindowProperty(property, value) {
    window[property] = value;
}

executeInPage(inPageAssignToWindowProperty, false, '', 'messenger', messenger);

console.log('window.messenger:', window.messenger);

    
<script>
/* 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;
};
</script>
&#13;
&#13;
&#13;