diff --git a/mozilla/camino/resources/localized/English.lproj/Localizable.strings b/mozilla/camino/resources/localized/English.lproj/Localizable.strings index 5489ce23dd9..a12c2b4e5d3 100644 Binary files a/mozilla/camino/resources/localized/English.lproj/Localizable.strings and b/mozilla/camino/resources/localized/English.lproj/Localizable.strings differ diff --git a/mozilla/camino/src/application/MainController.h b/mozilla/camino/src/application/MainController.h index 1f37dd740bf..ccdba99741a 100644 --- a/mozilla/camino/src/application/MainController.h +++ b/mozilla/camino/src/application/MainController.h @@ -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. diff --git a/mozilla/camino/src/application/MainController.mm b/mozilla/camino/src/application/MainController.mm index 9c7575dfd65..053fd8fd083 100644 --- a/mozilla/camino/src/application/MainController.mm +++ b/mozilla/camino/src/application/MainController.mm @@ -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 hist = do_GetService("@mozilla.org/browser/global-history;2"); + if (hist) + hist->RemoveAllPages(); + } +} + // // manageBookmarks: // diff --git a/mozilla/camino/src/bookmarks/Bookmark.mm b/mozilla/camino/src/bookmarks/Bookmark.mm index 0950592cdfe..f99d60b71be 100644 --- a/mozilla/camino/src/bookmarks/Bookmark.mm +++ b/mozilla/camino/src/bookmarks/Bookmark.mm @@ -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]; } } diff --git a/mozilla/camino/src/bookmarks/BookmarkViewController.mm b/mozilla/camino/src/bookmarks/BookmarkViewController.mm index 55bf61671b5..5e27e9f19c4 100644 --- a/mozilla/camino/src/bookmarks/BookmarkViewController.mm +++ b/mozilla/camino/src/bookmarks/BookmarkViewController.mm @@ -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) diff --git a/mozilla/camino/src/browser/BrowserWindowController.mm b/mozilla/camino/src/browser/BrowserWindowController.mm index 8fb0871a9b6..8d58b5b77fd 100644 --- a/mozilla/camino/src/browser/BrowserWindowController.mm +++ b/mozilla/camino/src/browser/BrowserWindowController.mm @@ -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 diff --git a/mozilla/camino/src/browser/BrowserWrapper.mm b/mozilla/camino/src/browser/BrowserWrapper.mm index a8192a6def5..478792e876f 100644 --- a/mozilla/camino/src/browser/BrowserWrapper.mm +++ b/mozilla/camino/src/browser/BrowserWrapper.mm @@ -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]; diff --git a/mozilla/camino/src/browser/GoMenu.h b/mozilla/camino/src/browser/GoMenu.h index 88a2b19c220..0fe20268726 100644 --- a/mozilla/camino/src/browser/GoMenu.h +++ b/mozilla/camino/src/browser/GoMenu.h @@ -68,6 +68,8 @@ - (void)setItemBeforeHistoryItems:(NSMenuItem*)inItem; - (NSMenuItem*)itemBeforeHistoryItems; +- (void)addLastItems; + @end diff --git a/mozilla/camino/src/browser/GoMenu.mm b/mozilla/camino/src/browser/GoMenu.mm index 77d72964d0b..0849abe6812 100644 --- a/mozilla/camino/src/browser/GoMenu.mm +++ b/mozilla/camino/src/browser/GoMenu.mm @@ -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 diff --git a/mozilla/camino/src/browser/RemoteDataProvider.h b/mozilla/camino/src/browser/RemoteDataProvider.h index 29703357854..6fd1ff279ec 100644 --- a/mozilla/camino/src/browser/RemoteDataProvider.h +++ b/mozilla/camino/src/browser/RemoteDataProvider.h @@ -38,11 +38,11 @@ #import -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 diff --git a/mozilla/camino/src/browser/RemoteDataProvider.mm b/mozilla/camino/src/browser/RemoteDataProvider.mm index a323025f719..f55f1ad0adc 100644 --- a/mozilla/camino/src/browser/RemoteDataProvider.mm +++ b/mozilla/camino/src/browser/RemoteDataProvider.mm @@ -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 diff --git a/mozilla/camino/src/browser/SiteIconProvider.h b/mozilla/camino/src/browser/SiteIconProvider.h index a2522557abd..abc33143b45 100644 --- a/mozilla/camino/src/browser/SiteIconProvider.h +++ b/mozilla/camino/src/browser/SiteIconProvider.h @@ -48,8 +48,10 @@ class NeckoCacheHelper; @interface SiteIconProvider : NSObject { - 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 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 element) +// then this will take that into account. +- (NSImage*)favoriteIconForPage:(NSString*)inPageURI; + @end diff --git a/mozilla/camino/src/browser/SiteIconProvider.mm b/mozilla/camino/src/browser/SiteIconProvider.mm index 8c3ad859185..4427ea8f1e4 100644 --- a/mozilla/camino/src/browser/SiteIconProvider.mm +++ b/mozilla/camino/src/browser/SiteIconProvider.mm @@ -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 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 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 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 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 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 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 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 pageURI; + nsresult rv = NS_NewURI(getter_AddRefs(pageURI), inPageURI, nsnull, nsnull); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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; diff --git a/mozilla/camino/src/embedding/CHBrowserListener.h b/mozilla/camino/src/embedding/CHBrowserListener.h index 17adfd1453a..41a71e5659e 100644 --- a/mozilla/camino/src/embedding/CHBrowserListener.h +++ b/mozilla/camino/src/embedding/CHBrowserListener.h @@ -83,6 +83,11 @@ public: void SetContainer(id aContainer); +protected: + + nsresult HandleBlockedPopupEvent(nsIDOMEvent* inEvent); + nsresult HandleLinkAddedEvent(nsIDOMEvent* inEvent); + private: CHBrowserView* mView; // WEAK - it owns us NSMutableArray* mListeners; diff --git a/mozilla/camino/src/embedding/CHBrowserListener.mm b/mozilla/camino/src/embedding/CHBrowserListener.mm index 21c3ed2abf0..0c8e5afe4c4 100644 --- a/mozilla/camino/src/embedding/CHBrowserListener.mm +++ b/mozilla/camino/src/embedding/CHBrowserListener.mm @@ -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 aContainer) } NS_IMETHODIMP -CHBrowserListener::HandleEvent(nsIDOMEvent *event) +CHBrowserListener::HandleEvent(nsIDOMEvent* inEvent) { - nsCOMPtr 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 blockEvent = do_QueryInterface(inEvent); + if (blockEvent) { nsCOMPtr 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 target; + inEvent->GetTarget(getter_AddRefs(target)); + nsCOMPtr 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 domDoc; + linkElement->GetOwnerDocument (getter_AddRefs(domDoc)); + + nsCOMPtr docView(do_QueryInterface(domDoc)); + NS_ENSURE_TRUE(docView, NS_ERROR_FAILURE); + + nsCOMPtr abstractView; + docView->GetDefaultView(getter_AddRefs(abstractView)); + + nsCOMPtr domWin(do_QueryInterface(abstractView)); + NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE); + + nsCOMPtr topDomWin; + domWin->GetTop(getter_AddRefs(topDomWin)); + + nsCOMPtr domWinAsISupports(do_QueryInterface(domWin)); + nsCOMPtr 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 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 documentURI; + rv = NS_NewURI(getter_AddRefs(documentURI), docURISpec); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsCOMPtr 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; +} + diff --git a/mozilla/camino/src/embedding/CHBrowserView.h b/mozilla/camino/src/embedding/CHBrowserView.h index 5bce4dea665..a332b202329 100644 --- a/mozilla/camino/src/embedding/CHBrowserView.h +++ b/mozilla/camino/src/embedding/CHBrowserView.h @@ -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 { diff --git a/mozilla/camino/src/embedding/CHBrowserView.mm b/mozilla/camino/src/embedding/CHBrowserView.mm index bc1c1229e6b..b8040530206 100644 --- a/mozilla/camino/src/embedding/CHBrowserView.mm +++ b/mozilla/camino/src/embedding/CHBrowserView.mm @@ -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 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; } diff --git a/mozilla/camino/src/extensions/NSMenu+Utils.m b/mozilla/camino/src/extensions/NSMenu+Utils.m index 07939b75ab8..f19d8ec95cc 100644 --- a/mozilla/camino/src/extensions/NSMenu+Utils.m +++ b/mozilla/camino/src/extensions/NSMenu+Utils.m @@ -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; diff --git a/mozilla/camino/src/history/HistoryDataSource.h b/mozilla/camino/src/history/HistoryDataSource.h index f38824bc2e8..91a14b7ace8 100644 --- a/mozilla/camino/src/history/HistoryDataSource.h +++ b/mozilla/camino/src/history/HistoryDataSource.h @@ -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; diff --git a/mozilla/camino/src/history/HistoryDataSource.mm b/mozilla/camino/src/history/HistoryDataSource.mm index 0e271081927..f12cd85adc4 100644 --- a/mozilla/camino/src/history/HistoryDataSource.mm +++ b/mozilla/camino/src/history/HistoryDataSource.mm @@ -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 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]; diff --git a/mozilla/camino/src/history/HistoryItem.h b/mozilla/camino/src/history/HistoryItem.h index 95520780585..5e01889f363 100644 --- a/mozilla/camino/src/history/HistoryItem.h +++ b/mozilla/camino/src/history/HistoryItem.h @@ -39,17 +39,23 @@ #import 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 diff --git a/mozilla/camino/src/history/HistoryItem.mm b/mozilla/camino/src/history/HistoryItem.mm index fdb70b86fb0..45f5f0ba43a 100644 --- a/mozilla/camino/src/history/HistoryItem.mm +++ b/mozilla/camino/src/history/HistoryItem.mm @@ -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 diff --git a/mozilla/camino/src/history/HistoryOutlineViewDelegate.mm b/mozilla/camino/src/history/HistoryOutlineViewDelegate.mm index 3300d2555b2..bab8439e4c5 100644 --- a/mozilla/camino/src/history/HistoryOutlineViewDelegate.mm +++ b/mozilla/camino/src/history/HistoryOutlineViewDelegate.mm @@ -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