помилка localStorage html5 з Safari: "QUOTA_EXCEEDED_ERR: DOM Виняток 22: Була зроблена спроба додати щось у сховище, що перевищило квоту."


133

У мого веб-сайту є помилки javascript у приватному перегляді ios safari:

JavaScript: помилка

невизначений

QUOTA_EXCEEDED_ERR: DOM Виняток 22: Була спроба додати щось у сховище ...

мій код:

localStorage.setItem('test',1)

Використовуйте функцію виявлення тестів для цієї конкретної проблеми . Якщо пам’яті недоступно, розгляньте, як локальне зберігання зберігається пам’яттюStorage . відмова від відповідальності: Я є автором пов'язаних пакетів
Stijn de Witt

4
Привіт, люди, я допомагаю підтримувати safaridriver. Ця проблема є давньою помилкою в WebKit, яку нещодавно виправили. Місцеве зберігання та зберігання сеансів зараз працюють у Safari 10.1 та новіших версіях. Це виправлення впливає на звичайний режим приватного перегляду та режим автоматизації (використовується WebDriver).
Брайан Бург

Відповіді:


183

Мабуть, це задумом. Коли Safari (OS X або iOS) перебуває в режимі приватного перегляду, воно виглядає як би localStorageдоступним, але спроба виклику setItemкидає виняток.

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

Що відбувається, так це те, що об’єкт вікна все ще відкривається localStorageв глобальному просторі імен, але коли ви телефонуєте setItem, цей виняток кидається. Будь-які дзвінки до removeItemігноруються.

Я вважаю, що найпростішим виправленням (хоча я ще не перевіряв цей крос-браузер) було б змінити функцію, isLocalStorageNameSupported()щоб перевірити, що ви також можете встановити якесь значення.

https://github.com/marcuswestin/store.js/isissue/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
Це не обов'язково повинно бути пов’язано з режимом анонімного перегляду ... хоча я думаю, що ОП не хотіла зберігати кілька мегабайт даних;)
Крістоф

5
Ознайомтеся з цією історією, де показана коротка історія виявлення місцевих сховищ Полом Ірландським.
Мотті

4
Тож у випадку, якщо localStorage не збирається працювати в Safari, чи зберігає все у файлах cookie наступний найкращий варіант?
Буде Хічкок

5
Аналогічно прикладу Paul Irish, я пропоную зміна return localStorageName in win && win[localStorageName];до return true. Тоді у вас є функція, яка безпечно повертає значення true чи false в залежності від доступності localStorage. Наприклад:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT

1
Перевірено, що проблема не лише з приватним вікном, але і з звичайним вікном сафарі.
кодемір

38

Виправлення, розміщене на вище посиланні, не працювало для мене. Це зробило:

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

Отримано з http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5


20
З якоїсь конкретної причини ви (і @KingKongFrog) використовуєте window.sessionStorage, щоб виявити, чи можете ви написати на localStorage чи ми знаходимося в дивному циклі друку-копіювання?
Йетті

@Yetti, якщо ви помітили помилку, чому ви не виправите це ні в редакції, ні в коментарі? Наскільки я знаю, window.sessionStorageце правильно. Це, безумовно, працює в моєму коді. Фактично вкажіть виправлення проблеми, про яку, здається, знаєте.
Новокаїн

7
@Novocaine У моєму коментарі було вказано, що вони використовують sessionStorage у функції, яка існує для перевірки підтримки локальноїStorage. Так, це, швидше за все, спрацює, але, як написано, назва функції вводить в оману щодо того, що насправді тестується. Я вирішив коментувати, а не редагувати, тому що думав, що мені щось не вистачає, і сподівався навчитися у цих хлопців. На жаль, вони не відповіли і не внесли виправлення, тому ось ми.
Ітті

3
@Yetti Дякую за уточнення. Я бачу, про що ти зараз говорив. ; -]
Новокаїн

