Когда вы впервые услышите о расширениях Chrome, вы, вероятно, подумаете, что они должны быть написаны на C, C ++, JAVA или другом языке, который кажется слишком «сложным» для новичка. Но потом, когда вы поймете, что это всего лишь HTML5, CSS3 и Javascript, вы, вероятно, подумаете, что это простой веб-сайт, и начнете принижать значение тех, кто пишет расширение для Chrome. Что ж, легко написать расширение для выполнения простой работы, такой как сбор данных с веб-сайта, отображение закладок, учитывая тот факт, что Chrome предоставляет API для всех этих функций. Но когда вы начинаете писать полноценное приложение для расширения Chrome с несколькими страницами просмотра, вы начинаете сталкиваться с проблемами.

Проблема 1: всплывающее окно одноразовое

В первую очередь проблема, с которой вы столкнетесь, связана с тем, как расширение Chrome практически предназначено для работы. Расширение chrome по сути разделено на две части, которые я бы назвал пользовательским интерфейсом и фоном. Есть одна фоновая страница, на которой может быть постоянно запущен скрипт или страница события (непостоянная). И еще одна часть, которую я называю пользовательским интерфейсом, - это всплывающее окно, которое открывается при нажатии на значок вашего расширения помимо адресной строки. Проблема с пользовательским интерфейсом заключается в том, что это всплывающее окно, т. Е. Оно уничтожается или не существует после закрытия. Это означает, что вы не можете открыть его, пока фокус находится на вкладке браузера. Я еще вернусь к тому, что это стало большой проблемой при разработке нашего приложения. Также обратите внимание, что сейчас мы говорим о расширениях, которые имеют представления, а не о тех, которые состоят только из фоновых сценариев и сценариев содержимого.

Проблема 2: Отсутствие фреймворков

Еще одна проблема, с которой вы сталкиваетесь, заключается в том, что у вас нет фреймворка для написания расширения Chrome со всплывающим окном. Когда вы копаетесь в коде большинства расширений, которые вы можете найти в Интернете, все, что вы обнаруживаете, - это кодовая база на основе jQuery, которая использует классы css для переключения контента на экране в соответствии с данными и каждой логикой пользовательского интерфейса приложения, забитой в отдельный файл. Я хотел разбить код на модули, но не мог придумать, как разделить код с помощью jQuery.

У меня была задача написать простое расширение таймера, в котором пользователь входит в систему, настраивает свои параметры и запускает свой таймер, а таймер начинает работать в расширении. Насколько это может быть сложно? Ну, я тоже думал то же самое, пока, конечно, не начал сталкиваться с проблемами. Для начала, пользовательский интерфейс требовал входа в систему, настройки и просмотра страниц с работающим таймером, что делало его тремя страницами для отображения. Меня очень раздражало то, что я не нашел ничего, кроме примеров приложений jQuery.

Я знал, что хочу чего-то лучшего. Я хотел использовать UI-фреймворк, но вы знаете, как фреймворки в мире JavaScripts растут как грибы. Поскольку я хотел, чтобы размер расширения был минимальным, а мой код был модульным, я искал в Google все легкие фреймворки javascript. Фреймворк, который привлек мое внимание, был MVP-фреймворком RIOT.js, поскольку он был легким, легким в освоении и, что самое главное, имел минифицированную сборку, специально предназначенную для поддержки Политики безопасности контента, которая применяется в расширениях chrome. Политика безопасности контента не разрешает встроенный JavaScript по умолчанию, чтобы отключить атаки межсайтового скриптинга. Вы можете отключить его, передав флаг в манифесте, но с RIOT.js мне не нужно было этого делать. Если вы знаете React, то Riot не составит труда осознать, он основан на той же концепции.

Реализовать фреймворк было довольно просто, поскольку он был должным образом задокументирован, и я был доволен тем, как получилось приложение. Я мог заставить его работать и переключать представления, используя простую маршрутизацию (монтирование и размонтирование страниц), а логика просмотра приложения была разбита на несколько файлов. Это было замечательно. Я думал, что это будет конец моих проблем, так как приложение выглядело законченным.

Проблема 3: ответ AJAX не получен

Затем расширение было выпущено примерно для 100 пользователей. Начали появляться другие проблемы. В нашей реализации запросы AJAX отправлялись из пользовательского интерфейса (всплывающее окно). И очевидно, что мы не всегда можем рассчитывать на быстрое подключение к Интернету для пользователей. Мы были поражены проблемами, связанными с событиями действий пользователя, которые часто не выполнялись, даже если пользователь их инициировал. При проверке мы заметили, что серверам иногда требовалось больше нескольких секунд для обработки запроса или из-за плохого подключения к Интернету запрос был отправлен на сервер, а ответ еще не был получен пользовательским интерфейсом. Мы обрабатывали запросы AJAX во всплывающем окне, и когда всплывающее окно было закрыто, прослушиватель для обратного вызова никогда не выполнялся. Поэтому в следующий раз, когда они открыли расширение, они увидели устаревший таймер, который уже был остановлен на сервере. Мы думали о временном решении проблемы, отправляя запрос AJAX для любого запущенного таймера на сервере периодически или во время открытия всплывающего окна, но это означало бы много запросов и потребовало бы масштабирования наших серверов. Кроме того, пользователям приходилось ждать, пока расширение получит данные, прежде чем они смогут использовать их каждый раз, когда открывают всплывающее окно расширения. Это мешало удобству использования расширения. Так что нам нужен был другой подход.

