2 * Copyright (C) 2005, 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #import "WebHistoryItemInternal.h"
30 #import "WebHistoryItemPrivate.h"
32 #import "WebFrameInternal.h"
33 #import "WebFrameView.h"
34 #import "WebHTMLViewInternal.h"
35 #import "WebIconDatabase.h"
36 #import "WebKitLogging.h"
37 #import "WebKitNSStringExtras.h"
38 #import "WebNSArrayExtras.h"
39 #import "WebNSDictionaryExtras.h"
40 #import "WebNSObjectExtras.h"
41 #import "WebNSURLExtras.h"
42 #import "WebNSURLRequestExtras.h"
43 #import "WebNSViewExtras.h"
44 #import "WebPluginController.h"
45 #import "WebTypesInternal.h"
46 #import <WebCore/HistoryItem.h>
47 #import <WebCore/Image.h>
48 #import <WebCore/KURL.h>
49 #import <WebCore/PageCache.h>
50 #import <WebCore/PlatformString.h>
51 #import <WebCore/ThreadCheck.h>
52 #import <WebCore/WebCoreObjCExtras.h>
53 #import <runtime/InitializeThreading.h>
54 #import <wtf/Assertions.h>
55 #import <wtf/MainThread.h>
56 #import <wtf/StdLibExtras.h>
58 // Private keys used in the WebHistoryItem's dictionary representation.
59 // see 3245793 for explanation of "lastVisitedDate"
60 static NSString *lastVisitedTimeIntervalKey = @"lastVisitedDate";
61 static NSString *visitCountKey = @"visitCount";
62 static NSString *titleKey = @"title";
63 static NSString *childrenKey = @"children";
64 static NSString *displayTitleKey = @"displayTitle";
65 static NSString *lastVisitWasFailureKey = @"lastVisitWasFailure";
66 static NSString *lastVisitWasHTTPNonGetKey = @"lastVisitWasHTTPNonGet";
67 static NSString *redirectURLsKey = @"redirectURLs";
68 static NSString *dailyVisitCountKey = @"D"; // short key to save space
69 static NSString *weeklyVisitCountKey = @"W"; // short key to save space
71 // Notification strings.
72 NSString *WebHistoryItemChangedNotification = @"WebHistoryItemChangedNotification";
74 using namespace WebCore;
77 typedef HashMap<HistoryItem*, WebHistoryItem*> HistoryItemMap;
79 static inline WebHistoryItemPrivate* kitPrivate(WebCoreHistoryItem* list) { return (WebHistoryItemPrivate*)list; }
80 static inline WebCoreHistoryItem* core(WebHistoryItemPrivate* list) { return (WebCoreHistoryItem*)list; }
82 static HistoryItemMap& historyItemWrappers()
84 DEFINE_STATIC_LOCAL(HistoryItemMap, historyItemWrappers, ());
85 return historyItemWrappers;
88 void WKNotifyHistoryItemChanged(HistoryItem*)
90 [[NSNotificationCenter defaultCenter]
91 postNotificationName:WebHistoryItemChangedNotification object:nil userInfo:nil];
94 @implementation WebHistoryItem
98 JSC::initializeThreading();
99 WTF::initializeMainThreadToProcessMainThread();
100 WebCoreObjCFinalizeOnMainThread(self);
105 return [self initWithWebCoreHistoryItem:HistoryItem::create()];
108 - (id)initWithURLString:(NSString *)URLString title:(NSString *)title lastVisitedTimeInterval:(NSTimeInterval)time
110 WebCoreThreadViolationCheckRoundOne();
111 return [self initWithWebCoreHistoryItem:HistoryItem::create(URLString, title, time)];
116 if (WebCoreObjCScheduleDeallocateOnMainThread([WebHistoryItem class], self))
120 HistoryItem* coreItem = core(_private);
122 historyItemWrappers().remove(coreItem);
129 WebCoreThreadViolationCheckRoundOne();
130 // FIXME: ~HistoryItem is what releases the history item's icon from the icon database
131 // It's probably not good to release icons from the database only when the object is garbage-collected.
132 // Need to change design so this happens at a predictable time.
134 HistoryItem* coreItem = core(_private);
136 historyItemWrappers().remove(coreItem);
141 - (id)copyWithZone:(NSZone *)zone
143 WebCoreThreadViolationCheckRoundOne();
144 WebHistoryItem *copy = (WebHistoryItem *)NSCopyObject(self, 0, zone);
145 RefPtr<HistoryItem> item = core(_private)->copy();
146 copy->_private = kitPrivate(item.get());
147 historyItemWrappers().set(item.release().releaseRef(), copy);
152 // FIXME: Need to decide if this class ever returns URLs and decide on the name of this method
153 - (NSString *)URLString
155 ASSERT_MAIN_THREAD();
156 return nsStringNilIfEmpty(core(_private)->urlString());
159 // The first URL we loaded to get to where this history item points. Includes both client
160 // and server redirects.
161 - (NSString *)originalURLString
163 ASSERT_MAIN_THREAD();
164 return nsStringNilIfEmpty(core(_private)->originalURLString());
169 ASSERT_MAIN_THREAD();
170 return nsStringNilIfEmpty(core(_private)->title());
173 - (void)setAlternateTitle:(NSString *)alternateTitle
175 core(_private)->setAlternateTitle(alternateTitle);
178 - (NSString *)alternateTitle
180 return nsStringNilIfEmpty(core(_private)->alternateTitle());
185 return [[WebIconDatabase sharedIconDatabase] iconForURL:[self URLString] withSize:WebIconSmallSize];
188 - (NSTimeInterval)lastVisitedTimeInterval
190 ASSERT_MAIN_THREAD();
191 return core(_private)->lastVisitedTime();
196 return [(NSString*)core(_private)->urlString() hash];
199 - (BOOL)isEqual:(id)anObject
201 ASSERT_MAIN_THREAD();
202 if (![anObject isMemberOfClass:[WebHistoryItem class]]) {
206 return core(_private)->urlString() == core(((WebHistoryItem*)anObject)->_private)->urlString();
209 - (NSString *)description
211 ASSERT_MAIN_THREAD();
212 HistoryItem* coreItem = core(_private);
213 NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@", [super description], (NSString*)coreItem->urlString()];
214 if (!coreItem->target().isEmpty()) {
215 NSString *target = coreItem->target();
216 [result appendFormat:@" in \"%@\"", target];
218 if (coreItem->isTargetItem()) {
219 [result appendString:@" *target*"];
221 if (coreItem->formData()) {
222 [result appendString:@" *POST*"];
225 if (coreItem->children().size()) {
226 const HistoryItemVector& children = coreItem->children();
227 int currPos = [result length];
228 unsigned size = children.size();
229 for (unsigned i = 0; i < size; ++i) {
230 WebHistoryItem *child = kit(children[i].get());
231 [result appendString:@"\n"];
232 [result appendString:[child description]];
234 // shift all the contents over. A bit slow, but hey, this is for debugging.
235 NSRange replRange = {currPos, [result length]-currPos};
236 [result replaceOccurrencesOfString:@"\n" withString:@"\n " options:0 range:replRange];
244 @interface WebWindowWatcher : NSObject
248 @implementation WebHistoryItem (WebInternal)
250 HistoryItem* core(WebHistoryItem *item)
255 ASSERT(historyItemWrappers().get(core(item->_private)) == item);
257 return core(item->_private);
260 WebHistoryItem *kit(HistoryItem* item)
265 WebHistoryItem *kitItem = historyItemWrappers().get(item);
269 return [[[WebHistoryItem alloc] initWithWebCoreHistoryItem:item] autorelease];
272 + (WebHistoryItem *)entryWithURL:(NSURL *)URL
274 return [[[self alloc] initWithURL:URL title:nil] autorelease];
277 static WebWindowWatcher *_windowWatcher = nil;
279 + (void)initWindowWatcherIfNecessary
283 _windowWatcher = [[WebWindowWatcher alloc] init];
284 [[NSNotificationCenter defaultCenter] addObserver:_windowWatcher selector:@selector(windowWillClose:)
285 name:NSWindowWillCloseNotification object:nil];
288 - (id)initWithURL:(NSURL *)URL target:(NSString *)target parent:(NSString *)parent title:(NSString *)title
290 return [self initWithWebCoreHistoryItem:HistoryItem::create(URL, target, parent, title)];
293 - (id)initWithURLString:(NSString *)URLString title:(NSString *)title displayTitle:(NSString *)displayTitle lastVisitedTimeInterval:(NSTimeInterval)time
295 return [self initWithWebCoreHistoryItem:HistoryItem::create(URLString, title, displayTitle, time)];
298 - (id)initWithWebCoreHistoryItem:(PassRefPtr<HistoryItem>)item
300 WebCoreThreadViolationCheckRoundOne();
301 // Need to tell WebCore what function to call for the
302 // "History Item has Changed" notification - no harm in doing this
303 // everytime a WebHistoryItem is created
304 // Note: We also do this in [WebFrameView initWithFrame:] where we do
305 // other "init before WebKit is used" type things
306 WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
310 _private = kitPrivate(item.releaseRef());
311 ASSERT(!historyItemWrappers().get(core(_private)));
312 historyItemWrappers().set(core(_private), self);
316 - (void)setTitle:(NSString *)title
318 core(_private)->setTitle(title);
321 - (void)setVisitCount:(int)count
323 core(_private)->setVisitCount(count);
326 - (void)setViewState:(id)statePList
328 core(_private)->setViewState(statePList);
331 - (void)_mergeAutoCompleteHints:(WebHistoryItem *)otherItem
333 ASSERT_ARG(otherItem, otherItem);
334 core(_private)->mergeAutoCompleteHints(core(otherItem->_private));
337 - (id)initFromDictionaryRepresentation:(NSDictionary *)dict
339 ASSERT_MAIN_THREAD();
340 NSString *URLString = [dict _webkit_stringForKey:@""];
341 NSString *title = [dict _webkit_stringForKey:titleKey];
343 // Do an existence check to avoid calling doubleValue on a nil string. Leave
344 // time interval at 0 if there's no value in dict.
345 NSString *timeIntervalString = [dict _webkit_stringForKey:lastVisitedTimeIntervalKey];
346 NSTimeInterval lastVisited = timeIntervalString == nil ? 0 : [timeIntervalString doubleValue];
348 self = [self initWithURLString:URLString title:title displayTitle:[dict _webkit_stringForKey:displayTitleKey] lastVisitedTimeInterval:lastVisited];
350 // Check if we've read a broken URL from the file that has non-Latin1 chars. If so, try to convert
351 // as if it was from user typing.
352 if (![URLString canBeConvertedToEncoding:NSISOLatin1StringEncoding]) {
353 NSURL *tempURL = [NSURL _web_URLWithUserTypedString:URLString];
355 NSString *newURLString = [tempURL _web_originalDataAsString];
356 core(_private)->setURLString(newURLString);
357 core(_private)->setOriginalURLString(newURLString);
360 int visitCount = [dict _webkit_intForKey:visitCountKey];
362 // Can't trust data on disk, and we've had at least one report of this (<rdar://6572300>).
363 if (visitCount < 0) {
364 LOG_ERROR("visit count for history item \"%@\" is negative (%d), will be reset to 1", URLString, visitCount);
367 core(_private)->setVisitCount(visitCount);
369 if ([dict _webkit_boolForKey:lastVisitWasFailureKey])
370 core(_private)->setLastVisitWasFailure(true);
372 BOOL lastVisitWasHTTPNonGet = [dict _webkit_boolForKey:lastVisitWasHTTPNonGetKey];
373 NSString *tempURLString = [URLString lowercaseString];
374 if (lastVisitWasHTTPNonGet && ([tempURLString hasPrefix:@"http:"] || [tempURLString hasPrefix:@"https:"]))
375 core(_private)->setLastVisitWasHTTPNonGet(lastVisitWasHTTPNonGet);
377 if (NSArray *redirectURLs = [dict _webkit_arrayForKey:redirectURLsKey]) {
378 NSUInteger size = [redirectURLs count];
379 OwnPtr<Vector<String> > redirectURLsVector = adoptPtr(new Vector<String>(size));
380 for (NSUInteger i = 0; i < size; ++i)
381 (*redirectURLsVector)[i] = String([redirectURLs _webkit_stringAtIndex:i]);
382 core(_private)->setRedirectURLs(redirectURLsVector.release());
385 NSArray *dailyCounts = [dict _webkit_arrayForKey:dailyVisitCountKey];
386 NSArray *weeklyCounts = [dict _webkit_arrayForKey:weeklyVisitCountKey];
387 if (dailyCounts || weeklyCounts) {
388 Vector<int> coreDailyCounts([dailyCounts count]);
389 Vector<int> coreWeeklyCounts([weeklyCounts count]);
391 // Daily and weekly counts < 0 are errors in the data read from disk, so reset to 0.
392 for (size_t i = 0; i < coreDailyCounts.size(); ++i)
393 coreDailyCounts[i] = max([[dailyCounts _webkit_numberAtIndex:i] intValue], 0);
394 for (size_t i = 0; i < coreWeeklyCounts.size(); ++i)
395 coreWeeklyCounts[i] = max([[weeklyCounts _webkit_numberAtIndex:i] intValue], 0);
397 core(_private)->adoptVisitCounts(coreDailyCounts, coreWeeklyCounts);
400 NSArray *childDicts = [dict objectForKey:childrenKey];
402 for (int i = [childDicts count] - 1; i >= 0; i--) {
403 WebHistoryItem *child = [[WebHistoryItem alloc] initFromDictionaryRepresentation:[childDicts objectAtIndex:i]];
404 core(_private)->addChildItem(core(child->_private));
412 - (NSPoint)scrollPoint
414 ASSERT_MAIN_THREAD();
415 return core(_private)->scrollPoint();
418 - (void)_visitedWithTitle:(NSString *)title increaseVisitCount:(BOOL)increaseVisitCount
420 core(_private)->visited(title, [NSDate timeIntervalSinceReferenceDate], increaseVisitCount ? IncreaseVisitCount : DoNotIncreaseVisitCount);
423 - (void)_recordInitialVisit
425 core(_private)->recordInitialVisit();
430 @implementation WebHistoryItem (WebPrivate)
432 - (id)initWithURL:(NSURL *)URL title:(NSString *)title
434 return [self initWithURLString:[URL _web_originalDataAsString] title:title lastVisitedTimeInterval:0];
437 - (NSDictionary *)dictionaryRepresentation
439 ASSERT_MAIN_THREAD();
440 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:8];
442 HistoryItem* coreItem = core(_private);
444 if (!coreItem->urlString().isEmpty())
445 [dict setObject:(NSString*)coreItem->urlString() forKey:@""];
446 if (!coreItem->title().isEmpty())
447 [dict setObject:(NSString*)coreItem->title() forKey:titleKey];
448 if (!coreItem->alternateTitle().isEmpty())
449 [dict setObject:(NSString*)coreItem->alternateTitle() forKey:displayTitleKey];
450 if (coreItem->lastVisitedTime() != 0.0) {
451 // Store as a string to maintain backward compatibility. (See 3245793)
452 [dict setObject:[NSString stringWithFormat:@"%.1lf", coreItem->lastVisitedTime()]
453 forKey:lastVisitedTimeIntervalKey];
455 if (coreItem->visitCount())
456 [dict setObject:[NSNumber numberWithInt:coreItem->visitCount()] forKey:visitCountKey];
457 if (coreItem->lastVisitWasFailure())
458 [dict setObject:[NSNumber numberWithBool:YES] forKey:lastVisitWasFailureKey];
459 if (coreItem->lastVisitWasHTTPNonGet()) {
460 ASSERT(coreItem->urlString().startsWith("http:", false) || coreItem->urlString().startsWith("https:", false));
461 [dict setObject:[NSNumber numberWithBool:YES] forKey:lastVisitWasHTTPNonGetKey];
463 if (Vector<String>* redirectURLs = coreItem->redirectURLs()) {
464 size_t size = redirectURLs->size();
466 NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:size];
467 for (size_t i = 0; i < size; ++i)
468 [result addObject:(NSString*)redirectURLs->at(i)];
469 [dict setObject:result forKey:redirectURLsKey];
473 const Vector<int>& dailyVisitCounts = coreItem->dailyVisitCounts();
474 if (dailyVisitCounts.size()) {
475 NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:13];
476 for (size_t i = 0; i < dailyVisitCounts.size(); ++i)
477 [array addObject:[NSNumber numberWithInt:dailyVisitCounts[i]]];
478 [dict setObject:array forKey:dailyVisitCountKey];
482 const Vector<int>& weeklyVisitCounts = coreItem->weeklyVisitCounts();
483 if (weeklyVisitCounts.size()) {
484 NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:5];
485 for (size_t i = 0; i < weeklyVisitCounts.size(); ++i)
486 [array addObject:[NSNumber numberWithInt:weeklyVisitCounts[i]]];
487 [dict setObject:array forKey:weeklyVisitCountKey];
491 if (coreItem->children().size()) {
492 const HistoryItemVector& children = coreItem->children();
493 NSMutableArray *childDicts = [NSMutableArray arrayWithCapacity:children.size()];
495 for (int i = children.size() - 1; i >= 0; i--)
496 [childDicts addObject:[kit(children[i].get()) dictionaryRepresentation]];
497 [dict setObject: childDicts forKey:childrenKey];
505 ASSERT_MAIN_THREAD();
506 return nsStringNilIfEmpty(core(_private)->target());
511 return core(_private)->isTargetItem();
516 ASSERT_MAIN_THREAD();
517 return core(_private)->visitCount();
520 - (NSString *)RSSFeedReferrer
522 return nsStringNilIfEmpty(core(_private)->referrer());
525 - (void)setRSSFeedReferrer:(NSString *)referrer
527 core(_private)->setReferrer(referrer);
530 - (NSArray *)children
532 ASSERT_MAIN_THREAD();
533 const HistoryItemVector& children = core(_private)->children();
534 if (!children.size())
537 unsigned size = children.size();
538 NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease];
540 for (unsigned i = 0; i < size; ++i)
541 [result addObject:kit(children[i].get())];
546 - (void)setAlwaysAttemptToUsePageCache:(BOOL)flag
548 // Safari 2.0 uses this for SnapBack, so we stub it out to avoid a crash.
553 ASSERT_MAIN_THREAD();
554 const KURL& url = core(_private)->url();
560 // This should not be called directly for WebHistoryItems that are already included
561 // in WebHistory. Use -[WebHistory setLastVisitedTimeInterval:forItem:] instead.
562 - (void)_setLastVisitedTimeInterval:(NSTimeInterval)time
564 core(_private)->setLastVisitedTime(time);
567 // FIXME: <rdar://problem/4880065> - Push Global History into WebCore
568 // Once that task is complete, this accessor can go away
569 - (NSCalendarDate *)_lastVisitedDate
571 ASSERT_MAIN_THREAD();
572 return [[[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate:core(_private)->lastVisitedTime()] autorelease];
575 - (WebHistoryItem *)targetItem
577 ASSERT_MAIN_THREAD();
578 return kit(core(_private)->targetItem());
581 + (void)_releaseAllPendingPageCaches
583 pageCache()->releaseAutoreleasedPagesNow();
586 - (id)_transientPropertyForKey:(NSString *)key
588 return core(_private)->getTransientProperty(key);
591 - (void)_setTransientProperty:(id)property forKey:(NSString *)key
593 core(_private)->setTransientProperty(key, property);
596 - (BOOL)lastVisitWasFailure
598 return core(_private)->lastVisitWasFailure();
601 - (void)_setLastVisitWasFailure:(BOOL)failure
603 core(_private)->setLastVisitWasFailure(failure);
606 - (BOOL)_lastVisitWasHTTPNonGet
608 return core(_private)->lastVisitWasHTTPNonGet();
611 - (NSArray *)_redirectURLs
613 Vector<String>* redirectURLs = core(_private)->redirectURLs();
617 size_t size = redirectURLs->size();
619 NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:size];
620 for (size_t i = 0; i < size; ++i)
621 [result addObject:(NSString*)redirectURLs->at(i)];
622 return [result autorelease];
625 - (size_t)_getDailyVisitCounts:(const int**)counts
627 HistoryItem* coreItem = core(_private);
628 *counts = coreItem->dailyVisitCounts().data();
629 return coreItem->dailyVisitCounts().size();
632 - (size_t)_getWeeklyVisitCounts:(const int**)counts
634 HistoryItem* coreItem = core(_private);
635 *counts = coreItem->weeklyVisitCounts().data();
636 return coreItem->weeklyVisitCounts().size();
642 // FIXME: <rdar://problem/4886761>.
643 // This is a bizarre policy. We flush the page caches ANY time ANY window is closed?
645 @implementation WebWindowWatcher
647 - (void)windowWillClose:(NSNotification *)notification
649 if (!pthread_main_np()) {
650 [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
654 pageCache()->releaseAutoreleasedPagesNow();