Add site icons to the history outliner and history menus.

Add support for site icons specified in link elements (bug 162893), part of patch by Ludovic Hirlimann.
Add a site icon image cache, so that we can get site icons without a round trip through necko, and avoid duplicate images (bug 294675).
Add a Clear History item to the end of the go menu (bug 294205).
Make the history menus no more than 100 items long, with a "Show More" item at the end, to avoid long delays when showing them (bug 291414).
This commit is contained in:
smfr%smfr.org 2005-05-23 03:36:08 +00:00
parent 62ece01069
commit 161e295483
23 changed files with 730 additions and 139 deletions

View File

@ -140,6 +140,7 @@ typedef enum EBookmarkOpenBehavior
-(IBAction) viewSource:(id)aSender;
-(IBAction) manageBookmarks: (id)aSender;
-(IBAction) showHistory:(id)aSender;
-(IBAction) clearHistory:(id)aSender;
-(IBAction) reloadWithCharset:(id)aSender;
// Bookmarks menu actions.

View File

@ -1079,6 +1079,26 @@ Otherwise, we return the URL we originally got. Right now this supports .url and
[[browserWindow windowController] manageHistory: aSender];
}
//
// -clearHistory:
//
// clear the global history, after showing a warning
//
-(IBAction)clearHistory:(id)aSender
{
if (NSRunCriticalAlertPanel(NSLocalizedString(@"ClearHistoryTitle", nil),
NSLocalizedString(@"ClearHistoryMessage", nil),
NSLocalizedString(@"ClearHistoryButton", nil),
NSLocalizedString(@"CancelButtonText", nil),
nil) == NSAlertDefaultReturn)
{
// clear history
nsCOMPtr<nsIBrowserHistory> hist = do_GetService("@mozilla.org/browser/global-history;2");
if (hist)
hist->RemoveAllPages();
}
}
//
// manageBookmarks:
//

View File

@ -177,8 +177,16 @@ NSString* const URLLoadSuccessKey = @"url_bool";
-(void) refreshIcon
{
if ([BookmarkManager sharedBookmarkManager]) {
[[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon:self forURI:[self url] allowNetwork:NO];
if ([BookmarkManager sharedBookmarkManager])
{
NSImage* siteIcon = [[SiteIconProvider sharedFavoriteIconProvider] favoriteIconForPage:[self url]];
if (siteIcon)
[self setIcon:siteIcon];
else
[[SiteIconProvider sharedFavoriteIconProvider] fetchFavoriteIconForPage:[self url]
withIconLocation:nil
allowNetwork:NO
notifyingClient:self];
}
}

View File

@ -697,7 +697,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999;
return mBookmarksEditingView;
}
- (void) focus
- (void)focus
{
[[mBookmarksOutlineView window] makeFirstResponder:mBookmarksOutlineView];
@ -706,7 +706,8 @@ static const int kDisabledQuicksearchPopupItemTag = 9999;
// manager view resized correctly. If we did it earlier, it would resize again
// to stretch proportionally to the size of the browser window, destroying
// the width we just set.
if (!mSplittersRestored) {
if (!mSplittersRestored)
{
const float kDefaultSplitWidth = kMinContainerSplitWidth;
float savedWidth = [[NSUserDefaults standardUserDefaults] floatForKey:USER_DEFAULTS_CONTAINER_SPLITTER_WIDTH];
if (savedWidth < kDefaultSplitWidth)

View File

@ -1557,6 +1557,11 @@ enum BWCOpenDest {
-(IBAction)manageHistory: (id)aSender
{
[self loadURL:@"about:history" referrer:nil activate:YES allowPopups:YES];
// aSender could be a history menu item with a represented object of
// an item that we wish to reveal. However, it belongs to a different
// data source than the one we just created. need a way to find the one
// to reveal...
}
- (IBAction)goToLocationFromToolbarURLField:(id)sender

View File

@ -480,18 +480,37 @@ static NSString* const kOfflineNotificationName = @"offlineModeChanged";
- (void)onLocationChange:(NSString*)urlSpec
{
BOOL useSiteIcons = [[PreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" withSuccess:NULL];
BOOL useSiteIcons = [[PreferenceManager sharedInstance] getBooleanPref:"browser.chrome.favicons" withSuccess:NULL];
BOOL siteIconLoadInitiated = NO;
SiteIconProvider* faviconProvider = [SiteIconProvider sharedFavoriteIconProvider];
NSString* faviconURI = [SiteIconProvider faviconLocationStringFromURI:urlSpec];
if (useSiteIcons && [faviconURI length] > 0)
{
// if the favicon uri has changed, fire off favicon load. When it completes, our
// imageLoadedNotification selector gets called.
if (![faviconURI isEqualToString:mSiteIconURI])
siteIconLoadInitiated = [faviconProvider loadFavoriteIcon:self forURI:urlSpec allowNetwork:YES];
{
// first get a cached image for this site, if we have one. we'll go ahead
// and request the load anyway, in case the site updated their icon.
NSImage* cachedImage = [faviconProvider favoriteIconForPage:urlSpec];
NSString* cachedImageURI = nil;
if (cachedImage)
cachedImageURI = faviconURI;
// immediately update the site icon (to the cached one, or the default)
[self updateSiteIconImage:cachedImage withURI:cachedImageURI];
// note that this is the only time we hit the network for site icons.
// note also that we may get a site icon from a link element later,
// which will replace any we get from the default location.
[faviconProvider fetchFavoriteIconForPage:urlSpec
withIconLocation:nil
allowNetwork:YES
notifyingClient:self];
}
}
else
{
@ -499,13 +518,13 @@ static NSString* const kOfflineNotificationName = @"offlineModeChanged";
faviconURI = urlSpec;
else
faviconURI = @"";
[self updateSiteIconImage:nil withURI:faviconURI];
}
if (!siteIconLoadInitiated)
[self updateSiteIconImage:nil withURI:faviconURI];
[mDelegate updateLocationFields:urlSpec ignoreTyping:NO];
// see if someone wants to replace the main view
[self checkForCustomViewOnLoad:urlSpec];
}
@ -648,6 +667,27 @@ static NSString* const kOfflineNotificationName = @"offlineModeChanged";
}
}
// Called when a "shortcut icon" link element is noticed
- (void)onFoundShortcutIcon:(NSString*)inIconURI
{
BOOL useSiteIcons = [[PreferenceManager sharedInstance] getBooleanPref:"browser.chrome.favicons" withSuccess:NULL];
if (!useSiteIcons)
return;
if ([inIconURI length] > 0)
{
// if the favicon uri has changed, fire off favicon load. When it completes, our
// imageLoadedNotification selector gets called.
if (![inIconURI isEqualToString:mSiteIconURI])
{
[[SiteIconProvider sharedFavoriteIconProvider] fetchFavoriteIconForPage:[self getCurrentURLSpec]
withIconLocation:inIconURI
allowNetwork:YES
notifyingClient:self];
}
}
}
// Called when a context menu should be shown.
- (void)onShowContextMenu:(int)flags domEvent:(nsIDOMEvent*)aEvent domNode:(nsIDOMNode*)aNode
{
@ -838,8 +878,8 @@ static NSString* const kOfflineNotificationName = @"offlineModeChanged";
siteIcon = [NSImage imageNamed:@"globe_ico"];
}
[self setSiteIconImage: siteIcon];
[self setSiteIconURI: inSiteIconURI];
[self setSiteIconImage:siteIcon];
[self setSiteIconURI:inSiteIconURI];
// update the proxy icon
[mDelegate updateSiteIcons:mSiteIconImage ignoreTyping:NO];

View File

@ -68,6 +68,8 @@
- (void)setItemBeforeHistoryItems:(NSMenuItem*)inItem;
- (NSMenuItem*)itemBeforeHistoryItems;
- (void)addLastItems;
@end

View File

@ -64,7 +64,7 @@
// the maximum number of history entry menuitems to display
static const int kMaxItems = 15;
static const int kMaxNumHistoryItems = 100;
// the maximum number of "today" items to show on the main menu
static const int kMaxTodayItems = 12;
@ -301,15 +301,16 @@ static const unsigned int kMaxTitleLength = 80;
{
NSMenuItem* newItem = nil;
// XXX should we impose a max number of items in any one menu, to avoid crazy 2000-item menus?
if ([curChild isKindOfClass:[HistorySiteItem class]])
{
newItem = [[[NSMenuItem alloc] initWithTitle:[HistoryMenu menuItemTitleForHistoryItem:curChild]
action:@selector(openHistoryItem:)
keyEquivalent:@""] autorelease];
[newItem setImage:[curChild iconAllowingLoad:NO]];
[newItem setTarget:self];
[newItem setRepresentedObject:curChild];
[self addItem:newItem];
}
else if ([curChild isKindOfClass:[HistoryCategoryItem class]] && ([curChild numberOfChildren] > 0))
{
@ -321,6 +322,7 @@ static const unsigned int kMaxTitleLength = 80;
[mAdditionalItemsParent autorelease];
mAdditionalItemsParent = [curChild retain];
// put the kMaxTodayItems most recent items into this menu (which is the go menu)
NSMutableArray* menuTodayItems = [NSMutableArray arrayWithArray:[curChild children]];
while ((int)[menuTodayItems count] > kMaxTodayItems)
[menuTodayItems removeObjectAtIndex:kMaxTodayItems];
@ -333,13 +335,14 @@ static const unsigned int kMaxTitleLength = 80;
action:@selector(openHistoryItem:)
keyEquivalent:@""] autorelease];
[todayMenuItem setImage:[curTodayItem iconAllowingLoad:NO]];
[todayMenuItem setTarget:self];
[todayMenuItem setRepresentedObject:curTodayItem];
[self addItem:todayMenuItem];
separatorPending = YES;
}
// make a submenu for "earlier today"
// and make a submenu for "earlier today"
if ([curChild numberOfChildren] > kMaxTodayItems)
{
if (separatorPending)
@ -352,12 +355,14 @@ static const unsigned int kMaxTitleLength = 80;
newItem = [[[NSMenuItem alloc] initWithTitle:itemTitle
action:nil
keyEquivalent:@""] autorelease];
[newItem setImage:[curChild iconAllowingLoad:NO]];
HistoryMenu* newSubmenu = [[HistoryMenu alloc] initWithTitle:itemTitle];
[newSubmenu setRootHistoryItem:curChild];
[newSubmenu setNumLeadingItemsToIgnore:kMaxTodayItems];
[newItem setSubmenu:newSubmenu];
[self addItem:newItem];
}
}
else
@ -373,21 +378,40 @@ static const unsigned int kMaxTitleLength = 80;
newItem = [[[NSMenuItem alloc] initWithTitle:itemTitle
action:nil
keyEquivalent:@""] autorelease];
[newItem setImage:[curChild iconAllowingLoad:NO]];
HistoryMenu* newSubmenu = [[HistoryMenu alloc] initWithTitle:itemTitle];
[newSubmenu setRootHistoryItem:curChild];
[newItem setSubmenu:newSubmenu];
[self addItem:newItem];
}
}
if (newItem)
[self addItem:newItem];
// if we're not the Go menu, stop after kMaxNumHistoryItems items
if (![self isKindOfClass:[GoMenu class]] && ([self numberOfItems] == kMaxNumHistoryItems))
break;
}
[self addLastItems];
mHistoryItemsDirty = NO;
}
- (void)addLastItems
{
if ([self numberOfItems] >= kMaxNumHistoryItems)
{
// this will only be called for submenus
[self addItem:[NSMenuItem separatorItem]];
NSMenuItem* showMoreItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"ShowMoreHistoryMenuItem", @"")
action:@selector(showHistory:)
keyEquivalent:@""] autorelease];
[showMoreItem setRepresentedObject:mRootItem];
[self addItem:showMoreItem];
}
}
- (void)menuWillBeDisplayed
{
[self rebuildHistoryItems];
@ -478,4 +502,16 @@ static const unsigned int kMaxTitleLength = 80;
[super menuWillBeDisplayed];
}
- (void)addLastItems
{
// at the bottom of the go menu, add a Clear History item
if ([[mRootItem children] count] > 0)
[self addItem:[NSMenuItem separatorItem]];
NSMenuItem* clearHistoryItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"ClearHistoryMenuItem", @"")
action:@selector(clearHistory:)
keyEquivalent:@""] autorelease];
[self addItem:clearHistoryItem];
}
@end

