blob: 5293749f547e33159d7ce0b44f30ecc89eaca165 [file] [log] [blame]
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "SearchPopupMenuCocoa.h"
namespace WebCore {
static NSString * const dateKey = @"date";
static NSString * const itemsKey = @"items";
static NSString * const searchesKey = @"searches";
static NSString * const searchStringKey = @"searchString";
static NSString *searchFieldRecentSearchesStorageDirectory()
{
NSString *appName = [[NSBundle mainBundle] bundleIdentifier];
if (!appName)
appName = [[NSProcessInfo processInfo] processName];
return [[NSHomeDirectory() stringByAppendingPathComponent:@"Library/WebKit"] stringByAppendingPathComponent:appName];
}
static NSString *searchFieldRecentSearchesPlistPath()
{
return [searchFieldRecentSearchesStorageDirectory() stringByAppendingPathComponent:@"RecentSearches.plist"];
}
static RetainPtr<NSMutableDictionary> readSearchFieldRecentSearchesPlist()
{
return adoptNS([[NSMutableDictionary alloc] initWithContentsOfFile:searchFieldRecentSearchesPlistPath()]);
}
static WallTime toSystemClockTime(NSDate *date)
{
ASSERT(date);
return WallTime::fromRawSeconds(date.timeIntervalSince1970);
}
static NSDate *toNSDateFromSystemClock(WallTime time)
{
return [NSDate dateWithTimeIntervalSince1970:time.secondsSinceEpoch().seconds()];
}
static NSMutableArray *typeCheckedRecentSearchesArray(NSMutableDictionary *itemsDictionary, NSString *name)
{
NSMutableDictionary *nameDictionary = [itemsDictionary objectForKey:name];
if (![nameDictionary isKindOfClass:[NSDictionary class]])
return nil;
NSMutableArray *recentSearches = [nameDictionary objectForKey:searchesKey];
if (![recentSearches isKindOfClass:[NSArray class]])
return nil;
return recentSearches;
}
static NSDate *typeCheckedDateInRecentSearch(NSDictionary *recentSearch)
{
if (![recentSearch isKindOfClass:[NSDictionary class]])
return nil;
NSDate *date = [recentSearch objectForKey:dateKey];
if (![date isKindOfClass:[NSDate class]])
return nil;
return date;
}
static RetainPtr<NSDictionary> typeCheckedRecentSearchesRemovingRecentSearchesAddedAfterDate(NSDate *date)
{
if ([date isEqualToDate:[NSDate distantPast]])
return nil;
RetainPtr<NSMutableDictionary> recentSearchesPlist = readSearchFieldRecentSearchesPlist();
NSMutableDictionary *itemsDictionary = [recentSearchesPlist objectForKey:itemsKey];
if (![itemsDictionary isKindOfClass:[NSDictionary class]])
return nil;
RetainPtr<NSMutableArray> keysToRemove = adoptNS([[NSMutableArray alloc] init]);
for (NSString *key in itemsDictionary) {
if (![key isKindOfClass:[NSString class]])
return nil;
NSMutableArray *recentSearches = typeCheckedRecentSearchesArray(itemsDictionary, key);
if (!recentSearches)
return nil;
RetainPtr<NSMutableArray> entriesToRemove = adoptNS([[NSMutableArray alloc] init]);
for (NSDictionary *recentSearch in recentSearches) {
NSDate *dateAdded = typeCheckedDateInRecentSearch(recentSearch);
if (!dateAdded)
return nil;
if ([dateAdded compare:date] == NSOrderedDescending)
[entriesToRemove addObject:recentSearch];
}
[recentSearches removeObjectsInArray:entriesToRemove.get()];
if (!recentSearches.count)
[keysToRemove addObject:key];
}
[itemsDictionary removeObjectsForKeys:keysToRemove.get()];
if (!itemsDictionary.count)
return nil;
return recentSearchesPlist;
}
static void writeEmptyRecentSearchesPlist()
{
auto emptyItemsDictionary = adoptNS([[NSDictionary alloc] init]);
auto emptyRecentSearchesDictionary = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:emptyItemsDictionary.get(), itemsKey, nil]);
[emptyRecentSearchesDictionary writeToFile:searchFieldRecentSearchesPlistPath() atomically:YES];
}
void saveRecentSearches(const String& name, const Vector<RecentSearch>& searchItems)
{
if (name.isEmpty())
return;
RetainPtr<NSDictionary> recentSearchesPlist = readSearchFieldRecentSearchesPlist();
RetainPtr<NSMutableDictionary> itemsDictionary = [recentSearchesPlist objectForKey:itemsKey];
// The NSMutableDictionary method we use to read the property list guarantees we get only
// mutable containers, but it does not guarantee the file has a dictionary as expected.
if (![itemsDictionary isKindOfClass:[NSDictionary class]]) {
itemsDictionary = adoptNS([[NSMutableDictionary alloc] init]);
recentSearchesPlist = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:itemsDictionary.get(), itemsKey, nil]);
}
if (searchItems.isEmpty())
[itemsDictionary removeObjectForKey:name];
else {
RetainPtr<NSMutableArray> items = adoptNS([[NSMutableArray alloc] initWithCapacity:searchItems.size()]);
for (auto& searchItem : searchItems)
[items addObject:adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:searchItem.string, searchStringKey, toNSDateFromSystemClock(searchItem.time), dateKey, nil]).get()];
[itemsDictionary setObject:adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:items.get(), searchesKey, nil]).get() forKey:name];
}
[recentSearchesPlist writeToFile:searchFieldRecentSearchesPlistPath() atomically:YES];
}
Vector<RecentSearch> loadRecentSearches(const String& name)
{
Vector<RecentSearch> searchItems;
if (name.isEmpty())
return searchItems;
RetainPtr<NSMutableDictionary> recentSearchesPlist = readSearchFieldRecentSearchesPlist();
if (!recentSearchesPlist)
return searchItems;
NSMutableDictionary *items = [recentSearchesPlist objectForKey:itemsKey];
if (![items isKindOfClass:[NSDictionary class]])
return searchItems;
NSArray *recentSearches = typeCheckedRecentSearchesArray(items, name);
if (!recentSearches)
return searchItems;
for (NSDictionary *item in recentSearches) {
NSDate *date = typeCheckedDateInRecentSearch(item);
if (!date)
continue;
NSString *searchString = [item objectForKey:searchStringKey];
if (![searchString isKindOfClass:[NSString class]])
continue;
searchItems.append({ String{ searchString }, toSystemClockTime(date) });
}
return searchItems;
}
void removeRecentlyModifiedRecentSearches(WallTime oldestTimeToRemove)
{
NSDate *date = toNSDateFromSystemClock(oldestTimeToRemove);
auto recentSearchesPlist = typeCheckedRecentSearchesRemovingRecentSearchesAddedAfterDate(date);
if (recentSearchesPlist)
[recentSearchesPlist writeToFile:searchFieldRecentSearchesPlistPath() atomically:YES];
else
writeEmptyRecentSearchesPlist();
}
}