html5 errore localStorage con Safari: "QUOTA_EXCEEDED_ERR: DOM Eccezione 22: Tentativo di aggiungere qualcosa allo spazio di archiviazione che ha superato la quota."


133

La mia webapp presenta errori javascript nella navigazione privata di ios safari:

JavaScript: Errore

non definito

QUOTA_EXCEEDED_ERR: DOM Eccezione 22: è stato effettuato un tentativo di aggiungere qualcosa allo spazio di archiviazione ...

il mio codice:

localStorage.setItem('test',1)

Utilizzare una funzione di rilevamento che verifica questo problema specifico . Se lo spazio di archiviazione non è disponibile, prendere in considerazione lo shunting localStorage con memoryStorage . disclaimer: sono l'autore dei pacchetti collegati
Stijn de Witt,

4
Ciao gente, aiuto a mantenere Safaridriver. Questo problema è un bug di vecchia data in WebKit che è stato risolto di recente. Lo spazio di archiviazione locale e di sessione ora funziona in Safari 10.1 e versioni successive. Questa correzione riguarda la normale modalità di navigazione privata e la modalità di automazione (utilizzata da WebDriver).
Brian Burg,

Risposte:


183

Apparentemente questo è di progettazione. Quando Safari (OS X o iOS) è in modalità di navigazione privata, appare come se localStoragefosse disponibile, ma il tentativo di chiamare setItemgenera un'eccezione.

store.js line 73
"QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."

Quello che succede è che l'oggetto finestra si espone ancora localStoragenello spazio dei nomi globale, ma quando si chiama setItem, viene generata questa eccezione. Qualsiasi chiamata a removeItemviene ignorata.

Credo che la soluzione più semplice (anche se non ho ancora testato questo browser incrociato) sarebbe quella di modificare la funzione isLocalStorageNameSupported()per testare che puoi anche impostare un valore.

https://github.com/marcuswestin/store.js/issues/42

function isLocalStorageNameSupported() 
{
    var testKey = 'test', storage = window.sessionStorage;
    try 
    {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return localStorageName in win && win[localStorageName];
    } 
    catch (error) 
    {
        return false;
    }
}

1
Questo non deve essere necessariamente dovuto alla modalità di navigazione in incognito ... anche se immagino che l'OP non volesse memorizzare diversi megabyte di dati;)
Christoph,

5
Dai un'occhiata a questo riassunto che mostra una breve storia di rilevamento dell'archiviazione locale di Paul Irish.
Mottie,

4
Quindi, nel caso in cui localStorage non funzionerà in Safari, la memorizzazione della migliore opzione è la memorizzazione di tutto nei cookie?
Will Hitchcock,

5
Simile all'esempio di Paul Irish, suggerisco di passare return localStorageName in win && win[localStorageName];a return true. Quindi si dispone di una funzione che restituisce in modo vero o falso a seconda della disponibilità di LocalStorage. Ad esempio:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT

1
Ho verificato che il problema non riguarda solo la finestra privata ma anche la normale finestra Safari.
codemirror,

38

La correzione pubblicata sul link sopra non ha funzionato per me. Questo ha fatto:

function isLocalStorageNameSupported() {
  var testKey = 'test', storage = window.localStorage;
  try {
    storage.setItem(testKey, '1');
    storage.removeItem(testKey);
    return true;
  } catch (error) {
    return false;
  }
}

Derivato da http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5


20
Qualche motivo particolare per cui tu (e @KingKongFrog) state usando window.sessionStorage per rilevare se è possibile scrivere su localStorage o siamo in uno strano ciclo di battitura incolla copia?
Yetti,

@Yetti se hai notato un errore di battitura, perché non lo correggi in una modifica o nel tuo commento? Per quanto ne so, window.sessionStorageè corretto. Funziona sicuramente nel mio codice. Indica la correzione del problema che sembra conoscere.
Novocaina,

7
@Novocaine Il mio commento ha sottolineato che stanno utilizzando sessionStorage in una funzione esistente per verificare il supporto di localStorage. Sì, probabilmente continuerà a funzionare, ma, come scritto, il nome della funzione è fuorviante per ciò che viene effettivamente testato. Ho scelto di commentare, piuttosto che modificarlo, perché pensavo che mi mancasse qualcosa e speravo di imparare da questi ragazzi. Sfortunatamente, non hanno risposto o apportato una correzione, quindi eccoci qui.
Yetti,

3
@Yetti Grazie per il chiarimento. Vedo di cosa ti occupavi adesso. ; -]
Novocaine,