2
@DawsonToth ні, це не було, тому що я викликав функцію isLocalStorageNameSupportedі перевіряв window.sessionStorage. Кінцевий результат, але трохи заплутаний. Відповідь було відредаговано для уточнення.
cyberwombat

25

Як згадується в інших відповідях, ви завжди отримаєте QuotaExceededError в режимі приватного браузера Safari для iOS та OS X, коли localStorage.setItem(або sessionStorage.setItem) викликається.

Одне рішення - зробити спробу / ловити або перевірити Modernizr у кожному випадку використання setItem.

Однак, якщо ви хочете, щоб ця лайка просто глобально зупиняла скидання цієї помилки, щоб запобігти порушенню решти JavaScript, ви можете скористатися цим:

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

У моєму контексті щойно розвинулася класова абстракція. Коли моя програма запускається, я перевіряю, чи працює localStorage, зателефонувавши на getStorage () . Ця функція також повертає:

  • або localStorage, якщо localStorage працює
  • або реалізація користувацького класу LocalStorageAlternative

У своєму коді я ніколи не телефоную localStorage безпосередньо. Я називаю cusSto global var, я ініціалізувався, викликавши getStorage () .

Таким чином, це працює з приватним переглядом або певними версіями 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
Дякую, П'єр, твоя відповідь мене надихнула. Я закінчив упакувати все це в чудовий модуль, який називається memorystorage . Звісно з відкритим кодом. Для інших людей із тим самим питанням перевірте, чи може вам це допомогти.
Штійн де Вітт

Ха. Я робив те саме (незалежно). Все ж використовуйте змінну localStorage (хоча б у Safari принаймні ви не можете замінити змінну localStorage (це лише для читання), але ви можете перепризначити setItem / removeItem / getItem).
Рубен Мартінес-молодший

@StijndeWitt, як я можу отримати доступ до своїх значень пам’яті на інших сторінках? Наприклад, я маю це у своєму магазині varper.php var = MemoryStorage ("мій додаток"); store.setItem ('myString', 'Hello MemoryStorage!'); Я хочу отримати доступ до значення myString у preda.php. Я спробував започаткувати зберігання пам'яті на сторінці, але вона все ще показує порожній об'єкт.
користувач1149244

@ user1149244 Пам'ять пам'яті локальна для сторінки. Він імітує API веб-сховища і як такий може бути використаний як резервний варіант, коли localStorage та sessionStorage недоступні. Однак дані зберігатимуться лише в пам'яті сторінки (звідси і назва). Якщо вам потрібні дані для зберігання на сторінках, файли cookie можуть вам допомогти. Але це дуже обмежений обсяг даних, який можна зберігати. Крім того, що зробити це не так багато.
Штійн де Віт

2
@ user1149244 Хіба це нібито не зберігає значення у браузері? Ні, не може. Існує 3 способи зберігання клієнтських речей з однієї сторінки на оновлення сторінки на іншу: cookie, sessionStorage / localStorage та IndexedDB. Останні два відносно нові. sessionStorage та localStorage широко підтримуються, тому ви можете використовувати його в основному скрізь. за винятком приватного режиму перегляду , про що йдеться у цьому питанні. Програми зламалися, оскільки місця зберігання не було. пам'ять просто забезпечує резервну копію, яка завжди працює на сторінці, але фактично не може зберегти дані. Це заглушка. Але помилок немає.
Штійн де Вітт

5

Здається, що Safari 11 змінює поведінку, і тепер локальне зберігання працює у вікні приватного браузера. Ура!

Наш веб-додаток, який раніше не працював у приватному перегляді Safari, зараз працює бездоганно. Це завжди добре працювало в режимі приватного перегляду Chrome, що завжди дозволяло записувати в локальний сховище.

Це задокументовано в примітках до випуску Safari Technology Preview від Apple - та примітках до випуску WebKit до випуску - до випуску 29, який відбувся у травні 2017 року.

