Safari Private Browsing and LocalStorage QuotaExceededError
This article provides an overview of how the Safari web browser
manages LocalStorage and triggers the
QuotaExceededError exception during Private Browsing mode.
It covers the significant shift from Safari's legacy zero-byte quota
restriction to its modern in-memory storage allocation, explains why the
exception occurs, and demonstrates how developers can properly catch and
handle this error to prevent web applications from crashing.
Legacy Behavior: The Zero-Byte Quota (Safari 10 and Earlier)
Historically, Safari handled LocalStorage in Private
Browsing mode in a way that frequently broke web applications. Instead
of disabling the window.localStorage object entirely,
Safari kept the object available and defined, meaning standard
feature-detection checks like if (window.localStorage)
would return true.
However, Safari assigned a storage quota of 0 bytes to
LocalStorage during Private Browsing sessions. Reading from
storage using getItem() functioned without error (simply
returning null), but any attempt to write data using
setItem() immediately exceeded the 0-byte limit.
Consequently, Safari threw a DOMException with the name
QuotaExceededError (error code 22). If a web application
did not explicitly wrap write operations in a try...catch
block, this unhandled exception would halt JavaScript execution and
break the application.
Modern Behavior: Ephemeral In-Memory Storage (Safari 11 and Later)
Starting with iOS 11 and macOS High Sierra (Safari 11), Apple changed
how Private Browsing handles LocalStorage to align better
with other modern browsers.
In current versions of Safari, Private Browsing creates a temporary,
in-memory data store for LocalStorage. Web applications can
read and write data normally during the private session, up to the
standard 5MB per-origin limit. In this modern environment, Safari only
throws a QuotaExceededError if the web application
genuinely attempts to store more than the 5MB limit allows. Once the
user closes the Private Browsing tab or window, Safari immediately
purges this in-memory storage.
Recent Privacy Protections: Partitioned Storage
In addition to ephemeral storage, modern Safari utilizes Intelligent
Tracking Prevention (ITP), which partitions LocalStorage in
both standard and Private Browsing modes. If a third-party iframe
attempts to write to LocalStorage, that storage is isolated
to the specific top-level site the user is visiting, and its quota is
managed separately. Exceeding the limit within that specific partition
will also trigger a QuotaExceededError.
How to Handle the Exception in Code
Because users may be on older devices, or may genuinely run out of
the allotted 5MB of storage space in modern Safari, relying on a simple
existence check for window.localStorage is insufficient.
Developers must wrap setItem() calls in a
try...catch block to handle the
QuotaExceededError gracefully.
Here is the standard pattern for safely writing to
LocalStorage in Safari:
function safeSetLocalStorage(key, value) {
try {
window.localStorage.setItem(key, value);
return true;
} catch (error) {
if (
error instanceof DOMException &&
(error.code === 22 ||
error.name === "QuotaExceededError" ||
error.name === "NS_ERROR_DOM_QUOTA_REACHED")
) {
// Handle the QuotaExceededError (e.g., fallback to in-memory storage)
console.warn("LocalStorage quota exceeded or disabled in Private Mode.");
return false;
}
// Rethrow if it is a different, unexpected error
throw error;
}
}Implementing an In-Memory Fallback
When Safari throws a QuotaExceededError, the most
effective mitigation strategy is to fall back to a temporary JavaScript
object. This mimics the behavior of modern Private Browsing by holding
data in the browser's RAM until the page is refreshed or closed.
const memoryStorage = {};
function setStorageData(key, value) {
const success = safeSetLocalStorage(key, value);
if (!success) {
memoryStorage[key] = String(value);
}
}
function getStorageData(key) {
const value = window.localStorage.getItem(key);
if (value !== null) {
return value;
}
return memoryStorage[key] || null;
}By intercepting the QuotaExceededError and redirecting
data to an in-memory object, web applications can ensure uninterrupted
functionality regardless of the Safari version or the user's browsing
mode.