Хабрахабр

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте

Длинное, нудное вступление с претензией на манию величия

Однажды я обнаружил, что меня, как всегда, что-то сильно не устраивает в этом мире. А именно, введя какой-то длинный запрос в поисковике на настольном компьютере и затем перейдя на планшет, я никак не мог вспомнить дословно текст запроса, чтобы выйти ровно на те же результаты. А начиналось все так хорошо. Я увидел в поисковике ссылку на ответ на свой вопрос и понял, что она сулит долгое чтиво. Тогда я выключил комп и плюхнулся на диван с планшетом с мыслью о том, что вот сейчас я просто заново вобью все это в поисковик, открою ту ссылку теперь уже на планшете и лежа, спокойно, в более удобной позе прочитаю… Но не тут-то было. Какие-то мелкие разночтения в тексте — и моей ссылки уже нет в выдаче поисковика. Воспроизвести саму ссылку — тоже не вариант: она слишком длинная. Ломая голову над вариантами текста запроса, я чуть было в ярости не сломал планшет. Черт побери, пришлось вставать, снова включать компьютер, запускать браузер и копаться в истории, чтобы найти точный текст своего запроса.

Расширение, установленное в Chrome и Firefox

Идея

Я подумал, а вот было бы неплохо написать такое расширение для браузера, которое бы позволило перекинуть любые свои открытые вкладки через сервер на любой другой свой компьютер и продолжить работать с ними там. Да, в некоторых браузерах уже есть облачная функция — достаточно лишь авторизоваться на обоих устройствах и… Но в том-то и загвоздка. А что, если у меня на компьютере Хром, а на планшете Фаерфокс? И потом… Всего лишь авторизоваться, ага. Если бы я еще помнил свой пароль от гугло-аккаунта. А аккаунта в Фаерфоксе у меня просто нет. Я даже не в курсе, там, вообще, бывают какие-либо аккаунты? Есть, конечно, сторонние облачные сервисы, где можно зарегистрироваться и наверно как-то текстом перекинуть самому себе нужную ссылку. Но это же еще надо там проходить регистрацию и накрепко зазубрить еще один пароль… Нет, это совершенно не реально. Можно, в конце концов, перекинуть себе ссылку через почту. Но все это долго и копи-паста из, а затем в адресную строку, чем, например, на планшете заниматься крайне неудобно. Нет, все это чушь какая-то…

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

Лирическое отступление

К слову, когда я пишу что-то свое, то я делаю это также по-своему. Я пишу на чистом javaScript в продвинутом блокноте, не пользуюсь гитхабом, не пользуюсь сторонними библиотеками, даже jquery не использую. Поэтому никакого туториала о том, как написать расширение для браузера, здесь не будет. Этому я вас не научу. Не делайте так, как я. Я вас предупреждаю — это плохой пример. Также хочу сказать всем потенциальным критикам, предвосхищая их реакцию — да я совершенно согласен со всем вашим праведным гневом. Да, нельзя пихать весь скрипт в один-два файла, а надо крошить все, как салат, на множество маленьких кусочков, чтобы оно потом собиралось 3 часа по длинной, умной команде из командной строки. Да, нельзя совмещать javascript-код и html-код в одном файле — лучше навернуть еще один уровень абстракции, который будет их объединять. Да, нельзя использовать табличную верстку, но, черт, побери, она железная! И, уж, тем более — сейчас меня сожгут — нельзя использовать innerHtml и создавать сложную html-структуру одним махом, а надо прикреплять и откреплять в цикле все children и старательно навешивать, а затем удалять каждый обработчик… Да, я делаю все неправильно. Грешен, каюсь. Но я делаю это осознанно, потому что так проще. А кто мне запретит? В конце концов, это же не противозаконно, да? Я не умею программировать так, как того требует современная мода, и даже не хочу пытаться, потому что я считаю, что схема процесса разработки излишне усложнена. Я за то, чтобы писать код руками, а не конструировать его из чьих-то кусков, не написав, при этом, ни строчки. Однако, мой код получается легковесным, его не нужно собирать специальной командой и он быстро работает. Я пишу код по технологии F5. Это когда для того, чтобы в любой момент процесса разработки посмотреть, как работает текущая версия, достаточно просто нажать в браузере F5.

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

Хранение вкладок