Конкретно:

  • Фіксована помилка QuotaExceededError під час збереження до localStorage в режимі приватного перегляду або сеансів WebDriver - r215315

4

Щоб розширити відповіді інших людей, ось компактне рішення, яке не відкриває / не додає нових змінних. Він охоплює не всі бази, але він повинен підходити для більшості людей, які просто хочуть, щоб додаток на одній сторінці залишався функціональним (незважаючи на відсутність збереження даних після перезавантаження).

(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

У мене була така ж проблема з використанням Ionic Framework (Angular + Cordova). Я знаю, що це не вирішує проблему, але це код для Angular Apps на основі відповідей вище. У вас буде ефемерне рішення для localStorage в iOS версії Safari.

Ось код:

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);
            }
        }
    }
]);

Джерело: https://gist.github.com/jorgecasar/61fda6590dc2bb17e871

Насолоджуйтесь кодуванням!


1
Хоча це не відповідає на питання, це перше, що з’явилося, коли я переглянув це питання. Наступним кроком був би пошук рішення для Angular, але завдяки цьому коментарю мені не треба йти в іншому місці. Так що, можливо, не відповів би прямо на це питання, але це було чудово для мене і, ймовірно, для інших!
Леонард

2

Ось рішення для AngularJS з використанням IIFE та використання факту, що послуги одинакові .

Це призводить до isLocalStorageAvailableвстановлення негайно, коли послуга вводиться вперше, і уникає зайвого запуску чеку кожного разу, коли потрібно отримати доступ до місцевого зберігання.

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

Я просто створив це репо, щоб забезпечити sessionStorageіlocalStorage функції для непідтримуваних або відключених браузерів.

Підтримувані веб-переглядачі

  • IE5 +
  • Chrome усі версії
  • Mozilla всі версії
  • Яндекс всі версії

Як це працює

Він визначає функцію з типом зберігання.

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

Набори StorageService.localStorageдля , window.localStorageякщо вона підтримується або створює сховище печива. Набори StorageService.sessionStorageдля , window.sessionStorageякщо вона підтримується або створює в пам'яті зберігання для SPA, зберігання печива з особливостями Sesion для відсутності SPA.


1
Спасибі, ваша бібліотека дуже допомогла!
Матьє

1

Ось варіант служби Angular2 + для зберігання пам’яті, який ви можете просто ввести у свої компоненти, грунтуючись на відповіді П’єра Ле Ру.

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

Не використовуйте його, якщо він не підтримується, а для перевірки підтримки просто зателефонуйте до цієї функції

обмін в Es6 повноцінним читанням і записом LocalStorage Прикладу з перевіркою підтримки

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);

Це забезпечить правильне встановлення та відновлення ваших ключів у всіх браузерах.


0

Я створив виправлення для проблеми. Просто я перевіряю, чи підтримує браузер localStorage або sessionStorage чи ні. Якщо ні, то механізмом зберігання буде Cookie. Але негативна сторона полягає в тому, що у Cookie є дуже крихітна пам'ять :(

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

Прийнята відповідь здається неадекватною у кількох ситуаціях.

Щоб перевірити, чи підтримується localStorageабо sessionStorage, я використовую наступний фрагмент від 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);
    }
}

Використовуйте такий фрагмент, як цей, і запасний файл, наприклад, за допомогою файлу cookie:

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

Я створив пакет backbackstorage, який використовує цей фрагмент, щоб перевірити наявність пам’яті та резервне копіювання для вручну реалізованої пам’яті MemoryStorage.

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
Можливо, ви хочете додати кілька пояснень?
bogl

-2

Наступний сценарій вирішив мою проблему:

// 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();
}

Він перевіряє, чи існує LocalStorage і чи можна його використовувати, і в негативному випадку він створює підроблений локальний сховище та використовує його замість оригінального localStorage. Будь ласка, повідомте мене, якщо вам потрібна додаткова інформація.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.