2
@DawsonNo è stato perché ho chiamato la funzione isLocalStorageNameSupportede stavo controllando window.sessionStorage. Stesso risultato finale ma è stato un po 'confuso. La risposta è stata modificata per chiarire.
Cyberwombat,

25

Come menzionato in altre risposte, otterrai sempre QuotaExceededError in modalità Browser privato Safari sia su iOS che su OS X quando localStorage.setItem(o sessionStorage.setItem) viene chiamato.

Una soluzione consiste nell'eseguire un controllo try / catch o Modernizr in ciascuna istanza di utilizzo setItem.

Tuttavia, se si desidera uno shim che interrompa semplicemente questo errore a livello globale, per impedire la rottura del resto del JavaScript, è possibile utilizzare questo:

https://gist.github.com/philfreo/68ea3cd980d72383c951

// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem
// throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem
// to avoid the entire page breaking, without having to do a check at each usage of Storage.
if (typeof localStorage === 'object') {
    try {
        localStorage.setItem('localStorage', 1);
        localStorage.removeItem('localStorage');
    } catch (e) {
        Storage.prototype._setItem = Storage.prototype.setItem;
        Storage.prototype.setItem = function() {};
        alert('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.');
    }
}

11

Nel mio contesto, ho appena sviluppato un'astrazione di classe. Quando la mia applicazione viene avviata, controllo se localStorage funziona chiamando getStorage () . Questa funzione restituisce anche:

  • localStorage se localStorage funziona
  • o un'implementazione di una classe personalizzata LocalStorageAlternative

Nel mio codice non chiamo mai localStorage direttamente. Chiamo cusSto global var, avevo inizializzato chiamando getStorage () .

In questo modo, funziona con la navigazione privata o specifiche versioni di Safari

function getStorage() {

    var storageImpl;

     try { 
        localStorage.setItem("storage", ""); 
        localStorage.removeItem("storage");
        storageImpl = localStorage;
     }
     catch (err) { 
         storageImpl = new LocalStorageAlternative();
     }

    return storageImpl;

}

function LocalStorageAlternative() {

    var structureLocalStorage = {};

    this.setItem = function (key, value) {
        structureLocalStorage[key] = value;
    }

    this.getItem = function (key) {
        if(typeof structureLocalStorage[key] != 'undefined' ) {
            return structureLocalStorage[key];
        }
        else {
            return null;
        }
    }

    this.removeItem = function (key) {
        structureLocalStorage[key] = undefined;
    }
}

cusSto = getStorage();

2
Grazie Pierre, la tua risposta mi ha ispirato. Ho finito per impacchettare tutto in un bel modulo chiamato memorystorage . Open Source ovviamente. Per altre persone con lo stesso problema, controlla che potrebbe aiutarti.
Stijn de Witt,

Ha. Ho fatto la stessa cosa (indipendentemente). Usa comunque la variabile localStorage (almeno in Safari, non puoi sovrascrivere la variabile localStorage (è 'sola lettura'), ma puoi riassegnare setItem / removeItem / getItem).
Ruben Martinez Jr.,

@StijndeWitt, come posso accedere ai miei valori di archiviazione in altre pagine? Ad esempio, ho questo nel mio helper.php var store = MemoryStorage ('my-app'); store.setItem ('myString', 'Hello MemoryStorage!'); Voglio accedere al valore di myString in lecture.php. Ho provato ad avviare memorystorage nella pagina ma mostra ancora un oggetto vuoto.
user1149244

@ user1149244 Memorystorage è locale nella pagina. Simula l'API di archiviazione Web e come tale può essere utilizzato come fallback per quando localStorage e sessionStorage non sono disponibili. Tuttavia, i dati verranno conservati solo nella memoria della pagina (da cui il nome). Se hai bisogno di conservare i dati su più pagine, i cookie potrebbero aiutarti. Ma è molto limitato nella quantità di dati che possono essere memorizzati. A parte questo, non c'è molto da fare.
Stijn de Witt,

2
@ user1149244 Non si suppone che questo non memorizzi i valori nel browser? No, non può. Esistono 3 modi per archiviare le cose sul lato client da un aggiornamento della pagina all'altra: cookie, sessionStorage / localStorage e IndexedDB. Gli ultimi due sono relativamente nuovi. sessionStorage e localStorage sono ampiamente supportati in modo da poterlo utilizzare praticamente ovunque. ad eccezione della modalità di navigazione privata , che è questo il problema. I programmi si sono interrotti perché la memoria non era presente. memorystorage fornisce solo un fallback che funziona sempre mentre si trova sulla pagina, ma in realtà non può salvare i dati. È un troncone. Ma nessun errore.
Stijn de Witt,

