Поняття пісочниці при розробці розширень для браузера Google Chrome

За 5 років розробки розширень для браузера Google Chrome накопичився певний досвід, яким хотілося б поділитися в циклі статей і, по можливості, пояснити деякі тонкощі, підводні камені, а також описати як були вдало застосовані сучасні фронтенд-технології.

Частина 1.

Введення

Розширення для браузера Google Chrome я зіткнувся в 2012-му році, коли активно купував товари з вітрини Amazon і було страшенно незручно шукати продавців, які доставляли товар в РФ і в кого вигідніше купувати з урахуванням вартості доставки. Ось тут я і вирішив полегшити життя собі, так і іншим покупцям теж, створивши розширення «Amazon ships to you», про яке була навіть коли стаття на Хабре. З часом воно стало не актуально, оскільки на вітрині Amazon зробили через пару років нормальні фільтри і я його зняв з публікації.

Далі настав час користування сервісом Я. Музика і Я. Радіо і дуже вже мені не вистачало управління плеєром на сайті Я. Музики коли він грає у фоновій вкладці, ну хоча б гарячими клавішами. В результаті, я, як людина досвідчена, не знайшовши аналогів, вирішив зробити для цього розширення “Яндекс.Музика — управління плеєром», яке є моїм хобі і донині, незважаючи навіть на вихід офіційного розширення від Яндекса.

Передбачається, що читач має бути вже знайомий з базовою структурою елементів розширення (manifest.json, фонова сторінка, контент-скрипт, попап). Короткий лікнеп під спойлером нижче.

Лікнеп з розширеннямРозширення — це програмний пакет, який розширює функціонал браузера. Поширюється через офіційний Chrome Web Store, також можна завантажити локальне розширення в режимі розробника на сторінці chrome://extensions/, однак з ними Google бореться все більше закручуючи гайки.

Раніше можна було локальними розширеннями користуватися без проблем, потім браузер почав сповіщати повідомленням про наявність таких розширень, тепер же подібні розширення за замовчуванням відключаються при запуску браузера і їх треба активувати вручну (що чимало насолило розробників розширень при активній розробці).


Всі метадані розширення описуються в маніфесті, але про нього нічого сказати, документації все описано.

Розширення складається з фонової сторінкиbackground page або event page, спливаючого вікна, сторінки налаштувань впроваджуваного на цільовий сайт контент-скрипта і специфічних override pages, які зумовлюють стандартні сторінки браузера: менеджер історії, закладок, нова сторінка і т. п.

З точки зору UI, розширення містить елемент browserAction або pageAction — іконку на панелі браузера праворуч від адресного рядка, натискання на яку може ініціювати яку-небудь дію, або ж відкриває ще один UI елемент — спливаюче вікно. Також на цій піктограмі можна виводити badge — значок, наприклад, кількість непрочитаних листів.

Браузер надає розширенням множинні API для різних потреб, наприклад через chrome.tabs API можна отримати доступ до всіх вкладках, закривати, відкривати нові, бачити метадані вкладок і т. п.

Плюс до цього, між різними сторінками розширення є певні механізми передачі даних.

Пісочниці

Типова задача розширення — розширення функціоналу деякого сайту, для чого необхідно запровадити код на цільову сторінку. Візьмемо для прикладу Я. Музику (тут і далі): для управління плеєром на сайті через спливаюче вікно розширення (див. скріншот №1), нам необхідно відстежити відкриття сторінки, потім впровадити в неї наш код, який в подальшому буде взаємодіяти з js-кодом і DOM вітрини і передавати всі зміни в розширення, щоб при відкритті спливаючого вікна завжди відображати актуальний стан плеєра на вітрині. Ось тут і з'являється щось важливе, про що я хотів би для початку розповісти і про що свідчить назва розділу: пісочниці.

image
(скріншот №1)

