Надійний localStorage для букмарклет

На відміну від розширень, букмарклет гарні простотою і кросбраузерністю. Звичайно, вони обмежені контекстом вікна (вміст сторінки), але часто цього достатньо. А з виникненням механізму
localStorage
у них з'явився простий спосіб зберігати і запитувати дані на стороні клієнта.

Наприклад, я користуюся таким найпростішим букмарклетом, щоб зберігати проміжні «закладки» на місця в довгих статтях, які не вдається прочитати за один присід.

javascript:(function(d, scrT){
scrT = d.documentElement.scrollTop || d.body.scrollTop;
if (scrT) {
localStorage['bmk_' + d.location.href] = scrT;
}
else {
scrollTo(0, localStorage['bmk_' + d.location.href] || 0);
}
})(document)

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

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

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

Як уберегтися в таких випадках? Звичайно, можна перейти на
IndexedDB
(якщо сайт її, знову-таки, не очищає) або написати розширення (Google Chrome виділяє розширенням свій
localStorage
, Firefox цього не дозволяє, але забезпечує свій спосіб зберігання даних в єдиній базі налаштувань) — ось тільки для таких простих випадків користуватися такими складними інструментами не дуже сподручно.

На жаль, не вийде скористатися і
localStorage
, що належить внутрішнім фреймах сторінки, якщо такі виявляться: цього не дозволить нам зробити принцип однакового джерела.

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

Допоможе нам технологія
Window.postMessage
. Нам лише потрібно створити крихітну веб-сторінку, ось таку:

<!doctype html>
<html>
<head><meta charset="UTF-8"><title></title>
<script>
function processMessage(event) {
if (event.data.action == 'get') {
event.source.postMessage(localStorage[event.data.key], event.origin);
}
else {
localStorage[event.data.key] = event.data.value;
}
}
window.addEventListener. ('message', processMessage, false);
</script>
</head>
<body></body>
</html>

Потім помістити її на будь-хостинг, який підтримує протоколи
http:
та
https:
— наприклад, на GitHub Pages, які доступні всім зареєстрованим користувачам. Після цього ми можемо вбудовувати цю сторінку як внутрішній кадр у будь-який документ, налагоджувати з нею спілкування з вікна основного документа, в контексті якого працює букмарклет, і користуватися нею як проксі-сервером між букмарклетом і
localStorage
цієї самої сторінки, тобто майже як маленьким сервером бази даних.

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

Залишається переробити наш букмарклет під новий метод. Розмір, звичайно, зросте з дев'яти рядків до майже сорока (при читається, форматування), але все одно код залишиться досить простим. Робота з букмарклетом для користувача зовсім не зміниться.

javascript:(function(d, mySt, scrT){
scrT = d.documentElement.scrollTop || d.body.scrollTop;
mySt.origin = d.location.protocol + '//user.github.io';
mySt.URL = mySt.origin + '/storage/storage.html';
mySt.iframe = d.querySelector('iframe#myStorageIframe');
if (mySt.iframe) {
mySt.iframe.contentWindow.postMessage(
scrT ?
{'action': 'set', 'key': 'bmk_' + d.location.href 'value': scrT}
:
{'action': 'get', 'key': 'bmk_' + d.location.href},
mySt.origin
);
}
else {
function processMessage(event) {
if (event.origin == mySt.origin) {
scrollTo(0, event.data || 0);
}
}
addEventListener. ('message', processMessage, false);
mySt.iframe = d.body.appendChild(d.createElement('iframe'));
mySt.iframe.style.display = 'none';
mySt.iframe.id = 'myStorageIframe';
mySt.iframe.src = mySt.URL;
mySt.iframe.addEventListener. ('load', function() {
mySt.iframe.contentWindow.postMessage(
scrT ?
{'action': 'set', 'key': 'bmk_' + d.location.href 'value': scrT}
:
{'action': 'get', 'key': 'bmk_' + d.location.href},
mySt.origin
);
});
}
})(document, {})

Головний принцип залишається тим же: букмарклет аналізує стан прокрутки у початковому її положенні витягує закладку і переходить по ній, в проміжному положенні прокрутки зберігає стан.

Зміни полягають у наступному.

Спершу букмарклет створює службовий об'єкт
mySt
(myStorage) з трьома властивостями:
origin
— для забезпечення інтерфейсу необхідною інформацією про джерело адресата,
URL
(на підставі попереднього властивості) — для завдання фрейму повної адреси,
iframe
— для посилання на сам кадр. Так як сторінки з протоколом
https:
не дозволять вбудувати в себе сторінку з звичайним протоколом, на першому ж етапі ми визначаємо поточний протокол і в залежності від нього формуємо властивості
origin
та
URL
(у прикладі дан недіючий умовний URL, «user» потрібно буде замінити на реальне ім'я користувача, якщо сторінка буде розміщена саме на цьому сайті).

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

Якщо кадру немає, тоді ми робимо наступні кроки.

1. Запас вішаємо на основний документ обробник відповідей нашого майбутнього віконця-посередника.
2. Вбудовуємо саме віконце і приховуємо його, щоб не заважало.
3. Вішаємо на віконце-посередник одноразовий обробник завантаження, щоб почати спілкування, коли наш проксі буде готовий. У цьому процесорі ми зробимо ті ж дії, що і раніше описаному випадку, при виявленні готової сторінки-посередника.

Код ні в якому разі не претендує на эталонность, бібліотечну універсальність і чистоту, адже я не професійний програміст. Так що будь-які поправки і застереження будуть сприйняті з вдячністю.

Поки ж робота букмарклета успішно перевірена на останніх версіях Firefox (Nightly) Google Chrome (Canary), Internet Explorer (11).

Джерело: Хабрахабр

0 коментарів

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