2GIS for% browser% або як ми робили розширення

    Одного разу ми поставили собі питання: як ми можемо допомогти користувачеві вибрати компанію за межами 2gis.ru ? Варіант реалізації ідеї у вигляді браузерного розширення був запропонований практично відразу, і після етапів дослідження і планування ми приступили до розробки 2GIS for browsers .
 
В якості основного варіанту реалізації ми зупинилися на кнопці збоку адресного рядка, при натисканні на яку відкривається інформаційне вікно. Додатково — підсвічувати номери телефонів усередині вмісту сайтів.
 
 
 
 
 
Для більшої зручності було вирішено визначати поточні координати користувача, щоб показувати йому достовірну інформацію про відстань до цікавить фірми і давати швидку посилання на пошук проїзду від поточного місця розташування до фірми. Згодом визначення координат стало більш важливим, оскільки необхідно було визначити місто, в якому знаходиться користувач, щоб показувати йому дані з найбільш відповідного джерела — 2GIS або Google.
 
 
 

Фонові і контентні скрипти

З технічної точки зору, середньостатистичне браузерні розширення — це відносно просте JavaScript-додаток, з однією або більше точками входу.
 
Незалежно від того, для якого браузера пишеться розширення, всередині програми можна виділити дві групи скриптів, а саме — фонові і контентні.
 
 Фонові скрипти (background scripts) виконуються в пісочниці (sandboxed evaluation) на рівні додатку, і їхні дані єдині для запущеного екземпляра браузера. Вони запускаються один раз за сесію або при інсталяції розширення, або при запуску браузера з встановленим розширенням.
 
 Контентні скрипти , якщо вони є, виконуються в контексті вмісту проглядається сайту і мають доступ до дерева DOM, але при цьому для кожного вікна і вкладки браузера їхні дані різняться. Запускаються контентні скрипти кожен раз при настанні події onload завантажується в браузер сторінки. У фонових скриптів немає прямого доступу до вмісту сторінок, але движки браузерів надають механізми взаємодії між скриптами в фоні і скриптами в тематичній області. При цьому у кожного движка є свої особливості в організації обміну даними.
 
Крім фонових і контентних скриптів розширення може включати інші точки входу — по одній на кожен додатковий ресурс. Наприклад, спливаюче інформаційне вікно і одна або більше сторінок опцій розширення. Скрипти додаткових ресурсів виконуються кожного разу при їх ініціалізації.
 
 
 

Фреймворки для створення розширень. Kango Extensions

Ми хотіли, щоб розширення працювало в різних браузерах, тому звернули увагу на фреймворки для кросбраузерності розробки. Деякі з них надавалися як SaaS, що нас зовсім не влаштовувало. Інші — вміли працювати тільки з контентной областю і були застосовні скоріше для реалізації кросбраузерності користувача скриптів (userscripts). І тільки два фреймворка з усього цього «розмаїття» відповідали вимогам. Ми зупинилися на фреймворку Kango Extensions, так як у нього:
 
     
  • менший поріг входження;
  •  
  • кроссплатформенная збірка;
  •  
  • російська розробник у складі їх команди :).
  •  
У безкоштовної версії, яку ми і вибрали, можна створювати пакети розширень для Mozilla Firefox, Safari, Google Chrome і його похідних. Інтерфейс досить обмежений, загальний для всіх браузерів, але його вистачає для створення кнопок в адресному рядку і спливаючих вікон.
 
До складу фреймворку входять:
 
     
  • постійне сховище даних kango.storage, доступне тільки з фонових скриптів. Цікаво, що на різних браузерах сховище поводиться трохи по-різному. Наприклад в firefox дані зберігаються при видаленні і повторної установки розширення, а в webkit-браузерах — стираються при видаленні розширення;
  •  
  • інтерфейс для управління браузером kango.browser, з якого можна не тільки отримати поточний стан вікон і вкладок, а й управляти ними;
  •  
  • обгортка для XMLHttpRequest під назвою kango.xhr, вирішальна проблему зі створенням екземпляра XMLHttpRequest в фонових скриптах Firefox;
  •  
  • відладочний інтерфейс kango.console, з єдиним методом log для виведення налагоджувальні повідомлення з скриптів в фонової пісочниці;
  •  
  • єдиний інтерфейс для обміну повідомленнями між фоновими і контентного скриптами. Методи фонових скриптів доступні через kango.invokeAsync, також існує механізм диспетчеризації повідомлень в контентную область з фону через методи KangoBrowserTab.dispatchMessage і kango.addMessageListener. Вищезазначені методи отримують дані асинхронно, тому подієве програмування доведеться застосовувати повною мірою і стежити за потенційними race conditions.
  •  
 

Використовувані дані і зовнішні сервіси

Більша частина даних 2GIS for browsers зберігається в фонових скриптах і, щоб уникнути зайвих запитів, там же і кешується. Серед них, наприклад — поточне місцезнаходження користувача і дані про організації, отримані при пошуку по домену або по телефону. Дані організацій підтягуються з 2GIS і Google Places через їх API . Місцезнаходження користувача в свою чергу визначається або за допомогою HTML5 Geolocation API, або за допомогою стороннього відкритого GeoIP-сервісу, якщо API повернуло помилку або недоступно.
 
 
 