View File

@ -38,11 +38,11 @@
#import <Foundation/Foundation.h>
extern NSString* RemoteDataLoadRequestNotificationName;
extern NSString* RemoteDataLoadRequestURIKey;
extern NSString* RemoteDataLoadRequestDataKey;
extern NSString* RemoteDataLoadRequestUserDataKey;
extern NSString* RemoteDataLoadRequestResultKey;
extern NSString* const RemoteDataLoadRequestNotificationName;
extern NSString* const RemoteDataLoadRequestURIKey;
extern NSString* const RemoteDataLoadRequestDataKey;
extern NSString* const RemoteDataLoadRequestUserDataKey;
extern NSString* const RemoteDataLoadRequestResultKey;
// RemoteDataProvider is a class that can be used to do asynchronous loads
// from URIs using necko, and passing back the result of the load to a

View File

@ -47,11 +47,11 @@
#include "nsICacheEntryDescriptor.h"
#include "nsICachingChannel.h"
NSString* RemoteDataLoadRequestNotificationName = @"remoteload_notification_name";
NSString* RemoteDataLoadRequestURIKey = @"remoteload_uri_key";
NSString* RemoteDataLoadRequestDataKey = @"remoteload_data_key";
NSString* RemoteDataLoadRequestUserDataKey = @"remoteload_user_data_key";
NSString* RemoteDataLoadRequestResultKey = @"remoteload_result_key";
NSString* const RemoteDataLoadRequestNotificationName = @"remoteload_notification_name";
NSString* const RemoteDataLoadRequestURIKey = @"remoteload_uri_key";
NSString* const RemoteDataLoadRequestDataKey = @"remoteload_data_key";
NSString* const RemoteDataLoadRequestUserDataKey = @"remoteload_user_data_key";
NSString* const RemoteDataLoadRequestResultKey = @"remoteload_result_key";
// this has to retain the load listener, to ensure that the listener lives long

View File

