CustomEvent.detail испорчен?

Я разрабатываю расширение для Chrome, чтобы сделать веб-сайт более удобным.
У меня есть доступ к модели DOM страницы, но мне также нужно взаимодействовать со "собственным" JS на этой странице, чего я не могу сделать с помощью своего расширения. .

Я могу вводить произвольные теги на страницу (в первую очередь также теги <script>), но поскольку экранирование строк, таких как

{
    html: '<div onclick="doSomething(this, \'someName\')"></div>'
}

это настоящая боль, я хотел бы свести вводимый код к абсолютному минимуму.

Я попытался внедрить прослушиватели событий на страницу, чтобы получить переменные JS со страницы, но столкнулся с проблемой.
Похоже, что если CustomEvent передается из расширения на веб-сайт или обратно, и если CustomEvent.detail содержит определенные типы объектов (по крайней мере, функций и ошибок) где-то, все CustomEvent.detail будет очищено, т.е. обнулено.

Пример

Скрипт (расширение.js):

(function()
{
    var script = document.createElement('script');
    script.innerHTML = [
"window.addEventListener('xyz', function(ev)",
"    {                                      ",
"        console.log('after dispatch:');    ",
"        console.log(ev.detail);            ",
"    });                                    ",
    ].join('\n');
    document.head.appendChild(script);
    // JSON-serializable data
    var e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger' } });
    console.log('before dispatch:')
    console.log(e.detail);
    window.dispatchEvent(e);
    // non-JSON-serializable data
    var detail = { x: 42, name: 'Schroedinger' };
    detail.detail = detail; // Create circular reference
    e = new CustomEvent('xyz', { detail: detail });
    console.log('before dispatch:')
    console.log(e.detail);
    window.dispatchEvent(e);
    // data with function
    e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', func: function(){} } });
    console.log('before dispatch:');
    console.log(e.detail);
    window.dispatchEvent(e);
    // data with error object
    e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', err: new Error() } });
    console.log('before dispatch:');
    console.log(e.detail);
    window.dispatchEvent(e);
})();

Вывод (абзац для удобочитаемости):

before dispatch:
Object {x: 42, name: "Schroedinger"}
after dispatch:
Object {x: 42, name: "Schroedinger"}

before dispatch:
Object {x: 42, name: "Schroedinger", detail: Object}
after dispatch:
Object {x: 42, name: "Schroedinger", detail: Object}

before dispatch:
Object {x: 42, name: "Schroedinger", func: function (){}}
after dispatch:
null

before dispatch:
Object {x: 42, name: "Schroedinger", err: Error at chrome-extension://...}
after dispatch:
null

Сначала я думал, что проблема заключается в JSON-сериализуемости, но циклические ссылки прекрасно проходят в событиях, тогда как при JSON-сериализации они ломались бы.
Похоже, что некоторые объекты "искажают" детали события таким же образом изображения, не относящиеся к другим источникам, портят холсты, за исключением того, что в консоли ничего нет.

Мне не удалось найти какую-либо документацию, касающуюся этого поведения, и (как предположил Пол С.) для этого не существует «привилегии» на Список разрешений Chrome.

Протестировано в Chrome 40.0.2214.115m, 43.0.2357.124m и 48.0.2547.0-dev.


person Siguza    schedule 24.02.2015    source источник
comment
Выполняется ли код, создающий эти события, с тем же уровнем привилегий, что и остальная часть страницы? Возможно, вы столкнулись с функцией безопасности, предотвращающей передачу потенциально конфиденциальных методов и т. д.   -  person Paul S.    schedule 24.02.2015
comment
Разве это не должно быть ev.detail в вашем коде? Не знаю, что такое event.   -  person Bergi    schedule 24.02.2015
comment
На самом деле да. Я изменил его выше, но результат тот же. Я предполагаю, что window.event доступен при запуске обработчика событий... и @PaulS.: Кажется, для этого нет разрешения на этот список.   -  person Siguza    schedule 24.02.2015
comment
Итак, CustomEvent.detail сериализуется или нет?   -  person Vlas Bashynskyi    schedule 02.11.2015
comment
@VLAS Это не так. Я обновил и свой вопрос, и свой ответ, так как узнал, что он клонирован .   -  person Siguza    schedule 02.11.2015


Ответы (1)


Что я узнал

Сначала я подумал, что это функция безопасности, в основном потому, что Firefox ведет себя таким образом.

Мы выполнили эквивалентный тест в Firefox, поместив прослушиватель событий в отдельный файл, который можно было загрузить через mozIJSSubScriptLoader:

test.js:

(function()
{
    window.addEventListener('xyz', function(ev)
    {
        console.log('after dispatch:');
        console.log(ev.detail);
    });
})();

firefox.js:

(function()
{
    var mozIJSSubScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
    window.addEventListener('load', function load(event)
    {
        window.removeEventListener('load', load);
        window.gBrowser.addEventListener('DOMContentLoaded', function(event)
        {
            mozIJSSubScriptLoader.loadSubScript('chrome://my-extension/content/test.js', window.content, 'UTF-8');
            // JSON-serializable data
            var e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger' } });
            console.log('before dispatch:')
            console.log(e.detail);
            window.content.dispatchEvent(e);
            // non-JSON-serializable data
            e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', func: function(){} } });
            console.log('before dispatch:');
            console.log(e.detail);
            window.content.dispatchEvent(e);
        });
    });
})();

Результат:

журнал консоли

(Обратите внимание, что ошибка возникает дважды.)

Таким образом, в Firefox даже не имеет значения, что содержит detail — пока это происходит из расширения, странице не разрешен доступ к нему.
Мне кажется, что это функция безопасности.

Причина, по которой я поместил вышеизложенное в цитату, заключается в том, что в Chrome это несколько отличается!

После более глубокого изучения оказалось, что, хотя расширение и страница имеют общее дерево DOM, они существуют в двух разных контекстах.
Я не знаю, является ли это на самом деле функцией безопасности или просто техническим последствием, но это, конечно, имеет следствием то, что только клонируемые объекты могут передаваться туда и обратно.

Что меня озадачивает, так это тот факт, что операция молча терпит неудачу, когда, согласно стандарт HTML, §2.7.5 (структурированное клонирование), вся операция завершится ошибкой:

↪ Если ввод является другим собственным типом объекта (например, ошибка, функция)
↪ Если ввод является хост-объектом (например, узлом DOM)
            Создайте DataCloneError и прервать общее алгоритм структурированного клонирования.

Обходной путь

В итоге я использовал довольно простой (хотя и не очень красивый) обходной путь:
В Chrome нет эквивалента mozIJSSubScriptLoader, но вы можете добавлять теги <script> к странице из вашего расширения (вам не разрешено чтобы сделать это в FF).
Вместе с chrome.extension.getURL это можно использовать для запуска JS-файла, упакованного с расширением, в контексте страницы:

(function()
{
    var script = document.createElement('script');
    script.src = chrome.extension.getURL('extension.js');
    document.head.appendChild(script);
})();

Конечно, это требует этого

"web_accessible_resources": [ "extension.js" ]

установлен в manifest.json, что некрасиво, но не должно быть реальной проблемой.

Недостатком этого, конечно, является то, что изнутри extension.js у вас больше нет доступа к любому API Chrome, к которому имеет доступ ваше расширение, но в моем случае мне это не нужно. Для этого было бы несложно настроить прокси через CustomEvent, так как большая часть API Chrome требует и возвращает только те данные, которые можно клонировать.

person Siguza    schedule 19.06.2015