46f45fe8402df3c04451732fcc1463025cef0479
[WebKit-https.git] / WebKit / History.subproj / WebHistoryItem.m
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28
29 #import <WebKit/WebHistoryItemPrivate.h>
30
31 #import <WebKit/WebAssertions.h>
32 #import <WebKit/WebFramePrivate.h>
33 #import <WebKit/WebFrameView.h>
34 #import <WebKit/WebHTMLViewPrivate.h>
35 #import <WebKit/WebIconDatabase.h>
36 #import <WebKit/WebIconLoader.h>
37 #import <WebKit/WebKitLogging.h>
38 #import <WebKit/WebKitNSStringExtras.h>
39 #import <WebKit/WebNSDictionaryExtras.h>
40 #import <WebKit/WebNSObjectExtras.h>
41 #import <WebKit/WebNSURLExtras.h>
42 #import <WebKit/WebNSURLRequestExtras.h>
43 #import <WebKit/WebNSViewExtras.h>
44 #import <WebKit/WebPluginController.h>
45
46 #import <CoreGraphics/CoreGraphicsPrivate.h>
47
48 // Private keys used in the WebHistoryItem's dictionary representation.
49 // see 3245793 for explanation of "lastVisitedDate"
50 static NSString *WebLastVisitedTimeIntervalKey = @"lastVisitedDate";
51 static NSString *WebVisitCountKey = @"visitCount";
52 static NSString *WebTitleKey = @"title";
53 static NSString *WebChildrenKey = @"children";
54 static NSString *WebDisplayTitleKey = @"displayTitle";
55
56 // Notification strings.
57 NSString *WebHistoryItemChangedNotification = @"WebHistoryItemChangedNotification";
58
59 @interface WebHistoryItemPrivate : NSObject
60 {
61 @public
62     NSString *URLString;
63     NSString *originalURLString;
64     NSString *target;
65     NSString *parent;
66     NSString *title;
67     NSString *displayTitle;
68     NSCalendarDate *lastVisitedDate;
69     NSTimeInterval lastVisitedTimeInterval;
70     NSPoint scrollPoint;
71     NSArray *documentState;
72     NSMutableArray *subItems;
73     NSMutableDictionary *pageCache;
74     BOOL isTargetItem;
75     BOOL alwaysAttemptToUsePageCache;
76     int visitCount;
77     // info used to repost form data
78     NSArray *formData;
79     NSString *formContentType;
80     NSString *formReferrer;
81     // info used to support RSS feeds
82     NSString *RSSFeedReferrer;
83 }
84 @end
85
86 @implementation WebHistoryItemPrivate
87 - (void)dealloc
88 {
89     [URLString release];
90     [originalURLString release];
91     [target release];
92     [parent release];
93     [title release];
94     [displayTitle release];
95     [lastVisitedDate release];
96     [documentState release];
97     [subItems release];
98     [pageCache release];
99     [formData release];
100     [formContentType release];
101     [formReferrer release];
102     [RSSFeedReferrer release];
103
104     [super dealloc];
105 }
106 @end
107
108 @implementation WebHistoryItem
109
110 - (id)init
111 {
112     return [self initWithURLString:nil title:nil lastVisitedTimeInterval:0];
113 }
114
115 - (id)initWithURLString:(NSString *)URLString title:(NSString *)title lastVisitedTimeInterval:(NSTimeInterval)time
116 {
117     self = [super init];
118     _private = [[WebHistoryItemPrivate alloc] init];
119     _private->lastVisitedTimeInterval = time;
120     _private->title = [title copy];
121     _private->URLString = [URLString copy];
122     _private->originalURLString = [_private->URLString retain];
123     [self _retainIconInDatabase:YES];
124     return self;
125 }
126
127 - (void)dealloc
128 {
129     [self _retainIconInDatabase:NO];
130
131     [_private release];
132     
133     [super dealloc];
134 }
135
136 - (void)finalize
137 {
138     // FIXME: Probably not good to release icons from the database only
139     // when the object is garbage-collected. Need to change design so
140     // this happens at a predictable time.
141     [self _retainIconInDatabase:NO];
142     [super finalize];
143 }
144
145 - (id)copyWithZone:(NSZone *)zone
146 {
147     WebHistoryItem *copy = NSCopyObject(self, 0, zone);
148     copy->_private = [[WebHistoryItemPrivate alloc] init];
149     copy->_private->URLString = [_private->URLString copy];
150     [copy _retainIconInDatabase:YES];
151     copy->_private->originalURLString = [_private->originalURLString copy];
152     copy->_private->target = [_private->target copy];
153     copy->_private->parent = [_private->parent copy];
154     copy->_private->title = [_private->title copy];
155     copy->_private->displayTitle = [_private->displayTitle copy];
156     copy->_private->lastVisitedTimeInterval = _private->lastVisitedTimeInterval;
157     copy->_private->lastVisitedDate = [_private->lastVisitedDate copy];
158     copy->_private->scrollPoint = _private->scrollPoint;
159     copy->_private->documentState = [_private->documentState copy];
160     if (_private->subItems) {
161         copy->_private->subItems = [[NSMutableArray alloc] initWithArray:_private->subItems copyItems:YES];
162     }
163     copy->_private->isTargetItem = _private->isTargetItem;
164     copy->_private->formData = [_private->formData copy];
165     copy->_private->formContentType = [_private->formContentType copy];
166     copy->_private->formReferrer = [_private->formReferrer copy];
167     copy->_private->RSSFeedReferrer = [_private->RSSFeedReferrer copy];
168
169     return copy;
170 }
171
172 // FIXME: need to decide it this class ever returns URLs, and the name of this method
173 - (NSString *)URLString
174 {
175     return _private->URLString;
176 }
177
178 // The first URL we loaded to get to where this history item points.  Includes both client
179 // and server redirects.
180 - (NSString *)originalURLString
181 {
182     return _private->originalURLString;
183 }
184
185 - (NSString *)title
186 {
187     return _private->title;
188 }
189
190 - (void)setAlternateTitle:(NSString *)alternateTitle
191 {
192     NSString *newDisplayTitle;
193     if (alternateTitle && [alternateTitle isEqualToString:_private->title]) {
194         newDisplayTitle = [_private->title retain];
195     } else {
196         newDisplayTitle = [alternateTitle copy];
197     }
198     [_private->displayTitle release];
199     _private->displayTitle = newDisplayTitle;
200
201     [[NSNotificationCenter defaultCenter]
202         postNotificationName: WebHistoryItemChangedNotification object: self userInfo: nil];
203 }
204
205
206 - (NSString *)alternateTitle;
207 {
208     return _private->displayTitle;
209 }
210
211 - (NSImage *)icon
212 {
213     // Always get fresh icon from database. It's a client's responsibility to watch
214     // for updates to the database if desired.
215     return [[WebIconDatabase sharedIconDatabase] iconForURL:_private->URLString withSize:WebIconSmallSize];
216 }
217
218
219 - (NSTimeInterval)lastVisitedTimeInterval
220 {
221     return _private->lastVisitedTimeInterval;
222 }
223
224 - (unsigned)hash
225 {
226     return [_private->URLString hash];
227 }
228
229 - (BOOL)isEqual:(id)anObject
230 {
231     if (![anObject isMemberOfClass:[WebHistoryItem class]]) {
232         return NO;
233     }
234     
235     NSString *otherURL = ((WebHistoryItem *)anObject)->_private->URLString;
236     return _private->URLString == otherURL || [_private->URLString isEqualToString:otherURL];
237 }
238
239 - (NSString *)description
240 {
241     NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@", [super description], _private->URLString];
242     if (_private->target) {
243         [result appendFormat:@" in \"%@\"", _private->target];
244     }
245     if (_private->isTargetItem) {
246         [result appendString:@" *target*"];
247     }
248     if (_private->formData) {
249         [result appendString:@" *POST*"];
250     }
251     if (_private->subItems) {
252         int currPos = [result length];
253         int i;
254         for (i = 0; i < (int)[_private->subItems count]; i++) {
255             WebHistoryItem *child = [_private->subItems objectAtIndex:i];
256             [result appendString:@"\n"];
257             [result appendString:[child description]];
258         }
259         // shift all the contents over.  A bit slow, but hey, this is for debugging.
260         NSRange replRange = {currPos, [result length]-currPos};
261         [result replaceOccurrencesOfString:@"\n" withString:@"\n    " options:0 range:replRange];
262     }
263     return result;
264 }
265
266 @end
267
268 @interface WebWindowWatcher : NSObject
269 @end
270
271 @implementation WebHistoryItem (WebPrivate)
272
273 - (void)_retainIconInDatabase:(BOOL)retain
274 {
275     if (_private->URLString) {
276         WebIconDatabase *iconDB = [WebIconDatabase sharedIconDatabase];
277         if (retain) {
278             [iconDB retainIconForURL:_private->URLString];
279         } else {
280             [iconDB releaseIconForURL:_private->URLString];
281         }
282     }
283 }
284
285 + (WebHistoryItem *)entryWithURL:(NSURL *)URL
286 {
287     return [[[self alloc] initWithURL:URL title:nil] autorelease];
288 }
289
290 - (id)initWithURL:(NSURL *)URL title:(NSString *)title
291 {
292     return [self initWithURLString:[URL _web_originalDataAsString] title:title lastVisitedTimeInterval:0];
293 }
294
295 - (id)initWithURL:(NSURL *)URL target:(NSString *)target parent:(NSString *)parent title:(NSString *)title
296 {
297     self = [self initWithURLString:[URL _web_originalDataAsString] title:title lastVisitedTimeInterval:0];
298     if (self) {
299         _private->target = [target copy];
300         _private->parent = [parent copy];
301     }
302     return self;
303 }
304
305 - (NSURL *)URL
306 {
307     return _private->URLString ? [NSURL _web_URLWithDataAsString:_private->URLString] : nil;
308 }
309
310 - (NSString *)target
311 {
312     return _private->target;
313 }
314
315 - (NSString *)parent
316 {
317     return _private->parent;
318 }
319
320 - (void)setURLString:(NSString *)string
321 {
322     if (!(string == _private->URLString || [string isEqual:_private->URLString])) {
323         [self _retainIconInDatabase:NO];
324         [_private->URLString release];
325         _private->URLString = [string copy];
326         [self _retainIconInDatabase:YES];
327     }
328     
329     [[NSNotificationCenter defaultCenter]
330         postNotificationName: WebHistoryItemChangedNotification object: self userInfo: nil];
331 }
332
333 - (void)setURL:(NSURL *)URL
334 {
335     [self setURLString:[URL _web_originalDataAsString]];
336 }
337
338 // The first URL we loaded to get to where this history item points.  Includes both client
339 // and server redirects.
340 - (void)setOriginalURLString:(NSString *)URL
341 {
342     NSString *newURL = [URL copy];
343     [_private->originalURLString release];
344     _private->originalURLString = newURL;
345
346     [[NSNotificationCenter defaultCenter]
347         postNotificationName: WebHistoryItemChangedNotification object: self userInfo: nil];
348 }
349
350 - (void)setTitle:(NSString *)title
351 {
352     NSString *newTitle;
353     if (title && [title isEqualToString:_private->displayTitle]) {
354         newTitle = [_private->displayTitle retain];
355     } else {
356         newTitle = [title copy];
357     }
358     [_private->title release];
359     _private->title = newTitle;
360
361     [[NSNotificationCenter defaultCenter]
362         postNotificationName: WebHistoryItemChangedNotification object: self userInfo: nil];
363 }
364
365 - (void)setTarget:(NSString *)target
366 {
367     NSString *copy = [target copy];
368     [_private->target release];
369     _private->target = copy;
370 }
371
372 - (void)setParent:(NSString *)parent
373 {
374     NSString *copy = [parent copy];
375     [_private->parent release];
376     _private->parent = copy;
377 }
378
379 - (void)_setLastVisitedTimeInterval:(NSTimeInterval)time
380 {
381     if (_private->lastVisitedTimeInterval != time) {
382         _private->lastVisitedTimeInterval = time;
383         [_private->lastVisitedDate release];
384         _private->lastVisitedDate = nil;
385         _private->visitCount++;
386     }
387
388     [[NSNotificationCenter defaultCenter]
389         postNotificationName: WebHistoryItemChangedNotification object: self userInfo: nil];
390 }
391
392 // FIXME:  Remove this accessor and related ivar.
393 - (NSCalendarDate *)_lastVisitedDate
394 {
395     if (!_private->lastVisitedDate){
396         _private->lastVisitedDate = [[NSCalendarDate alloc]
397                     initWithTimeIntervalSinceReferenceDate:_private->lastVisitedTimeInterval];
398     }
399     return _private->lastVisitedDate;
400 }
401
402 - (int)visitCount
403 {
404     return _private->visitCount;
405 }
406
407 - (void)setVisitCount:(int)count
408 {
409     _private->visitCount = count;
410 }
411
412 - (void)setDocumentState:(NSArray *)state;
413 {
414     NSArray *copy = [state copy];
415     [_private->documentState release];
416     _private->documentState = copy;
417 }
418
419 - (NSArray *)documentState
420 {
421     return _private->documentState;
422 }
423
424 - (NSPoint)scrollPoint
425 {
426     return _private->scrollPoint;
427 }
428
429 - (void)setScrollPoint:(NSPoint)scrollPoint
430 {
431     _private->scrollPoint = scrollPoint;
432 }
433
434 - (BOOL)isTargetItem
435 {
436     return _private->isTargetItem;
437 }
438
439 - (void)setIsTargetItem:(BOOL)flag
440 {
441     _private->isTargetItem = flag;
442 }
443
444 // Main diff from the public method is that the public method will default to returning
445 // the top item if it can't find anything marked as target and has no kids
446 - (WebHistoryItem *)_recurseToFindTargetItem
447 {
448     if (_private->isTargetItem) {
449         return self;
450     } else if (!_private->subItems) {
451         return nil;
452     } else {
453         int i;
454         for (i = [_private->subItems count]-1; i >= 0; i--) {
455             WebHistoryItem *match = [[_private->subItems objectAtIndex:i] _recurseToFindTargetItem];
456             if (match) {
457                 return match;
458             }
459         }
460         return nil;
461     }
462 }
463
464 - (WebHistoryItem *)targetItem
465 {
466     if (_private->isTargetItem || !_private->subItems) {
467         return self;
468     } else {
469         return [self _recurseToFindTargetItem];
470     }
471 }
472
473 - (void)_setFormInfoFromRequest:(NSURLRequest *)request
474 {
475     NSArray *newData = nil;
476     NSString *newContentType = nil;
477     NSString *newReferrer = nil;
478     if ([[request HTTPMethod] _webkit_isCaseInsensitiveEqualToString:@"POST"]) {
479         // save form state iff this is a POST
480
481         // FIXME: Eventually we have to make this smart enough to handle the case where
482         // we have a stream for the body to handle the "data interspersed with files" feature.
483         NSData *body = [request HTTPBody];
484         if (body) {
485             body = [body copy];
486             newData = [[NSArray alloc] initWithObjects:body, nil];
487             [body release];
488         }
489
490         newContentType = [[request _web_HTTPContentType] copy];
491         newReferrer = [[request _web_HTTPReferrer] copy];
492     }
493
494     [_private->formData release];
495     _private->formData = newData;
496
497     [_private->formContentType release];
498     _private->formContentType = newContentType;
499     
500     [_private->formReferrer release];
501     _private->formReferrer = newReferrer;
502 }
503
504 - (NSArray *)formData
505 {
506     return _private->formData;
507 }
508
509 - (NSString *)formContentType
510 {
511     return _private->formContentType;
512 }
513
514 - (NSString *)formReferrer
515 {
516     return _private->formReferrer;
517 }
518
519 - (NSString *)RSSFeedReferrer
520 {
521     return _private->RSSFeedReferrer;
522 }
523
524 - (void)setRSSFeedReferrer:(NSString *)referrer
525 {
526     NSString *copy = [referrer copy];
527     [_private->RSSFeedReferrer release];
528     _private->RSSFeedReferrer = copy;
529 }
530
531 - (NSArray *)children
532 {
533     return _private->subItems;
534 }
535
536 - (void)_mergeAutoCompleteHints:(WebHistoryItem *)otherItem
537 {
538     if (otherItem != self) {
539         _private->visitCount += otherItem->_private->visitCount;
540     }
541 }
542
543 - (void)addChildItem:(WebHistoryItem *)item
544 {
545     if (!_private->subItems) {
546         _private->subItems = [[NSMutableArray arrayWithObject:item] retain];
547     } else {
548         [_private->subItems addObject:item];
549     }
550 }
551
552 - (WebHistoryItem *)childItemWithName:(NSString *)name
553 {
554     int i;
555     for (i = (_private->subItems ? [_private->subItems count] : 0)-1; i >= 0; i--) {
556         WebHistoryItem *child = [_private->subItems objectAtIndex:i];
557         if ([[child target] isEqualToString:name]) {
558             return child;
559         }
560     }
561     return nil;
562 }
563
564 - (NSDictionary *)dictionaryRepresentation
565 {
566     NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:6];
567
568     if (_private->URLString) {
569         [dict setObject:_private->URLString forKey:@""];
570     }
571     if (_private->title) {
572         [dict setObject:_private->title forKey:WebTitleKey];
573     }
574     if (_private->displayTitle) {
575         [dict setObject:_private->displayTitle forKey:WebDisplayTitleKey];
576     }
577     if (_private->lastVisitedTimeInterval != 0.0) {
578         // store as a string to maintain backward compatibility (see 3245793)
579         [dict setObject:[NSString stringWithFormat:@"%.1lf", _private->lastVisitedTimeInterval]
580                  forKey:WebLastVisitedTimeIntervalKey];
581     }
582     if (_private->visitCount) {
583         [dict setObject:[NSNumber numberWithInt:_private->visitCount] forKey:WebVisitCountKey];
584     }
585     if (_private->subItems != nil) {
586         NSMutableArray *childDicts = [NSMutableArray arrayWithCapacity:[_private->subItems count]];
587         int i;
588         for (i = [_private->subItems count]; i >= 0; i--) {
589             [childDicts addObject: [[_private->subItems objectAtIndex:i] dictionaryRepresentation]];
590         }
591         [dict setObject: childDicts forKey:WebChildrenKey];
592     }
593
594     return dict;
595 }
596
597 - (id)initFromDictionaryRepresentation:(NSDictionary *)dict
598 {
599     NSString *URLString = [dict _webkit_stringForKey:@""];
600     NSString *title = [dict _webkit_stringForKey:WebTitleKey];
601
602     // Do an existence check to avoid calling doubleValue on a nil string. Leave
603     // time interval at 0 if there's no value in dict.
604     NSString *timeIntervalString = [dict _webkit_stringForKey:WebLastVisitedTimeIntervalKey];
605     NSTimeInterval lastVisited = timeIntervalString == nil ? 0 : [timeIntervalString doubleValue];
606
607     self = [self initWithURLString:URLString title:title lastVisitedTimeInterval:lastVisited];
608
609     // Check if we've read a broken URL from the file that has non-Latin1 chars.  If so, try to convert
610     // as if it was from user typing.
611     if (![_private->URLString canBeConvertedToEncoding:NSISOLatin1StringEncoding]) {
612         NSURL *tempURL = [NSURL _web_URLWithUserTypedString:_private->URLString];
613         ASSERT(tempURL);
614         [_private->URLString release];
615         _private->URLString = [[tempURL _web_originalDataAsString] copy];
616         [_private->originalURLString release];
617         _private->originalURLString = [_private->URLString retain];
618     }
619
620     [self setAlternateTitle:[dict _webkit_stringForKey:WebDisplayTitleKey]];
621
622     _private->visitCount = [dict _webkit_intForKey:WebVisitCountKey];
623
624     NSArray *childDicts = [dict objectForKey:WebChildrenKey];
625     if (childDicts) {
626         _private->subItems = [[NSMutableArray alloc] initWithCapacity:[childDicts count]];
627         int i;
628         for (i = [childDicts count]; i >= 0; i--) {
629             WebHistoryItem *child = [[WebHistoryItem alloc] initFromDictionaryRepresentation: [childDicts objectAtIndex:i]];
630             [_private->subItems addObject: child];
631         }
632     }
633
634     return self;
635 }
636
637 - (void)setAlwaysAttemptToUsePageCache: (BOOL)flag
638 {
639     _private->alwaysAttemptToUsePageCache = flag;
640 }
641
642 - (BOOL)alwaysAttemptToUsePageCache
643 {
644     return _private->alwaysAttemptToUsePageCache;
645 }
646
647
648 static WebWindowWatcher *_windowWatcher;
649 static NSMutableSet *_pendingPageCacheToRelease = nil;
650 static NSTimer *_pageCacheReleaseTimer = nil;
651
652 - (BOOL)hasPageCache;
653 {
654     return _private->pageCache != nil;
655 }
656
657 + (void)_invalidateReleaseTimer
658 {
659     if (_pageCacheReleaseTimer){
660         [_pageCacheReleaseTimer invalidate];
661         [_pageCacheReleaseTimer release];
662         _pageCacheReleaseTimer = nil;
663     }
664 }
665
666 + (void)_scheduleReleaseTimer
667 {
668     if (!_pageCacheReleaseTimer){
669         _pageCacheReleaseTimer = [[NSTimer scheduledTimerWithTimeInterval: 2.5 target:self selector:@selector(_releasePageCache:) userInfo:nil repeats:NO] retain];
670         if (_pendingPageCacheToRelease == nil){
671             _pendingPageCacheToRelease = [[NSMutableSet alloc] init];
672         }
673     }
674 }
675
676 - (void)_scheduleRelease
677 {
678     LOG (PageCache, "Scheduling release of %@", [self URLString]);
679     [WebHistoryItem _scheduleReleaseTimer];
680
681     if (_private->pageCache){
682         [_pendingPageCacheToRelease addObject: _private->pageCache];
683         [_private->pageCache release]; // Last reference held by _pendingPageCacheToRelease.
684         _private->pageCache = nil;
685     }
686     
687     if (!_windowWatcher){
688         _windowWatcher = [[WebWindowWatcher alloc] init];
689         [[NSNotificationCenter defaultCenter] addObserver:_windowWatcher selector:@selector(windowWillClose:)
690                         name:NSWindowWillCloseNotification object:nil];
691     }
692 }
693
694 + (void)_destroyAllPluginsInPendingPageCaches
695 {
696     NSEnumerator *pageCaches = [_pendingPageCacheToRelease objectEnumerator];
697     NSMutableDictionary *pageCache;
698     
699     while ((pageCache = [pageCaches nextObject]) != nil) {
700         WebHTMLView *HTMLView = [pageCache objectForKey:WebPageCacheDocumentViewKey];
701         if ([HTMLView isKindOfClass:[WebHTMLView class]]) {
702             // Don't destroy plug-ins that are currently being viewed.
703             if ([[[HTMLView _frame] frameView] documentView] != HTMLView) {
704                 [[HTMLView _pluginController] destroyAllPlugins];
705             }
706         }
707     }
708 }
709
710 + (void)_releaseAllPendingPageCaches
711 {
712     LOG (PageCache, "releasing %d items\n", [_pendingPageCacheToRelease count]);
713     [WebHistoryItem _invalidateReleaseTimer];
714     // Plug-ins could retain anything including the WebHTMLView or the window.
715     // To avoid any possible retain cycle, call destroyPlugin on all the plug-ins
716     // instead of completely relying on dealloc of WebHTMLView.
717     [self _destroyAllPluginsInPendingPageCaches];
718     [_pendingPageCacheToRelease removeAllObjects];
719 }
720
721 + (void)_releasePageCache: (NSTimer *)timer
722 {
723     CGSRealTimeDelta userDelta;
724     CFAbsoluteTime loadDelta;
725     
726     loadDelta = CFAbsoluteTimeGetCurrent()-[WebFrame _timeOfLastCompletedLoad];
727     userDelta = CGSSecondsSinceLastInputEvent(kCGSAnyInputEventType);
728
729     [_pageCacheReleaseTimer release];
730     _pageCacheReleaseTimer = nil;
731
732     if ((userDelta < 0.5 || loadDelta < 1.25) && [_pendingPageCacheToRelease count] < 42){
733         LOG (PageCache, "postponing again because not quiescent for more than a second (%f since last input, %f since last load).", userDelta, loadDelta);
734         [self _scheduleReleaseTimer];
735         return;
736     }
737     else
738         LOG (PageCache, "releasing, quiescent for more than a second (%f since last input, %f since last load).", userDelta, loadDelta);
739
740     [WebHistoryItem _releaseAllPendingPageCaches];
741 }
742
743 - (void)setHasPageCache: (BOOL)f
744 {
745     if (f && !_private->pageCache)
746         _private->pageCache = [[NSMutableDictionary alloc] init];
747     if (!f && _private->pageCache){
748         [self _scheduleRelease];
749     }
750 }
751
752 - (NSMutableDictionary *)pageCache
753 {
754     return _private->pageCache;
755 }
756
757
758 @end
759
760 @implementation WebWindowWatcher
761 -(void)windowWillClose:(NSNotification *)notification
762 {
763     [WebHistoryItem _releaseAllPendingPageCaches];
764 }
765 @end
766
767