@ -48,8 +48,10 @@ class NeckoCacheHelper;
@interface SiteIconProvider : NSObject<RemoteLoadListener>
{
NeckoCacheHelper* mMissedIconsCacheHelper;
NSMutableDictionary *mRequestDict;
NeckoCacheHelper* mIconsCacheHelper;
NSMutableDictionary* mRequestDict; // dict of favicon url -> request url
NSMutableDictionary* mIconDictionary; // map of favorite url -> NSImage
}
+ (SiteIconProvider*)sharedFavoriteIconProvider;
@ -68,4 +70,27 @@ class NeckoCacheHelper;
- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI allowNetwork:(BOOL)inAllowNetwork;
// fetch the icon for the given page.
// inIconURI is the URI of the icon (if specified via a <link> element), or nil for the default
// site icon location.
// when the load is done, a SiteIconLoadNotificationName notification will be sent with
// |inClient| as the object.
// returns YES if the load is initiated, and the client can expect a notification.
//
// there are various reasons why this might fail to load a site icon from the cache,
// when we know we can load it if we hit the network:
// i) it's a https URL, which is never put in the disk cache.
// ii) the url might have moved (301 response), and we don't handle that.
- (BOOL)fetchFavoriteIconForPage:(NSString*)inPageURI
withIconLocation:(NSString*)inIconURI
allowNetwork:(BOOL)inAllowNetwork
notifyingClient:(id)inClient;
// image cache method
// get the image for the given page URI. if not available, returns nil.
// if the image was fetched with a specific location (e.g. because of a <link> element)
// then this will take that into account.
- (NSImage*)favoriteIconForPage:(NSString*)inPageURI;
@end

View File

