blob: f688dc89a2bc5f9847df9fb9ee49edd7058d60d0 [file] [log] [blame]
/*
* Copyright (C) 2005, 2007, 2008 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.
* 3. Neither the name of Apple 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 "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 "WebTypesInternal.h"
#import <JavaScriptCore/InitializeThreading.h>
#import <WebCore/BackForwardCache.h>
#import <WebCore/HistoryItem.h>
#import <WebCore/Image.h>
#import <WebCore/ThreadCheck.h>
#import <WebCore/WebCoreObjCExtras.h>
#import <wtf/Assertions.h>
#import <wtf/MainThread.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/RunLoop.h>
#import <wtf/StdLibExtras.h>
#import <wtf/URL.h>
#import <wtf/cocoa/VectorCocoa.h>
#import <wtf/text/WTFString.h>
#if PLATFORM(IOS_FAMILY)
#import <WebCore/WebCoreThreadMessage.h>
NSString *WebViewportInitialScaleKey = @"initial-scale";
NSString *WebViewportMinimumScaleKey = @"minimum-scale";
NSString *WebViewportMaximumScaleKey = @"maximum-scale";
NSString *WebViewportUserScalableKey = @"user-scalable";
NSString *WebViewportShrinkToFitKey = @"shrink-to-fit";
NSString *WebViewportFitKey = @"viewport-fit";
NSString *WebViewportWidthKey = @"width";
NSString *WebViewportHeightKey = @"height";
NSString *WebViewportFitAutoValue = @"auto";
NSString *WebViewportFitContainValue = @"contain";
NSString *WebViewportFitCoverValue = @"cover";
static NSString *scaleKey = @"scale";
static NSString *scaleIsInitialKey = @"scaleIsInitial";
static NSString *scrollPointXKey = @"scrollPointX";
static NSString *scrollPointYKey = @"scrollPointY";
#endif
// Private keys used in the WebHistoryItem's dictionary representation.
// see 3245793 for explanation of "lastVisitedDate"
static NSString *lastVisitedTimeIntervalKey = @"lastVisitedDate";
static NSString *titleKey = @"title";
static NSString *childrenKey = @"children";
static NSString *displayTitleKey = @"displayTitle";
static NSString *lastVisitWasFailureKey = @"lastVisitWasFailure";
static NSString *redirectURLsKey = @"redirectURLs";
// Notification strings.
NSString *WebHistoryItemChangedNotification = @"WebHistoryItemChangedNotification";
using namespace WebCore;
@implementation WebHistoryItemPrivate
@end
typedef HashMap<HistoryItem*, WebHistoryItem*> HistoryItemMap;
static inline WebCoreHistoryItem* core(WebHistoryItemPrivate* itemPrivate)
{
return itemPrivate->_historyItem.get();
}
static HistoryItemMap& historyItemWrappers()
{
static NeverDestroyed<HistoryItemMap> historyItemWrappers;
return historyItemWrappers;
}
void WKNotifyHistoryItemChanged(HistoryItem&)
{
#if !PLATFORM(IOS_FAMILY)
[[NSNotificationCenter defaultCenter]
postNotificationName:WebHistoryItemChangedNotification object:nil userInfo:nil];
#else
WebThreadPostNotification(WebHistoryItemChangedNotification, nil, nil);
#endif
}
@implementation WebHistoryItem
+ (void)initialize
{
#if !PLATFORM(IOS_FAMILY)
JSC::initialize();
WTF::initializeMainThread();
#endif
}
- (instancetype)init
{
return [self initWithWebCoreHistoryItem:HistoryItem::create()];
}
- (instancetype)initWithURLString:(NSString *)URLString title:(NSString *)title lastVisitedTimeInterval:(NSTimeInterval)time
{
WebCoreThreadViolationCheckRoundOne();
WebHistoryItem *item = [self initWithWebCoreHistoryItem:HistoryItem::create(URLString, title)];
item->_private->_lastVisitedTime = time;
return item;
}
- (void)dealloc
{
if (WebCoreObjCScheduleDeallocateOnMainThread([WebHistoryItem class], self))
return;
historyItemWrappers().remove(_private->_historyItem.get());
[_private release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
WebCoreThreadViolationCheckRoundOne();
WebHistoryItem *copy = [[[self class] alloc] initWithWebCoreHistoryItem:core(_private)->copy()];
copy->_private->_lastVisitedTime = _private->_lastVisitedTime;
historyItemWrappers().set(core(copy->_private), copy);
return copy;
}
// FIXME: Need to decide if this class ever returns URLs and decide on the name of this method
- (NSString *)URLString
{
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
{
return nsStringNilIfEmpty(core(_private)->originalURLString());
}
- (NSString *)title
{
return nsStringNilIfEmpty(core(_private)->title());
}
- (void)setAlternateTitle:(NSString *)alternateTitle
{
core(_private)->setAlternateTitle(alternateTitle);
}
- (NSString *)alternateTitle
{
return nsStringNilIfEmpty(core(_private)->alternateTitle());
}
#if !PLATFORM(IOS_FAMILY)
- (NSImage *)icon
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
return [[WebIconDatabase sharedIconDatabase] iconForURL:[self URLString] withSize:WebIconSmallSize];
ALLOW_DEPRECATED_DECLARATIONS_END
}
#endif
- (NSTimeInterval)lastVisitedTimeInterval
{
return _private->_lastVisitedTime;
}
- (NSUInteger)hash
{
return [(NSString*)core(_private)->urlString() hash];
}
- (BOOL)isEqual:(id)anObject
{
if (![anObject isMemberOfClass:[WebHistoryItem class]])
return NO;
return core(_private)->urlString() == core(((WebHistoryItem*)anObject)->_private)->urlString();
}
- (NSString *)description
{
HistoryItem* coreItem = core(_private);
NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@", [super description], (NSString*)coreItem->urlString()];
if (!coreItem->target().isEmpty()) {
NSString *target = coreItem->target();
[result appendFormat:@" in \"%@\"", target];
}
if (coreItem->isTargetItem()) {
[result appendString:@" *target*"];
}
if (coreItem->formData()) {
[result appendString:@" *POST*"];
}
if (coreItem->children().size()) {
const auto& children = coreItem->children();
int currPos = [result length];
unsigned size = children.size();
for (unsigned i = 0; i < size; ++i) {
WebHistoryItem *child = kit(const_cast<HistoryItem*>(children[i].ptr()));
[result appendString:@"\n"];
[result appendString:[child description]];
}
// shift all the contents over. A bit slow, but hey, this is for debugging.
NSRange replRange = { static_cast<NSUInteger>(currPos), [result length] - currPos };
[result replaceOccurrencesOfString:@"\n" withString:@"\n " options:0 range:replRange];
}
return result;
}
HistoryItem* core(WebHistoryItem *item)
{
if (!item)
return nullptr;
ASSERT(historyItemWrappers().get(core(item->_private)) == item);
return core(item->_private);
}
WebHistoryItem *kit(HistoryItem* item)
{
if (!item)
return nil;
if (auto wrapper = historyItemWrappers().get(item))
return [[wrapper retain] autorelease];
return [[[WebHistoryItem alloc] initWithWebCoreHistoryItem:*item] autorelease];
}
+ (WebHistoryItem *)entryWithURL:(NSURL *)URL
{
return [[[self alloc] initWithURL:URL title:nil] autorelease];
}
- (id)initWithURLString:(NSString *)URLString title:(NSString *)title displayTitle:(NSString *)displayTitle lastVisitedTimeInterval:(NSTimeInterval)time
{
auto item = [self initWithWebCoreHistoryItem:HistoryItem::create(URLString, title, displayTitle)];
if (!item)
return nil;
item->_private->_lastVisitedTime = time;
return item;
}
- (id)initWithWebCoreHistoryItem:(Ref<HistoryItem>&&)item
{
WebCoreThreadViolationCheckRoundOne();
// 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
// FIXME: This means that if we mix legacy WebKit and modern WebKit in the same process, we won't get both notifications.
WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
if (!(self = [super init]))
return nil;
_private = [[WebHistoryItemPrivate alloc] init];
_private->_historyItem = WTFMove(item);
ASSERT(!historyItemWrappers().get(core(_private)));
historyItemWrappers().set(core(_private), self);
return self;
}
- (void)setTitle:(NSString *)title
{
core(_private)->setTitle(title);
}
- (void)setViewState:(id)statePList
{
core(_private)->setViewState(statePList);
}
- (id)initFromDictionaryRepresentation:(NSDictionary *)dict
{
NSString *URLString = [dict _webkit_stringForKey:@""];
NSString *title = [dict _webkit_stringForKey:titleKey];
// 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:lastVisitedTimeIntervalKey];
NSTimeInterval lastVisited = timeIntervalString == nil ? 0 : [timeIntervalString doubleValue];
self = [self initWithURLString:URLString title:title displayTitle:[dict _webkit_stringForKey:displayTitleKey] 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 _webkit_URLWithUserTypedString:URLString];
ASSERT(tempURL);
NSString *newURLString = [tempURL _web_originalDataAsString];
core(_private)->setURLString(newURLString);
core(_private)->setOriginalURLString(newURLString);
}
if ([dict _webkit_boolForKey:lastVisitWasFailureKey])
core(_private)->setLastVisitWasFailure(true);
if (NSArray *redirectURLs = [dict _webkit_arrayForKey:redirectURLsKey])
_private->_redirectURLs = makeUnique<Vector<String>>(makeVector<String>(redirectURLs));
NSArray *childDicts = [dict objectForKey:childrenKey];
if (childDicts) {
for (int i = [childDicts count] - 1; i >= 0; i--) {
WebHistoryItem *child = [[WebHistoryItem alloc] initFromDictionaryRepresentation:[childDicts objectAtIndex:i]];
core(_private)->addChildItem(*core(child->_private));
[child release];
}
}
#if PLATFORM(IOS_FAMILY)
NSNumber *scaleValue = [dict objectForKey:scaleKey];
NSNumber *scaleIsInitialValue = [dict objectForKey:scaleIsInitialKey];
if (scaleValue && scaleIsInitialValue)
core(_private)->setScale([scaleValue floatValue], [scaleIsInitialValue boolValue]);
if (id viewportArguments = [dict objectForKey:@"WebViewportArguments"])
[self _setViewportArguments:viewportArguments];
NSNumber *scrollPointXValue = [dict objectForKey:scrollPointXKey];
NSNumber *scrollPointYValue = [dict objectForKey:scrollPointYKey];
if (scrollPointXValue && scrollPointYValue)
core(_private)->setScrollPosition(IntPoint([scrollPointXValue intValue], [scrollPointYValue intValue]));
#endif
return self;
}
- (NSPoint)scrollPoint
{
return core(_private)->scrollPosition();
}
- (void)_visitedWithTitle:(NSString *)title
{
core(_private)->setTitle(title);
_private->_lastVisitedTime = [NSDate timeIntervalSinceReferenceDate];
}
@end
@implementation WebHistoryItem (WebPrivate)
- (id)initWithURL:(NSURL *)URL title:(NSString *)title
{
return [self initWithURLString:[URL _web_originalDataAsString] title:title lastVisitedTimeInterval:0];
}
// FIXME: The only iOS difference here should be whether YES or NO is passed to dictionaryRepresentationIncludingChildren:
#if PLATFORM(IOS_FAMILY)
- (NSDictionary *)dictionaryRepresentation
{
return [self dictionaryRepresentationIncludingChildren:YES];
}
- (NSDictionary *)dictionaryRepresentationIncludingChildren:(BOOL)includesChildren
#else
- (NSDictionary *)dictionaryRepresentation
#endif
{
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:8];
HistoryItem* coreItem = core(_private);
if (!coreItem->urlString().isEmpty())
[dict setObject:(NSString*)coreItem->urlString() forKey:@""];
if (!coreItem->title().isEmpty())
[dict setObject:(NSString*)coreItem->title() forKey:titleKey];
if (!coreItem->alternateTitle().isEmpty())
[dict setObject:(NSString*)coreItem->alternateTitle() forKey:displayTitleKey];
if (_private->_lastVisitedTime) {
// Store as a string to maintain backward compatibility. (See 3245793)
[dict setObject:[NSString stringWithFormat:@"%.1lf", _private->_lastVisitedTime]
forKey:lastVisitedTimeIntervalKey];
}
if (coreItem->lastVisitWasFailure())
[dict setObject:@YES forKey:lastVisitWasFailureKey];
if (auto redirectURLs = _private->_redirectURLs.get())
[dict setObject:createNSArray(*redirectURLs).get() forKey:redirectURLsKey];
#if PLATFORM(IOS_FAMILY)
if (includesChildren && coreItem->children().size()) {
#else
if (coreItem->children().size()) {
#endif
const auto& children = coreItem->children();
NSMutableArray *childDicts = [NSMutableArray arrayWithCapacity:children.size()];
for (int i = children.size() - 1; i >= 0; i--)
[childDicts addObject:[kit(const_cast<HistoryItem*>(children[i].ptr())) dictionaryRepresentation]];
[dict setObject: childDicts forKey:childrenKey];
}
#if PLATFORM(IOS_FAMILY)
[dict setObject:[NSNumber numberWithFloat:core(_private)->scale()] forKey:scaleKey];
[dict setObject:[NSNumber numberWithBool:core(_private)->scaleIsInitial()] forKey:scaleIsInitialKey];
NSDictionary *viewportArguments = [self _viewportArguments];
if (viewportArguments)
[dict setObject:viewportArguments forKey:@"WebViewportArguments"];
IntPoint scrollPosition = core(_private)->scrollPosition();
[dict setObject:@(scrollPosition.x()) forKey:scrollPointXKey];
[dict setObject:@(scrollPosition.y()) forKey:scrollPointYKey];
#endif
return dict;
}
- (NSString *)target
{
return nsStringNilIfEmpty(core(_private)->target());
}
- (BOOL)isTargetItem
{
return core(_private)->isTargetItem();
}
- (NSString *)RSSFeedReferrer
{
return nsStringNilIfEmpty(core(_private)->referrer());
}
- (void)setRSSFeedReferrer:(NSString *)referrer
{
core(_private)->setReferrer(referrer);
}
- (NSArray *)children
{
auto& children = core(_private)->children();
if (children.isEmpty())
return nil;
return createNSArray(children, [] (auto& item) {
return kit(const_cast<HistoryItem*>(item.ptr()));
}).autorelease();
}
- (NSURL *)URL
{
const URL& url = core(_private)->url();
if (url.isEmpty())
return nil;
return url;
}
#if !PLATFORM(IOS_FAMILY)
+ (void)_releaseAllPendingPageCaches
{
}
#endif
- (BOOL)lastVisitWasFailure
{
return core(_private)->lastVisitWasFailure();
}
- (NSArray *)_redirectURLs
{
auto& redirectURLs = _private->_redirectURLs;
if (!redirectURLs)
return nil;
return createNSArray(*redirectURLs).autorelease();
}
#if PLATFORM(IOS_FAMILY)
- (void)_setScale:(float)scale isInitial:(BOOL)aFlag
{
core(_private)->setScale(scale, aFlag);
}
- (float)_scale
{
return core(_private)->scale();
}
- (BOOL)_scaleIsInitial
{
return core(_private)->scaleIsInitial();
}
- (NSDictionary *)_viewportArguments
{
const ViewportArguments& viewportArguments = core(_private)->viewportArguments();
NSMutableDictionary *argumentsDictionary = [NSMutableDictionary dictionary];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.zoom] forKey:WebViewportInitialScaleKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.minZoom] forKey:WebViewportMinimumScaleKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.maxZoom] forKey:WebViewportMaximumScaleKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.width] forKey:WebViewportWidthKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.height] forKey:WebViewportHeightKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.userZoom] forKey:WebViewportUserScalableKey];
[argumentsDictionary setObject:[NSNumber numberWithFloat:viewportArguments.shrinkToFit] forKey:WebViewportShrinkToFitKey];
return argumentsDictionary;
}
- (void)_setViewportArguments:(NSDictionary *)arguments
{
ViewportArguments viewportArguments;
viewportArguments.zoom = [[arguments objectForKey:WebViewportInitialScaleKey] floatValue];
viewportArguments.minZoom = [[arguments objectForKey:WebViewportMinimumScaleKey] floatValue];
viewportArguments.maxZoom = [[arguments objectForKey:WebViewportMaximumScaleKey] floatValue];
viewportArguments.width = [[arguments objectForKey:WebViewportWidthKey] floatValue];
viewportArguments.height = [[arguments objectForKey:WebViewportHeightKey] floatValue];
viewportArguments.userZoom = [[arguments objectForKey:WebViewportUserScalableKey] floatValue];
viewportArguments.shrinkToFit = [[arguments objectForKey:WebViewportShrinkToFitKey] floatValue];
core(_private)->setViewportArguments(viewportArguments);
}
- (CGPoint)_scrollPoint
{
return core(_private)->scrollPosition();
}
- (void)_setScrollPoint:(CGPoint)scrollPoint
{
core(_private)->setScrollPosition(IntPoint(scrollPoint));
}
#endif // PLATFORM(IOS_FAMILY)
- (BOOL)_isInBackForwardCache
{
return core(_private)->isInBackForwardCache();
}
- (BOOL)_hasCachedPageExpired
{
return core(_private)->hasCachedPageExpired();
}
@end