Мы знали, что нам нужно переместить AJAX-запросы расширения на постоянный фон, чтобы закрытие всплывающего окна не повлияло на надежность расширения. Именно тогда мы решили полностью переделать расширение. Мы переместили все асинхронные задачи на задний план и сделали UI немым или просто презентационными компонентами. Итак, теперь часть пользовательского интерфейса расширения будет отправлять сообщение для любых событий запуска / остановки, которые должны произойти, а фон теперь отправит запрос на сервер и получит ответ, сохранит его в локальном хранилище расширения Chrome и отправит это обратно как ответ обратного вызова на сообщение, отправленное пользовательским интерфейсом.

Проблема 4: порт обмена сообщениями среды выполнения Chrome отключается

Мы использовали внутренний api обмена сообщениями Chrome для связи между фоном и всплывающим окном. Таким образом, нажатие кнопки остановки отправит действие остановки во всплывающем окне, которое будет отправлено через api хрома на постоянный фон. Фон получает действие и отправляет запрос AJAX, который по завершении отправляет ответ на всплывающее окно через обратный вызов (функция sendResponse в API). Нам также нужно было отображать загрузчик во всплывающем окне до тех пор, пока запрос не будет обработан, поэтому мы устанавливаем загрузчик, затем отправляем действие, и только после получения ответа загрузчик удаляется.
Но в этой реализации был большой недостаток. также. Мы ждали ответа от сервера, чтобы скрыть загрузчик, но когда всплывающее окно закрывается, даже внутренний порт api обмена сообщениями хрома, который ожидает ответа, уничтожается, поэтому пользовательский интерфейс не получит ответа, даже если запрос завершится успешно, что делает представление данные недостоверны. Это вызовет ошибку на фоне отключения порта, поскольку пользовательский интерфейс, ожидающий ответа, больше не присутствует. Даже если всплывающее окно откроется снова (это не тот же экземпляр, что и раньше, поэтому не получил ответа).
Поэтому мне пришлось еще больше упростить подход, отправив запрос в фоновый режим из пользовательского интерфейса, но не ожидая отклик. Теперь пользовательский интерфейс просто установит загрузчик в пользовательском интерфейсе и отправит сообщение. Затем Background получит сообщение и установит значение загрузки true в локальном хранилище до тех пор, пока не будет получен ответ. Затем, как только запрос будет обработан, фоновый сценарий обновит локальное хранилище, установив для загрузчика значение false, и отправит сообщение об обновлении в пользовательский интерфейс с новыми параметрами, необходимыми для обновления страницы. Теперь это решило обе мои проблемы. Если пользовательский интерфейс открыт, то отправленное сообщение принимается и обновляется, и если оно было закрыто при отправке сообщения, оно не получает сообщение, но при следующем открытии оно будет извлекать данные из локального хранилища.

Проблема 5: Написание тестов

Теперь он работал отлично, как и предполагалось, но как насчет тестового покрытия? Я пробовал много пакетов узлов, так как не смог найти ни одного примера, подходящего для моего варианта использования. Я пробовал Mocha, Karma и множество других тестовых фреймворков и столкнулся с большими трудностями при выборе из них. Поскольку вся тяжелая работа теперь выполнялась в фоновом режиме, а всплывающее окно пользовательского интерфейса занималось только отображением информации, я знал, что мне нужно, по крайней мере, провести модульное тестирование фонового скрипта. Короче говоря, я закончил тем, что использовал Mocha вместе с sinon-chrome, чтобы заглушить chrome apis и протестировать фоновый скрипт, а также использовал Jest для тестирования моментальных снимков для части пользовательского интерфейса путем монтирования представлений.

Проблема 6: Тестируемость и ремонтопригодность с помощью обратных вызовов

Что ж, теперь расширение работало и тестировалось, но ремонтопригодность все еще оставалась проблемой, поскольку у меня выполнялась серия обратных вызовов, поскольку расширению необходимо было иметь последовательность асинхронных запросов для получения информации о пользователе и переключения таймера все один за другим. . Нам удалось заставить его работать, передавая обратные вызовы от одной функции к другой, но в результате получился настолько глубокий вложенный код, что было удивительно не найти там Адель. Простая отладка кода потребовала бы от меня почти полного обхода кода, поскольку обратные вызовы заставляли бы переходить от одной функции к другой. Это было похоже на игру в горячую картошку. Даже не пытайтесь понять, как тяжело было писать тесты со всеми этими обратными вызовами. В конце концов я переключился на обещания, ТОГДА жизнь стала намного проще (вы видели, что я там делал ... Надеюсь, вы не УЗНАЛИ, что я делаю это). Было много переделок, но отдача была отличной. Кому не нужен модульный код с легко тестируемыми функциями. Я написал отдельную статью о обещаниях, если это вас заинтересовало.

РЕЗЮМЕ

Вот краткое изложение перечисленных выше проблем и способов их решения:

  • Всплывающие окна являются одноразовыми и должны быть только презентационными.
  • Используйте такие фреймворки, как RIOT.js или React, чтобы модулировать кодовую базу.
  • Асинхронные операции, такие как запросы AJAX, должны быть отделены от презентационных компонентов или представлений и должны быть делегированы постоянному фону.
  • Порт обмена сообщениями среды выполнения Chrome отключается при закрытии всплывающего окна и должен использоваться только для отправки сообщений и ожидания ответа, если отправляемое действие синхронно. В противном случае, если должна быть отправлена ​​асинхронная задача, должен быть только односторонний поток (sendResponse использовать не следует)
  • Фон должен быть модульно протестирован, и для всплывающего окна должно быть достаточно тестирования моментальных снимков, но вы всегда можете написать модульные тесты и для всплывающего окна.

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



Вот несколько полезных ссылок, чтобы начать работу с расширениями Chrome: