Yes, it is thread safe. However, your code is not atomic, and that is your problem. I will get to the localStorage security localStorage , but first, how to fix your problem.
Both tabs can pass if checks together and write to an element that overwrites each other. The correct way to solve this problem is to use StorageEvent s.
They allow you to notify other windows when the key has changed in localStorage, effectively solving the problem for you in the in-line message passing the safe path. That's nice to read about them . Here is an example:
// tab 1 localStorage.setItem("Foo","Bar"); // tab 2 window.addEventListener("storage",function(e){ alert("StorageChanged!"); // this will run when the localStorage is changed });
Now, what I promised about thread safety :)
As I like it - let's watch it from two sides - from the specification and use of the implementation.
Specification
Show it is thread safe by specification.
If we check the Web Storage specification , we will see that it is especially the notes :
Due to the use of the storage mutex, multiple view contexts will be able to access local storage areas at the same time so that scripts cannot detect any simultaneous execution of the script.
Thus, the length attribute of the Storage object and the value of various properties of this object cannot change while the script is running, other than what the script itself predicts.
He describes in even more detail:
Whenever the properties of a localStorage object localStorage the Storage attribute localStorage be checked, returned, set, or deleted, regardless of whether it is part of direct access to properties when checking for a property while listing properties, when determining the number of properties present, or as part of any methods or attributes defined on the storage interface , the user agent must first obtain the storage mutex .
The emphasis is mine. He also notes that some developers do not like this as a note.
On practice
Show it thread safe in implementation.
Choosing a random browser, I chose WebKit (because I did not know where exactly this code is located). If we check the implementation of WebKit Storage , we will see that it has its share in the mutex tariffs.
Let it take it from the start. When you call setItem or assign, this happens:
void Storage::setItem(const String& key, const String& value, ExceptionCode& ec) { if (!m_storageArea->canAccessStorage(m_frame)) { ec = SECURITY_ERR; return; } if (isDisabledByPrivateBrowsing()) { ec = QUOTA_EXCEEDED_ERR; return; } bool quotaException = false; m_storageArea->setItem(m_frame, key, value, quotaException); if (quotaException) ec = QUOTA_EXCEEDED_ERR; }
Next, this happens in StorageArea :
void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException) { ASSERT(!m_isShutdown); ASSERT(!value.isNull()); blockUntilImportComplete(); String oldValue; RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException); if (newMap) m_storageMap = newMap.release(); if (quotaException) return; if (oldValue == value) return; if (m_storageAreaSync) m_storageAreaSync->scheduleItemForSync(key, value); dispatchStorageEvent(key, oldValue, value, sourceFrame); }
Please note that blockUntilImportComplete here. Let's take a look at this:
void StorageAreaSync::blockUntilImportComplete() { ASSERT(isMainThread()); // Fast path. We set m_storageArea to 0 only after m_importComplete being true. if (!m_storageArea) return; MutexLocker locker(m_importLock); while (!m_importComplete) m_importCondition.wait(m_importLock); m_storageArea = 0; }
They also reached a wonderful note:
// FIXME: In the future, we should allow use of StorageAreas while it importing (when safe to do so). // Blocking everything until the import is complete is by far the simplest and safest thing to do, but // there is certainly room for safe optimization: Key/length will never be able to make use of such an // optimization (since the order of iteration can change as items are being added). Get can return any // item currently in the map. Get/remove can work whether or not it in the map, but we'll need a list // of items the import should not overwrite. Clear can also work, but it'll need to kill the import // job first.
The explanation of this works, but it may be more effective.