213 lines
7.9 KiB
Plaintext
213 lines
7.9 KiB
Plaintext
/*
|
|
* 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"
|
|
|
|
#import <wtf/cocoa/VectorCocoa.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 {
|
|
auto items = createNSArray(searchItems, [] (auto& item) {
|
|
return adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:item.string, searchStringKey, toNSDateFromSystemClock(item.time), dateKey, nil]);
|
|
});
|
|
[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();
|
|
}
|
|
|
|
}
|