Safari的html5 localStorage错误:“ QUOTA_EXCEEDED_ERR:DOM异常22:试图向存储中添加超出配额的内容。”


133

我的webapp在ios Safari私人浏览中出现javascript错误:

JavaScript:错误

未定义

QUOTA_EXCEEDED_ERR:DOM异常22:试图向存储中添加内容...

我的代码:

localStorage.setItem('test',1)

使用功能检测该特定问题的测试。如果存储不可用,请考虑使用memoryStorage填充 localStorage 。免责声明:我是链接软件包的作者
Stijn de Witt

4
大家好,我帮助维护safaridriver。此问题是WebKit中的一个长期错误,最近已修复。本地存储和会话存储现在可以在Safari 10.1和更高版本中使用。此修复程序影响正常的专用浏览模式和自动化模式(由WebDriver使用)。
布赖恩·伯格

Answers:


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/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
这不一定是由于隐身模式造成的……虽然我猜OP不想存储几兆字节的数据;)
Christoph

5
查看此要点,其中简要介绍了Paul Irish检测本地存储的历史。
Mottie 2014年

4
因此,在localStorage在Safari中无法使用的情况下,将所有内容存储在cookie中是下一个最佳选择吗?
希区柯克将于2015年

5
与保罗·爱尔兰(Paul Irish)的例子类似,我建议更改return localStorageName in win && win[localStorageName];return true。然后,您将具有一个函数,该函数根据localStorage的可用性安全地返回true或false。例如:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT

1
验证问题不仅存在于私有窗口中,而且还存在于正常的野生动物园窗口中。
codemirror17年

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

@Yetti,如果您发现错字,为什么不在编辑或评论中更正呢?据我所知window.sessionStorage是正确的。它当然可以在我的代码中工作。请实际指出您似乎知道的问题的解决方法。
Novocaine 2015年

7
@Novocaine我的评论指出他们正在使用sessionStorage来检查localStorage支持的功能。是的,它可能仍然可以工作,但是按照书面说明,函数名称对于实际测试的内容具有误导性。我选择发表评论,而不是编辑,因为我认为我错过了一些东西,并希望向这些人学习。不幸的是,他们没有答复或未更正,所以我们来了。
Yetti

3
@Yetti谢谢您的澄清。我知道你现在在做什么。;-]
Novocaine 2015年

2
@DawsonToth不,是因为我调用了函数isLocalStorageNameSupported并正在检查window.sessionStorage。最终结果相同,但有点混乱。答案已被编辑以澄清。
cyberwombat

25

如其他答案中所述,在调用localStorage.setItem(或sessionStorage.setItem)时,在iOS和OS X上的Safari私有浏览器模式下,您始终会收到QuotaExceededError 。

一种解决方案是在使用的每个实例中进行try / catch或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

在我的上下文中,仅开发了一个类抽象。启动应用程序时,我会通过调用getStorage()来检查localStorage是否正常工作。此函数还返回:

  • 如果localStorage在工作,则为localStorage
  • 或自定义类LocalStorageAlternative的实现

在我的代码中,我从不直接调用localStorage。我将cusSto称为全局变量,是通过调用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的不错的模块中。当然是开源的。对于其他遇到相同问题的人,请查看它可能对您有帮助。
Stijn de Witt

哈。我(独立地)做了同样的事情。不过,仍然要使用localStorage变量(至少在Safari中,您不能覆盖localStorage变量(它是“只读”),但是可以重新分配setItem / removeItem / getItem)。
鲁宾·马丁内斯(Ruben Martinez)2016年

@StijndeWitt,如何访问其他页面中的存储值?例如,我在helper.php中有这个。var store = MemoryStorage('my-app'); store.setItem('myString','Hello MemoryStorage!'); 我想在演讲者php中访问myString的值。我尝试在页面中启动内存存储,但仍然显示一个空对象。
user1149244

@ user1149244 Memorystorage在页面本地。它模拟了Web Storage API,因此当localStorage和sessionStorage不可用时,可以用作备用。但是,数据将仅保留在页面存储器中(因此命名)。如果您需要跨页面保留数据,则cookie可能会为您提供帮助。但是可存储的数据量非常有限。除此之外,没有什么可以做的。
Stijn de Witt

2
@ user1149244 这不是应该在浏览器中存储值吗?不,不能。有三种方法可以将客户端刷新内容从一个页面刷新到另一页面:cookie,sessionStorage / localStorage和IndexedDB。后两个相对较新。广泛支持sessionStorage和localStorage,因此您基本上可以在任何地方使用它。除了在私有浏览模式下,这就是这个问题所在。程序中断,因为存储不存在。内存存储只是提供了一个后备功能,该后备功能始终在页面上有效,但实际上无法保存数据。这是一个存根。但是没有错误。
Stijn de Witt

5

似乎Safari 11改变了行为,现在本地存储可在专用浏览器窗口中工作。万岁!

我们过去无法在Safari私人浏览中失败的Web应用程序现在可以完美运行。在Chrome的专用浏览模式下,它始终可以正常运行,该模式始终允许写入本地存储。

这在Apple的Safari技术预览版发行说明WebKit发行说明中都有记录。 -于2017年5月发布的第29版中对此进行了记录。

特别:

  • 修复了在私人浏览模式或WebDriver会话中保存到localStorage时出现QuotaExceededError- 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

使用离子框架(Angular + Cordova)时,我遇到了同样的问题。我知道这不能解决问题,但这是基于以上答案的Angular Apps的代码。您将在iOS版本的Safari上获得针对localStorage的临时解决方案。

这是代码:

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
尽管这不能回答问题,但这是我在Google上搜索该问题时首先想到的事情。下一步将是为Angular搜索解决方案,但是由于有了此评论,我不必再去其他地方了。因此,可能无法直接回答问题,但对我和其他人都非常有用!
伦纳德(Leonard)2015年

2

这是一个使用IIFE并利用服务为单例的 AngularJS解决方案。

这样可以在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

我刚刚创建的这个回购协议,以提供sessionStoragelocalStorage功能不支持的或禁用浏览器。

支持的浏览器

  • IE5 +
  • Chrome所有版本
  • Mozilla所有版本
  • Yandex所有版本

这个怎么运作

它使用存储类型检测功能。

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

设置StorageService.localStoragewindow.localStorage是否受支持或创建cookie存储。设置StorageService.sessionStoragewindow.sessionStorage是否支持SPA或在SPA中创建内存存储,为非SPA创建具有会话功能的cookie存储。


1
谢谢,您的图书馆帮了很多忙!
Mathieu

1

这是用于内存存储替代的Angular2 +服务版本,您可以根据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

如果不支持,请不要使用它,要检查支持,只需调用此函数

在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

在几种情况下,可接受的答案似乎不足。

要检查localStorageor 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=/";
}

我制作了fallbackstorage软件包,该软件包使用此片段来检查存储可用性并回退到手动实现的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。如果您需要更多信息,请告诉我。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.