Содержание статьи
Проблема №2: копирование сцены стоит дорого
Вторая проблема с
заключается в том, что он требует сериализации больших частей документа перед отправкой их в плагин.
Оказывается, что люди могут создавать очень, очень большие документы в Figma до такой степени, что достигают пределов памяти. Например, в файле систем проектирования Microsoft (который мы потратили месяц на оптимизацию в прошлом году) потребовалось 14 секунд, чтобы сериализовать документ и отправить его плагину прежде чем плагин мог даже работать . Учитывая, что большинство плагинов будут включать быстрые действия, такие как «поменять два элемента в моем выборе», это сделает плагины непригодными для использования.
Загрузка данных с приращением или ленивым также не является возможным вариантом, потому что :
- Это может занять месяцы перестройки основного продукта.
- Любой API, который может нуждаться в ожидании части данных, которая еще не поступила, будет теперь будем асинхронными.
В итоге, поскольку документы Figma могут иметь действительно большой объем данных с большим количеством взаимозависимостей,
не сработали для нас .
Поскольку подход
был исключен, нам пришлось вернуться назад в нашем исследовании.
Мы вернулись к чертежной доске и провели две долгих недели обсуждая различные подходы. Поскольку простое решение не сработало, нам пришлось серьезно рассмотреть более экзотические идеи. Их было много — слишком много, чтобы заполнить поля этого сообщения в блоге.
Но большинство подходов имели один или несколько существенных недостатков:
- Иметь API, который было бы слишком сложно использовать (например, доступ к документу с помощью REST API или GraphQL-подобный метод)
- Зависит от функций браузера, которые производители браузера удалили или пытаются (например, синхронный xhr + сервисный работник, общие буферы)
- Требует значительной исследовательской работы или ре-архитектуры нашего приложения, которое может занять месяцы, прежде чем мы сможем даже проверить, что оно может работать (например, загрузить копию Figma в iframe + синхронизировать через CRDT, взломать зеленые потоки в JavaScript с генераторами путем кросс-компиляции)
. В итоге мы пришли к выводу, что нам нужно найти способ создать модель, в которой плагины могут напрямую манипулировать документом. Написание плагина должно чувствовать себя дизайнером, автоматизирующим свои действия. Поэтому мы знали, что нам нужно разрешить плагинам запускаться в главном потоке.
Последствия запуска в главном потоке
Прежде чем мы углубимся в попытку №2, нам нужно сделать шаг Вернитесь назад и еще раз проверьте, что означает запуск плагинов в главном потоке. В конце концов, мы сначала не рассматривали это, потому что знали, что это может быть опасно. Работа в основном потоке звучит очень похоже на eval (UNSAFE_CODE)
.
Преимущества работы в основном потоке заключаются в том, что плагины могут:
- Непосредственно редактируйте документ, а не его копию, устраняя проблемы со временем загрузки.
- Запустите нашу логику обновления сложных компонентов и ограничения, не имея необходимости иметь две копии этого кода.
- Выполнять синхронные вызовы API в ситуациях, когда вы ожидаете синхронный API. Не было бы никакой путаницы с загрузкой или сбросом обновлений.
- Писать можно более интуитивно понятным способом: плагины — это просто автоматизация действий, которые в противном случае пользователь делал бы вручную с помощью нашего пользовательского интерфейса.
Однако теперь у нас есть следующие проблемы:
- Плагины могут зависать, и нет никакого способа прервать плагин.
- Плагины могут выполнять сетевые запросы, как фигма .com.
- Плагины могут получать доступ и изменять глобальное состояние. Это включает в себя изменение нашего пользовательского интерфейса, создание зависимостей от внутреннего состояния приложения вне API или выполнение совершенно злонамеренных действий, таких как изменение значения
({}) .__ proto __
которое отравляет каждый новый и существующий объект JavaScript.
Мы решили, что можем отказаться от требования (1). Когда плагины зависают, это влияет на воспринимаемую стабильность Figma. Однако наша модель плагинов работает так, что они когда-либо запускаются только при явном действии пользователя. Изменяя пользовательский интерфейс при запуске плагина, зависания всегда будут приписываться плагину. Это также означает, что плагин не может «взломать» документ.
Что значит для eval
быть опасным?
иметь дело с Вопрос о том, что плагины могут выполнять сетевые запросы и получать доступ к глобальному состоянию, мы должны сначала точно понять, что означает, что «любой произвольный код JavaScript опасен».
Если вариант JavaScript, назовем его SimpleScript, имел только способность делать арифметику, такую как 7 * 24 * 60 * 60
было бы вполне безопасно eval
.
Вы можете добавить некоторые функции в SimpleScript как переменную присваивание и операторы if делают его более похожим на язык программирования, и это все равно будет очень безопасно. В конце концов, это все еще сводится к арифметике. Добавьте оценку функции, и теперь у вас есть лямбда-исчисление и полнота Тьюринга.
Другими словами, в JavaScript нет чтобы быть опасным. В своей наиболее редукционистской форме это просто расширенный способ выполнения арифметики. опасно когда у него есть доступ к входу и выходу. Это включает доступ к сети, доступ к DOM и т. Д. Опасные API браузера
И все API являются глобальными переменными. Так что скрывайте глобальные переменные!
Скрытие глобальных переменных
Теперь сокрытие глобальных переменных звучит хорошо в теории, но сложно создать безопасные реализации, просто «скрывая» их. Вы можете рассмотреть, например, удаление всех свойств объекта окна
или установку для них значения null
но код все равно может получить доступ к глобальным значениям, таким как ({ }). конструктор
. Было бы очень сложно найти все возможные пути утечки какого-либо глобального значения.
Скорее нам нужна более сильная форма «песочницы», в которой эти глобальные значения никогда не существовали.
Другими словами, JavaScript не должен быть опасным.
Рассмотрим предыдущий пример гипотетического SimpleScript, который поддерживает только арифметику. Простое упражнение CS 101 — написать программу арифметической оценки. В любой разумной реализации этой программы SimpleScript просто не сможет делать ничего, кроме арифметики.
Теперь расширяйте SimpleScript, чтобы поддерживать больше возможностей языка, пока он не станет JavaScript, и эта программа называется интерпретатором — это то, как запускается JavaScript, динамический интерпретируемый язык.
Реализация JavaScript — это слишком много работы для небольшого запуска, такого как наш. Вместо этого, чтобы проверить этот подход, мы взяли Duktape, облегченный интерпретатор JavaScript, написанный на C ++, и скомпилировали его в WebAssembly.
Чтобы убедиться, что он работает, мы запустили на нем test262, стандартный набор тестов JavaScript. Он проходит все тесты ES5, за исключением нескольких несущественных сбоев. Чтобы запустить код плагина с помощью Duktape, мы бы назвали функцию eval
из скомпилированным интерпретатором .
Каковы свойства этого подхода?
- Этот интерпретатор работает в основном потоке, что означает, что мы можем создать API на основе основного потока.
- Он безопасен, о чем легко думать. Duktape не поддерживает API-интерфейсы браузера — и это особенность! Кроме того, он работает как WebAssembly, которая сама является изолированной средой, которая не имеет доступа к API-интерфейсам браузера. Другими словами, код плагина может взаимодействовать с внешним миром только через явные API-интерфейсы из белого списка.
- Он медленнее обычного JavaScript, поскольку этот интерпретатор не является JIT, но это нормально.
- требует, чтобы браузер скомпилировал бинарный файл WASM среднего размера, что имеет определенную стоимость.
- Инструменты отладки браузера не работают по умолчанию, но мы потратили день на реализацию консоли для интерпретатора, чтобы проверить, что он будет по крайней мере, можно отлаживать плагины.
- Duktape поддерживает только ES5, но в веб-сообществе уже широко распространена практика кросс-компиляции новых версий JavaScript с использованием таких инструментов, как Babel.
(Кроме того, несколько месяцев спустя Фабрис Беллард выпустил QuickJS, который изначально поддерживает ES6.)
Теперь, собираем интерпретатор JavaScript! В зависимости от ваших склонностей или эстетики как программиста, вы можете подумать:
ЭТО УДИВИТЕЛЬНО! 🤩
или
… на самом деле? Механизм JavaScript в браузере, который уже имеет механизм JavaScript? 🤨. Что дальше, операционная система в браузере?
И некоторые подозрения полезны! Лучше избегать повторной реализации браузера, если в этом нет необходимости. Мы уже потратили много усилий на внедрение всей системы рендеринга. Это было необходимо для обеспечения производительности и межбраузерной поддержки, и мы рады, что сделали это, но мы все еще пытаемся не изобретать колесо.
Это не тот подход, которым мы в конечном итоге придерживались. Есть еще лучший подход. Тем не менее, важно было осветить шаг к пониманию нашей окончательной модели песочницы, которая является более сложной.
Хотя у нас был многообещающий подход к компиляции интерпретатора JS, был еще один инструмент, на который нужно было обратить внимание. Мы нашли технологию, называемую Shim Realms, созданную ребятами из Agoric.
Эта технология описывает создание песочницы и поддержку плагинов в качестве потенциального варианта использования. Многообещающее описание! API Realms выглядит примерно так: