Are cookie read/write atomic in browser - javascript

I am trying to implement a cross tab mutex for my needs. I found a implementation here. which seems quite promising. Basically, it implements Leslie Lamport's algorithm with needs atomic read/writes for creating a mutex.
However it relies on localStorage providing atomic read/writes. This works well in most browsers except for Chrome.
So my question is, can I use cookie read/write instead? Are cookie reads/writes atomic in all mainstream browsers (IE, Chrome, Safari, Firefox)?

Neither cookies, nor localStorage provide atomic transactions.
I think you might have misunderstood that blog post, it doesn't say that his implementation doesn't work in Chrome, it does not rely on localStorage providing atomic read/writes. He says that normal localStorage access is more volatile in Chrome. I'm assuming this is related to the fact that Chrome uses a separate process for each tab, whereas most other browsers tend to use a single process for all tabs. His code implements a locking system on top of localStorage which should protect against things getting overwritten.
Another solution would be to use IndexedDB. IndexedDB does provide atomic transactions. Being a new standard it is not supported in as many browsers as localStorage, but it does have good support in recent versions of Firefox, Chrome and IE10.

No. Even if the browsers probably implement a read and a write lock on the cookie it won't protect you from changes that happens between a read and a consequent write. This is easy to see by looking at the javascript API for cookies, there is no mutex functionality there...

I ran into this concurrency issue using localStorage today (two years alter..)
Scenario: Multiple tabs of a browser (e.g. Chrome) have identical script code that gets executed, basically at the same time (called by e.g. SignalR). The code reads and writes to localStorage. Since the tabs run in different processes but access the shared local storage collectively, reading and writing leads to undefined results since a locking mechanism is missing here. In my case I wanted to make sure that only one of the tabs actually works with the local storage and not all of them..
I tried the locking mechanism of Benjamin Dumke-von der Ehe metioned in the question above but got undesired results. So I decided to roll my own experimental code:
localStorage lock:
Object.getPrototypeOf(localStorage).lockRndId = new Date().getTime() + '.' + Math.random();
Object.getPrototypeOf(localStorage).lock = function (lockName, maxHold, callback) {
var that = this;
var value = this.getItem(lockName);
var start = new Date().getTime();
var wait = setInterval(function() {
if ((value == null) || (parseInt(value.split('_')[1]) + maxHold < start)) {
that.setItem(lockName, that.lockRndId + '_' + start);
setTimeout(function () {
if (that.getItem(lockName) == (that.lockRndId + '_' + start)) {
clearInterval(wait);
try { callback(); }
catch (e) { throw 'exeption in user callback'; }
finally { localStorage.removeItem(lockName); }
}
}, 100);
}
}, 200);
};
usage:
localStorage.lock(lockName, maxHold, callback);
lockName - a global scope unique name for the lock - string
maxHold - the maximum time to protect the script in milliseconds - integer
callback - the function containing the script that gets protected
example: "only play a sound in one tab"
//var msgSound = new Audio('/sounds/message.mp3');
localStorage.lock('lock1', 5000, function(){
// only one of the tabs / browser processes gets here at a time
console.log('lock aquired:' + new Date().getTime());
// work here with local storage using getItem, setItem
// e.g. only one of the tabs is supposed to play a sound and only if none played it within 3 seconds
var tm = new Date().getTime();
if ((localStorage.lastMsgBeep == null)||(localStorage.lastMsgBeep <tm-3000)) {
localStorage.lastMsgBeep = tm;
//msgSound.play();
console.log('beep');
}
});

Related

Feature detection for Firefox 57+ shared worker bug

As discussed in this question and this bug, shared workers have been badly broken in Firefox since version 57. Basically, the "shared" part is missing. The way it's supposed to work is that creating a shared worker with the same worker script in two tabs will only create one instance of the worker, but in Firefox it creates two instances.
Anyone using shared workers is familiar with this type of problem, because shared workers are not supported in IE, Edge, or Safari. So you have to make due with web workers. That's fine, I could do the same thing with buggy Firefox versions, except... how do I do feature detection for this, so I can send the non-shared version of my worker in this case?
I figured I could use a worker like this and see if the counter resets when it shouldn't:
var count = 0; onconnect = function (e) { var port = e.ports[0]; port.postMessage(count); count += 1; }
My first idea was to just create two shared workers in the same page and see if the counter is 0 for both of them. But it's not. This:
function createWorker(url, workerNum) {
var worker = new SharedWorker(url);
worker.port.onmessage = function (e) {
console.log("Worker " + workerNum + ": " + e.data);
};
}
for (var i = 0; i < 2; i++) {
createWorker("worker.js", i);
}
produces the correct output:
Worker 0: 0
Worker 1: 1
Then I thought maybe I could run that script in two iframes within the same page, but no dice, this is also correct:
Worker 0: 0
Worker 1: 1
Worker 0: 2
Worker 1: 3
If I open the page in two tabs, I can see the problem - the counter resets to 0 in each tab. But if it requires users to open a new tab, it's not very useful feature detection. Even worse, the bug seems to be intermittent. Sometimes the counter does correctly persist across tabs! So this approach is not going to work...
This seems like a very nasty bug to do feature detection for. Does anyone have any ideas? Or am I stuck parsing user agent strings?