5

Sembra che Safari 11 cambi il comportamento e ora l'archiviazione locale funziona in una finestra del browser privata. Evviva!

La nostra app Web che un tempo falliva nella navigazione privata di Safari ora funziona perfettamente. Funzionava sempre bene nella modalità di navigazione privata di Chrome, che ha sempre permesso di scrivere nella memoria locale.

Questo è documentato nelle note di rilascio di Safari Technology Preview di Apple - e nelle note di rilascio di WebKit - per la versione 29, che era nel maggio 2017.

In particolare:

  • Risolto QuotaExceededError durante il salvataggio in localStorage in modalità di navigazione privata o sessioni WebDriver - r215315

4

Per espandere le risposte degli altri, ecco una soluzione compatta che non espone / aggiunge nuove variabili. Non copre tutte le basi, ma dovrebbe adattarsi alla maggior parte delle persone che vogliono solo che un'app a pagina singola rimanga funzionale (nonostante la persistenza dei dati dopo il ricaricamento).

(function(){
    try {
        localStorage.setItem('_storage_test', 'test');
        localStorage.removeItem('_storage_test');
    } catch (exc){
        var tmp_storage = {};
        var p = '__unique__';  // Prefix all keys to avoid matching built-ins
        Storage.prototype.setItem = function(k, v){
            tmp_storage[p + k] = v;
        };
        Storage.prototype.getItem = function(k){
            return tmp_storage[p + k] === undefined ? null : tmp_storage[p + k];
        };
        Storage.prototype.removeItem = function(k){
            delete tmp_storage[p + k];
        };
        Storage.prototype.clear = function(){
            tmp_storage = {};
        };
    }
})();

3

Ho avuto lo stesso problema usando la struttura ionica (Angolare + Cordova). So che questo non risolve il problema, ma è il codice per le app angolari basato sulle risposte sopra. Avrai una soluzione effimera per localStorage sulla versione iOS di Safari.

Ecco il codice:

angular.module('myApp.factories', [])
.factory('$fakeStorage', [
    function(){
        function FakeStorage() {};
        FakeStorage.prototype.setItem = function (key, value) {
            this[key] = value;
        };
        FakeStorage.prototype.getItem = function (key) {
            return typeof this[key] == 'undefined' ? null : this[key];
        }
        FakeStorage.prototype.removeItem = function (key) {
            this[key] = undefined;
        };
        FakeStorage.prototype.clear = function(){
            for (var key in this) {
                if( this.hasOwnProperty(key) )
                {
                    this.removeItem(key);
                }
            }
        };
        FakeStorage.prototype.key = function(index){
            return Object.keys(this)[index];
        };
        return new FakeStorage();
    }
])
.factory('$localstorage', [
    '$window', '$fakeStorage',
    function($window, $fakeStorage) {
        function isStorageSupported(storageName) 
        {
            var testKey = 'test',
                storage = $window[storageName];
            try
            {
                storage.setItem(testKey, '1');
                storage.removeItem(testKey);
                return true;
            } 
            catch (error) 
            {
                return false;
            }
        }
        var storage = isStorageSupported('localStorage') ? $window.localStorage : $fakeStorage;
        return {
            set: function(key, value) {
                storage.setItem(key, value);
            },
            get: function(key, defaultValue) {
                return storage.getItem(key) || defaultValue;
            },
            setObject: function(key, value) {
                storage.setItem(key, JSON.stringify(value));
            },
            getObject: function(key) {
                return JSON.parse(storage.getItem(key) || '{}');
            },
            remove: function(key){
                storage.removeItem(key);
            },
            clear: function() {
                storage.clear();
            },
            key: function(index){
                storage.key(index);
            }
        }
    }
]);

Fonte: https://gist.github.com/jorgecasar/61fda6590dc2bb17e871

Buona codifica!


1
Anche se questo non risponde alla domanda, questa è la prima cosa che è emersa quando ho cercato su Google il problema. Il prossimo passo sarebbe stato cercare la soluzione per Angular ma grazie a questo commento non devo andare altrove. Quindi, potrebbe non rispondere direttamente alla domanda, ma è stato fantastico per me e probabilmente per gli altri!
Leonard,

2

Ecco una soluzione per AngularJS che utilizza un IIFE e sfrutta il fatto che i servizi sono singoli .

