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

Мы занимались разработкой... скажем так, системы отображения интерактивного контента для рынка одной азиатской страны. Пользователь имел "умное устройство", например, ТВ-приставку или смарт-телевизор, а "интерактивный контент" представлял собой по сути дела html/js/css-приложение, которое прилетало на устройство с трансляции или из интернета и отображалось в прозрачном окне поверх видео. В качестве веб-движка использовался модифицированный Blink из гугловского Chrome.

И вот, в один прекрасный день после какого-то из обновлений, один наш партнер (читай "поставщик контента") обратился к нам с проблемой: что-то не работает. "Что-то не работает" означало, что в приложении не отображается большая часть текста. Начали разбираться. Как я уже сказал, страна была азиатская и пользовалась, скажем так, непривычной нам письменностью. То ли из-за каких-то проблем, то ли просто желая поиграться со шрифтами, разработчики контента использовали так называемый SVG font: когда символы в шрифте представляют собой по сути дела SVG-изображения.

Такой веб-стандарт реально существует, но есть одно НО: он уже давно объявлен как deprecated и выпилен из всех современных веб-движков. Ожидать, что поставщик контента изменит свой контент, было бесполезно: контент есть, в старых продуктах раньше работал, да и вообще, у конкурентов все тоже работает. Проверили  — у конкурентов действительно работает, но разгадка оказалась простая: они просто использовали очень древние браузерные движки, из которых поддержку этого добра еще не выкинули, а мы, как самые прогрессивные и идущие в ногу со временем, на эту проблему наткнулись первые.

Стали думать, что делать дальше.

Вариант "а давайте возьмем поддержку формата из старой версии Blink'а и портируем ее в новую" отмели сразу. Blink  — проект огромный, код поддержки SVG fonts был тоже немаленький, и имел довольно большое количество точек соприкосновения с другими компонентами движка рендеринга. Даже если бы один раз удалось "перетащить" его в новую версию и ничего не сломать, что заняло бы немало времени и сил, то потом при каждом обновлении пришлось бы каждый тратить еще больше времени, особенно учитывая, что сам движок постоянно рефакторится, внутренности переписываются, изменяются API.

Тут достаточно вспомнить старую историю с Яндексом. Когда Google наконец-то выкинул NPAPI (старый интерфейс плагинов) из своего Chrome, Яндекс объявил, что они будут продолжать его поддерживать в своем Я.Браузере. Хватило их не то чтобы очень надолго... Это реально сизифов труд, и даже Яндекс с их экспертизой и ресурсами подобное не осилил.

Мозговой штурм продолжился. Следущая мысль: конвертнуть SVG-шрифт во что-то более привычное, типа TTF. Взяли первый попавшийся онлайн-ковертер, конвертнули, подсунули в контент  — отлично, работает, и выглядит точно так, как должно. Найти внутри браузера место между тем, когда шрифт уже скачан из сети и тем, когда он начинал использоваться для рендеринга, особой сложности тоже не было, как и определить по MIME или сигнатуре тип файла и при необходимости вызвать функцию-конвертер, которая подменит шрифт в массиве байт. Библиотека-конвертер, написанная на Си, тоже нашлась легко, еще немного времени было потрачено на избавление ее от внешних зависимостей (от чего-то удалось отвязаться выставлением нужных define'ов, какие-то функции удалось быстро и просто переписать самим), и уже буквально к следующему вечеру у нас был патч, с которым контент показывался как надо.

Но тут всплыло еще одно но: лицензия. Изначальный проект, из которого нам подошла библиотека, был опубликован автором под BSD-лицензией, потом передан сообществу, которое его частично перелицензировало под GPL, и в итоге с лицензиями в проекте творился полный бардак: часть файлов под BSD, часть под GPL, часть вообще без указания лицензии (!). После обмозговывания ситуации совместно с руководством и legal-командой, стало ясно, что использовать это нельзя.

А других подходящих библиотек на C или C++ не было. Писать самим конвертер с нуля было бы безумной затеей — на разбирательство с рендерингом SVG, со спецификациями форматов TTF или OpenFont, на вникание в специфику шрифтовой магии, реализацию всего этого дела и тестирование ушло бы ну просто очень много времени. Зато нашлась еще одна библиотека, написанная на JS. Хорошо работающая. Подходящая по лицензии. "А почему бы и нет?"  — решили мы. Браузер, сам по себе, изначально имеет высокопроизводительный JS-интерпретатор внутри, и к его API можно обращаться почти откуда угодно. Сама библиотека была написана для NodeJS, ее легко удалось конвертнуть в браузеропонятный вид с browserifyjs, а дальше дело техники и чуть-чуть магии: движок V8 предоставляет методы Compile и Run, позволяющие запустить произвольный скрипт и получить результат его работы  — в нашем случае объект-функцию, конвертирующую шрифты. Объект-функция сохраняется для дальнейшего использования, и когда наступает время, мы создаем C++-реинкарнацию джаваскриптового ArrayBuffer с содержимым файла шрифта, вызываем функцию, передавая его как аргумент, и получаем на выходе тот же самый ArrayBuffer, обращением к публичному полю которого можно получить массив байт с уже преобразованным файлом. И всё работало. Вот так внутри сурового и плотно сбитого C++ кода появился javascript-блоб.

Пытливые читатели, наверное, сразу же спросят про производительность всего этого дела. Отвечу: бенчмарков не делали, но каких-либо явно видимых лагов при отображении страницы на используемом железе не наблюдалось. Кто-то, может быть, начнет бухтеть про быдлокодинг и js-хипстерство, а я скажу наоборот: как по мне, это был именно инженерный подход к решению проблемы. Сам патч для движка уместился в пару десятков строчек и использовал только стабильные API, что обеспечило его беспроблемную переносимость при дальнейших апгрейдах  — раз. Задача была решена в кратчашие сроки, что особенно хорошо, учитывая, что она была не high-priority и у нас было много других гораздо более важных дел  — два. И вполне приемлемая производительность у клиента  — три.

Такие дела.

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


  1. datacompboy
    15.09.2021 03:20
    +7

    Не хватает тега "нормальное программирование"


  1. EragonRussia
    15.09.2021 08:07
    +7

    Long story short, юристы опять мешают программистам.


  1. BarakAdama
    15.09.2021 08:31
    +3

    Историю с поддержкой NPAPI в Яндекс.Браузере вы не совсем точно пересказали :) Тот анонс (а точнее, даже два) как раз я писал в блоге, поэтому хорошо помню.

    Там изначально речь шла о временной поддержке, чтобы дать разработчикам, в особенности игр на Unity Web Player, время на переход с NPAPI. Примерно год так выиграли им (с апреля 2015 по июль 2016). За этот год последний оплот этого API в лице FF успел объявить о прекращении поддержки. Unity закрыл свой плагин. Java объявил о прекращении поддержки. Сами понимаете, когда весь мир прекратил поддержку, а разработчики разбежались по другим технологиям, то поддерживать его дальше в гордом одиночестве не имело никакого практического смысла.


    1. F0iL Автор
      15.09.2021 09:35
      +2

      А, ну окей, значит не "не смогли", а "не было смысла", понятно :)
      Мы просто тоже очень долгое время таскали поддержку NPAPI у себя, с теми же целями, что и в статье -- для совместимости со старым контентом, который никто не собирался переписывать. Правда, в итоге остановились на поддержке определенной ограниченной части фнукционала NPAPI, иначе бы портировать из версии в версию за разумное время и не ломая ничего было просто невозможно.


  1. tyomitch
    15.09.2021 09:35
    +3

    Отличная история, но по два мемасика на экран — это перебор.


    1. F0iL Автор
      15.09.2021 09:41
      +2

      Серьезно? Ну вот, а я старался :( Убрал половину, теперь только один мемасик на экран.


      1. andreyegor
        15.09.2021 13:44

        Теперь интересно как было :( Мемасиков много не бывает.


        1. F0iL Автор
          15.09.2021 14:26

          Был мемасик про азиатские языки, про бенчарки и еще один про опенсорс-лицензии :)


          1. EragonRussia
            15.09.2021 23:11
            +3

            Шикарные мемасики были, зря убрали :(


      1. Fi1osof
        16.09.2021 10:39

        А под спойлер спрятать не? То же вот люблю мемасики.


  1. Dmitriy_Volkov
    15.09.2021 11:06
    +2

    JS используется даже в embedded, например, QML. В т.ч. HMI в ряде автомобилей написан на нём. По хорошему на нём предполагается реализовывать сугубо вьюшку, но по факту иногда оказывается намного больше


    1. F0iL Автор
      15.09.2021 14:03
      +1

      а можно еще вспомнить Espruino... правда, в продакшн-изделиях его все-таки не используют, мне, по крайней мере, ни разу такое не встречалось :)


    1. event1
      15.09.2021 17:02

      Те времена когда встроенные системы были сильно ограниченными по ресурсам давно канули в Лету. Сегодня можно запросто встроить процессор на несколько ядер и гигагерц и гигобайт оперативы. А NAND флеша меньше 256 Мб и не бывает уже, по-моему. Особенно если нет ограничений по электричеству (как в машине). По-этому, ничего удивительного что в машине пишут на джаваскрипте. К нам тоже регулярно приходят потенциальные клиенты и просят питон на маршрутизаторе


    1. flashmozzg
      17.09.2021 16:29

      Для QML есть AoT.


  1. Alexey2005
    15.09.2021 13:21

    Похожие проблемы возникают при попытке построить приложение на основе натренированной нейронки, например для сегментации изображений. Вдруг оказывается, что реально кроссплатформенное решение — это только JS, потому что разработчики нативных фреймворков намертво присосались к сиське NVIDIA, да так, что и не оторвёшь, а потому никакой кроссплатформой там не пахнет.


    1. avdosev
      15.09.2021 16:36

      Вроде бы все фреймворки для нейронок способны считать на cpu, да и поддержка amd видеокарт начинает появляться


      1. Alexey2005
        16.09.2021 01:36

        Способны, только вот скорость работы на CPU уровня Celeron N2830 примерно в 100 раз ниже, чем на Tensorflow.js, который проталкивает расчёты на интегрированный GPU через кроссплатформенный GL ES.

        поддержка amd видеокарт начинает появляться
        Что AMD, что NVIDIA — это главным образом десктоп и не самые бюджетные ноуты. Поддержка этих карточек конечно нужна, но для массового сегмента требуется в первую очередь фреймворк с поддержкой интегрированных Intel GPU, причём таких, где даже DirectX 12 не поддерживается.


  1. Arbane
    16.09.2021 00:32
    +1

    Лонгстори, но что-то кода совсем нет. Даже если код тривиален, без этих строчек больше похоже на историю "представьте, мы взяли и смогли".


    1. F0iL Автор
      16.09.2021 10:02

      Код во-первых тривиален, во-вторых тащить код из коммерческого проекта в паблик не ок, а в-третьих его у меня и не осталось, давно было дело, и я там уже не работаю. Так что да, это больше из разряда "представьте, мы упоролись и сделали".


  1. napa3um
    16.09.2021 01:34

    Я почему-то предположил, что вы JS превратите в Си с помощью чего-то типа https://github.com/andrei-markeev/ts2c или https://github.com/timruffles/js-to-c . Но я зашёл покритиковать такое решение и сказать, что полифилы на JS для поддержки нужных веб-стандартов - это норма :).


    1. F0iL Автор
      16.09.2021 10:02

      Мы рассматривали подобные конверторы, не знаю как сейчас, а тогда они ES5-то не полностью поддерживали, не говоря уж о ES6, и даже попытки заbabel'ить библиотеку в итоге ни к чему рабочему не привели.