SWFObject event undefined in Chrome works in IE

I want to get the currentFrame of my Flash movie when it is loaded. I followed the the tutorial found here http://learnswfobject.com/advanced-topics/executing-javascript-when-the-swf-has-finished-loading/index.html and SWFOBJECT CurrentFrame Javascript. I am using SWFObject 2.3 beta. This works perfectly fine on Internet Explorer however it does not work on Google Chrome.
In Chrome I get the error
Uncaught TypeError: e.ref.currentFrame is not a function
Checking e it returns [object Object]
Checking e.ref returns [object HTMLObjectElement]
Checking e.ref.totalFrames returns undefined
var flashvars = {};
var params = {};
var attributes = {};
function mycall(e){
setInterval(function(){console.log("Frame: " + e.ref.currentFrame)},1000);
}
swfobject.embedSWF("notmyswf.swf", "course", "100%", "100%", "6.0.0", false, flashvars, params, attributes, mycall);
Why is this not working on Chrome but works well with IE? Is the event e not detected? Is there a work-around on how to make this work on Chrome?
The purpose of this is for me to create a check if the user is really using the course he has opened and not just leaving it idle. I have already added a code that will check idle but it is not enough. Most learners, have figured out a way to just open a course, leave it there to accumulate hours of training. Some even have a program running in their computers that will just move the mouse 1-pixel every few seconds so that the computer does not go to idle. If I can check the current frame of the Flash movie, I can create a function that will calculate the current page the user is viewing every 15 minutes. If he is stuck in the same page I can then show a prompt that the user must click in order to continue viewing the course or it will automatically close.
I suggest dropping the SWF-based currentFrame approach in favor of monitoring your calls to the database using JavaScript. (Based on your comments, it sounds like the DB calls are being sent by JS, so this shouldn't be a problem.)
If the course bookmark is auto-saved every 3 minutes (as described in your comments), you can cache the value in your page's JS and do a compare every time the save is performed. If the value hasn't changed in x number of minutes, you can display your timeout warning.
If you're using a SCORM wrapper (or similar), this is really simple, just modify the wrapper to include your timer code. Something like:
//Old code (pseudocode, not tested)
function setBoomark (val){
API.SetValue("cmi.core.lesson_location", val);
}
//New code (pseudocode, not tested)
var current_location = "";
var activityTimer;
function disableCourse(){
//do stuff to disable course because it timed out
}
function setBoomark (val){
API.SetValue("cmi.core.lesson_location", val);
if(val === current_location){
//do nothing, timer keeps ticking
} else {
//reset timer using new bookmark value
if(activityTimer){ clearTimeout(activityTimer); }
activityTimer = setTimeout(disableCourse, 15000);
//Update current_location value
current_location = val;
}
}
This is a rough sketch but hopefully you get the idea.
I feel stupid!
It did not work in Chrome and Firefox because I used the wrong casing for the functions but in IE11 it works no matter the case.
So the correct functions are:
e.ref.CurrentFrame() //I used currentFrame() which still works in IE11
e.ref.TotalFrames() //I used totalFrames() which still works in IE11
e.ref.PercentLoaded() //I used this correctly and was able to get the value

How to prevent Chrome and Firefox from consuming vast amounts of memory over long periods of time?

I have a long-polling application written in JS fetching XML files to update a web page. It fetches every 5 seconds and is about 8KB of data. I have had this web page open for about 1 week straight (although computer goes to sleep in evening).
When first opening Chrome it starts at about 33K of my PC's memory. After I left it open for a week, constantly updating while the PC was awake, it was consuming 384K for just one tab. This is a common method that my application will be run (leaving the web page open for very long periods of time).
I feel like I am hindering Chrome's GC or am not doing some specific memory management (or maybe even a memory leak). I don't really know how a memory leak would be achievable in JS.
My app paradigm is very typical, following this endless sequence:
function getXml(file){
return $.get(file);
}
function parseXml(Xml){
return {
someTag : $(Xml).find('someTag').attr('val'),
someOtherTag: $(Xml).find('someOtherTag').attr('val')
}
}
function polling(modules){
var defer = $.Deferred();
function module1(){
var xmlData = getXml('myFile.xml').done(function(xmlData){
var data = parseXml(xmlData);
modules.module1.update(data);
}).fail(function(){
alert('error getting XML');
}).always(function(){
module2();
});
});
function module2(){
var xmlData = getXml('myFile.xml').done(function(xmlData){
var data = parseXml(xmlData);
modules.module2.update(data);
}).fail(function(){
alert('error getting XML');
}).always(function(){
defer.resolve(modules);
});
});
return defer.promise(modules);
}
$(document).on('ready', function(){
var myModules = {
module1 : new Module(),
module2 : new ModuleOtherModule()
}
// Begin polling
var update = null;
polling(myModules).done(function(modules){
update = setInterval(polling.bind(this, modules), 5000);
});
That's the jist of it... Is there some manual memory management I should be doing for apps built like this? Do I need to better management my variables or memory? Or is this just a typical symptom of having a web browser (crome/ff) open for 1-2 weeks?
Thanks
Your code seems ok but You don't posted what happens on method "udpate" inside "modules". Why I said that? Because could be that method who is leaking your app.
I recomender you two things:
Deep into update method and look how are you updating the DOM (be careful if there are a lot of nodes). Check if this content that you are updating could have associated events because if you assign a event listener to a node and then you remove the dom node, your listener still kepts in memory (until javascript garbage collector trash it)
Read this article. It's the best way to find your memory leak: https://developer.chrome.com/devtools/docs/javascript-memory-profiling

Persistent unique ID for Chrome tabs that lasts between browser sessions

I'm trying to ascertain some way to establish a unique ID for Chrome tabs that meets the following conditions:
Uniquely identifies each tab
Stays the same for a given tab between browser restarts (session-restored tabs)
Stays the same if a tab is closed and then reopened with Undo Closed Tab (Ctrl+Shift+T)
Stays distinct if a tab is duplicated
I've done some rather aggressive research to find a comprehensive solution, but nothing seems to quite do the trick. Here are the methods I have tried, in increasing order of efficacy:
Use Chrome's provided tab.id: does not persist between browser sessions or close/undo-close
Put a GUID in cookies: is not unique per tab, only per domain/URL
Put a GUID in localStorage: persists between browser sessions and close/undo-close, but is not unique per tab, only per domain
Put a GUID in sessionStorage: unique per tab, persists across close/undo-close, unique for duplicated tabs, but is wiped out between browser sessions
Use identifiable webpage document attributes as a unique key: this is the best approach I've found so far. A key can be constructed via a content script from the following values: [location.href, document.referrer, history.length].
Regarding this last approach, the constructed key is unique across all tabs which share a common URL, referrer, and history length. Those values will remain the same for a given tab between browser restarts/session-restores and close/undo-closes. While this key is "pretty" unique, there are cases where it is ambiguous: for example, 3 new tabs opened to http://www.google.com would all have the same key in common (and this kind of thing happens pretty often in practice).
The "put GUID in sessionStorage" method can additionally be used to disambiguate between multiple tabs with the same constructed key for the close/undo-close and duplicated-tab cases during the current browser session. But this does not solve the ambiguity problem between browser restarts.
This last ambiguity can be partially mitigated during session restore by observing which tabs Chrome opens together in which windows, and extrapolating for a given ambiguous key which tab belongs to which window based on the presence of expected 'sibling' tabs (recorded during the previous browser session). As you might imagine, implementing this solution is quite involved and rather dodgy. And it can only disambiguate between same-keyed tabs that Chrome restores into different windows. That leaves same-keyed tabs that restore into the same window as irreconcilably ambiguous.
Is there a better way? A guaranteed unique, browser-generated, per-tab GUID that persists between browser restarts (session restores) and close/undo-close would be ideal but so far I haven't found anything like this.
The question here does most of the discovery work, and the accepted answer basically completes it, but there's a big implementation gap still for people looking to implement something which requires persistent tab IDs. I've attempted to distill this into an actual implementation.
To recap: Tabs can be (almost) uniquely and consistently identified as required by the question by maintaining a register of tabs which stores the following combination of variables in local persistent storage:
Tab.id
Tab.index
A 'fingerprint' of the document open in the tab - [location.href, document.referrer, history.length]
These variables can be tracked and stored in the registry using listeners on a combination of the following events:
onUpdated
onCreated
onMoved
onDetached
onAttached
onRemoved
onReplaced
There are still ways to fool this method, but in practice they are probably pretty rare - mostly edge cases.
Since it looks like I'm not the only one who has needed to solve this problem, I built my implementation as a library with the intention that it could be used in any Chrome extension. It's MIT licensed and available on GitHub for forking and pull requests (in fact, any feedback would be welcome - there are definitely possible improvements).
If I correctly understand your problem, your 5th method should do the trick, but along with these two criteria:
chrome.tabs.windowId (The ID of the window the tab is contained within)
chrome.tabs.index (The zero-based index of the tab within its window)
All these values need to be stored inside your extension. Besides that, you will also have to hook up your extension to chrome.tabs.onUpdated() and updated accordingly, when tabs are being dragged around, moved across owner windows, etc.
Put this as a persistent background script in manifest.json:
"background": {
"scripts": [ "background.js" ],
"persistent": true
},
Here is background.js.
Hopefully the code is self explanatory.
var tabs_hashes = {};
var tabs_hashes_save_queued = false;
function Start(){
chrome.tabs.query({windowType: "normal"}, function(querytabs){
querytabs.forEach(function(tab){
tabs_hashes[tab.id] = GetHash(tab.url);
});
if (localStorage.getItem("tabs_hashes") !== null){
var ref_load = JSON.parse(localStorage["tabs_hashes"]);
var ref_tabId = {};
querytabs.forEach(function(tab){
for (var t = 0; t < ref_load.length; t++){
if (ref_load[t][1] === tabs_hashes[tab.id]){
ref_tabId[ref_load[t][0]] = tab.id;
ref_load.splice(t, 1);
break;
}
}
});
// do what you have to do to convert previous tabId to the new one
// just use ref_tabId[your_previous_tabId] to get the current corresponding new tabId
console.log(ref_tabId);
}
});
}
function SaveHashes(){
if (!tabs_hashes_save_queued && Object.keys(tabs_hashes).length > 0){
tabs_hashes_save_queued = true;
chrome.tabs.query({windowType: "normal"}, function(querytabs){
var data = [];
querytabs.forEach(function(tab){
if (tabs_hashes[tab.id]){
data.push([tab.id, tabs_hashes[tab.id]]);
} else {
data.push([tab.id, GetHash(tab.url)]);
}
});
localStorage["tabs_hashes"] = JSON.stringify(data);
setTimeout(function(){ tabs_hashes_save_queued = false; }, 1000);
});
}
}
function GetHash(s){
var hash = 0;
if (s.length === 0){
return 0;
}
for (var i = 0; i < s.length; i++){
hash = (hash << 5)-hash;
hash = hash+s.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
chrome.tabs.onCreated.addListener(function(tab){
SaveHashes();
});
chrome.tabs.onAttached.addListener(function(tabId){
SaveHashes();
});
chrome.tabs.onRemoved.addListener(function(tabId){
delete tabs_hashes[tabId];
SaveHashes();
});
chrome.tabs.onDetached.addListener(function(tabId){
SaveHashes();
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo){
if (changeInfo.pinned != undefined || changeInfo.url != undefined){
delete tabs_hashes[tabId];
SaveHashes();
}
});
chrome.tabs.onMoved.addListener(function(tabId){
SaveHashes();
});
chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId){
delete tabs_hashes[removedTabId];
SaveHashes();
});
Start();
I use array to save data, because in this way I can preserve tabs order, which is unlikely if data would be saved in the object. When loading data after browser's restart, even if url is not unique, I can trust that it will be under some "close enough" index. I would do it more complex, for example reverse check if tab was not found, but this works ok so far.

using javascript to track another javascript script?

I was just wondering whether there are any way (libraries, frameworks, tutorials) to do javascript tracking with another script? Basically, i want to track as the user work with the site, which function gets executed with what parameters and so on, as detailed as possible.
thanks a lot!
The extent of detail you're expecting will be challenging for any solution to gather and report on without severely slowing down your scripts -- consider that, for every call, at least 1 other call would need to occur to gather this.
You'd be better to pick a few key events (mouse clicks, etc.) and track only a few details (such as time) for them. If you're using ajax, keep JavaScript and the browser oblivious and just track this on server-side.
There's a few options but I'm not sure if there are any "great" ones. I take it Firebug/IE Dev toolbar profiling won't work because you are trying to track remote user's actions.
So, one option (I'm not highly recommending for production purposes), will work in some but not all browsers.
Essentially you overwrite every function, with a wrapper that you then inject your logging.
(I haven't tested this, trying to recall it from memory... hopefully in "pseudo code" you get the idea...)
//e.g. get all functions defined on the global window object
function logAll(){
var funcs = [];
var oldFunc;
for(var i in window){
try {
if(typeof(window[i]) == 'function'){
if(i != 'logAll'){
funcs.push(i);
}
}
} catch(ex){
//handle as desired
}
}
var x;
for(var i in funcs){
x = '_' + new Date().getTime();
window[x] = window[i];//save the old function as new function
//redefine original
window[i] = function(){
//do your logging here...
//then call the real function (and pass all params along)
call(window[x]);
};
}
};

Categories