Ciò si traduce nell'impostazione isLocalStorageAvailableimmediata alla prima iniezione del servizio ed evita inutilmente di eseguire il controllo ogni volta che è necessario accedere all'archiviazione locale.

angular.module('app.auth.services', []).service('Session', ['$log', '$window',
  function Session($log, $window) {
    var isLocalStorageAvailable = (function() {
      try {
        $window.localStorage.world = 'hello';
        delete $window.localStorage.world;
        return true;
      } catch (ex) {
        return false;
      }
    })();

    this.store = function(key, value) {
      if (isLocalStorageAvailable) {
        $window.localStorage[key] = value;
      } else {
        $log.warn('Local Storage is not available');
      }
    };
  }
]);

1

Ho appena creato questo repository per fornire sessionStoragee localStoragefunzionalità per browser non supportati o disabilitati.

Browser supportati

  • IE5 +
  • Chrome tutte le versioni
  • Mozilla tutte le versioni
  • Yandex tutte le versioni

Come funziona

Rileva la funzione con il tipo di archiviazione.

function(type) {
    var testKey = '__isSupported',
        storage = window[type];
    try {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return true;
    } catch (error) {
        return false;
    }
};

Imposta StorageService.localStorageper window.localStoragese è supportato o crea una memorizzazione dei cookie. Imposta StorageService.sessionStorageper window.sessionStoragese è supportato o crea una in memoria di archiviazione per SPA, la memorizzazione dei cookie con le caratteristiche Sesion per non SPA.


1
Grazie, la tua biblioteca ha aiutato molto!
Mathieu,

1

Ecco una versione del servizio Angular2 + per l'alternativa alla memoria, puoi semplicemente iniettare nei tuoi componenti, in base alla risposta di Pierre Le Roux.

import { Injectable } from '@angular/core';

// Alternative to localstorage, memory
// storage for certain browsers in private mode
export class LocalStorageAlternative {
    private  structureLocalStorage = {};

    setItem(key: string, value: string): void {
        this.structureLocalStorage[key] = value;
    }

    getItem(key: string): string {
        if (typeof this.structureLocalStorage[key] !== 'undefined' ) {
            return this.structureLocalStorage[key];
        }
        return null;
    }

    removeItem(key: string): void {
        this.structureLocalStorage[key] = undefined;
    }
}

@Injectable()
export class StorageService {
    private storageEngine;

    constructor() {
        try {
            localStorage.setItem('storage_test', '');
            localStorage.removeItem('storage_test');
            this.storageEngine = localStorage;
        } catch (err) {
            this.storageEngine = new LocalStorageAlternative();
        }
    }

    setItem(key: string, value: string): void {
        this.storageEngine.setItem(key, value);
    }

    getItem(key: string): string {
        return this.storageEngine.getItem(key);
    }

    removeItem(key: string): void {
        this.storageEngine.removeItem(key);
    }

}

0

Non utilizzarlo se non supportato e per controllare il supporto basta chiamare questa funzione

condivisione in Es6 lettura completa e scrittura localStorage Esempio con controllo supporto

const LOCAL_STORAGE_KEY = 'tds_app_localdata';

const isSupported = () => {
  try {
    localStorage.setItem('supported', '1');
    localStorage.removeItem('supported');
    return true;
  } catch (error) {
    return false;
  }
};


const writeToLocalStorage =
  components =>
    (isSupported ?
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(components))
      : components);

const isEmpty = component => (!component || Object.keys(component).length === 0);

const readFromLocalStorage =
  () => (isSupported ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || {} : null);

Questo assicurerà che le tue chiavi siano impostate e recuperate correttamente su tutti i browser.


0

Ho creato una patch per il problema. Sto semplicemente controllando se il browser supporta localStorage o sessionStorage o meno. In caso contrario, il motore di archiviazione sarà Cookie. Ma il lato negativo è che i cookie hanno una memoria di archiviazione molto piccola :(

function StorageEngine(engine) {
    this.engine = engine || 'localStorage';

    if(!this.checkStorageApi(this.engine)) {
        // Default engine would be alway cooke
        // Safari private browsing issue with localStorage / sessionStorage
        this.engine = 'cookie';
    }
}

StorageEngine.prototype.checkStorageApi = function(name) {
    if(!window[name]) return false;
    try {
        var tempKey = '__temp_'+Date.now();
        window[name].setItem(tempKey, 'hi')
        window[name].removeItem(tempKey);
        return true;
    } catch(e) {
        return false;
    }
}

StorageEngine.prototype.getItem = function(key) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        return window[this.engine].getItem(key);
    } else if('cookie') {
        var name = key+"=";
        var allCookie = decodeURIComponent(document.cookie).split(';');
        var cval = [];
        for(var i=0; i < allCookie.length; i++) {
            if (allCookie[i].trim().indexOf(name) == 0) {
                cval = allCookie[i].trim().split("=");
            }   
        }
        return (cval.length > 0) ? cval[1] : null;
    }
    return null;
}

