Let's say I make a call to local storage like so:
window.localStorage.setItem("key", bigJsonObject);
And immediately afterwards, the user closes their web browser. What will be the result of
window.localStorage.getItem("key")
Will the bigJsonObject be partially written? Or will the whole write fail? Is their any way to guarantee that there will be no partial writes?
Refer to §4.1 of the "web storage" specification:
The setItem() and removeItem() methods must be atomic with respect to failure. In the case of failure, the method does nothing. That is, changes to the data storage area must either be successful, or the data storage area must not be changed at all.
However, there have (historically) been browser bugs in this regard, e.g. some time before Chrome 21 until some time before Chrome 29.
Related
tl;dr I noticed inconsistent behaviour between browsers when writing to localStorage at the exact same time.
Requirement: Even when multiple tabs are open, a specific action (POST-request to refresh OAuth session) should be executed only once. Which tab executes the action does not matter. The point in time to do the refresh derives from the expiration time of the session and is the exact same in all tabs.
Approch: All tabs generate a random number, store it and write to localStorage. They then read the localStorage and if both are the same, then the tab is allowed to execute the action.
let tab = Math.random();
localStorage.setItem('tab',tab);
if(JSON.parse(localStorage.getItem('tab')) === tab) {
console.log('aquired lock');
} else {
console.log('did not aquire lock');
}
JSFiddle - In order to test the behaviour you need to open the fiddle in two tabs and then press Run in both. The timeout is calculated to execute at the next full 10-seconds. (Second 0, 10, 20, 30, 40, 50)
Expectation: Tabs A and B set localStorage['tab'] to a random value, on retriving the value only one tab retrieves the same value as it randomly generated and therefore is allowed to execute the action.
Result: Both A and B still retrieve their own generated value.
I added some timeout the let the memory-dust settle:
let tab = Math.random();
localStorage.setItem('tab',tab);
setTimeout(function(){
if(JSON.parse(localStorage.getItem('tab')) === tab) {
console.log('aquired lock');
} else {
console.log('did not aquire lock');
}
}, 1000);
JSFiddle
Result (Firefox): Tab A retrieves value tab B generated, and vice versa. So no tab is allowed to execute the action.
This is where I got a bit spooked. I checked for console-timestamps, which where exactly the same, and the localStorage in the dev-tools, which showed different values in the different tabs. (Even reloading the tab did show different values in different tabs.)
If writing the value later on (e.g. via the console) all tabs update the value accordingly.
Result (Chrome, Edge): Only one tab logs aquired lock as expected.
Is there any explaination why Firefox can have different values in localStorage per tab?
I already solved the problem by subscribing to the StorageEvent. The tab with the smallest random-number gets to executed the action.
Used browsers:
Firefox 68.0 and 60.8.0esr (both 64bit)
Chrome 75.0.3770.142 (64bit)
Edge 44.18362.1.0
This is a fairly standard situation when dealing with shared memory. Each thread accessing shared memory is allowed to keep its own copy (a "cache") for performance reasons until/unless some synchronization occurs, at which point the local copy must be reconciled with the shared copy.
The old storage specification talked about acquiring a storage mutex on every storage operation:
Whenever the properties of a localStorage attribute's Storage object are to be examined, returned, set, or deleted, whether as part of a direct property access, when checking for the presence of a property, during property enumeration, when determining the number of properties present, or as part of the execution of any of the methods or attributes defined on the Storage interface, the user agent must first obtain the storage mutex.
But that specification has been subsumed into the WHAT-WG "HTML" specification (which is about a lot more than HTML) in §11 ("Web Storage") and the requirement that every operation must acquire a storage mutex has been dropped. (I don't know why, but I would guess for performance reasons.) The current specification says:
Warning: The localStorage attribute provides access to shared state. This specification does not define the interaction with other browsing contexts in a multiprocess user agent, and authors are encouraged to assume that there is no locking mechanism.
The specification also doesn't discuss synchronization of storage across browsing contexts. That means implementations are free to optimize.
Looking into it with a modified version of your script, it looks like Firefox optimizes by having a local copy of local storage for each browsing context (tab) which it appears to update based on the storage event from other contexts (tabs). But if both tabs set the value (generating a storage event for the other tab) before the storage event from the other tab is processed, they both get and process the storage event from the other, updating with that value (the other tab's value), causing the behavior you describe.
Side note: The operation writing to persistent storage (what a third tab would see if you opened it after doing all this) also appears to be asynchronous, and the two tabs are in a race to see which one writes last (a race that is not always won by the last one writing to its local copy!).
This is effectively a large-scale version of what happens with shared memory between threads when there's only loose synchronization between the threads and no locking semantics, which the spec no longer requires.
Chrome would appear to be doing locking or similar.
I'm using the navigator.storage.persist() API on Chrome, and have managed to get it set to true. But I would like to (at least for testing) be able to clear the setting putting back to false.
The API definition does not include a method or flag to clear as far as I can tell. See https://developer.mozilla.org/en-US/docs/Web/API/StorageManager and the living standard: https://storage.spec.whatwg.org/#storagemanager
However, for my purposes it would also be acceptable if there was a way from 'Site settings', the clear cache options, or even a custom page like the chrome://appcache-internals/ page for appcache.
If not, where does the flag get stored? i.e. what would I need to delete in the file system to reset things?
I has not been able to find a way to clear the flag for a specific website.
But for testing purposes (as you requested), the flag returned by the StorageManager.persisted() can be cleared by:
navigate to chrome://settings/?search=cooki
click on Clear browsing data
select Cookies and other site data in the popup and click Clear
data button
After performing the above steps, StorageManager.persisted() starts to return false.
To display an item (whose key is "myinfo"), previously stored locally in the browser, I use something like:
$('div:first').html( localStorage.getItem('myinfo') );
It works well until I open a PDF generated on this page using wkhtmltopdf. In that case, my DIV's content is empty, showing that the localStorage function returned null.
Now that would suggest to me that the webkit engine underneath wkhtmltopdf does not implement localStorage.
Is that the correct reason for this behaviour?
How else can I store and retrieve stuff locally using javascript and wkhtmltopdf?
Now, I've thought through, and can answer the first part of my question:
Is that the correct reason for this behaviour?
No, it isn't.
The reason my approach won't work is:
I saved some data to local storage on "page1.html" in Browser1. Now going to "page2.html" on the same browser, I called wkhtmltopdf (via an AJAX request to the server). Since wkhtmltopdf is essentially another browser (let's call it "Browser2"), there's no way it's going to be able to access data that was previously saved by "Browser1".
Therefore, it's looking increasing to me like: there's no way to programmatically persist data between my Browser1 and wkhtmltopdf without first saving that data on the server. If I am wrong, then my second question:
How else can I store and retrieve stuff locally using javascript and
wkhtmltopdf?
is still waiting for an answer!
I have a AugularJS controller that have something like the following when initialized
$scope.urlChanged = false;
and the url is like /firstpage/test
There is a button in the page and when user clicks the button, the following is executed
$scope.urlChanged = true;
window.location = '/secondpage/test';
The page goes to /secondpage/test as expected. When clicking the browser back button, the page goes back to /firstpage/test. But the $scope.urlChanged is false, not the final value true. Is this expected in Angular? How do I make $scope.urlChanged = true when going back?
Scope variables are not saved when you navigate. In fact not even services will retain their values/state. When you navigate with browser back you actually request a whole new angular app.
This is not angular's fault. That is how the browser is expected to handle a new request. The way to persist data in this case is saving any data in something that will persists between requests.
As i see it you have three(ish) options:
Save state in cookies: Well supported by almost all browsers but take caution to handle them as clientside cookies or you won't be able to save data on a page you did not submit (excatly your problem with navigate back in browser).
Save server-side. This has the same problems as the server side cookies. You need to post data to the server for it to persist - which you could do with ajax calls and 'auto-save' with a timeout function. You also need a way of tracking who you are so you can get the correct data from the server - which is often done with cookies, but can be done with querystring parameters but also with (basic) authentication.
LocalStorage. This is my favorite option and is pretty well supported, if you don't need to support legacy IE browsers. There are good frameworks designed for angular that makes it easy to use - and some even have fallback to cookies if not supported.
Check out this link for LocalStorage support:
https://github.com/grevory/angular-local-storage
On change of view,new controller comes into picture and the previous view's controller's instance gets finished. Also , as every controller has its private scope which gets destroyed once view is changed to avoid confusion.
In my web app, when a user logs in, I add his Id to a vector of valid Ids in the servlet, when he logs out, I remove his Id from the vector, so I can see how many current users are active, if a user forgets to log out, my servelt generated html has :
<meta http-equiv="Refresh" content="30; url=My_Servlet?User_Action=logout&User_Id=1111">
in the tag to automatically log him out.
But I've noticed many users are there for ever, never logged out. I found out why, by closing their browsers, they never manually or automatically logged out, so their user Ids will never be removed from the valid user Ids vector.
So, my question is : how do I detect users closing their browsers, so my servlet can remove their Ids from the vector ?
I see some light at the end of the tunnel, but there is still a problem, my program has something like this :
Active User List :
User_1 : Machine_1 [ IP_1 address ]
User_2 : Machine_2 [ IP_2 address ]
User_3 : Machine_3 [ IP_3 address ]
...
How do I know, from the session listener, which user's session has ended and therefore remove him from my list?
I was hoping when the session ends, the HttpServlet's destroy() method would be called and I can remove the user Id in there, but it never gets called when user closes his browser, why? And is there any other method in the HttpServlet that gets called when a session closes?
There is no way to know on the server-side (unless you are using some JavaScript to send a message to the server) that the browser has closed. How could there be? Think of how HTTP works - everything is request and response.
However, the application server will track when Sessions are active and will even tell you when a Session has been destroyed (such as due to time-out). Take a look at this page to see how to configure a HttpSessionListener to receive these events. Then you can simply keep track of the number of active sessions.
The number of active sessions will lag behind the actual number of current users, since some period of (configurable) time has to elapse before a session is timed out; however, this should be somewhat close (you can lower the session-timeout to increase the accuracy) and it is a lot cleaner and easier than 1) tracking Sessions yourself or 2) sending some asynchronous JavaScript to the server when a browser is closed (which is not guaranteed to be sent).
I suggest you remove the ID when the Servlet engine destroys the session. Register a HttpSessionListener that removes the user's ID when sessionDestroyed() is called.
Diodeus's idea will only help you detect that the session is over more immediately.
in JavaScript you can use the onbeforeclose event to pass a call back to the server when the user closes the browser.
I typically use a synchronous Ajax call to do this.
I had to do that recently, and after some searches, I found some solutions on the Net... all of them non working universally!
onbeforeclose and onclose events are used for this task. But there are two catches: they are fired when the user reload the page or even just change the current page. There are tricks to see if the event is actually a window/page/tab closing (looking at some Dom properties going haywire on closing event), but:
They are browser dependent
The tricks are undocumented, thus brittle
And actually they vary along the browser version/update...
And worst of all, these events are now ignored by most modern browsers, because they have been abused by rogue ads popping out windows when browser was closing. They are not fired in Safari, Opera, IE7, etc.
As pointed out, most Web applications with login destroy the user session after a while, eg. half an hour. I was asked to logout on browser closing to free faster a precious resource: licenses. Because users often forget to log out...
The solution I gave was to ping with an Ajax request (sending the user ID) the server on regular intervals (say 1 minute). If the server receives no ping for, say, 3 minutes, it disconnect the user.
There is no foolproof way to do what you're trying to do, but both sblundy and Diodeus have plans that will cover most circumstances. There is nothing you can do about someone who turns off Javascript in their browser, or their internet connection goes down, or their power goes out. You should just cull sessions after a certain period of inactivity (which I think is what sblundy's suggestion of listening for session destruction will do).