Фонова сторінка розширення, будучи основним контролером розширення, при наявності належних розширень (tabs, activeTab) може відстежити відкриття сторінки music.yandex.ru використовуючи chrome.tabs.onUpdated і впровадити наш код (контент-скрипт) на вітрину через chrome.tabs.executeScript. Однак, вбудований код виявляється у своїй пісочниці (Б), з якої є доступ до DOM-документу вітрини, але не до js-коду вітрини, який виконується в своїй пісочниці (А). В теорії можна було б відслідковувати зміни DOM-елементів на вітрині щоб транслювати поточний стан в розширення (наприклад, змінився трек — взяти з DOM назву треку, виконавця), але це все працює не так як хотілося б: з затримками на оновлення вітрини, не завжди присутніми цільовими DOM-елементами в поточному відображенні вітрини та іншими речами, які в реальному використанні розширення дають рассинхронизацию даних між вітриною і відображенням в розширенні. Як же нам отримати доступ до js-коду вітрини (пісочниці А)?

image
Для впровадження js-коду в пісочницю А з контент-скрипта нам може допомогти наступний «хак» (тут і далі нотація es6):

function injectCode(func, ...args) {
let script = document.createElement('script');
script.textContent = 'try {(' + func + ')(' + args + '); } catch(e) {console.error("injected error", e);};';
(document.head || document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
}

В результаті наш контент-скрипт може впровадити свій код в пісочницю js-коду вітрини для того, щоб отримати доступ до API вітрини і на низькому рівні мати доступ до об'єктів і подій плеєра, плейлиста та інших сутностей вітрини. Але це ще не все: доступ до API отриманий, наприклад, подія початку програвання треку наш вбудований код «спіймав», але як його передати назад у контент-скрипт, який, у свою чергу, має механізми передачі даних на фонову сторінку розширення?

image
На жаль, доступу з пісочниці А в пісочницю Б ні, у всякому разі, мені він невідомий. Але це не страшно, тому що ми пам'ятаємо, до чого має доступ контент-скрипт зі своєї пісочниці Б: до DOM-документу вітрини, звідси вихід: вбудований в пісочницю А код може створювати довільні події:

document.dispatchEvent(new CustomEvent(CUSTOM_EVENT_NAME, {detail: payload}));

А контент-скрипт може їх ловити:

document.addEventListener. (CUSTOM_EVENT_NAME, e => {
console.log(e.detail); //payload
});

Виходить така схема:

image
Після цього, контент-скрипт передає цю подію на фонову сторінку одним з механізмів: chrome.runtime.sendMessage або chrome.runtime.connect. Відмінність їх у тому, що перший метод відкриває канал, що передає дані, закриває канал, другий же створює постійний канал, в рамках якого відбувається передача даних. При програванні треків йде постійна передача подій progress (поточний час програвання), тому для мене став вибір очевидний: піднімати постійний канал зв'язку контент-скрипта з фоновою сторінкою, що дає ще один очевидний бонус: контроль втрати зв'язку з вітриною з різних причин, від яких винятків js-код до закриття вкладки (хоча саме закриття вкладки легко відстежується через chrome.tabs.onRemoved).

З даними пісочницями закінчили, але це ще не все: є ще пісочниця (В) фонової сторінки і пісочниця (Р) спливаючого вікна. Тут все трохи простіше, дані пісочниці мають доступ один до одного через API:

Але тут є підводний камінь: спливаюче вікно не може, наприклад, додати свій слухач подій на фонову сторінку, але до об'єкта window один одного вищеописані методи доступ дають (т. к. вони запущені в одному потоці). Для однаковості интеркоммуникаций і для примусової ізоляції коду фонової сторінки від коду спливаючого вікна (popup може модифікувати об'єкти bg, що порушить односпрямований потік даних з контент-скрипта на фонову сторінку для зберігання актуального стану і звідти у спливаюче вікно), я перейшов до такого ж підняття каналу як і між контент-скриптом і фонової сторінкою.

Коду і прикладів мало, оскільки все є в документації, на яку я максимально посилаюся при будь-яких згадок методів і API, в частині коду мені додати тут нічого, т. к. на статус туториала (коли можна скопипастить весь код і щось заробить) — не претендую. А ось саму концепцію розжувати коштувало, т. к. неодноразово колегам по программистскому цеху пояснював її. Можна резюмувати розділ наступною ілюстрацією:

image
У наступній частині я планую розповісти про історію перекладу розширення “Яндекс.Музика — управління плеєром" на нині популярний React+Redux, який, як не можна, до речі, підійшов для відображення стану плеєра у спливаючому вікні розширення.
Джерело: Хабрахабр

0 коментарів

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