StorageEngine.prototype.setItem = function(key, val, exdays) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        window[this.engine].setItem(key, val);
    } else if('cookie') {
        var d = new Date();
        var exdays = exdays || 1;
        d.setTime(d.getTime() + (exdays*24*36E5));
        var expires = "expires="+ d.toUTCString();
        document.cookie = key + "=" + val + ";" + expires + ";path=/";
    }
    return true;
}


// ------------------------
var StorageEngine = new StorageEngine(); // new StorageEngine('localStorage');
// If your current browser (IOS safary or any) does not support localStorage/sessionStorage, then the default engine will be "cookie"

StorageEngine.setItem('keyName', 'val')

var expireDay = 1; // for cookie only
StorageEngine.setItem('keyName', 'val', expireDay)
StorageEngine.getItem('keyName')

0

La risposta accettata non sembra adeguata in diverse situazioni.

Per verificare se sono supportati localStorageo sessionStorage, utilizzo il seguente frammento di MDN .

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

Usa questo snippet in questo modo e fallback, ad esempio, usando i cookie:

if (storageAvailable('localStorage')) {
  // Yippee! We can use localStorage awesomeness
}
else {
  // Too bad, no localStorage for us
  document.cookie = key + "=" + encodeURIComponent(value) + expires + "; path=/";
}

Ho creato il pacchetto fallbackstorage che utilizza questo frammento per verificare la disponibilità di archiviazione e il fallback in un MemoryStorage implementato manualmente.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work

-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }

1
Forse vuoi aggiungere qualche parola di spiegazione?
bogl

-2

Il seguente script ha risolto il mio problema:

// Fake localStorage implementation. 
// Mimics localStorage, including events. 
// It will work just like localStorage, except for the persistant storage part. 

var fakeLocalStorage = function() {
  var fakeLocalStorage = {};
  var storage; 

  // If Storage exists we modify it to write to our fakeLocalStorage object instead. 
  // If Storage does not exist we create an empty object. 
  if (window.Storage && window.localStorage) {
    storage = window.Storage.prototype; 
  } else {
    // We don't bother implementing a fake Storage object
    window.localStorage = {}; 
    storage = window.localStorage; 
  }

  // For older IE
  if (!window.location.origin) {
    window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
  }

  var dispatchStorageEvent = function(key, newValue) {
    var oldValue = (key == null) ? null : storage.getItem(key); // `==` to match both null and undefined
    var url = location.href.substr(location.origin.length);
    var storageEvent = document.createEvent('StorageEvent'); // For IE, http://stackoverflow.com/a/25514935/1214183

    storageEvent.initStorageEvent('storage', false, false, key, oldValue, newValue, url, null);
    window.dispatchEvent(storageEvent);
  };

  storage.key = function(i) {
    var key = Object.keys(fakeLocalStorage)[i];
    return typeof key === 'string' ? key : null;
  };

  storage.getItem = function(key) {
    return typeof fakeLocalStorage[key] === 'string' ? fakeLocalStorage[key] : null;
  };

  storage.setItem = function(key, value) {
    dispatchStorageEvent(key, value);
    fakeLocalStorage[key] = String(value);
  };

  storage.removeItem = function(key) {
    dispatchStorageEvent(key, null);
    delete fakeLocalStorage[key];
  };

  storage.clear = function() {
    dispatchStorageEvent(null, null);
    fakeLocalStorage = {};
  };
};

// Example of how to use it
if (typeof window.localStorage === 'object') {
  // Safari will throw a fit if we try to use localStorage.setItem in private browsing mode. 
  try {
    localStorage.setItem('localStorageTest', 1);
    localStorage.removeItem('localStorageTest');
  } catch (e) {
    fakeLocalStorage();
  }
} else {
  // Use fake localStorage for any browser that does not support it.
  fakeLocalStorage();
}

Verifica se localStorage esiste e può essere utilizzato e, in caso negativo, crea un archivio locale falso e lo utilizza al posto del localStorage originale. Per favore fatemi sapere se avete bisogno di ulteriori informazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.