📜 ⬆️ ⬇️

Reliable localStorage for bookmarklets

Unlike extensions, bookmarklets are good in simplicity and cross-browser compatibility. Of course, they are limited to the context of the window (page content), but often this is enough. And with the advent of the localStorage mechanism, they had an easy way to save and request data on the client side.

For example, I use such a simple bookmarklet to keep intermediate “bookmarks” for places in long articles that cannot be read in one sitting.

 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) 

The same bookmarklet works at the same time to save the reading place and to go to it. If the page is just loaded or is in the initial scrolling state (for example, the Home key was pressed), the bookmarklet assumes that we need to go to the previously saved place, tries to extract it from localStorage and scroll to the page. If the page is already scrolled, the bookmarklet retains the current scrolling state.

If the size and resolution of the monitor, the size of the browser window and the size of its main part (minus the sidebar and toolbars) do not change, the page scale and main structure do not change - the program works like a clock. That is, in most cases, its simplicity can be relied upon.
')
However, sites treat their localStorage . Some do not use it at all. Some use actively, but only change or delete keys that they themselves create (for example, Twitter). But there are absolute monarchs: let's say, Facebook periodically completely clears its localStorage , most likely localStorage.clear() method. Of course, there is nothing to reproach the latter type of sites - they are sovereign owners in this area and do not have to reckon with the fact that someone else may want to take advantage of their property.

How to protect yourself in such cases? Of course, you can go to IndexedDB (if the site doesn’t clear it again) or write an extension (Google Chrome allocates its localStorage to extensions, Firefox doesn’t allow it, but provides its own way of storing data in the general settings database) simple cases to use such complex tools is not very handy.

Unfortunately, it will not be possible to use localStorage belonging to internal frames of the page, if such are found: this will not allow us to make the principle of the same source .

However, there is a way out - clumsy at first glance, but in practice it is quite simple and convenient, if you adjust everything once and stock up on templates for all occasions.

The Window.postMessage technology will help us. We just need to create a tiny web page, like this:

 <!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> 

Then put it on any hosting that supports the http: and https: protocols — for example, on GitHub Pages , which are accessible to all registered users. After that, we can embed this page as an internal frame into any document, establish communication with it from the main document window, in the context of which the bookmarklet works, and use it as a proxy server between the bookmarklet and localStorage this very page, that is, almost like a small server Database.

Unfortunately, it’s impossible to embed a local page for these needs due to security restrictions, but loading such a small page happens completely unnoticed, even if it is not cached.

It remains to remake our bookmarklet for a new method. The size, of course, will grow from nine lines to almost forty (with readable formatting), but still the code will remain fairly simple. Working with a bookmarklet for the user will not change at all.

 javascript:(function(d, mySt, scrT){ scrT = d.documentElement.scrollTop || d.body.scrollTop; mySt.origin = d.location.protocol + '//user.imtqy.com'; 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, {}) 

The main principle remains the same: the bookmarklet analyzes the scrolling state, retrieves the bookmark in its initial position and goes over it, saves the state in the intermediate scrolling position.

The changes are as follows.

First, the bookmarklet creates a mySt (myStorage) service object with three properties: origin - to provide the interface with the necessary information about the source of the addressee, URL (based on the previous property) - to set the full address for the frame, and iframe - to reference the frame itself. Since the pages with the https: protocol do not allow to embed a page with the usual protocol, at the first stage we determine the current protocol and, depending on it, create the origin and URL properties (in the example, an invalid conditional URL is given, the “user” will need to be replaced by real username if the page will be posted on this site).

Then the bookmarklet checks whether our mediation page is already built in (if we have read this article for some time and have saved bookmarks before, without closing the window, just in case or temporarily leaving, it is quite possible that the frame is already embedded and need to repeat the insert). If the page is embedded, we immediately proceed to the communication: either we send the key and the value to save, or we request the value of the previously saved key. Since the communication is asynchronous, we have nothing more to do - even if we requested a value, the response will be picked up by the handler that we hung up earlier when we embedded the frame (see further about it).

If there is no frame, then we take the following steps.

1. For the future, we hang up on the main document the processor of the answers of our future intermediary window.
2. We build the window itself and hide it so as not to interfere.
3. We hang a one-time download handler on the intermediary window to start communication when our proxy is ready. In this handler, we will perform the same actions as in the previously described case, when a ready mediation page is detected.

The code does not in any way claim to the standards, library universality and purity, I'm not a professional programmer. So any corrections and warnings will be greatly appreciated.

In the meantime, the work of the bookmarklet has been successfully tested on the latest versions of Firefox (Nightly), Google Chrome (Canary), Internet Explorer (11).

Source: https://habr.com/ru/post/249949/


All Articles