localstorage

This commit is contained in:
Ilya Kantor 2019-03-09 10:33:47 +03:00
parent 8d4dbf0b7e
commit b2646fb4ff
16 changed files with 404 additions and 521 deletions

View file

@ -0,0 +1,10 @@
<!doctype html>
<textarea style="width:200px; height: 60px;" id="area" placeholder="Write here"></textarea>
<br>
<button onclick="localStorage.removeItem('area');area.value=''">Clear</button>
<script>
area.value = localStorage.getItem('area');
area.oninput = () => {
localStorage.setItem('area', area.value)
};
</script>

View file

@ -0,0 +1,2 @@
<!doctype html>
<textarea style="width:200px; height: 60px;" id="area"></textarea>

View file

@ -0,0 +1,10 @@
# Autosave a form field
Create a `textarea` field that "autosaves" its value on every change.
So, if the user occasionally closes the page, and opens it again, he'll find his unfinished input at place.
Like this:
[iframe src="solution" height=120]

View file

@ -1,381 +1,247 @@
# Cookies, document.cookie
# LocalStorage, sessionStorage
Cookies allow to store small pieces of data directly in the browser. They are not part of Javascript, but rather part of HTTP, defined by [RFC 6265](https://tools.ietf.org/html/rfc6265) specification.
Web storage objects `localStorage` and `sessionStorage` allow to save key/value pairs in the browser.
We can use Javascript or server-side headers to get/set cookies.
What's interesting about them is that the data survives a page refresh (for `sessionStorage`) and even a full browser restart (for `localStorage`). We'll see that very soon.
Then the browser sends them in `Cookie` header of every request to the domain that set the cookie, but not to other domains.
We already have cookies. Why additional objects?
So, cookies are mainly used for authorization and user-tracking. We set a cookie, and then get it at new visits of the same user, so we know who he is.
- Unlike cookies, web storage objects are not sent to server with each request. Because of that, we can store much more. Most browsers allow at least 2 megabytes of data (or more) and have settings to configure that.
- The server can't manipulate storage objects via HTTP headers, everything's done in JavaScript.
- The storage is bound to the origin (domain/protocol/port triplet). That is, different protocols or subdomains infer different storage objects, they can't access data from each other.
The browser provides a special accessor `document.cookie` for that. Managing it manually is quite a tedious task, so we'll compose a few several functions for that.
Both storage objects provide same methods and properties:
## Reading from document.cookie
- `setItem(key, value)` -- store key/value pair.
- `getItem(key)` -- get the value by key.
- `removeItem(key)` -- remove the key with its value.
- `clear()` -- delete everything.
- `key(index)` -- get the key on a given position.
- `length` -- the number of stored items.
```online
Do you have any cookies on that site? Let's see:
```
Let's see how it works.
```offline
Assuming you're on a website, it's possible to see the cookies, like this:
```
## localStorage demo
The main features of `localStorage` are:
- Shared between all tabs and windows from the same origin.
- The data does not expire. It remains after the browser restart and even OS reboot.
For instance, if you run this code...
```js run
alert( document.cookie ); // cookie1=value1; cookie2=value2;...
localStorage.setItem('test', 1);
```
The string consist of `name=value` pairs, delimited by `; `. So, to find a particular cookie, we can split `document.cookie` by `; `, and then find the right key. We can use either a regular expression or arrays for that.
## getCookie(name)
The shortest way to access cookie is to use a [regular expression](info:regular-expressions).
The function `getCookie(name)` returns the cookie with the given `name`:
```js
// returns the cookie with the given name,
// or undefined if not found
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
```
Here the regexp is generated dynamically, to match `; name=<value>`.
Please note that a cookie value can be an arbitrary string. If it contains characters that break the formatting, for instance spaces or `;`, such characters are encoded.
To decode them, we need to use a built-in `decodeURIComponent` function, the function does it also.
## Writing to document.cookie
The `document.cookie` is writable. But it's not a data property, but rather an accessor.
**A write operation to `document.cookie` passes through the browser that updates cookies mentioned in it, but doesn't touch other cookies.**
For instance, this call sets a cookie with the name `user` and value `John`:
...And close/open the browser or just open the same page in a different window, then you can get it like this:
```js run
document.cookie = "user=John";
alert(document.cookie);
alert( localStorage.getItem('test') ); // 1
```
If you run it, then probably you'll see multiple cookies. Only the cookie named `user` was altered.
We only have to be on the same domain/port/protocol, the url path can be different.
Cookies have several options, many of them are important and should be set.
The `localStorage` is shared, so if we set the data in one window, the change becomes visible in the other one.
The options are listed after `key=value`, delimited by `;`, for instance:
## Object-like access
We can also use a plain object way of getting/setting keys, like this:
```js run
document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
// set key
localStorage.test = 2;
// get key
alert( localStorage.test ); // 2
// remove key
delete localStorage.test;
```
## path
- **`path=/mypath`**
The url path prefix, where the cookie is accessible. By default, it's the current path.
Usually, we should set `path=/` to make the cookie accessible from all website pages.
Please note: the path must be absolute.
## domain
- **`domain=site.com`**
Domain where the cookie is accessible.
By default, cookie is accessible only at the domain that set it. So, if we set a cookie at `site.com`, we won't get it at `forum.site.com`.
```js
// at site.com
document.cookie = "user=John"
// at forum.site.com
alert(document.cookie); // no user
```
If we'd like subdomains to access the cookie, we should set `domain=site.com` explicitly. For historical reasons, `domain=.site.com` (a dot at the start) also works this way:
```js
// at site.com
document.cookie = "user=John; domain=site.com"
// at forum.site.com
alert(document.cookie); // with user
```
## expires, max-age
By default, a cookie disappears when the browser is closed. They are called "session cookies"
To change it, we must set either `expires` or `max-age` option.
- **`expires=Tue, 19 Jan 2038 03:14:07 GMT`**
Cookie expiration date (when it dies automatically).
The date must be exactly in this format, in GMT timezone. We can use `date.toUTCString` to get it. For instance, we can set the cookie to expire in 1 day:
```js
// +1 day from now
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;
```
If the date is in the past, the cookie will be deleted from the browser.
- **`max-age=3600`**
An alternative to `expires`, specifies the cookie expiration in seconds.
Can be either a number of seconds from the current moment, or `0` for immediate expiration (to remove the cookie):
```js
// cookie will die +1 hour from now
document.cookie = "user=John; max-age=3600";
// delete cookie (let it expiree right now)
document.cookie = "user=John; max-age=0";
```
## secure
- **`secure`**
The cookie should be transferred only over HTTPS.
That's important, by default if we set a cookie at `http://site.com`, then it also appears at `https://site.com` and vise versa.
With this option, if a cookie is set at `https://site.com`, the browser does not set it to `http://site.com`. So if a cookie has sensitive content, like authorization, then it's more secure.
```js
// set the cookie secure (only sent with HTTPS requests)
document.cookie = "user=John; secure";
```
## samesite
That's another security option, to protect from so-called XSRF (cross-site request forgery) attacks.
To understand when it's useful, let's introduce the following attack scenario.
### XSRF attack
Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` on every request, so that it recognizes you and performs all sensitive financial operations.
Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that has a `<form action="https://bank.com/pay">` with parameters containing hacker's account and JavaScript code that sends it automatically.
The form is submitted to the bank site, and your cookie is also sent, just because it's sent every time you visit `bank.com`. So that the bank actually performs the payment.
![](cookie-xsrf.png)
That's called a cross-site request forgery (or XSRF) attack.
Real banks are protected from it of course. All forms generated by `bank.com` have a special field, so called "xsrf protection token", that the evil page can't generate.
### Enter cookie samesite option
Now, cookie samesite option provides another way to protect from such attacks, that does not require "xsrf protection tokens".
It has two possible values:
- **`samesite=strict`, same as `samesite` without value**
A cookie with `samesite=strict` is never sent if the user comes from outside the site.
In other words, whether a user follows a link from the mail or submits a form from `evil.com`, the cookie is not sent. Then `bank.com` will not recognize the user, and will not proceed with the payment.
The protection is indeed reliable. Only operations originating from `bank.com` will send cookies. Although, it may be a little too strict.
When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are nots sent in that case.
So there's another, more relaxed value.
- **`samesite=lax`**
Lax mode, just like `strict`, forbids the browser to send cookies when coming from outside the site, but adds an exception.
A `samesite=lax` cookie is sent if both of these conditions are true:
1. The HTTP method is "safe" (e.g. GET, but not POST).
The full list safe of HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231). Basically, these are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method.
2. The operation performs top-level navigation (changes URL in the browser address bar).
That's usually true, but if the navigation is performed in an `<iframe>`, then it's not top-level. Also, AJAX requests do not get in.
So, what `samesite=lax` does is basically allows a most common "open URL" operation to bring cookies. Something more complicated, like AJAX request from another site or a form submittion, passes through, but loses cookies by the way.
If that's fine for you, then adding `samesite=lax` will probably not break the user experience and add protection.
Overall, `samesite` is great, but it has an important drawback:
- `samesite` is not supported, ignored by old browsers (like year 2017).
So if we solely rely on `samesite` to provide protection, then old browsers will be totally vulnerable.
But we surely can use `samesite` together with other protection measures, like xsrf tokens, to add an additional layer of protection.
## setCookie(name, value, options)
If we gather all options together, here's a small function that sets the cookie `name` to the given `value` with reasonable default options:
Если собрать все настройки воедино, вот такая функция ставит куки:
```js
function setCookie(name, value, options = {}) {
options = {
path: '/',
// add other defaults here if necessary
...options
};
if (options.expires.toUTCString) {
options.expires = options.expires.toUTCString();
}
options.value = encodeURIComponent(options.value);
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let optionKey in options) {
updatedCookie += "; " + optionKey;
let optionValue = options[optionKey];
if (optionValue !== true) {
updatedCookie += "=" + optionValue;
}
}
document.cookie = updatedCookie;
}
```
Аргументы:
name
: название cookie
value
: значение cookie (строка)
options
: Объект с дополнительными свойствами для установки cookie:
expires
: Время истечения cookie. Интерпретируется по-разному, в зависимости от типа:
- Число -- количество секунд до истечения. Например, `expires: 3600` -- кука на час.
- Объект типа [Date](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date) -- дата истечения.
- Если expires в прошлом, то cookie будет удалено.
- Если expires отсутствует или `0`, то cookie будет установлено как сессионное и исчезнет при закрытии браузера.
path
: Путь для cookie.
domain
: Домен для cookie.
secure
: Если `true`, то пересылать cookie только по защищенному соединению.
## Функция deleteCookie(name)
Здесь всё просто -- удаляем вызовом `setCookie` с датой в прошлом.
```js
function deleteCookie(name) {
setCookie(name, "", {
expires: -1
})
}
```
## Сторонние cookie
При работе с cookie есть важная тонкость, которая касается внешних ресурсов.
Теоретически, любой ресурс, который загружает браузер, может поставить cookie.
Например:
- Если на странице есть `<img src="http://mail.ru/counter.gif">`, то вместе с картинкой в ответ сервер может прислать заголовки, устанавливающие cookie.
- Если на странице есть `<iframe src="http://facebook.com/button.php">`, то во-первых сервер может вместе с `button.php` прислать cookie, а во-вторых JS-код внутри ифрейма может записать в `document.cookie`
При этом cookie будут принадлежать тому домену, который их поставил. То есть, на `mail.ru` для первого случая, и на `facebook.com` во втором.
**Такие cookie, которые не принадлежат основной странице, называются "сторонними" (3rd party) cookies. Не все браузеры их разрешают.**
Как правило, в настройках браузера можно поставить "Блокировать данные и файлы cookie сторонних сайтов" (Chrome).
**В Safari такая настройка включена по умолчанию и выглядит так:**
![](safari-nocookie.png)
### Тс-с-с. Большой брат смотрит за тобой.
Цель этого запрета -- защитить посетителей от слежки со стороны рекламодателей, которые вместе с картинкой-баннером присылают и куки, таким образом помечая посетителей.
Например, на многих сайтах стоят баннеры и другая реклама Google Ads. При помощи таких cookie компания Google будет знать, какие именно сайты вы посещаете, сколько времени вы на них проводите и многое другое.
Как? Да очень просто -- на каждом сайте загружается, к примеру, картинка с рекламой. При этом баннер берётся с домена, принадлежащего Google. Вместе с баннером Google ставит cookie со специальным уникальным идентификатором.
Далее, при следующем запросе на баннер, браузер пошлёт стандартные заголовки, которые включают в себя:
- Cookie с домена баннера, то есть уникальный идентификатор, который был поставлен ранее.
- Стандартный заголовок Referrer (его не будет при HTTPS!), который говорит, с какого сайта сделан запрос. Да, впрочем, Google и так знает, с какого сайта запрос, ведь идентификатор сайта есть в URL.
Так что Google может хранить в своей базе, какие именно сайты из тех, на которых есть баннер Google, вы посещали, когда вы на них были, и т.п. Этот идентификатор легко привязывается к остальной информации от других сервисов, и таким образом картина слежки получается довольно-таки глобальной.
Здесь я не утверждаю, что в конкретной компании Google всё именно так... Но во-первых, сделать так легко, во-вторых идентификаторы действительно ставятся, а в-третьих, такие знания о человеке позволяют решать, какую именно рекламу и когда ему показать. А это основная доля доходов Google, благодаря которой корпорация существует.
Возможно, компания Apple, которая выпустила Safari, поставила такой флаг по умолчанию именно для уменьшения влияния Google?
### А если очень надо?
Итак, Safari запрещает сторонние cookie по умолчанию. Другие браузеры предоставляют такую возможность, если посетитель захочет.
**А что, если ну очень надо поставить стороннюю cookie, и чтобы это было надёжно?**
Такая задача действительно возникает, например, в системе кросс-доменной авторизации, когда есть несколько доменов 2-го уровня, и хочется, чтобы посетитель, который входит в один сайт, автоматически распознавался во всей сетке. При этом cookie для авторизации ставятся на главный домен -- "мастер", а остальные сайты запрашивают их при помощи специального скрипта (и, как правило, копируют к себе для оптимизации, но здесь это не суть).
Ещё пример -- когда есть внешний виджет, например, `iframe` с информационным сервисом, который можно подключать на разные сайты. И этот `iframe` должен знать что-то о посетителе, опять же, авторизация или какие-то настройки, которые хорошо бы хранить в cookie.
Есть несколько способов поставить 3rd-party cookie для Safari.
Использовать ифрейм.
: Ифрейм является полноценным окном браузера. В нём должна быть доступна вся функциональность, в том числе cookie. Как браузер решает, что ифрейм "сторонний" и нужно запретить для него и его скриптов установку cookie? Критерий таков: "в ифрейме нет навигации". Если навигация есть, то ифрейм признаётся полноценным окном.
Например, в сторонний `iframe` можно сделать POST. И тогда, в ответ на POST, сервер может поставить cookie. Или прислать документ, который это делает. Ифрейм, в который прошёл POST, считается родным и надёжным.
Popup-окно
: Другой вариант -- использовать popup, то есть при помощи `window.open` открывать именно окно со стороннего домена, и уже там ставить cookie. Это тоже работает.
Редирект
: Ещё одно альтернативное решение, которое подходит не везде - это сделать интеграцию со сторонним доменом, такую что на него можно сделать редирект, он ставит cookie и делает редирект обратно.
## Дополнительно
- На Cookie наложены ограничения:
- Имя и значение (после `encodeURIComponent`) вместе не должны превышать 4кб.
- Общее количество cookie на домен ограничено 30-50, в зависимости от браузера.
- Разные домены 2-го уровня полностью изолированы. Но в пределах доменов 3-го уровня куки можно ставить свободно с указанием `domain`.
- Сервер может поставить cookie с дополнительным флагом `HttpOnly`. Cookie с таким параметром передаётся только в заголовках, оно никак не доступно из JavaScript.
- Иногда посетители отключают cookie. Отловить это можно проверкой свойства [navigator.cookieEnabled](https://developer.mozilla.org/en-US/docs/DOM/window.navigator.cookieEnabled)
That's allowed for historical reasons, and mostly works, but generally not recommended for two reasons:
1. If the key is user-generated, it can be anything, like `length` or `toString`, or another built-in method of `localStorage`. In that case `getItem/setItem` work fine, while object-like access fails:
```js run
if (!navigator.cookieEnabled) {
alert( 'Включите cookie для комфортной работы с этим сайтом' );
}
let key = 'length';
localStorage[key] = 5; // Error, can't assign length
```
...Конечно, предполагается, что включён JavaScript. Впрочем, посетитель без JS и cookie с большой вероятностью не человек, а бот.
2. There's a `storage` event, it triggers when we modify the data. That event does not happen for object-like access. We'll see that later in this chapter.
## Итого
## Looping over keys
Файл с функциями для работы с cookie: [cookie.js](cookie.js).
Methods provide get/set/remove functionality. But how to get all the keys?
Unfortunately, storage objects are not iterable.
One way is to use "array-like" iteration:
```js run
for(let i=0; i<localStorage.length; i++) {
let key = localStorage.key(i);
alert(`${key}: ${localStorage.getItem(key)}`);
}
```
Another way is to use object-specific `for key in localStorage` loop.
That iterates over keys, but also outputs few built-in fields that we don't need:
```js run
// bad try
for(let key in localStorage) {
alert(key); // shows getItem, setItem and other built-in stuff
}
```
...So we need either to filter fields from the prototype with `hasOwnProperty` check:
```js run
for(let key in localStorage) {
if (!localStorage.hasOwnProperty(key)) {
continue; // skip keys like "setItem", "getItem" etc
}
alert(`${key}: ${localStorage.getItem(key)}`);
}
```
...Or just get the "own" keys with `Object.keys` and then loop over them if needed:
```js run
let keys = Object.keys(localStorage);
for(let key of keys) {
alert(`${key}: ${localStorage.getItem(key)}`);
}
```
The latter works, because `Object.keys` only returns the keys that belong to the object, ignoring the prototype.
## Strings only
Please note that both key and value must be strings.
If we any other type, like a number, or an object, it gets converted to string automatically:
```js run
sessionStorage.user = {name: "John"};
alert(sessionStorage.user); // [object Object]
```
We can use `JSON` to store objects though:
```js run
sessionStorage.user = JSON.stringify({name: "John"});
// sometime later
let user = JSON.parse( sessionStorage.user );
alert( user.name ); // John
```
Also it is possible to stringify the whole storage object, e.g. for debugging purposes:
```js run
// added formatting options to JSON.stringify to make the object look nicer
alert( JSON.stringify(localStorage, null, 2) );
```
## sessionStorage
The `sessionStorage` object is used much less often than `localStorage`.
Properties and methods are the same, but it's much more limited:
- The `sessionStorage` exists only within the current browser tab.
- Another tab with the same page will have a different storage.
- But it is shared between iframes in the tab (assuming they come from the same origin).
- The data survives page refresh, but not closing/opening the tab.
Let's see that in action.
Run this code...
```js run
sessionStorage.setItem('test', 1);
```
...Then refresh the page. Now you can still get the data:
```js run
alert( sessionStorage.getItem('test') ); // after refresh: 1
```
...But if you open the same page in another tab, and try again there, the code above returns `null`, meaning "nothing found".
That's exactly because `sessionStorage` is bound not only to the origin, but also to the browser tab. For that reason, `sessionStorage` is used sparingly.
## Storage event
When the data gets updated in `localStorage` or `sessionStorage`, [storage](https://www.w3.org/TR/webstorage/#the-storage-event) event triggers, with properties:
- `key` the key that was changed (null if `.clear()` is called).
- `oldValue` the old value (`null` if the key is newly added).
- `newValue` the new value (`null` if the key is removed).
- `url` the url of the document where the update happened.
- `storageArea` either `localStorage` or `sessionStorage` object where the update happened.
The important thing is: the event triggers on all `window` objects where the storage is accessible, except the one that caused it.
Let's elaborate.
Imagine, you have two windows with the same site in each. So `localStorage` is shared between them.
```online
You might want to open this page in two browser windows to test the code below.
```
Now if both windows are listening for `window.onstorage`, then each one will react on updates that happened in the other one.
```js run
// triggers on updates made to the same storage from other documents
window.onstorage = event => {
if (event.key != 'now') return;
alert(event.key + ':' + event.newValue + " at " + event.url);
};
localStorage.setItem('now', Date.now());
```
Please note that the event also contains: `event.url` -- the url of the document where the data was updated.
Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `storageArea` references the one that was modified. We may event want to set something back in it, to "respond" to a change.
**That allows different windows from the same origin to exchange messages.**
Modern browsers also support [Broadcast channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API), the special API for same-origin inter-window communication, it's more full featured, but less supported. There are libraries that polyfill that API, based on `localStorage`, that make it available everywhere.
## Summary
Web storage objects `localStorage` and `sessionStorage` allow to store key/value in the browser.
- Both `key` and `value` must be strings.
- The limit is 2mb+, depends on the browser.
- They do not expire.
- The data is bound to the origin (domain/port/protocol).
| `localStorage` | `sessionStorage` |
|----------------|------------------|
| Shared between all tabs and windows with the same origin | Visible within a browser tab, including iframes from the same origin |
| Survives browser restart | Dies on tab close |
API:
- `setItem(key, value)` -- store key/value pair.
- `getItem(key)` -- get the value by key.
- `removeItem(key)` -- remove the key with its value.
- `clear()` -- delete everything.
- `key(index)` -- get the key on a given position.
- `length` -- the number of stored items.
- Use `Object.keys` to get all keys.
- Can use the keys as object properties, in that case `storage` event doesn't trigger.
Storage event:
- Triggers on `setItem`, `removeItem`, `clear` calls.
- Contains all the data about the operation, the document `url` and the storage object.
- Triggers on all `window` objects that have access to the storage except the one that generated it (within a tab for `sessionStorage`, globally for `localStorage`).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,45 +0,0 @@
// возвращает cookie с именем name, если есть, если нет, то undefined
function getCookie(name) {
var matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
// устанавливает cookie с именем name и значением value
// options - объект с свойствами cookie (expires, path, domain, secure)
function setCookie(name, value, options) {
options = options || {};
var expires = options.expires;
if (typeof expires == "number" && expires) {
var d = new Date();
d.setTime(d.getTime() + expires * 1000);
expires = options.expires = d;
}
if (expires && expires.toUTCString) {
options.expires = expires.toUTCString();
}
value = encodeURIComponent(value);
var updatedCookie = name + "=" + value;
for (var propName in options) {
updatedCookie += "; " + propName;
var propValue = options[propName];
if (propValue !== true) {
updatedCookie += "=" + propValue;
}
}
document.cookie = updatedCookie;
}
// удаляет cookie с именем name
function deleteCookie(name) {
setCookie(name, "", {
expires: -1
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

@ -0,0 +1,9 @@
<!doctype html>
<script>
window.addEventListener('storage', event => {
alert("iframe.html: onstorage");
});
</script>
<button onclick="sessionStorage.setItem('now', new Date())">sessionStorage.setItem</button>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!doctype html>
<script>
window.addEventListener('storage', event => {
alert("index.html: onstorage");
});
</script>
<button onclick="sessionStorage.setItem('now', new Date())">sessionStorage.setItem</button>
<iframe src="iframe.html" style="height:100px"></iframe>
</body>
</html>