Любопытные извращения из мира IT, или зачем мы JS в C++-код вкомпилили +36


AliExpress RU&CIS

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

Мы занимались разработкой... скажем так, системы отображения интерактивного контента для рынка одной азиатской страны. Пользователь имел "умное устройство", например, ТВ-приставку или смарт-телевизор, а "интерактивный контент" представлял собой по сути дела 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):