- PVSM.RU - https://www.pvsm.ru -

Способы синхронизации вкладок браузера

Способы синхронизации вкладок браузера - 1

Давным-давно в далёкой галактике появилась задача по синхронизации вкладок браузера для веб-плеера, наподобие VK: нужно было организовать обмен данными между вкладками, отслеживать их количество и назначать задачи некоторым из них. Всю реализацию нужно было выполнить на клиенте. Информации собрано много, и набралось на целую статью.

Ниже опишу различные способы решения подобных задач.

Рассмотрим наиболее популярные способы синхронизации вкладок браузера в порядке увеличения сложности.

Local Storage

localStorage [1] – локальное хранилище, свойство объекта window, позволяет получить доступ к локальному Storage [2] объекту. В нем можно хранить данные между сессиями пользователя. Есть аналогичное свойство – sessionStorage [3], но оно хранит данные только в течение сессии страницы.
Данные в storage добавляются с помощью метода setItem [4].

localStorage.setItem('key', 'value');

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

window.addEventListener('storage', function(event) {
    console.log(event.key);
});

Событие не работает на вкладке, которая вносит изменения, но срабатывает на остальных вкладках домена в браузере.
Способы синхронизации вкладок браузера - 2
Генерация события storage

Браузеры имеют различный уровень объема хранилищ для localStorage и sessionStorage:

  • Chrome, FireFox и Opera ~ 5 МБ.
  • IE ~ 4,8 МБ.
  • iOS Safari, OS X Safari ~ 2,5 МБ.
  • Android ~ 5 МБ.

Из недостатков можно отметить – объем хранилища браузера, а при его переполнении новый объект не будет записан.
Метод работает во всех браузерах, кроме Opera mini [5].

Post Message

postMessage [6] — это метод, который позволяет безопасно отправлять кросс-доменные запросы, то есть общаться друг с другом окнам и iframes с разных доменов.
Он очень удобен для взаимодействия внешних виджетов и сервисов, подключенных через iframe с основной страницы.
Передача сообщения:

const win = window.frames.target;
win.postMessage('send message', 'http://javascript.ru');

Передаваемые данные могут быть любым объектом, который поддерживает клонирование [7] (строка, объект, массив, Map, Date ...). Но IE поддерживает только строки.

Url указывает, что получать сообщения можно только окнам с данного источника.
Чтобы получать сообщения, окно должно подписаться на событие onmessage.

window.addEventListener('message', function(event) {
  if (event.origin != 'http://javascript.ru') {
    return;
  }

  console.log(event.data);
});

Любое окно может получить доступ к этому методу, для отправки ему сообщения независимо от местоположения документа в окне. Поэтому обязательно нужно проверять origin.

В браузере IE интерфейс postMessage [8] работает только с iframes и не работает между вкладками и окнами.

Broadcast Channel API

Broadcast Channel API обеспечивает простую связь между контекстом просмотра (окна, вкладки). Объект BroadcastChannel [9] создает общий канал, который позволяет получить любое сообщение, отправленное в него. Вкладки, окна, iframes могут подписаться на канал и установить с ним связь.

const bc = new BroadcastChannel('test_channel');

Метод postMessage публикует сообщение в канале. Аргументом является тип, который поддерживает клонирование [7].

bc.postMessage('This is a test message.'); 

Когда сообщение публикуется, message событие будет отправлено каждому объекту, подключенному к этому каналу.

bc.addEventListener('message', function (e) { console.log(e); })

Способы синхронизации вкладок браузера - 3
Публикация сообщения в канале для разных контекстов.

API довольно простое, его можно рассматривать как простую шину сообщений. Но у способа есть весомый недостаток: нет поддержки Safari и IE [10].

На первый взгляд можно найти несколько похожих методов передачи данных (например MessageChannel, WebSocket), но каждый из них служит определенной цели — их сравнение [11].

Web Workers

Это механизм, который позволяет скрипту выполняться в фоновом потоке, который отделен от основного потока веб-приложения. Он реализован с использованием js-файлов, которые включаются в страницу с применением асинхронного HTTP-запроса.

Воркеры [12] отлично подходят для того, чтобы выполнять тяжелые вычислительные операции, не замедляя работу пользовательского интерфейса.
Но с синхронизацией помочь могут только два вида воркеров.

Shared Worker [13]

Это особый вид воркера, к которому можно получить доступ из нескольких контекстов браузера. Напишем общий js-файл для вкладок, например shared-worker.js.

const worker = new SharedWorker('shared-worker.js');

Каждая вкладка может связываться с воркером через worker.port. Скрипт воркера также имеет доступ к своим портам. Каждый раз, когда вкладка подключается к воркеру, в сценарии запускается событие connect.

// shared-worker.js
const connections = [];
onconnect = function(e) {
   const port = e.ports[0];
   connections.push(port);
};

