/* * Copyright (C) 2005 Apple Computer, 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "WebHistoryItemInternal.h" #import "WebHistoryItemPrivate.h" #import "WebFrameBridge.h" #import "WebFrameInternal.h" #import "WebFrameView.h" #import "WebHTMLViewInternal.h" #import "WebIconDatabase.h" #import "WebKitLogging.h" #import "WebKitNSStringExtras.h" #import "WebNSDictionaryExtras.h" #import "WebNSObjectExtras.h" #import "WebNSURLExtras.h" #import "WebNSURLRequestExtras.h" #import "WebNSViewExtras.h" #import "WebPluginController.h" #import #import #import #import #import #import #import #import #import #import // Private keys used in the WebHistoryItem's dictionary representation. // see 3245793 for explanation of "lastVisitedDate" static NSString *WebLastVisitedTimeIntervalKey = @"lastVisitedDate"; static NSString *WebVisitCountKey = @"visitCount"; static NSString *WebTitleKey = @"title"; static NSString *WebChildrenKey = @"children"; static NSString *WebDisplayTitleKey = @"displayTitle"; // Notification strings. NSString *WebHistoryItemChangedNotification = @"WebHistoryItemChangedNotification"; using namespace WebCore; static inline WebHistoryItemPrivate* kitPrivate(WebCoreHistoryItem* list) { return (WebHistoryItemPrivate*)list; } static inline WebCoreHistoryItem* core(WebHistoryItemPrivate* list) { return (WebCoreHistoryItem*)list; } HashMap& historyItemWrappers() { static HashMap historyItemWrappers; return historyItemWrappers; } void WKNotifyHistoryItemChanged() { [[NSNotificationCenter defaultCenter] postNotificationName:WebHistoryItemChangedNotification object:nil userInfo:nil]; } @implementation WebHistoryItem #ifndef BUILDING_ON_TIGER + (void)initialize { WebCoreObjCFinalizeOnMainThread(self); } #endif - (id)init { return [self initWithWebCoreHistoryItem:(new HistoryItem)]; } - (id)initWithURLString:(NSString *)URLString title:(NSString *)title lastVisitedTimeInterval:(NSTimeInterval)time { WebCoreThreadViolationCheck(); return [self initWithWebCoreHistoryItem:(new HistoryItem(URLString, title, time))]; } - (void)dealloc { WebCoreThreadViolationCheck(); if (_private) { HistoryItem* coreItem = core(_private); coreItem->deref(); historyItemWrappers().remove(coreItem); } [super dealloc]; } - (void)finalize { WebCoreThreadViolationCheck(); // FIXME: ~HistoryItem is what releases the history item's icon from the icon database // It's probably not good to release icons from the database only when the object is garbage-collected. // Need to change design so this happens at a predictable time. if (_private) { HistoryItem* coreItem = core(_private); coreItem->deref(); historyItemWrappers().remove(coreItem); } [super finalize]; } - (id)copyWithZone:(NSZone *)zone { WebCoreThreadViolationCheck(); WebHistoryItem *copy = (WebHistoryItem *)NSCopyObject(self, 0, zone); RefPtr item = core(_private)->copy(); copy->_private = kitPrivate(item.get()); historyItemWrappers().set(item.release().releaseRef(), copy); return copy; } // FIXME: Need to decide if this class ever returns URLs and decide on the name of this method - (NSString *)URLString { ASSERT_MAIN_THREAD(); return nsStringNilIfEmpty(core(_private)->urlString()); } // The first URL we loaded to get to where this history item points. Includes both client // and server redirects. - (NSString *)originalURLString { ASSERT_MAIN_THREAD(); return nsStringNilIfEmpty(core(_private)->originalURLString()); } - (NSString *)title { ASSERT_MAIN_THREAD(); return nsStringNilIfEmpty(core(_private)->title()); } - (void)setAlternateTitle:(NSString *)alternateTitle { core(_private)->setAlternateTitle(alternateTitle); } - (NSString *)alternateTitle; { return nsStringNilIfEmpty(core(_private)->alternateTitle()); } - (NSImage *)icon { return [[WebIconDatabase sharedIconDatabase] iconForURL:[self URLString] withSize:WebIconSmallSize]; // FIXME: Ideally, this code should simply be the following - // return core(_private)->icon()->getNSImage(); // Once radar - // - NSImage returned from WebCore::Image may be incorrect size // is resolved } - (NSTimeInterval)lastVisitedTimeInterval { ASSERT_MAIN_THREAD(); return core(_private)->lastVisitedTime(); } - (unsigned)hash { return [(NSString*)core(_private)->urlString() hash]; } - (BOOL)isEqual:(id)anObject { ASSERT_MAIN_THREAD(); if (![anObject isMemberOfClass:[WebHistoryItem class]]) { return NO; } return core(_private)->urlString() == core(((WebHistoryItem*)anObject)->_private)->urlString(); } - (NSString *)description { ASSERT_MAIN_THREAD(); HistoryItem* coreItem = core(_private); NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@", [super description], (NSString*)coreItem->urlString()]; if (coreItem->target()) { [result appendFormat:@" in \"%@\"", (NSString*)coreItem->target()]; } if (coreItem->isTargetItem()) { [result appendString:@" *target*"]; } if (coreItem->formData()) { [result appendString:@" *POST*"]; } if (coreItem->children().size()) { const HistoryItemVector& children = coreItem->children(); int currPos = [result length]; unsigned size = children.size(); for (unsigned i = 0; i < size; ++i) { WebHistoryItem *child = kit(children[i].get()); [result appendString:@"\n"]; [result appendString:[child description]]; } // shift all the contents over. A bit slow, but hey, this is for debugging. NSRange replRange = {currPos, [result length]-currPos}; [result replaceOccurrencesOfString:@"\n" withString:@"\n " options:0 range:replRange]; } return result; } @end @interface WebWindowWatcher : NSObject @end @implementation WebHistoryItem (WebInternal) HistoryItem* core(WebHistoryItem *item) { if (!item) return 0; return core(item->_private); } WebHistoryItem *kit(HistoryItem* item) { if (!item) return nil; WebHistoryItem *kitItem = historyItemWrappers().get(item); if (kitItem) return kitItem; return [[[WebHistoryItem alloc] initWithWebCoreHistoryItem:item] autorelease]; } + (WebHistoryItem *)entryWithURL:(NSURL *)URL { return [[[self alloc] initWithURL:URL title:nil] autorelease]; } static WebWindowWatcher *_windowWatcher = nil; + (void)initWindowWatcherIfNecessary { if (_windowWatcher) return; _windowWatcher = [[WebWindowWatcher alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:_windowWatcher selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:nil]; } - (id)initWithURL:(NSURL *)URL target:(NSString *)target parent:(NSString *)parent title:(NSString *)title { return [self initWithWebCoreHistoryItem:(new HistoryItem(URL, target, parent, title))]; } - (id)initWithWebCoreHistoryItem:(PassRefPtr)item { WebCoreThreadViolationCheck(); // Need to tell WebCore what function to call for the // "History Item has Changed" notification - no harm in doing this // everytime a WebHistoryItem is created // Note: We also do this in [WebFrameView initWithFrame:] where we do // other "init before WebKit is used" type things WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged; self = [super init]; _private = kitPrivate(item.releaseRef()); ASSERT(!historyItemWrappers().get(core(_private))); historyItemWrappers().set(core(_private), self); return self; } - (void)setTitle:(NSString *)title { core(_private)->setTitle(title); } - (void)setVisitCount:(int)count { core(_private)->setVisitCount(count); } - (void)setViewState:(id)statePList; { core(_private)->setViewState(statePList); } - (void)_mergeAutoCompleteHints:(WebHistoryItem *)otherItem { ASSERT_ARG(otherItem, otherItem); core(_private)->mergeAutoCompleteHints(core(otherItem->_private)); } - (id)initFromDictionaryRepresentation:(NSDictionary *)dict { ASSERT_MAIN_THREAD(); NSString *URLString = [dict _webkit_stringForKey:@""]; NSString *title = [dict _webkit_stringForKey:WebTitleKey]; // Do an existence check to avoid calling doubleValue on a nil string. Leave // time interval at 0 if there's no value in dict. NSString *timeIntervalString = [dict _webkit_stringForKey:WebLastVisitedTimeIntervalKey]; NSTimeInterval lastVisited = timeIntervalString == nil ? 0 : [timeIntervalString doubleValue]; self = [self initWithURLString:URLString title:title lastVisitedTimeInterval:lastVisited]; // Check if we've read a broken URL from the file that has non-Latin1 chars. If so, try to convert // as if it was from user typing. if (![URLString canBeConvertedToEncoding:NSISOLatin1StringEncoding]) { NSURL *tempURL = [NSURL _web_URLWithUserTypedString:URLString]; ASSERT(tempURL); NSString *newURLString = [tempURL _web_originalDataAsString]; core(_private)->setURLString(newURLString); core(_private)->setOriginalURLString(newURLString); } core(_private)->setAlternateTitle([dict _webkit_stringForKey:WebDisplayTitleKey]); core(_private)->setVisitCount([dict _webkit_intForKey:WebVisitCountKey]); NSArray *childDicts = [dict objectForKey:WebChildrenKey]; if (childDicts) { for (int i = [childDicts count]; i >= 0; i--) { WebHistoryItem *child = [[WebHistoryItem alloc] initFromDictionaryRepresentation: [childDicts objectAtIndex:i]]; core(_private)->addChildItem(core(child->_private)); [child release]; } } return self; } - (NSPoint)scrollPoint { ASSERT_MAIN_THREAD(); return core(_private)->scrollPoint(); } @end @implementation WebHistoryItem (WebPrivate) - (id)initWithURL:(NSURL *)URL title:(NSString *)title { return [self initWithURLString:[URL _web_originalDataAsString] title:title lastVisitedTimeInterval:0]; } - (NSDictionary *)dictionaryRepresentation { ASSERT_MAIN_THREAD(); NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:6]; HistoryItem* coreItem = core(_private); if (!coreItem->urlString().isEmpty()) { [dict setObject:(NSString*)coreItem->urlString() forKey:@""]; } if (!coreItem->title().isEmpty()) { [dict setObject:(NSString*)coreItem->title() forKey:WebTitleKey]; } if (!coreItem->alternateTitle().isEmpty()) { [dict setObject:(NSString*)coreItem->alternateTitle() forKey:WebDisplayTitleKey]; } if (coreItem->lastVisitedTime() != 0.0) { // store as a string to maintain backward compatibility (see 3245793) [dict setObject:[NSString stringWithFormat:@"%.1lf", coreItem->lastVisitedTime()] forKey:WebLastVisitedTimeIntervalKey]; } if (coreItem->visitCount()) { [dict setObject:[NSNumber numberWithInt:coreItem->visitCount()] forKey:WebVisitCountKey]; } if (coreItem->children().size()) { const HistoryItemVector& children = coreItem->children(); NSMutableArray *childDicts = [NSMutableArray arrayWithCapacity:children.size()]; for (int i = children.size(); i >= 0; i--) [childDicts addObject:[kit(children[i].get()) dictionaryRepresentation]]; [dict setObject: childDicts forKey:WebChildrenKey]; } return dict; } - (NSString *)target { ASSERT_MAIN_THREAD(); return nsStringNilIfEmpty(core(_private)->target()); } - (BOOL)isTargetItem { return core(_private)->isTargetItem(); } - (int)visitCount { ASSERT_MAIN_THREAD(); return core(_private)->visitCount(); } - (NSString *)RSSFeedReferrer { return nsStringNilIfEmpty(core(_private)->rssFeedReferrer()); } - (void)setRSSFeedReferrer:(NSString *)referrer { core(_private)->setRSSFeedReferrer(referrer); } - (NSArray *)children { ASSERT_MAIN_THREAD(); const HistoryItemVector& children = core(_private)->children(); if (!children.size()) return nil; unsigned size = children.size(); NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease]; for (unsigned i = 0; i < size; ++i) [result addObject:kit(children[i].get())]; return result; } - (void)setAlwaysAttemptToUsePageCache:(BOOL)flag { // Safari 2.0 uses this for SnapBack, so we stub it out to avoid a crash. } - (NSURL *)URL { ASSERT_MAIN_THREAD(); KURL url = core(_private)->url(); return url.isEmpty() ? nil : url.getNSURL(); } // This should not be called directly for WebHistoryItems that are already included // in WebHistory. Use -[WebHistory setLastVisitedTimeInterval:forItem:] instead. - (void)_setLastVisitedTimeInterval:(NSTimeInterval)time { core(_private)->setLastVisitedTime(time); } // FIXME: - Push Global History into WebCore // Once that task is complete, this accessor can go away - (NSCalendarDate *)_lastVisitedDate { ASSERT_MAIN_THREAD(); return [[[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate:core(_private)->lastVisitedTime()] autorelease]; } - (WebHistoryItem *)targetItem { ASSERT_MAIN_THREAD(); HistoryItem* coreItem = core(_private); if (coreItem->isTargetItem() || !coreItem->hasChildren()) return self; return kit(coreItem->recurseToFindTargetItem()); } + (void)_releaseAllPendingPageCaches { pageCache()->releaseAutoreleasedPagesNow(); } - (id)_transientPropertyForKey:(NSString *)key { return core(_private)->getTransientProperty(key); } - (void)_setTransientProperty:(id)property forKey:(NSString *)key { core(_private)->setTransientProperty(key, property); } @end // FIXME: // This is a bizarre policy - we flush the page caches ANY time ANY window is closed? @implementation WebWindowWatcher -(void)windowWillClose:(NSNotification *)notification { pageCache()->releaseAutoreleasedPagesNow(); } @end