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



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

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

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

    Local Storage


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

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

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

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

    Событие не работает на вкладке, которая вносит изменения, но срабатывает на остальных вкладках домена в браузере.

    Генерация события storage

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

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

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

    Post Message


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

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

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

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

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

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

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

    Broadcast Channel API


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

    const bc = new BroadcastChannel('test_channel');

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

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

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

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


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

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

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

    Web Workers


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

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

    Shared Worker


    Это особый вид воркера, к которому можно получить доступ из нескольких контекстов браузера. Напишем общий 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.

    Service Worker


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

    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);
    }

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

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

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

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


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


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

    Итог


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


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

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

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

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

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

    Rambler Group

    80,00

    Компания

    Поделиться публикацией

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

    Комментарии 16
      0

      Спасибо за статью.
      Всё-таки я не понял про необходимость в этой синхронизации вкладок. На примере Вк не ясно — что именно там синхронизовалось.
      Вопрос: а как реализуется синхронизация в случае, когда нынешние версии хрома, лисы и пр. выгружают вкладки из памяти?

        +2
        Синхронизация в ВК, например, при запуске музыки. Если в одной вкладке играет Мелодия1, а в другой запускается Мелодия2, то, чтобы они не звучали одновременно, Мелодия1 останавливается.
          0

          А, вот оно, не знал. А данную технологию нельзя ли совместить с push'ами?

            0
            Вероятнее всего можно. Более того, ВК давно использует сокеты, таким образом тоже можно было бы синхронизировать состояния.

            Преимущество синхронизации через браузерный storage в том, что тут нет необходимости использовать бэк. Т.е. Каждая синхронизация вроде как дешевле на два запроса. (Первый на сервер от вкладки где что-то поменялось, второй от бэка к другой вкладке, которая должна синхронизироваться с первой.)
              0
              Более того. Такая синхронизация может быть использована для того чтоб все открытые вкладки использовали одно общее вебсокет соединение. Тогда человек открывший более одной вкладки по прежнему с сервером поддерживает одно соединение.
              0
              Для меня хороший пример синхронизации вкладок — это login / logout.
              0
              А насчет выгружаемых вкладок предположу следующее. Если вкладка выгружена, в ней ничего не происходит и беспокоиться о ней не надо. Когда вкладка становится активной, она проверяет, что за события произошли и обновляется соответственно.
              Это только мои предположения.
                0
                Да, я тоже так думаю.
                При выгрузке она усыпляется и не будет синхронизироваться, но когда станет активной — подхватит текущее состояние
                  0
                  Собственно, если вкладка фоновая, то она не видна и смысла синхронизации нет. Особенно если вкладка выгружается.
                  А серверная синхронизация вообще имеет смысл только, если идет серверный пуш.
                    0
                    Это уже зависит от задачи, в фоновой вкладке пользователь может слушать музыку.
                    А выгрузка — это личное дело пользователя, не каждый ее юзает.
                      0
                      Если там играет музыка или видео, она вроде как не выгрузится из памяти.
                0
                например, при включении музыки во второй вкладке в 1 она останавливается
                +1
                Спасибо за статью!

                Даже про мою поделку четырёхлетней давности вспомнили (__SE__), про которую тогда писал на хабре.

                Сейчас глянул исходники:
                 // It is not obvious, but the next line it is a point, where the application execution starts... :-)
                __SE__.Sync = __SE__.Sync;
                

                … всплакнул %)
                  0
                  Также можно использовать CookieStore.
                    0
                    Лет 10 назад делали такое в проекте, только тогда localstorage был слабораспространенной фичей.

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

                    Хорошо что больше нет необходимости так извращаться :)
                      0
                      Люто беру на вооружение в закладки!

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое