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/RunLoop.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>
57
#import <wtf/text/WTFString.h>
59
// Private keys used in the WebHistoryItem's dictionary representation.
60
// see 3245793 for explanation of "lastVisitedDate"
61
static NSString *lastVisitedTimeIntervalKey = @"lastVisitedDate";
62
static NSString *visitCountKey = @"visitCount";
63
static NSString *titleKey = @"title";
64
static NSString *childrenKey = @"children";
65
static NSString *displayTitleKey = @"displayTitle";
66
static NSString *lastVisitWasFailureKey = @"lastVisitWasFailure";
67
static NSString *lastVisitWasHTTPNonGetKey = @"lastVisitWasHTTPNonGet";
68
static NSString *redirectURLsKey = @"redirectURLs";
69
static NSString *dailyVisitCountKey = @"D"; // short key to save space
70
static NSString *weeklyVisitCountKey = @"W"; // short key to save space
72
// Notification strings.
73
NSString *WebHistoryItemChangedNotification = @"WebHistoryItemChangedNotification";
75
using namespace WebCore;
78
typedef HashMap<HistoryItem*, WebHistoryItem*> HistoryItemMap;
80
static inline WebHistoryItemPrivate* kitPrivate(WebCoreHistoryItem* list) { return (WebHistoryItemPrivate*)list; }
81
static inline WebCoreHistoryItem* core(WebHistoryItemPrivate* list) { return (WebCoreHistoryItem*)list; }
83
static HistoryItemMap& historyItemWrappers()
85
DEFINE_STATIC_LOCAL(HistoryItemMap, historyItemWrappers, ());
86
return historyItemWrappers;
89
void WKNotifyHistoryItemChanged(HistoryItem*)
91
[[NSNotificationCenter defaultCenter]
92
postNotificationName:WebHistoryItemChangedNotification object:nil userInfo:nil];
95
@implementation WebHistoryItem
99
JSC::initializeThreading();
100
WTF::initializeMainThreadToProcessMainThread();
101
WebCore::RunLoop::initializeMainRunLoop();
102
WebCoreObjCFinalizeOnMainThread(self);
107
return [self initWithWebCoreHistoryItem:HistoryItem::create()];
110
- (id)initWithURLString:(NSString *)URLString title:(NSString *)title lastVisitedTimeInterval:(NSTimeInterval)time
112
WebCoreThreadViolationCheckRoundOne();
113
return [self initWithWebCoreHistoryItem:HistoryItem::create(URLString, title, time)];
118
if (WebCoreObjCScheduleDeallocateOnMainThread([WebHistoryItem class], self))
122
HistoryItem* coreItem = core(_private);
124
historyItemWrappers().remove(coreItem);
131
WebCoreThreadViolationCheckRoundOne();
132
// FIXME: ~HistoryItem is what releases the history item's icon from the icon database
133
// It's probably not good to release icons from the database only when the object is garbage-collected.
134
// Need to change design so this happens at a predictable time.
136
HistoryItem* coreItem = core(_private);
138
historyItemWrappers().remove(coreItem);
143
- (id)copyWithZone:(NSZone *)zone
145
WebCoreThreadViolationCheckRoundOne();
146
WebHistoryItem *copy = [[[self class] alloc] initWithWebCoreHistoryItem:core(_private)->copy()];
147
historyItemWrappers().set(core(copy->_private), 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 = { static_cast<NSUInteger>(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.leakRef());
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();