Fixing localStorage and WebSQL Databases on iOS5.1

If you’re an iOS developer using an embedded webkit UIWebView in your app, then you’ve probably stuck the problem where your localStorage and WebSQL databases will just disappear when upgrading your app on iOS5.1. Other symptoms for this bug are not being able to call window.openDatabase() (it throws a “SECURITY_ERR: DOM Exception 18” error) or not being able to save to localStorage, after upgrading your app on iOS5.1. This was particularly painful for me when using a framework such as PhoneGap. The reason for this happening is that as of iOS5.1 the database files are stored in the Caches folder, which can potentially be wiped when an app is reinstalled or upgraded, or even if the device runs out of memory. Apparently it’s some kind of new thing to stop databases being automatically installed to iCloud. The other problem is that when upgrading/reinstalling your app, it gets given a new path on the storage, but the WebView preferences aren’t updated automatically to point to the new location.

PhoneGap have provided a plugin that works around the problem by periodically loading and saving the localStorage and WebSQL databases to and from the Documents folder inside the app. This is a bit inelegant, in my opinion, and I had found an alternative solution from Davide Bertola that would move the databases files to the safer Documents folder once and update the WebView preferences to permanently point to that Documents folder. Unfortunately the code I was referencing had a couple of bugs in it, that would prevent it from running correctly on first load of the app. I thought I needed to workaround the problem by running the fix code twice (once before the WebView was loaded and once after), but there was a nasty corner case where if you exited the app after first load, the databases would be in one place and the preferences would be pointing to another; all the data would appear to be lost.

Instead, I modified the code provided to handle the case that the preferences file had not yet been created and instead used the global NSUserDefaults object to save the preferences. This new code only needs to be loaded once at the beginning of your app’s init() function. The only downside with it is that the localStorage is lost the first time the fix is applied. It seems that the localStorage.db file is wiped before the app gets a chance to back it up to the correct location.

#ifndef IsAtLeastiOSVersion
#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)

/* Fix database locations by moving to a safe folder and updating settings.  Call from your init() function as  [self fixDatabaseLocations];
   Based on code originally provided by Davide Bertola
- (void)fixDatabaseLocations
    NSString* library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString* documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *localStorageSubdir = (IsAtLeastiOSVersion(@"5.1")) ? @"Caches" : @"WebKit/LocalStorage";
    NSString *localStoragePath = [library stringByAppendingPathComponent:localStorageSubdir];
    NSString *localStorageDb = [localStoragePath stringByAppendingPathComponent:@"file__0.localstorage"];
    NSString *WebSQLSubdir = (IsAtLeastiOSVersion(@"5.1")) ? @"Caches" : @"WebKit/Databases";
    NSString *WebSQLPath = [library stringByAppendingPathComponent:WebSQLSubdir];
    NSString *WebSQLIndex = [WebSQLPath stringByAppendingPathComponent:@"Databases.db"];
    NSString *WebSQLDb = [WebSQLPath stringByAppendingPathComponent:@"file__0"];
    NSString *ourLocalStoragePath = [documents stringByAppendingPathComponent:@"LocalStorage"];;
    NSString *ourLocalStorageDb = [documents stringByAppendingPathComponent:@"file__0.localstorage"];
    NSString *ourWebSQLPath = [documents stringByAppendingPathComponent:@"Databases"];
    NSString *ourWebSQLIndex = [ourWebSQLPath stringByAppendingPathComponent:@"Databases.db"];
    NSString *ourWebSQLDb = [ourWebSQLPath stringByAppendingPathComponent:@"file__0"];
    NSFileManager* fileManager = [NSFileManager defaultManager];
    BOOL copy;
    NSError *err = nil; 
    copy = [fileManager fileExistsAtPath:localStorageDb] && ![fileManager fileExistsAtPath:ourLocalStorageDb];
    if (copy) {
        [fileManager createDirectoryAtPath:ourLocalStoragePath withIntermediateDirectories:YES attributes:nil error:&err];
        [fileManager copyItemAtPath:localStorageDb toPath:ourLocalStorageDb error:&err];
        if (err == nil)
            [fileManager removeItemAtPath:localStorageDb error:&err];
    err = nil;
    copy = [fileManager fileExistsAtPath:WebSQLPath] && ![fileManager fileExistsAtPath:ourWebSQLPath];
    if (copy) {
        [fileManager createDirectoryAtPath:ourWebSQLPath withIntermediateDirectories:YES attributes:nil error:&err];
        [fileManager copyItemAtPath:WebSQLIndex toPath:ourWebSQLIndex error:&err];
        if (err == nil) {
            [fileManager copyItemAtPath:WebSQLDb toPath:ourWebSQLDb error:&err];
            if (err == nil)
                [fileManager removeItemAtPath:WebSQLPath error:&err];
    NSUserDefaults* appPreferences = [NSUserDefaults standardUserDefaults];
    BOOL dirty = NO;
    NSString *value;
    NSString *key = @"WebKitLocalStorageDatabasePathPreferenceKey";
    value = [appPreferences objectForKey: key];
    if (![value isEqual:ourLocalStoragePath]) {
        [appPreferences setObject:ourLocalStoragePath forKey:key];
        dirty = YES;
    key = @"WebDatabaseDirectory";
    value = [appPreferences objectForKey: key];
    if (![value isEqual:ourWebSQLPath]) {
        [appPreferences setObject:ourWebSQLPath forKey:key];
        dirty = YES;
    if (dirty) 
        BOOL ok = [appPreferences synchronize];
        NSLog(@"Fix applied for database locations?: %@", ok? @"YES":@"NO");

, , , , , ,

  1. #1 by Tom on 2012-05-23 - 10:10 am

    Thank You, Thank You, Thank You! We have been trying to figure this out for the last couple weeks and pulling our hair out.

Comments are closed.