З визначенням поточного місця розташування користувача пов'язано кілька непростих рішень, оскільки можливих ситуацій виявилося досить багато. Наприклад, що буде, якщо користувач закриє кришку ноутбука і поїде в інший кінець міста, а потім відкриє ноутбук? А якщо він просто перейде між двома точками Wi-Fi з короткочасною втратою зв'язку? Вирішити завдання в лоб — через вбудований метод navigator.geolocation.watchPosition — не вийшло. Подія positionChanged виникало значно рідше, ніж того хотілося б, до того ж воно не спрацьовувало в ситуації з закриттям і відкриттям кришки ноутбука. На допомогу прийшов прапор navigator.onLine. Стежачи за ним, ми могли викликати подія в момент появи інтернет-з'єднання. На жаль, прапор надійно працює тільки в webkit-браузерах, але це краще, ніж нічого.
 
Якщо організація знаходиться в тому ж місті, що і користувач, або в одному з міст 2ГІС, ми покажемо інформацію з 2ГІС, так як вважаємо її більш достовірної та вивіреної. Для отримання координат області, про яку у нас немає відомостей, використовується Google Places, але із застосуванням фільтрів. Інформація показується тільки в тому випадку, якщо домен в пошуковій видачі збігається з доменом, на якому знаходиться переглядається сторінка. Аналогічним чином ми чинимо і з пошуком за телефонами. Єдина відмінність — фільтруємо по телефону, а не по домену. Це корисно якщо телефон організації розміщений, наприклад, на сторонньому сайті оголошень.
 
Іншими словами, навіть якщо користувач знаходиться на іншому кінці земної кулі відносно організації, на сайт якої він зайшов, ми все одно спробуємо знайти і надати коректні дані про цю організацію.
 
 

Як воно працює

Розширення працює як звичайне javascript-додаток: отримує необхідні для роботи початкові дані, додає обробники на певні події і з їх допомогою обробляє які події. Таким чином реалізується поведінка при перемиканні вікон і вкладок, наприклад, зміна індикатора, залежно від наявності інформації по відкритому в даній вкладці сайту.
 
Крім перемикання вкладок ми також повинні реагувати на зміну URL самим користувачем. Для цього ми обробляємо подія onBeforeNavigate, яке браузер викликає перед тим, як почати завантажувати сторінку. Невелика складність виникла з Safari — він викликає ця подія не при кожній зміні адресного рядка, тільки якщо сторінка відсутня в кеші або кеш закінчився. Довелося йти на компроміс — додати обробник на подію onLoad, яке викликається вже після того, як сторінка завантажена.
 
Також ми зіткнулися з проблемою можливих редиректів через заголовки відповіді, через meta-тег або через js-виклик — програмні редіректи такого роду не генерують додаткових подій onBeforeNavigate. З іншого боку прив'язуватися на onLoad для всіх браузерів теж зовсім не хочеться, оскільки це візуально виглядало б дратівливо повільним. Як рішення вибрали періодичний опитування стану URL в поточній вкладці протягом 30 секунд і оновлення даних при необхідності.
 
 

Розпізнавання телефонів у веб-сторінках

У тематичній області сторінки розширення робить єдине завдання — виділення з контенту послідовностей цифр і символів, які можна вважати телефонами. Це дозволяє отримати інформацію за цими телефонами і подзвонити, відправивши виклик на ваш смартфон.
 
Завдання розпізнавання телефонів сама по собі є нетривіальною за великої різноманітності форматів, тому ми не мали ілюзій щодо повністю надійного рішення. Приклади форматів, з якими нам довелося зіткнутися: 34.76.35.05.39 — Франція, +1 234 345 6789 — США, 67 2354 9548 — Бразилія.
 
Найкращим рішенням став двоетапний фільтр на основі регулярних виразів і простих умов. На першому етапі з тексту виділяються послідовності символів, які теоретично нагадують телефони. Для цього використовуються відносно несуворі регулярні вирази і умови, крім того, для входжень перевіряється контекст, який може дати додаткову інформацію. Крім цього, на першому етапі довгі послідовності можуть розбиватися на короткі, також можуть виключатися частини послідовності з невідповідним контекстом.
 
 
 Другий етап приймає набір знайдених послідовностей і на основі вже більш строгих правил вирішує, чи прийняти цей вислів чи ні. Правила включають в себе перевірки за відомим маскам і простим умовам. Наприклад, чи не є передана послідовність строковим поданням числа з плаваючою крапкою.
 
Через деякі строгих правил не розпізнаються реальні номера, але це виправляється винятками або додатковим переглядом контексту. Сьогодні для перевірки парсера телефонів використовується набір з 385 тестових рядків, що включають як позитивні, так і негативні випадки. Готовність до продакшну оцінюється по порогу в 95% успішно прохідних тестів з набору.
 
 
У ході простого ітеративного обходу дерева по ширині, відбувається пошук телефонів у вмісті кожної текстової DOM-ноди документа. По кроках це виглядає приблизно так: підсвічується знайдений у тексті телефон → при наведенні на нього курсором до зовнішніх сервісів йде запит відомостей про компанію за номером телефону → отримані дані відображаються у спливаючому віконці → якщо нічого схожого не знайдено, то відображається посилання «Подзвонити».
 
Дані приймаються в тлі, що не заважає роботі сторінки, а кешування даних виключає додаткові запити за тим же телефоном з інших вкладок і вікон.
 
 

А якщо дані отримані за Ajax?

Якщо з розбором коду сторінки на етапі її завантаження все більш-менш зрозуміло, то що робити з телефонами, які можуть бути отримані динамічно через ajax або просто сформовані клієнтськими скриптами?
 
На цей випадок ми передбачили обробку нових текстових даних в сторінці на основі інтерфейсу MutationObserver, який підтримується всіма сучасними браузерами (виняток — Safari 5 for Windows). Для підписки на події, що виникають при зміні DOM ми використовували бібліотеку mutation-summary. Вона дозволяє легко і просто отримати додаються DOM-ноди. Для скорочення кількості оброблюваних даних, підписуємося лише на отримання нових текстових нод і намагаємося розібрати їх вміст. Цікаво спостерігати поведінку розширення в разі введення телефону в поле з одночасним його відображенням в іншої текстової ноді :
 
 
 

Інтернаціоналізація. We speak English

На поточний момент розширення випущено російською та англійською мовою, вибір мови залежить від поточної локалі браузера і його налаштувань. При перекладі ми зіткнулися з тим, що з контентних і додаткових скриптів достукатися до компонента i18n можна тільки через асинхронний виклик в фон. Це зовсім не вкладалося в уявлення про інтерфейс інтернаціоналізації. Тому був реалізований невеликий модуль, в завдання якого входило:
 
     
  • визначення поточної локалі;
  •  
  • завантаження списків локалізацій залежно від локалі, або через прямий виклик kango.io.getExtensionFileContents, або через асинхронний в разі, якщо модуль інстанцііровался в контенті або в додаткових скриптах;
  •  
  • надання простого інтерфейсу для отримання повідомлень локалізації з найпростішої інтерполяцією рядків.
  •  
Через потенційну асинхронности довелося забезпечити можливість ініціалізації локалей раніше інших об'єктів, оскільки в їх конструкторах могли міститися виклики отримання локалізованих текстів. При цьому ми не стали відходити від формату локалей, прийнятого в kango, оскільки крім kango.i18n, файли локалей частково використовуються при складанні пакетів розширення.
 
 

Збірка пакетів

Що стосується самої збірки, фреймворк надає простий спосіб зібрати розширення, використовуючи збирач на python. Для складання досить викликати скрипт kango.py з параметром build та зазначенням директорії, в якій знаходяться файли розширення. Після цього складальник створить необхідні розпаковані збірки і навіть упакує ті, які відносяться до Chrome і Firefox. На жаль, через непростого механізму підписування розширень, складальник «з коробки» не вміє упаковувати пакет для Safari. Можна обійтися складанням розширення через Safari extension builder, пройшовши всю процедуру отримання сертифікатів в Safari developer center. А можна скористатися патчений архіватором xar і своїми сертифікатами , щоб вміти збирати розширення автоматично і на будь * nix-системі.
 
Крім збирача kango і простого bash-скрипта для автоматизації складання під safari ми також використовуємо gulp для перевірки code style, прогону тестів і ряду допоміжних дій. По-перше, потрібно підставити в декілька файлів номер версії поточної збірки. По-друге — непогано б переконатися, що відключений весь зневаджувальної. Ну і нарешті, у нас є потреба в збірці особливих варіантів пакетів. Наприклад, addons.mozilla.org потребує відсутності в розширенні URL для автоматичного оновлення, мотивуючи це тим, що оновлення відбувається автоматично з їх сайту. З іншого боку, ми публікуємо розширення для Firefox через власне джерело, тому потрібно також варіант, де URL для оновлення буде присутній. Також ряд змін, пов'язаних з локалізаціями, потрібно для розміщення розширення в Opera store.
 
 

Що далі

Світ не стоїть на місці і 2GIS for browsers також росте і розвивається, і ось дещо з того, що ми збираємося зробити найближчим часом:
 
     
  • итеративное поліпшення распознавателя телефонів. Додаємо більше тест-кейсів, виправляємо парсер — жити стає краще й веселіше;
  •  
  • впровадження інтерактивної карти 2GIS під спливаюче вікно замість статичної картинки;
  •  
  • косметичні правки, що дозволяють нашим елементам відмінно виглядати навіть на дуже погано верстаючих сайтах;
  •  
  • ну і звичайно різноманітні фікси і поліпшення, пропозиції по яких ми чекаємо на пошту extension@2gis.ru
  •  
Спасибі за увагу. Робіть розширення для браузерів — це не тільки весело, але й корисно!
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.