Worker'ы — внятная реализация многопоточности в JavaScript. На момент сейчас они имеют достаточное количество ограничений. Для ознакомления с ними (как worker'ами, так и ограничениями) можно прочитать эту статью от хабраюзера Antelle. Там же есть и ссылки на первоисточники информации для интересующихся.


Сегодня же мне довелось столкнуться другой задачей. А именно: с проблемой создания worker'а из js-файла с другого домена, что на данный момент запрещено его спецификацией.

История началась с создания небольшого расширения Google Chrome для Chess.com, которое использовало стороннюю библиотеку. К несчастью, оказалось, что эта библиотека работает только как worker.

Проблема заключается в том, что у самого Chrome есть определенные ограничения на получение файлов из контекста страницы. Он разрешает внедрение стороннего javascript-кода на страницу, но не более того. А это значит, что код из дополнения может получать worker на страницу только с того домена, на котором он выполнялся. То есть в моем случае: Chess.com. Конечно, можно было бы рассчитывать, что когда-нибудь я получу доступ к боевым серверам Chess.com, но мне бы хотелось, чтобы все заработало уже сегодня. Пришлось гуглить.

К счастью, статья с html5rocks помогла найти решение: создание inline worker'а через Blob. Подробности здесь. Если говорить кратко, то можно создать любую текстовую строку и запихать ее в так называемый Blob — наскальный рисунок модельный прототип сырого внешнего файла.

Например так (взято с html5rocks):
var blob = new Blob([
    "onmessage = function(e) { postMessage('msg from worker'); }"]);

// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);

var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  // e.data == 'msg from worker'
};
worker.postMessage(); // Start the worker.


Здесь мы видим, что создается объект Blob, который в представлении браузера является «как-бы-файлом», и в него записывается исходный текст.

Но если мы можем создать из любого текста Blob, значит, можно загрузить любой текст с другого сайта, а потом запихнуть его в Blob?

Окей, давайте попробуем:
$.get("https://example.com/js/worker.js", {},
     function (workerCode) {
         var blob = new Blob([workerCode], {type : 'javascript/worker'});
         var worker = new Worker(window.URL.createObjectURL(blob));
     }
);


Работает. То есть, фактически, мы можем загрузить любой внешний файл на страницу и запустить его как worker, независимо от принадлежности файлов к одному или нескольким доменам. Нда, вот так день.

Напоследок замечу, что, несмотря на всю сенсационность заголовка, в принципе в этом трюке нет ничего удивительного. То же самое можно проделать и для однопоточного javascript-кода: загрузить его как текст и вызвать через eval. В этом случае остается непонятным лишь неспешное принятие решения о поддержке CORS в Web Workers.

Комментарии (7)


  1. BeLove
    05.07.2015 01:05
    +3

    То есть, фактически, мы можем загрузить любой внешний файл на страницу и запустить его как worker, независимо от принадлежности файлов к одному или нескольким доменам.

    Но чтобы загрузить файл (тот же внешний js с worker'ом) и получить доступ к телу ответа, нам нужен CORS со стороны как раз домена, где находится worker. Так что не любой внешний файл.

    UPD: А, речь же про расширение. Видимо с неограниченными запросами ко всем доменам. Может, стоит уточнить тему топика, что речь именно про расширения, да еще и с неприлично большими правами.


    1. artyfarty
      05.07.2015 14:30

      Ну, скачать-то можно и без корса по JSONP


      1. BeLove
        05.07.2015 14:32

        Но для этого нам снова нужен доступ к серверу, где хранится worker.
        Взаимоисключения.


        1. artyfarty
          05.07.2015 14:36

          Изначальная проблема, из-за которой требуется CORS в том, что даже имея внешний сервер (провайдер), современный JS затрудняет получение чего-бы то ни было оттуда с сайта, на котором у нас виджет или расширение (консьюмер). То есть вот у нас есть внешний сервер с ресурсами и апи, но фигушки наш виджет, работая в домене консьюмера (хоть и загруженный с нашего сервера) сможет сделать туда обычный XHR, только корс (что геморройно) или жсонп (что с ограничениями). А статья о том, что воркер мол вообще тривиально не создать на консьюмере с урла провайдера — только через вышеприведенный фокус.


          1. BeLove
            05.07.2015 14:39

            Я прекрасно понимаю, зачем нужен CORS.
            Как и трюк, который был приведен в статье. Вопросов к этому не было.


  1. Antelle
    05.07.2015 15:14

    В worker-е выполнить importScripts(...) — и CORS даже не надо.


    1. jfkz Автор
      05.07.2015 22:41

      Да, но только worker надо сначала создать. Об этом я и написал.