Итак, я решил, что открытые в браузере вкладки должны сохраняться в некие списки, которые будут называться компьютерами — Домашний компьютер, Рабочий компьютер, Локальный компьютер. Локальный — это когда список хранится не на сервере, а в браузерной базе данных, и пользоваться им, соответственно, можно только в том браузере, из которого он сохранен. Это для тех вкладок, которые не требуется перекидывать между разными компьютерами. Ну а остальные списки можно просматривать на любых компьютерах и браузерах из числа поддерживаемых расширением.

Мне, например, это удобно для того, чтобы запомнить, на какой серии я остановился при просмотре сериала, а также помечать для себя, что находится на странице, если по ее заголовку и виду ссылки это непонятно. К каждой вкладке пользователь может оставлять произвольный текстовый комментарий.

Основной скрипт занимается отображением интерфейса для пользователя, а фоновый скрипт может управлять вкладками браузера и обмениваться данными с сервером. Согласно документации по созданию расширений для Chrome и Firefox, код расширения должен располагаться в двух файлах — основном и фоновом. Например, обработчик события открытия или закрытия вкладок в Chrome выглядит так: Управление вкладками в Chrome и Firefox очень схоже.

chrome.tabs.onUpdated.addListener( function(tabId, changeInfo) {
});

А в Firefox так:

browser.tabs.onUpdated.addListener( function(tabId, changeInfo, tabInfo) {
});

А функция, которая выдаст список всех открытых вкладок, и вовсе записывается одинаково для обоих браузеров:

chrome.tabs.query(,function(data){
});

Да-да, в Firefox записывается тоже chrome… и т.д. Это немного странно, однако, очень радует то, что браузеры движутся к стандартизации своих api.

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

Серверный скрипт написан на PHP. После прочтения открытые в браузере вкладки отправляются на мой сервер и сохраняются там в обычной SQL базе данных. Эти данные — и есть адреса страниц, их названия и текстовые комментарии пользователя, хранимые в формате текстовых строк в поле типа text длинной около килобайта. Есть таблица пользователей, в которой хранятся ссылки на строки в другой таблице — таблице данных. На килобайт можно сохранить около 1000 вкладок. Данные хранятся в незашифрованном виде. Для каждого пользователя предусмотрено до 25 таких хранилищ («Компьютеров»).

Над платной версией и способами оплаты я еще работаю. Однако, бесплатная версия расширения ограничивает пользователя двумя хранилищами — Домашний компьютер и Рабочий компьютер, причем, в каждом можно сохранить не более 10 вкладок. Там будет до 25 «компьютеров» и примерно до 1000 вкладок в каждом, а также выбор фоновых изображений, иконок и названий для каждого компьютера, иерархические списки и многое другое.

Здесь ограничений никаких нет, поскольку данные вкладок хранятся в локальной базе данных браузера на компьютере пользователя. Есть еще одно хранилище — Локальный компьютер. Причем, поскольку данные хранятся не в Local Storage, а в indexedDB, то ограничение в 5 или сколько-то там мегабайт отсутствует. Здесь можно хранить сколько угодно вкладок, но не более, чем позволяет жесткий диск. Планирую еще сделать экспорт и импорт вкладок в текстовый файл. Важно только случайно не очистить это хранилище вместе с историей браузера.

В отличии от базы данных на сервере, здесь все данные не требуется перегонять в текстовую строку, а можно сохранять сразу в ассоциативном массиве как есть. Для работы с indexedDB я написал небольшую подбиблиотечку длинной всего 106 строк. А значением может быть и огромный ассоциативный массив. Принцип хранения там такой: ключ — значение.

Аутентификация пользователя

Firefox

Пока поддерживается Facebook и ВКонтакте, но я планирую увеличить число способов аутентификации пользователя. Авторизация, как я уже отметил, происходит через социальные сети, причем, на сервере, по протоколу OAuth. Замечу лишь, что мне не нравилось то, что при авторизации открывается новая вкладка браузера даже в том случае, если пользователь уже залогинен в соцсети и уже предоставил права расширению. Схема серверной авторизации по протоколу OAuth подробно описана в документации к обоим соцсетям, поэтому нет смысла останавливаться на ней подробно. Эту вкладку приходится отлавливать фоновым скриптом расширения и затем принудительно закрывать и вообще, она выглядит не эстетично, вклинивается к остальным вкладкам и мигает при появлении и уничтожении.

Чтобы ее закрыть, для ВКонтакте нужно проверять в обработчике событий вкладок наличие адреса:

oauth.vk.com/blank.html

А для Facebook:

https://www.facebook.com/connect/blank.html