@ -43,6 +43,7 @@
#include "nsString.h"
#include "nsISupports.h"
#include "nsNetUtil.h"
#include "nsIURL.h"
#include "nsICacheSession.h"
#include "nsICacheService.h"
#include "nsICacheEntryDescriptor.h"
@ -53,6 +54,10 @@ NSString* const SiteIconLoadImageKey = @"siteicon_load_image";
NSString* const SiteIconLoadURIKey = @"siteicon_load_uri";
NSString* const SiteIconLoadUserDataKey = @"siteicon_load_user_data";
static const char* const kMissingFaviconMetadataKey = "missing_favicon";
static const char* const kFaviconURILocationMetadataKey = "favicon_location";
//#define VERBOSE_SITE_ICON_LOADING
static inline PRUint32 PRTimeToSeconds(PRTime t_usec)
{
@ -63,37 +68,53 @@ static inline PRUint32 PRTimeToSeconds(PRTime t_usec)
LL_L2I(t_sec, t_usec);
return t_sec;
}
// this class is used for two things:
// 1. to store information about which sites do not have site icons,
// so that we don't continually hit them.
// 2. to store a mapping between page URIs and site icon URIs,
// so that we can reload page icons specified in <link> tags
// without having to reload the original page.
class NeckoCacheHelper
{
public:
NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue);
NeckoCacheHelper();
~NeckoCacheHelper() {}
nsresult Init(const char* inCacheSessionName);
nsresult ExistsInCache(const nsACString& inURI, PRBool* outExists);
nsresult PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds);
// get and save state about which icons are known missing. note that the URIs here
// are URIs for the favicons themselves
nsresult FaviconForURIIsMissing(const nsACString& inFaviconURI, PRBool* outKnownMissing);
nsresult SetFaviconForURIIsMissing(const nsACString& inFaviconURI, PRUint32 inExpirationTimeSeconds);
// get and save mapping between page URI and favicon URI, to store information
// for pages with icons specified in <link> tags. these should only be used
// for link-specified icons, and not those in the default location, to avoid
// unnecessary bloat.
nsresult SaveFaviconLocationForPageURI(const nsACString& inPageURI, const nsACString& inFaviconURI, PRUint32 inExpirationTimeSeconds);
nsresult GetFaviconLocationForPageURI(const nsACString& inPageURI, nsACString& outFaviconURI);
nsresult ClearCache();
void GetCanonicalPageURI(const nsACString& inPageURI, nsACString& outCanonicalURI);
protected:
const char* mMetaElement;
const char* mMetaValue;
nsCOMPtr<nsICacheSession> mCacheSession;
};
static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
NeckoCacheHelper::NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue)
: mMetaElement(inMetaElement)
, mMetaValue(inMetaValue)
NeckoCacheHelper::NeckoCacheHelper()
{
}
nsresult NeckoCacheHelper::Init(const char* inCacheSessionName)
nsresult
NeckoCacheHelper::Init(const char* inCacheSessionName)
{
nsresult rv;
@ -111,23 +132,31 @@ nsresult NeckoCacheHelper::Init(const char* inCacheSessionName)
}
nsresult NeckoCacheHelper::ExistsInCache(const nsACString& inURI, PRBool* outExists)
nsresult
NeckoCacheHelper::FaviconForURIIsMissing(const nsACString& inFaviconURI, PRBool* outKnownMissing)
{
*outKnownMissing = PR_FALSE;
NS_ASSERTION(mCacheSession, "No cache session");
nsCOMPtr<nsICacheEntryDescriptor> entryDesc;
nsresult rv = mCacheSession->OpenCacheEntry(inURI, nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
*outExists = NS_SUCCEEDED(rv) && (entryDesc != NULL);
nsresult rv = mCacheSession->OpenCacheEntry(inFaviconURI, nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
if (NS_SUCCEEDED(rv) && entryDesc)
{
nsXPIDLCString metadataString;
entryDesc->GetMetaDataElement(kMissingFaviconMetadataKey, getter_Copies(metadataString));
*outKnownMissing = metadataString.EqualsLiteral("1");
}
return NS_OK;
}
nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds)
nsresult
NeckoCacheHelper::SetFaviconForURIIsMissing(const nsACString& inFaviconURI, PRUint32 inExpirationTimeSeconds)
{
NS_ASSERTION(mCacheSession, "No cache session");
nsCOMPtr<nsICacheEntryDescriptor> entryDesc;
nsresult rv = mCacheSession->OpenCacheEntry(inURI, nsICache::ACCESS_WRITE, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
nsresult rv = mCacheSession->OpenCacheEntry(inFaviconURI, nsICache::ACCESS_WRITE, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
if (NS_FAILED(rv) || !entryDesc) return rv;
nsCacheAccessMode accessMode;
@ -138,7 +167,7 @@ nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpira
if (accessMode != nsICache::ACCESS_WRITE)
return NS_ERROR_FAILURE;
entryDesc->SetMetaDataElement(mMetaElement, mMetaValue); // just set a bit of meta data.
entryDesc->SetMetaDataElement(kMissingFaviconMetadataKey, "1"); // just set a bit of meta data.
entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds);
entryDesc->MarkValid();
@ -147,7 +176,83 @@ nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpira
return NS_OK;
}
nsresult NeckoCacheHelper::ClearCache()
nsresult
NeckoCacheHelper::SaveFaviconLocationForPageURI(const nsACString& inPageURI, const nsACString& inFaviconURI, PRUint32 inExpirationTimeSeconds)
{
NS_ASSERTION(mCacheSession, "No cache session");
// canonicalize the URI
nsCAutoString canonicalPageURI;
GetCanonicalPageURI(inPageURI, canonicalPageURI);
nsCOMPtr<nsICacheEntryDescriptor> entryDesc;
nsresult rv = mCacheSession->OpenCacheEntry(canonicalPageURI, nsICache::ACCESS_WRITE, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
if (NS_FAILED(rv) || !entryDesc) return rv;
nsCacheAccessMode accessMode;
rv = entryDesc->GetAccessGranted(&accessMode);
if (NS_FAILED(rv))
return rv;
if (accessMode != nsICache::ACCESS_WRITE)
return NS_ERROR_FAILURE;
entryDesc->SetMetaDataElement(kFaviconURILocationMetadataKey, PromiseFlatCString(inFaviconURI).get());
entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds);
entryDesc->MarkValid();
entryDesc->Close();
return NS_OK;
}
nsresult
NeckoCacheHelper::GetFaviconLocationForPageURI(const nsACString& inPageURI, nsACString& outFaviconURI)
{
NS_ASSERTION(mCacheSession, "No cache session");
// canonicalize the URI
nsCAutoString canonicalPageURI;
GetCanonicalPageURI(inPageURI, canonicalPageURI);
nsCOMPtr<nsICacheEntryDescriptor> entryDesc;
nsresult rv = mCacheSession->OpenCacheEntry(canonicalPageURI, nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc));
if (NS_FAILED(rv)) return rv;
if (!entryDesc) return NS_ERROR_FAILURE;
nsXPIDLCString faviconLocation;
rv = entryDesc->GetMetaDataElement(kFaviconURILocationMetadataKey, getter_Copies(faviconLocation));
if (NS_FAILED(rv)) return rv;
outFaviconURI = faviconLocation;
return NS_OK;
}
void
NeckoCacheHelper::GetCanonicalPageURI(const nsACString& inPageURI, nsACString& outCanonicalURI)
{
nsCOMPtr<nsIURI> pageURI;
nsresult rv = NS_NewURI(getter_AddRefs(pageURI), inPageURI, nsnull, nsnull);
if (NS_SUCCEEDED(rv))
{
nsCOMPtr<nsIURL> pageURL = do_QueryInterface(pageURI);
if (pageURL)
{
// remove any params or refs
//pageURL->SetParam(NS_LITERAL_CSTRING("")); // this is not implemented
pageURL->SetQuery(NS_LITERAL_CSTRING(""));
pageURL->SetRef(NS_LITERAL_CSTRING(""));
pageURL->GetSpec(outCanonicalURI);
return;
}
}
outCanonicalURI = inPageURI;
}
nsresult
NeckoCacheHelper::ClearCache()
{
NS_ASSERTION(mCacheSession, "No cache session");
@ -157,7 +262,8 @@ nsresult NeckoCacheHelper::ClearCache()
#pragma mark -
static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI)
static nsresult
MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI)
{
outFaviconURI.Truncate(0);
@ -184,8 +290,10 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
@interface SiteIconProvider(Private)
- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds;
- (BOOL)inMissedIconsCache:(const nsAString&)inURI;
- (NSString*)favoriteIconURLFromPageURL:(NSString*)inPageURL;
- (void)addToMissedIconsCache:(NSString*)inURI withExpirationSeconds:(unsigned int)inExpSeconds;
- (BOOL)inMissedIconsCache:(NSString*)inURI;
@end
@ -196,12 +304,14 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
{
if ((self = [super init]))
{
mMissedIconsCacheHelper = new NeckoCacheHelper("Favicon", "Missed");
mRequestDict = [[NSMutableDictionary alloc] initWithCapacity:5];
nsresult rv = mMissedIconsCacheHelper->Init("MissedIconsCache");
mRequestDict = [[NSMutableDictionary alloc] initWithCapacity:5];
mIconDictionary = [[NSMutableDictionary alloc] initWithCapacity:100];
mIconsCacheHelper = new NeckoCacheHelper();
nsresult rv = mIconsCacheHelper->Init("MissedIconsCache");
if (NS_FAILED(rv)) {
delete mMissedIconsCacheHelper;
mMissedIconsCacheHelper = NULL;
delete mIconsCacheHelper;
mIconsCacheHelper = NULL;
}
}
@ -210,52 +320,60 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
- (void)dealloc
{
delete mMissedIconsCacheHelper;
delete mIconsCacheHelper;
[mRequestDict release];
[mIconDictionary release];
[super dealloc];
}
- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds
- (NSString*)favoriteIconURLFromPageURL:(NSString*)inPageURL
{
if (mMissedIconsCacheHelper)
NSString* faviconURL = nil;
// do we have a link icon for this page?
if (mIconsCacheHelper)
{
mMissedIconsCacheHelper->PutInCache(NS_ConvertUCS2toUTF8(inURI), inExpSeconds);
nsCAutoString pageURLString([inPageURL UTF8String]);
nsCAutoString iconURLString;
if (NS_SUCCEEDED(mIconsCacheHelper->GetFaviconLocationForPageURI(pageURLString, iconURLString)))
{
faviconURL = [NSString stringWith_nsACString:iconURLString];
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"found linked icon url %@ for page %@", faviconURL, inPageURL);
#endif
}
}
if (!faviconURL)
faviconURL = [SiteIconProvider faviconLocationStringFromURI:inPageURL];
return faviconURL;
}
- (void)addToMissedIconsCache:(NSString*)inURI withExpirationSeconds:(unsigned int)inExpSeconds
{
if (mIconsCacheHelper)
{
mIconsCacheHelper->SetFaviconForURIIsMissing(nsCString([inURI UTF8String]), inExpSeconds);
}
}
- (BOOL)inMissedIconsCache:(const nsAString&)inURI
- (BOOL)inMissedIconsCache:(NSString*)inURI
{
PRBool inCache = PR_FALSE;
if (mMissedIconsCacheHelper)
mMissedIconsCacheHelper->ExistsInCache(NS_ConvertUCS2toUTF8(inURI), &inCache);
if (mIconsCacheHelper)
mIconsCacheHelper->FaviconForURIIsMissing(nsCString([inURI UTF8String]), &inCache);
return inCache;
}
- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI allowNetwork:(BOOL)inAllowNetwork
{
// look for a favicon
nsAutoString uriString;
[inURI assignTo_nsAString:uriString];
nsAutoString faviconURIString;
MakeFaviconURIFromURI(uriString, faviconURIString);
if (faviconURIString.Length() == 0)
return NO;
NSString* faviconString = [NSString stringWith_nsAString:faviconURIString];
// is this uri already in the missing icons cache?
if ([self inMissedIconsCache:faviconURIString])
{
return NO;
}
// preserve requesting URI for later notification
[mRequestDict setObject:inURI forKey:faviconString];
RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider];
return [dataProvider loadURI:faviconString forTarget:sender withListener:self withUserData:nil allowNetworking:inAllowNetwork];
return [self fetchFavoriteIconForPage:inURI withIconLocation:nil allowNetwork:inAllowNetwork notifyingClient:sender];
}
#define SITE_ICON_EXPIRATION_SECONDS (60 * 60 * 24 * 7) // 1 week
@ -263,6 +381,10 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
// this is called on the main thread
- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status
{
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"Site icon load %@ done, status 0x%08X", inURI, status);
#endif
nsAutoString uriString;
[inURI assignTo_nsAString:uriString];
@ -277,14 +399,14 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
NS_DURING
faviconImage = [[[NSImage alloc] initWithData:data] autorelease];
NS_HANDLER
NSLog(@"Exception \"%@ making\" favicon image for %@", localException, inURI);
NSLog(@"Exception \"%@\" making favicon image for %@", localException, inURI);
faviconImage = nil;
NS_ENDHANDLER
BOOL gotImageData = loadOK && (faviconImage != nil);
if (NS_SUCCEEDED(status) && !gotImageData) // error status indicates that load was attempted from cache
{
[self addToMissedIconsCache:uriString withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS];
[self addToMissedIconsCache:inURI withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS];
}
if (gotImageData)
@ -292,28 +414,139 @@ static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& o
[faviconImage setDataRetained:YES];
[faviconImage setScalesWhenResized:YES];
[faviconImage setSize:NSMakeSize(16, 16)];
// add the image to the cache
[mIconDictionary setObject:faviconImage forKey:inURI];
}
// figure out what URL triggered this favicon request
NSMutableArray* requestList = [mRequestDict objectForKey:inURI];
// send out a notification for every object in the request list
NSEnumerator* requestsEnum = [requestList objectEnumerator];
NSDictionary* curRequest;
while ((curRequest = [requestsEnum nextObject]))
{
NSString* requestURL = [curRequest objectForKey:@"request_uri"];
if (!requestURL)
requestURL = [NSString string];
id requestor = [curRequest objectForKey:@"icon_requestor"];
// if we got an image, cache the link url, if any
NSString* linkURL = [curRequest objectForKey:@"icon_uri"];
if (gotImageData && ![linkURL isEqual:[NSNull null]] && mIconsCacheHelper)
{
nsCAutoString pageURI([requestURL UTF8String]);
nsCAutoString iconURI([linkURL UTF8String]);
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"saving linked icon url %@ for page %@", linkURL, requestURL);
#endif
mIconsCacheHelper->SaveFaviconLocationForPageURI(pageURI, iconURI, SITE_ICON_EXPIRATION_SECONDS);
}
// we always send out the notification, so that clients know
// about failed requests
NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys:
inURI, SiteIconLoadURIKey,
faviconImage, SiteIconLoadImageKey, // may be nil
requestURL, SiteIconLoadUserDataKey,
nil];
NSNotification *note = [NSNotification notificationWithName: SiteIconLoadNotificationName object:requestor userInfo:notificationData];
[[NSNotificationQueue defaultQueue] enqueueNotification:note postingStyle:NSPostWhenIdle];
}
// figure out what URL triggered this favicon request
NSString *requestURL = [mRequestDict objectForKey:inURI];
if (!requestURL)
requestURL = [NSString string];
// we always send out the notification, so that clients know
// about failed requests
NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys:
inURI, SiteIconLoadURIKey,
faviconImage, SiteIconLoadImageKey, // may be nil
requestURL, SiteIconLoadUserDataKey,
nil];
NSNotification *note = [NSNotification notificationWithName: SiteIconLoadNotificationName object:target userInfo:notificationData];
[[NSNotificationQueue defaultQueue] enqueueNotification: note postingStyle:NSPostWhenIdle];
// cleanup our key holder
[mRequestDict removeObjectForKey:inURI];
}
#pragma mark -
- (NSImage*)favoriteIconForPage:(NSString*)inPageURI
{
if ([inPageURI length] == 0)
return nil;
// map uri to image location uri
NSString* iconURL = [self favoriteIconURLFromPageURL:inPageURI];
NSImage* siteIcon = [mIconDictionary objectForKey:iconURL];
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"got icon %@ for url %@", siteIcon, iconURL);
#endif
return siteIcon;
}
- (BOOL)fetchFavoriteIconForPage:(NSString*)inPageURI
withIconLocation:(NSString*)inIconURI
allowNetwork:(BOOL)inAllowNetwork
notifyingClient:(id)inClient
{
NSString* iconTargetURL = nil;
if (inIconURI)
{
// XXX clear any old cached uri for this page here?
// we should deal with the case of a page with a link element asking
// for the site icon, then a subsequent request with a nil inIconURI.
// however, we normally try to load the "default" favicon.ico before
// we see a link element, so the right thing happens.
iconTargetURL = inIconURI;
}
else
{
// look in the cache, and if it's not found, use the default location
iconTargetURL = [self favoriteIconURLFromPageURL:inPageURI];
}
if ([iconTargetURL length] == 0)
return NO;
// is this uri already in the missing icons cache?
if ([self inMissedIconsCache:iconTargetURL])
{
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"Site icon %@ known missing", iconTargetURL);
#endif
return NO;
}
id iconLocationString = inIconURI;
if (!iconLocationString)
iconLocationString = [NSNull null];
// preserve requesting URI for later notification
NSDictionary* loadInfo = [NSDictionary dictionaryWithObjectsAndKeys:
inClient, @"icon_requestor",
inPageURI, @"request_uri",
iconLocationString, @"icon_uri",
nil];
NSMutableArray* existingRequests = [mRequestDict objectForKey:iconTargetURL];
if (existingRequests)
{
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"Site icon request %@ added to load list", iconTargetURL);
#endif
[existingRequests addObject:loadInfo];
return YES;
}
existingRequests = [NSMutableArray arrayWithObject:loadInfo];
[mRequestDict setObject:existingRequests forKey:iconTargetURL];
RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider];
BOOL loadDispatched = [dataProvider loadURI:iconTargetURL forTarget:inClient withListener:self withUserData:nil allowNetworking:inAllowNetwork];
#ifdef VERBOSE_SITE_ICON_LOADING
NSLog(@"Site icon load %@ dispatched %d", iconTargetURL, loadDispatched);
#endif
return loadDispatched;
}
#pragma mark -
+ (SiteIconProvider*)sharedFavoriteIconProvider
{
static SiteIconProvider* sIconProvider = nil;

View File

@ -83,6 +83,11 @@ public:
void SetContainer(id <CHBrowserContainer> aContainer);
protected:
nsresult HandleBlockedPopupEvent(nsIDOMEvent* inEvent);
nsresult HandleLinkAddedEvent(nsIDOMEvent* inEvent);
private:
CHBrowserView* mView; // WEAK - it owns us
NSMutableArray* mListeners;

View File

@ -43,7 +43,13 @@
#include "nsIWebNavigation.h"
#include "nsIWebProgress.h"
#include "nsIURI.h"
#include "nsIDOMElement.h"
#include "nsIDOMWindow.h"
#include "nsIDOMDocument.h"
#include "nsIDOM3Document.h"
#include "nsIDOMDocumentView.h"
#include "nsIDOMAbstractView.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMPopupBlockedEvent.h"
// XPCOM and String includes
@ -53,10 +59,11 @@
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsNetError.h"
#include "nsNetUtil.h"
#import "CHBrowserView.h"
#include "CHBrowserListener.h"
#import "CHBrowserListener.h"
// informal protocol of methods that our embedding window might support
@ -663,10 +670,28 @@ CHBrowserListener::SetContainer(id <CHBrowserContainer> aContainer)
}
NS_IMETHODIMP
CHBrowserListener::HandleEvent(nsIDOMEvent *event)
CHBrowserListener::HandleEvent(nsIDOMEvent* inEvent)
{
nsCOMPtr<nsIDOMPopupBlockedEvent> blockEvent = do_QueryInterface(event);
if ( blockEvent ) {
NS_ENSURE_ARG(inEvent);
nsAutoString eventType;
inEvent->GetType(eventType);
if (eventType.Equals(NS_LITERAL_STRING("DOMPopupBlocked")))
return HandleBlockedPopupEvent(inEvent);
if (eventType.Equals(NS_LITERAL_STRING("DOMLinkAdded")))
return HandleLinkAddedEvent(inEvent);
return NS_OK;
}
nsresult
CHBrowserListener::HandleBlockedPopupEvent(nsIDOMEvent* inEvent)
{
nsCOMPtr<nsIDOMPopupBlockedEvent> blockEvent = do_QueryInterface(inEvent);
if (blockEvent) {
nsCOMPtr<nsIURI> blockedURI, blockedSite;
blockEvent->GetPopupWindowURI(getter_AddRefs(blockedURI));
blockEvent->GetRequestingWindowURI(getter_AddRefs(blockedSite));
@ -675,3 +700,77 @@ CHBrowserListener::HandleEvent(nsIDOMEvent *event)
return NS_OK;
}
nsresult
CHBrowserListener::HandleLinkAddedEvent(nsIDOMEvent* inEvent)
{
nsCOMPtr<nsIDOMEventTarget> target;
inEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMElement> linkElement = do_QueryInterface(target);
if (!linkElement)
return NS_ERROR_FAILURE;
nsAutoString relAttribute;
linkElement->GetAttribute(NS_LITERAL_STRING("rel"), relAttribute);
if (!relAttribute.EqualsIgnoreCase("shortcut icon") && !relAttribute.EqualsIgnoreCase("icon"))
return NS_OK;
// make sure the load is for the main window
nsCOMPtr<nsIDOMDocument> domDoc;
linkElement->GetOwnerDocument (getter_AddRefs(domDoc));
nsCOMPtr<nsIDOMDocumentView> docView(do_QueryInterface(domDoc));
NS_ENSURE_TRUE(docView, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMAbstractView> abstractView;
docView->GetDefaultView(getter_AddRefs(abstractView));
nsCOMPtr<nsIDOMWindow> domWin(do_QueryInterface(abstractView));
NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMWindow> topDomWin;
domWin->GetTop(getter_AddRefs(topDomWin));
nsCOMPtr<nsISupports> domWinAsISupports(do_QueryInterface(domWin));
nsCOMPtr<nsISupports> topDomWinAsISupports(do_QueryInterface(topDomWin));
// prevent subframes from setting the favicon
if (domWinAsISupports != topDomWinAsISupports)
return NS_OK;
// now get the uri of the icon
nsAutoString iconHref;
linkElement->GetAttribute(NS_LITERAL_STRING("href"), iconHref);
if (iconHref.IsEmpty())
return NS_OK;
// get the document uri
nsCOMPtr<nsIDOM3Document> doc = do_QueryInterface(domDoc);
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsAutoString docURISpec;
nsresult rv = doc->GetDocumentURI(docURISpec);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
nsCOMPtr<nsIURI> documentURI;
rv = NS_NewURI(getter_AddRefs(documentURI), docURISpec);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
nsCOMPtr<nsIURI> iconURI;
rv = NS_NewURI(getter_AddRefs(iconURI), NS_ConvertUCS2toUTF8(iconHref), nsnull, documentURI);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
// only accept http and https icons (should we allow https, even?)
PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE;
iconURI->SchemeIs("http", &isHTTP);
iconURI->SchemeIs("https", &isHTTPS);
if (!isHTTP && !isHTTPS)
return NS_OK;
nsCAutoString iconFullURI;
iconURI->GetSpec(iconFullURI);
NSString* iconSpec = [NSString stringWith_nsACString:iconFullURI];
[mContainer onFoundShortcutIcon:iconSpec];
return NS_OK;
}

View File

@ -80,6 +80,9 @@ class nsISupports;
- (void)onHideTooltip;
// Called when a popup is blocked
- (void)onPopupBlocked:(nsIURI*)inURIBlocked fromSite:(nsIURI*)inSite;
// Called when a "shortcut icon" link element is noticed
- (void)onFoundShortcutIcon:(NSString*)inIconURI;
@end
typedef enum {

View File

@ -180,11 +180,19 @@ const char kDirServiceContractID[] = "@mozilla.org/file/directory_service;1";
if ( rec )
rec->AddEventListenerByIID(clickListener, NS_GET_IID(nsIDOMMouseListener));
// register the CHBrowserListener as an event listener for popup-blocking events
// register the CHBrowserListener as an event listener for popup-blocking events,
// and link-added events.
nsCOMPtr<nsIDOMEventTarget> eventTarget = do_QueryInterface(rec);
if ( eventTarget )
if (eventTarget)
{
rv = eventTarget->AddEventListener(NS_LITERAL_STRING("DOMPopupBlocked"),
NS_STATIC_CAST(nsIDOMEventListener*, _listener), PR_FALSE);
NS_ASSERTION(NS_SUCCEEDED(rv), "AddEventListener failed");
rv = eventTarget->AddEventListener(NS_LITERAL_STRING("DOMLinkAdded"),
NS_STATIC_CAST(nsIDOMEventListener*, _listener), PR_FALSE);
NS_ASSERTION(NS_SUCCEEDED(rv), "AddEventListener failed");
}
}
return self;
}

View File

@ -58,10 +58,14 @@ static OSStatus MenuEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef
OSStatus err = GetEventParameter(inEvent, kEventParamDirectObject, typeMenuRef, NULL, sizeof(MenuRef), NULL, &theCarbonMenu);
if (err == noErr)
{
// we can't map from MenuRef to NSMenu, so we have to let receivers of the notification
// do the test.
[[NSNotificationCenter defaultCenter] postNotificationName:NSMenuWillDisplayNotification
object:[NSValue valueWithPointer:theCarbonMenu]];
NS_DURING
// we can't map from MenuRef to NSMenu, so we have to let receivers of the notification
// do the test.
[[NSNotificationCenter defaultCenter] postNotificationName:NSMenuWillDisplayNotification
object:[NSValue valueWithPointer:theCarbonMenu]];
NS_HANDLER
NSLog(@"Caught exception %@", localException);
NS_ENDHANDLER
}
}
break;

View File

@ -65,6 +65,7 @@ extern NSString* const kNotificationHistoryDataSourceChangedUserInfoChangedItemO
nsIBrowserHistory* mGlobalHistory; // owned (would be an nsCOMPtr)
nsHistoryObserver* mHistoryObserver; // owned
BOOL mShowSiteIcons;
NSString* mCurrentViewIdentifier;
NSString* mSortColumn;
@ -93,6 +94,8 @@ extern NSString* const kNotificationHistoryDataSourceChangedUserInfoChangedItemO
- (void)loadLazily;
- (HistoryItem*)rootItem;
- (BOOL)showSiteIcons;
// ideally sorting would be on the view, not the data source, but this keeps thing simpler
- (void)setSortColumnIdentifier:(NSString*)sortColumnIdentifier;
- (NSString*)sortColumnIdentifier;

View File

@ -47,6 +47,7 @@
#import "ExtendedOutlineView.h"
#import "PreferenceManager.h"
#import "HistoryItem.h"
#import "SiteIconProvider.h"
#import "BookmarkViewController.h" // only for +greyStringWithItemCount
@ -92,13 +93,14 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
// base class for a 'builder' object. This one just builds a flat list
@interface HistoryTreeBuilder : NSObject
{
HistoryCategoryItem* mRootItem;
HistoryDataSource* mDataSource; // not retained (it owns us)
HistoryCategoryItem* mRootItem; // retained
SEL mSortSelector;
BOOL mSortDescending;
}
// sets up the tree and sorts it
- (id)initWithItems:(NSArray*)items sortSelector:(SEL)sortSelector descending:(BOOL)descending;
- (id)initWithDataSource:(HistoryDataSource*)inDataSource items:(NSArray*)items sortSelector:(SEL)sortSelector descending:(BOOL)descending;
- (HistoryItem*)rootItem;
- (HistoryItem*)addItem:(HistorySiteItem*)item;
@ -159,6 +161,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
- (HistorySiteItem*)itemWithIdentifier:(NSString*)identifier;
- (NSString*)relativeDataStringForDate:(NSDate*)date;
- (void)siteIconLoaded:(NSNotification*)inNotification;
- (void)checkForNewDay;
@end
@ -200,11 +203,12 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
@implementation HistoryTreeBuilder
- (id)initWithItems:(NSArray*)items sortSelector:(SEL)sortSelector descending:(BOOL)descending
- (id)initWithDataSource:(HistoryDataSource*)inDataSource items:(NSArray*)items sortSelector:(SEL)sortSelector descending:(BOOL)descending
{
if ((self = [super init]))
{
mSortSelector = sortSelector;
mDataSource = inDataSource; // not retained
mSortSelector = sortSelector;
mSortDescending = descending;
[self buildTreeWithItems:items];
@ -239,7 +243,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
- (void)buildTreeWithItems:(NSArray*)items
{
mRootItem = [[HistoryCategoryItem alloc] initWithTitle:@"" childCapacity:[items count]];
mRootItem = [[HistoryCategoryItem alloc] initWithDataSource:mDataSource title:@"" childCapacity:[items count]];
[mRootItem addChildren:items];
[self resortFromItem:mRootItem];
@ -297,7 +301,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
if ([itemHostname isEqualToString:@"local_file"])
itemTitle = NSLocalizedString(@"LocalFilesCategoryTitle", @"");
hostCategory = [[HistorySiteCategoryItem alloc] initWithSite:itemHostname title:itemTitle childCapacity:10];
hostCategory = [[HistorySiteCategoryItem alloc] initWithDataSource:mDataSource site:itemHostname title:itemTitle childCapacity:10];
[mSiteDictionary setObject:hostCategory forKey:itemHostname];
[mRootItem addChild:hostCategory];
[hostCategory release];
@ -349,7 +353,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
[mSiteDictionary removeAllObjects];
[mRootItem release];
mRootItem = [[HistoryCategoryItem alloc] initWithTitle:@"" childCapacity:100];
mRootItem = [[HistoryCategoryItem alloc] initWithDataSource:mDataSource title:@"" childCapacity:100];
NSEnumerator* itemsEnum = [items objectEnumerator];
HistorySiteItem* item;
@ -404,7 +408,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
minute:0
second:0
timeZone:[nowDate timeZone]];
HistoryCategoryItem* todayItem = [[HistoryDateCategoryItem alloc] initWithStartDate:lastMidnight ageInDays:0 title:NSLocalizedString(@"Today", @"") childCapacity:10];
HistoryCategoryItem* todayItem = [[HistoryDateCategoryItem alloc] initWithDataSource:mDataSource startDate:lastMidnight ageInDays:0 title:NSLocalizedString(@"Today", @"") childCapacity:10];
[mDateCategories addObject:todayItem];
[todayItem release];
@ -414,7 +418,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
hours:0
minutes:0
seconds:0];
HistoryCategoryItem* yesterdayItem = [[HistoryDateCategoryItem alloc] initWithStartDate:startYesterday ageInDays:1 title:NSLocalizedString(@"Yesterday", @"") childCapacity:10];
HistoryCategoryItem* yesterdayItem = [[HistoryDateCategoryItem alloc] initWithDataSource:mDataSource startDate:startYesterday ageInDays:1 title:NSLocalizedString(@"Yesterday", @"") childCapacity:10];
[mDateCategories addObject:yesterdayItem];
[yesterdayItem release];
@ -430,14 +434,14 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
minutes:0
seconds:0];
HistoryCategoryItem* dayItem = [[HistoryDateCategoryItem alloc] initWithStartDate:curDayStart ageInDays:(i + 2) title:[curDayStart descriptionWithCalendarFormat:@"%A %B %d"] childCapacity:10];
HistoryCategoryItem* dayItem = [[HistoryDateCategoryItem alloc] initWithDataSource:mDataSource startDate:curDayStart ageInDays:(i + 2) title:[curDayStart descriptionWithCalendarFormat:@"%A %B %d"] childCapacity:10];
[mDateCategories addObject:dayItem];
[dayItem release];
}
// do "older"
NSDate* oldDate = [NSDate distantPast];
HistoryCategoryItem* olderItem = [[HistoryDateCategoryItem alloc] initWithStartDate:oldDate ageInDays:-1 title:NSLocalizedString(@"HistoryMoreThanAWeek", @"") childCapacity:100];
HistoryCategoryItem* olderItem = [[HistoryDateCategoryItem alloc] initWithDataSource:mDataSource startDate:oldDate ageInDays:-1 title:NSLocalizedString(@"HistoryMoreThanAWeek", @"") childCapacity:100];
[mDateCategories addObject:olderItem];
[olderItem release];
}
@ -485,7 +489,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
[dateCategory addChild:item];
}
mRootItem = [[HistoryCategoryItem alloc] initWithTitle:@"" childCapacity:[mDateCategories count]];
mRootItem = [[HistoryCategoryItem alloc] initWithDataSource:mDataSource title:@"" childCapacity:[mDateCategories count]];
[mRootItem addChildren:mDateCategories];
[self resortFromItem:mRootItem];
@ -548,7 +552,7 @@ public:
}
else
{
item = [[HistorySiteItem alloc] initWith_nsIHistoryItem:inHistoryItem];
item = [[HistorySiteItem alloc] initWithDataSource:mDataSource historyItem:inHistoryItem];
[mDataSource itemAdded:item];
[item release];
}
@ -650,6 +654,14 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
selector:@selector(refreshTimerFired:)
userInfo:nil
repeats:YES] retain];
// register for site icon loads
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(siteIconLoaded:)
name:SiteIconLoadNotificationName
object:nil];
mShowSiteIcons = [[PreferenceManager sharedInstance] getBooleanPref:"browser.chrome.favicons" withSuccess:NULL];
}
return self;
@ -788,7 +800,7 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
nsCOMPtr<nsIHistoryItem> thisItem = do_QueryInterface(thisEntry);
if (thisItem)
{
HistorySiteItem* item = [[HistorySiteItem alloc] initWith_nsIHistoryItem:thisItem];
HistorySiteItem* item = [[HistorySiteItem alloc] initWithDataSource:self historyItem:thisItem];
[mHistoryItems addObject:item];
[mHistoryItemsDictionary setObject:item forKey:[item identifier]];
[item release];
@ -803,6 +815,11 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
return [mTreeBuilder rootItem];
}
- (BOOL)showSiteIcons
{
return mShowSiteIcons;
}
- (void)notifyChanged:(HistoryItem*)changeRoot itemOnly:(BOOL)itemOnly
{
// if we are displaying search results, make sure that updates
@ -913,6 +930,22 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
return [calendarDate relativeDateDescription];
}
- (void)siteIconLoaded:(NSNotification*)inNotification
{
HistoryItem* theItem = [inNotification object];
// if it's not a site item, or it doesn't belong to this data source, ignore it
// (we instantiate multiple data sources)
if (![theItem isKindOfClass:[HistorySiteItem class]] || [theItem dataSource] != self)
return;
NSImage* iconImage = [[inNotification userInfo] objectForKey:SiteIconLoadImageKey];
if (iconImage)
{
[theItem setSiteIcon:iconImage];
[self notifyChanged:theItem itemOnly:YES];
}
}
- (void)checkForNewDay
{
int curDayOfCommonEra = [[NSCalendarDate calendarDate] dayOfCommonEra];

View File

@ -39,17 +39,23 @@
#import <AppKit/AppKit.h>
class nsIHistoryItem;
@class HistoryDataSource;
// HistoryItem is the base class for every object in the history outliner
@interface HistoryItem : NSObject
{
HistoryItem* mParentItem; // not retained
HistoryItem* mParentItem; // our parent item (not retained)
HistoryDataSource* mDataSource; // the data source that owns us (not retained)
}
- (id)initWithDataSource:(HistoryDataSource*)inDataSource;
- (HistoryDataSource*)dataSource;
- (NSString*)title;
- (BOOL)isSiteItem;
- (NSImage*)icon;
- (NSImage*)iconAllowingLoad:(BOOL)inAllowLoad;
- (NSString*)url;
- (NSDate*)firstVisit;
@ -84,7 +90,7 @@ class nsIHistoryItem;
NSMutableArray* mChildren; // array of HistoryItems (may be heterogeneous)
}
- (id)initWithTitle:(NSString*)title childCapacity:(int)capacity;
- (id)initWithDataSource:(HistoryDataSource*)inDataSource title:(NSString*)title childCapacity:(int)capacity;
- (NSString*)title;
- (NSString*)identifier; // return UUID for this folder
@ -101,7 +107,7 @@ class nsIHistoryItem;
NSString* mSite; // not user-visible; used for state tracking
}
- (id)initWithSite:(NSString*)site title:(NSString*)title childCapacity:(int)capacity;
- (id)initWithDataSource:(HistoryDataSource*)inDataSource site:(NSString*)site title:(NSString*)title childCapacity:(int)capacity;
- (NSString*)site;
@end
@ -113,7 +119,7 @@ class nsIHistoryItem;
int mAgeInDays; // -1 is used for "distant past"
}
- (id)initWithStartDate:(NSDate*)startDate ageInDays:(int)days title:(NSString*)title childCapacity:(int)capacity;
- (id)initWithDataSource:(HistoryDataSource*)inDataSource startDate:(NSDate*)startDate ageInDays:(int)days title:(NSString*)title childCapacity:(int)capacity;
- (NSDate*)startDate;
- (BOOL)isTodayCategory;
@ -128,12 +134,17 @@ class nsIHistoryItem;
NSString* mHostname;
NSDate* mFirstVisitDate;
NSDate* mLastVisitDate;
NSImage* mSiteIcon;
BOOL mAttemptedIconLoad;
}
- (id)initWith_nsIHistoryItem:(nsIHistoryItem*)inItem;
- (id)initWithDataSource:(HistoryDataSource*)inDataSource historyItem:(nsIHistoryItem*)inItem;
// return YES if anything changed
- (BOOL)updateWith_nsIHistoryItem:(nsIHistoryItem*)inItem;
- (BOOL)matchesString:(NSString*)searchString inFieldWithTag:(int)tag;
- (void)setSiteIcon:(NSImage*)inImage;
@end

View File

@ -40,6 +40,9 @@
#import "NSDate+Utils.h"
#import "HistoryItem.h"
#import "HistoryDataSource.h"
#import "SiteIconProvider.h"
#import "nsString.h"
#import "nsIHistoryItems.h"
@ -56,10 +59,11 @@ enum
@implementation HistoryItem
- (id)init
- (id)initWithDataSource:(HistoryDataSource*)inDataSource
{
if ((self = [super init]))
{
mDataSource = inDataSource; // not retained
}
return self;
}
@ -69,6 +73,11 @@ enum
[super dealloc];
}
- (HistoryDataSource*)dataSource
{
return mDataSource;
}
- (NSString*)title
{
return @""; // subclasses override
@ -84,6 +93,11 @@ enum
return nil;
}
- (NSImage*)iconAllowingLoad:(BOOL)inAllowLoad
{
return [self icon];
}
- (NSString*)url
{
return @"";
@ -173,9 +187,9 @@ enum
@implementation HistoryCategoryItem
- (id)initWithTitle:(NSString*)title childCapacity:(int)capacity
- (id)initWithDataSource:(HistoryDataSource*)inDataSource title:(NSString*)title childCapacity:(int)capacity
{
if ((self = [super init]))
if ((self = [super initWithDataSource:inDataSource]))
{
mTitle = [title retain];
mChildren = [[NSMutableArray alloc] initWithCapacity:capacity];
@ -337,9 +351,9 @@ enum
@implementation HistorySiteCategoryItem
- (id)initWithSite:(NSString*)site title:(NSString*)title childCapacity:(int)capacity
- (id)initWithDataSource:(HistoryDataSource*)inDataSource site:(NSString*)site title:(NSString*)title childCapacity:(int)capacity
{
if ((self = [super initWithTitle:title childCapacity:capacity]))
if ((self = [super initWithDataSource:inDataSource title:title childCapacity:capacity]))
{
mSite = [site retain];
}
@ -373,9 +387,9 @@ enum
@implementation HistoryDateCategoryItem
- (id)initWithStartDate:(NSDate*)startDate ageInDays:(int)days title:(NSString*)title childCapacity:(int)capacity
- (id)initWithDataSource:(HistoryDataSource*)inDataSource startDate:(NSDate*)startDate ageInDays:(int)days title:(NSString*)title childCapacity:(int)capacity
{
if ((self = [super initWithTitle:title childCapacity:capacity]))
if ((self = [super initWithDataSource:inDataSource title:title childCapacity:capacity]))
{
mStartDate = [startDate retain];
mAgeInDays = days;
@ -456,9 +470,9 @@ enum
@implementation HistorySiteItem
- (id)initWith_nsIHistoryItem:(nsIHistoryItem*)inItem
- (id)initWithDataSource:(HistoryDataSource*)inDataSource historyItem:(nsIHistoryItem*)inItem
{
if ((self = [super init]))
if ((self = [super initWithDataSource:inDataSource]))
{
nsCString identifier;
if (NS_SUCCEEDED(inItem->GetID(identifier)))
@ -530,6 +544,8 @@ enum
[mHostname release];
[mFirstVisitDate release];
[mLastVisitDate release];
[mSiteIcon release];
[super dealloc];
}
@ -570,8 +586,46 @@ enum
- (NSImage*)icon
{
// XXX todo: site icons
return [NSImage imageNamed:@"smallbookmark"];
return [self iconAllowingLoad:NO];
}
- (NSImage*)iconAllowingLoad:(BOOL)inAllowLoad
{
if (mSiteIcon)
return mSiteIcon;
if ([mDataSource showSiteIcons])
{
NSImage* siteIcon = [[SiteIconProvider sharedFavoriteIconProvider] favoriteIconForPage:[self url]];
if (siteIcon)
{
[self setSiteIcon:siteIcon];
return mSiteIcon;
}
}
// firing off site icon loads here interferes with history submenu display
// (maybe a slew of Carbon or other events causes events to get lost?)
if (inAllowLoad)
{
if (!mAttemptedIconLoad)
{
// fire off site icon load
[[SiteIconProvider sharedFavoriteIconProvider] fetchFavoriteIconForPage:[self url]
withIconLocation:nil
allowNetwork:NO
notifyingClient:self];
mAttemptedIconLoad = YES;
}
}
return [NSImage imageNamed:@"globe_ico"];
}
- (void)setSiteIcon:(NSImage*)inImage
{
[mSiteIcon autorelease];
mSiteIcon = [inImage retain];
}
// ideally, we'd strip the protocol from the URL before comparing so that https:// doesn't

View File

@ -334,7 +334,7 @@ static NSString* const kExpandedHistoryStatesDefaultsKey = @"history_expand_stat
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if ([[tableColumn identifier] isEqualToString:@"title"])
[cell setImage:[item icon]];
[cell setImage:[item iconAllowingLoad:YES]];
}
#if 0