Метод postMessage создан для отправки данных вкладки на общий воркер.

worker.port.postMessage('test message');

Получить данные воркера можно с помощью события message.

worker.port.onmessage = function (e) {
   console.log(e.data);
};

В SharedWorker API есть событие connect, но нет события disconnect, и поэтому данные не смогут самоочищаться в закрытых вкладках — они будут продолжать считаться открытыми. Это не приведет к ошибкам, но можно считать это недоработкой или фичей API.

Работает только в Chrome и FF [14].

Service Worker [15]

Это событийно-управляемый воркер, который может контролировать, перехватывать и модифицировать сетевые запросы и кешировать их.
Регистрация воркера:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('service-worker.js')
        .then(function() {
            return navigator.serviceWorker.ready;
        })
        .catch(function(error) {
            console.error('registration error : ', error);
        });
}

С помощью события message вкладки могут получить данные из js-файла воркера, а функция syncTabState используется для обработки сообщения.

self.addEventListener('message', function(e){
    const data = e.data;
    const tabId = e.source.id 

    self.syncTabState(data, tabId);
});

Функция sendTabState создана для отправки сообщений вкладкам.

self.sendTabState = function(client, data){
    client.postMessage(data);
}

Подробное использование и множество примеров тут [16].

У всех веб-воркеров нет доступа к объектам window и document.

Service worker не работает в IE и Opera mini [17].

Библиотеки синхронизации

Это способ для тех, кто не хочет велосипедить и готов рассмотреть уже имеющиеся решения.

  • tabs-router [18], использует Сookie и Local Storage;
  • Hermes [19], использует SharedWorker;
  • Visibility [20], использует Page Visibility API [21];
  • Duel [22], использует Local Storage и WebSocket;
  • __SE__ [23], использует Worker и localStorage;

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

Итог

Чтобы подвести окончательные итоги, сравним методы по поддержке браузерами наглядно.

Способы синхронизации вкладок браузера - 4
Используйте LocalStorage, BroadcastChannel и PostMessage для простых случаев, когда вам нужно отправить сообщение потенциально нескольким окнам/вкладкам или iframes.

Для управления блокировками совместного состояния и совместных файлов наиболее подходящим решением является Shared Workers и Service Worker.

А для задачи с веб-плеером был выбран LocalStorage, так как есть поддержка IE.
Надеюсь, что статья помогла вам в выборе подходящего способа синхронизации.

Благодарю команду Афиши [24] за помощь и поддержку!

Используемые статьи:

Автор: fat32elena

Источник [30]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/291790

Ссылки в тексте:

[1] localStorage: https://developer.mozilla.org/ru/docs/Web/API/Window/localStorage

[2] Storage: https://developer.mozilla.org/ru/docs/Web/API/Storage

[3] sessionStorage: https://developer.mozilla.org/ru/docs/Web/API/Window.sessionStorage

[4] setItem: https://developer.mozilla.org/ru/docs/Web/API/Storage/setItem

[5] Opera mini: https://caniuse.com/#search=localStorage

[6] postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

[7] клонирование: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

[8] postMessage: https://caniuse.com/#search=postMessage

[9] BroadcastChannel: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel

[10] Safari и IE: https://caniuse.com/#search=BroadcastChannel

[11] их сравнение: https://developers.google.com/web/updates/2016/09/broadcastchannel

[12] Воркеры: https://developer.mozilla.org/ru/docs/Web/API/Web_Workers_API

[13] Shared Worker: https://developer.mozilla.org/ru/docs/Web/API/SharedWorker

[14] Chrome и FF: https://caniuse.com/#search=shared%20worker

[15] Service Worker: https://developer.mozilla.org/ru/docs/Web/API/Service_Worker_API

[16] тут: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

[17] IE и Opera mini: https://caniuse.com/#search=service%20worker

[18] tabs-router: https://github.com/istem/tabs-router

[19] Hermes: https://github.com/arnellebalane/hermes

[20] Visibility: https://github.com/ai/visibilityjs

[21] Page Visibility API: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API

[22] Duel: https://github.com/studentIvan/dueljs

[23] __SE__: https://github.com/Sombressoul/__SE__

[24] Афиши: https://www.afisha.ru/

[25] Messaging Between Tabs Using Service Worker: https://www.loxodrome.io/post/tab-state-service-workers/

[26] Sending data across different browser tabs: https://blog.arnellebalane.com/sending-data-across-different-browser-tabs-6225daac93ec

[27] How JavaScript works: The building blocks of Web Workers + 5 cases when you should use them: https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a

[28] Задача коммуникации между вкладками и выявления активной вкладки: https://habr.com/post/247739/

[29] Библиотека для обмена событиями, данными и задачами между вкладками браузера: https://habr.com/post/220297/

[30] Источник: https://habr.com/post/422545/?utm_campaign=422545