И еще на всякий случай:

facebook.com/connect/login_success.html

Поэтому вначале я отправил в Google Web Store сборку расширения, в которой авторизация производилась на клиенте с предварительным подтягиванием соответствующего клиентского javascript api социальной сети. Причем, поскольку расширение установлено у пользователя, а авторизовать мне надо на своем сервере, то я еще и в фоновом скрипте расширения в и-фрейме подтягивал html со своего сервера, а вот уже в нем подтягивал api соцсети…

Индексная страница расширения отправляет команду (информацию о клике, например) в фоновый скрипт расширения. И схема обмена данными с сервером была такая. I-frame отправляет данные php скрипту на сервер. Фоновый скрипт при помощи postMessage отправляет данные в i-frame html. Уф… И главное — все это работало! Сервер отвечает в i-frame, i-frame отвечает в фоновый скрипт, фоновый скрипт отвечает в индекс, пользователь видит ответ.

Гугл завернул мое расширение с рядом формулировок, среди которых было и что-то расплывчатое о том, что, мол, ваше расширение не соответствует нашей политике безопасности. В общем, это не прокатило. А именно, им не нравилось то, что расширение подтягивает html и js со сторонних серверов… В общем, пришлось оказаться от клиентской авторизации и вернуться к серверной. Путем переписки с поддержкой, мне удалось добиться конкретики. Ну и ладно. Да, отправлять http post запрос из фонового скрипта расширения напрямую на свой сервер, все же, оказалось разрешено. Получилось короче и не нужно тянуть javascript api из социальных сетей — это лишнее время ожидания для пользователя. Так даже лучше.

Просмотр вкладок

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

Список вкладок в Chrome

Функция принимает url. Цвета для плиток подбираются автоматически на основе имени домена сайта. Это преобразование производится следующим образом. Имя домена делится на три примерно равные группы символов, Первые символы из каждой группы преобразуются в значения компонентов цвета: R, G, B. Даже символы русского алфавита будут приводиться к этом диапазону. Коды этих трех символов приводится к диапазону d1, от 0 до 25, путем взятия остатка от деления на 26. Затем три полученных числа из диапазона d1 (0... Неважно, что 27-я буква там станет первой: нам не требуется, чтобы компоненты цвета всех букв алфавита были непременно различными, пусть повторяются. 255). 25) пропорционально приводятся к диапазону d2 (150... Я взял 150... Вообще, эти диапазоны заданы в начале функции и их можно менять на любые другие. Если задать, например, 0... 255, чтобы компоненты цвета были яркими. Таким образом, три символа из имени домена преобразуются в 3 числа из диапазона 150... 100, то плитки получатся темными. И затем, как вы уже наверно догадались, они интерпретируются как компоненты цвета плитки R, G и B. 255. Все очень просто.

getSiteColor=function(url) { var d1=[0,25], d2=[150,255]; var code=0, codep=0, color=0, str='', domen='', ar=[], inc=0; ar=url.split('//'); if (ar.length>1) {str=ar[1];} else {str=ar[0];}; str=str.split('www.').join(''); domen=str.split('/')[0]; str=domen.split('.').join(''); ar=[]; inc=Math.floor(str.length/3); if (str.length % 3 >0) {inc++;}; for (var i=0; i<str.length; i+=inc) { code=str.slice(i, i+1).charCodeAt(0); codep=code % (d1[1]-d1[0]+1); color=Math.round( codep * (d2[1]-d2[0]) / (d1[1]-d1[0]) ) + d2[0]; ar.push(color); }; return {r:ar[0], g:ar[1], b:ar[2], domen:domen};
}

Структура html документа

Html документ состоит из нескольких элементов canvas с абсолютным позиционированием, совмещенных путем наложения, и блока div, в который выводится список. На ресайз окна браузера повешена функция, которая изменяет размер всех этих элементов, подстраивая их под новые высоту и ширину. Есть также минимальный размер, преодоление которого вызывает появление скролл-баров. На нижнем канвасе отображается фоновое изображение. Я решил, что для разных списков (то есть, Компьютеров) надо задавать разные фоны, чтобы пользователю было легче ориентироваться. Фоновая картинка заполняет собой окно с сохранением своих пропорций и чуть осветляется, чтобы не перетягивать внимание на себя — поверх нее рисуется белый полупрозрачный прямоугольник. Выше фонового канваса лежит канвас главного меню, он заполняет только верхнюю часть экрана. Ну а еще выше лежит блок div, в котором формируется сам список вкладок. Навигация по канвас-меню осуществляется посредством моей мини-библиотечки.

Опера и Яндекс браузер

Локализация

На главном экране есть флажок, по нажатию на который происходит переключение между русским и английским языком интерфейса. Здесь делается переадресация страницы на тот же адрес, но с параметром в командной строке. Этот параметр читается перед загрузкой всех остальных элементов. И на основании его значения в ассоциативный массив для фраз подгружается тот или иной js-файл с фразами русскими либо английскими: lng_en.js, либо lng_ru.js. А в самом приложении для вывода фраз интерфейса используются уже ссылки на текстовые строки из этого ассоциативного массива. Также, в зависимости от значения в адресной строке, подгружается файл, содержащий элементы графического интерфейса — кнопки: buttons_en.png, либо buttons_ru.png. Я решил, что лучше создать кнопки прямо вместе с иконками и надписями на каждом языке в графическом редакторе, чем создать одну пустую кнопку и накладывать на нее текстом иконки и надписи, тем более, что их не много и весь этот спрайт-лист весит всего 50 Кб. Я планирую в дальнейшем поработать над дизайном и сделать все кнопки разными по форме.

Общий же файл со спрайтами main.png, который содержит графические элементы, не привязанные к конкретной локализации, загружается в любом случае.

Веб-версия

Я подумал, что было бы неплохо, чтобы старательно сохраненные с домашнего или рабочего компьютера ссылки можно было бы просмотреть и в пути с телефона. Но не на всех мобильных ОС браузеры поддерживают расширения. Поэтому я создал веб-сборку. Зайдя на сайт по определенному адресу, можно авторизоваться все так же посредством одной из соцсетей и просмотреть свои вкладки.

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

Веб-версия в IE 11

Реклама

Продвижением своего расширения я пока особо не занимался. Только создал группы в соцсетях и пригласил туда всех друзей. Да запустил рекламную кампанию в Яндекс Директе за 1500 рублей. Каких-то особых результатов она не дала, но, возможно я неправильно составил объявление. Или, может быть, проблема в том, что я указал ссылку на англоязычную версию домашней странички расширения. А, может быть, стоило давать ссылку не на домашнюю страничку, а сразу на один из магазинов расширений. Как вы понимаете, я не гений маркетинга. В общем, попробую еще раз с другими параметрами. Кампания дала 39 кликов. Сложно сказать, приобрел ли я в ее результате каких-то новых пользователей.

Была одна странность с версией под Firefox.

Статистика за 17 октября показывает аж 227 загрузок. Не знаю, способствовала ли этому кампания в Директе или нет, но дальше почему-то опять идет по 1-2 загрузки в день. При этом, для расширения Firefox есть понятие ежедневных пользователей:

Почему загрузок было 227, но ежедневных пользователей оставалось всего 1-2, непонятно. Возможно, это был какой-то сбой. В общем, я так и не понял, что это было, и где все эти люди…

Статистика по магазину расширений Chrome Web Store радовала каким-то вечно не загружающимся графиком — что там, я так и не увидел — и 5-10 ежедневных пользователей.

Также, я отправлял сборку в магазин расширений Opera. Однако, выложив две версии и так и не дождавшись модерации ни одной из них, я в какой-то момент плюнул на то, чтобы выкладывать туда свежие. И, действительно, зачем это надо, если в Оперу отлично устанавливаются расширения из магазина Хром.

Итог

Я создал три сборки расширения — для Chrome, Firefox и веб-версию. Собственно, все три сборки — это одна и та же сборка, в которой просто в конфиге указывается, какой браузерный движок используется в данный момент. И инициализируются соответствующие функции для работы с вкладками. Я меняю это значение перед запаковкой в zip и отправкой пакета в соответствующий магазин расширений.

Причем, регистрация в качестве разработчика расширений у Google стоит $5. Версии для Chrome и Firefox прошли проверки и были размещены в соответствующих официальных магазинах расширений. Веб-версия расширения находится на сервере. У Mozilla — бесплатно. Планирую выпуск новых версий с еще более расширенными функциями. Сборка из магазина Chrome прекрасно работает и в совместимых браузерах: ее можно установить также в Yandex Browser и Opera.

Теперь мне не требуется вставать с дивана, а также нет никакой необходимости в ярости ломать планшет. В общем, браузерные расширения — это сила. Всем добра. Это полный дзен.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»