From 3e9dcd40e5d0b16002d66c871bee234362a922a0 Mon Sep 17 00:00:00 2001 From: "sfraser%netscape.com" Date: Thu, 15 Aug 2002 18:08:12 +0000 Subject: [PATCH] Changes for bug 160725: adding site icon support to the proxy icon, tabs, bookmarks (in sidebar, tolbar and menu). Site icons are fetched via the SiteIconProvider, which uses the more generic RemoteDataProvider under the hood. RemoteDataProvider talks to necko, getting the resulting data into an NSData. Notifications are used to indicate to listeners when the load is done. Site icons that are loaded to into the necko cache. We also cache data for missing site icons, to avoid continual refetches. Site icons are on by default, but can be turned off via the "browser.chrome.site_icons" pref. --- camino/BookmarksDataSource.h | 5 +- camino/BookmarksDataSource.mm | 72 ++-- camino/BookmarksService.h | 29 +- camino/BookmarksService.mm | 251 +++++++++---- camino/BrowserWindowController.h | 3 + camino/BrowserWindowController.mm | 8 + camino/CHBookmarksButton.h | 15 +- camino/CHBookmarksButton.mm | 23 +- camino/CHBookmarksToolbar.h | 11 +- camino/CHBookmarksToolbar.mm | 18 +- camino/CHBrowserWrapper.h | 9 +- camino/CHBrowserWrapper.mm | 183 ++++++++-- camino/CHIconTabViewItem.h | 3 +- camino/CHIconTabViewItem.mm | 9 +- camino/CHPageProxyIcon.h | 2 +- camino/CHPageProxyIcon.mm | 13 + camino/CHPreferenceManager.h | 13 +- camino/CHPreferenceManager.mm | 161 ++++++--- camino/MainController.h | 2 +- camino/MainController.mm | 54 +-- camino/RemoteDataProvider.h | 90 +++++ camino/RemoteDataProvider.mm | 298 ++++++++++++++++ camino/SiteIconProvider.h | 69 ++++ camino/SiteIconProvider.mm | 330 ++++++++++++++++++ camino/src/application/MainController.h | 2 +- camino/src/application/MainController.mm | 54 +-- camino/src/bookmarks/BookmarksButton.h | 15 +- camino/src/bookmarks/BookmarksButton.mm | 23 +- camino/src/bookmarks/BookmarksDataSource.h | 5 +- camino/src/bookmarks/BookmarksDataSource.mm | 72 ++-- camino/src/bookmarks/BookmarksService.h | 29 +- camino/src/bookmarks/BookmarksService.mm | 251 +++++++++---- camino/src/bookmarks/BookmarksToolbar.h | 11 +- camino/src/bookmarks/BookmarksToolbar.mm | 18 +- camino/src/browser/BrowserWindowController.h | 3 + camino/src/browser/BrowserWindowController.mm | 8 + camino/src/browser/BrowserWrapper.h | 9 +- camino/src/browser/BrowserWrapper.mm | 183 ++++++++-- camino/src/browser/PageProxyIcon.h | 2 +- camino/src/browser/PageProxyIcon.mm | 13 + camino/src/browser/RemoteDataProvider.h | 90 +++++ camino/src/browser/RemoteDataProvider.mm | 298 ++++++++++++++++ camino/src/browser/SiteIconProvider.h | 69 ++++ camino/src/browser/SiteIconProvider.mm | 330 ++++++++++++++++++ camino/src/extensions/IconTabViewItem.h | 3 +- camino/src/extensions/IconTabViewItem.mm | 9 +- camino/src/preferences/PreferenceManager.h | 13 +- camino/src/preferences/PreferenceManager.mm | 161 ++++++--- chimera/BookmarksDataSource.h | 5 +- chimera/BookmarksDataSource.mm | 72 ++-- chimera/BookmarksService.h | 29 +- chimera/BookmarksService.mm | 251 +++++++++---- chimera/BrowserWindowController.h | 3 + chimera/BrowserWindowController.mm | 8 + chimera/CHBookmarksButton.h | 15 +- chimera/CHBookmarksButton.mm | 23 +- chimera/CHBookmarksToolbar.h | 11 +- chimera/CHBookmarksToolbar.mm | 18 +- chimera/CHBrowserWrapper.h | 9 +- chimera/CHBrowserWrapper.mm | 183 ++++++++-- chimera/CHIconTabViewItem.h | 3 +- chimera/CHIconTabViewItem.mm | 9 +- chimera/CHPageProxyIcon.h | 2 +- chimera/CHPageProxyIcon.mm | 13 + chimera/CHPreferenceManager.h | 13 +- chimera/CHPreferenceManager.mm | 161 ++++++--- chimera/MainController.h | 2 +- chimera/MainController.mm | 54 +-- chimera/RemoteDataProvider.h | 90 +++++ chimera/RemoteDataProvider.mm | 298 ++++++++++++++++ chimera/SiteIconProvider.h | 69 ++++ chimera/SiteIconProvider.mm | 330 ++++++++++++++++++ chimera/src/application/MainController.h | 2 +- chimera/src/application/MainController.mm | 54 +-- chimera/src/bookmarks/BookmarksButton.h | 15 +- chimera/src/bookmarks/BookmarksButton.mm | 23 +- chimera/src/bookmarks/BookmarksDataSource.h | 5 +- chimera/src/bookmarks/BookmarksDataSource.mm | 72 ++-- chimera/src/bookmarks/BookmarksService.h | 29 +- chimera/src/bookmarks/BookmarksService.mm | 251 +++++++++---- chimera/src/bookmarks/BookmarksToolbar.h | 11 +- chimera/src/bookmarks/BookmarksToolbar.mm | 18 +- chimera/src/browser/BrowserWindowController.h | 3 + .../src/browser/BrowserWindowController.mm | 8 + chimera/src/browser/BrowserWrapper.h | 9 +- chimera/src/browser/BrowserWrapper.mm | 183 ++++++++-- chimera/src/browser/PageProxyIcon.h | 2 +- chimera/src/browser/PageProxyIcon.mm | 13 + chimera/src/browser/RemoteDataProvider.h | 90 +++++ chimera/src/browser/RemoteDataProvider.mm | 298 ++++++++++++++++ chimera/src/browser/SiteIconProvider.h | 69 ++++ chimera/src/browser/SiteIconProvider.mm | 330 ++++++++++++++++++ chimera/src/extensions/IconTabViewItem.h | 3 +- chimera/src/extensions/IconTabViewItem.mm | 9 +- chimera/src/preferences/PreferenceManager.h | 13 +- chimera/src/preferences/PreferenceManager.mm | 161 ++++++--- 96 files changed, 5680 insertions(+), 1004 deletions(-) create mode 100644 camino/RemoteDataProvider.h create mode 100644 camino/RemoteDataProvider.mm create mode 100644 camino/SiteIconProvider.h create mode 100644 camino/SiteIconProvider.mm create mode 100644 camino/src/browser/RemoteDataProvider.h create mode 100644 camino/src/browser/RemoteDataProvider.mm create mode 100644 camino/src/browser/SiteIconProvider.h create mode 100644 camino/src/browser/SiteIconProvider.mm create mode 100644 chimera/RemoteDataProvider.h create mode 100644 chimera/RemoteDataProvider.mm create mode 100644 chimera/SiteIconProvider.h create mode 100644 chimera/SiteIconProvider.mm create mode 100644 chimera/src/browser/RemoteDataProvider.h create mode 100644 chimera/src/browser/RemoteDataProvider.mm create mode 100644 chimera/src/browser/SiteIconProvider.h create mode 100644 chimera/src/browser/SiteIconProvider.mm diff --git a/camino/BookmarksDataSource.h b/camino/BookmarksDataSource.h index f05a76b7a544..f0a280af1f29 100644 --- a/camino/BookmarksDataSource.h +++ b/camino/BookmarksDataSource.h @@ -49,6 +49,7 @@ class BookmarksService; @class BookmarkInfoController; +// data source for the bookmarks sidebar. We make one per browser window. @interface BookmarksDataSource : NSObject { BookmarksService* mBookmarks; @@ -106,14 +107,16 @@ class BookmarksService; @interface BookmarkItem : NSObject { nsIContent* mContentNode; + NSImage* mSiteIcon; } - (nsIContent*)contentNode; - (void)setContentNode: (nsIContent*)aContentNode; +- (void)setSiteIcon:(NSImage*)image; - (NSString*)url; +- (NSImage*)siteIcon; - (NSNumber*)contentID; - (id)copyWithZone:(NSZone *)aZone; - (BOOL)isFolder; @end - diff --git a/camino/BookmarksDataSource.mm b/camino/BookmarksDataSource.mm index 77cdc6c36687..9b5915765329 100644 --- a/camino/BookmarksDataSource.mm +++ b/camino/BookmarksDataSource.mm @@ -41,6 +41,7 @@ #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" +#import "SiteIconProvider.h" #include "nsCOMPtr.h" #include "nsIContent.h" @@ -55,17 +56,16 @@ #include "nsVoidArray.h" #import "BookmarksService.h" -#import "StringUtils.h" @implementation BookmarksDataSource -(id) init { - if ( (self = [super init]) ) { - mBookmarks = nsnull; - mCachedHref = nil; - } - return self; + if ( (self = [super init]) ) { + mBookmarks = nsnull; + mCachedHref = nil; + } + return self; } -(void) awakeFromNib @@ -86,16 +86,16 @@ -(void) ensureBookmarks { - if (mBookmarks) - return; - - mBookmarks = new BookmarksService(self); - mBookmarks->AddObserver(); - - [mOutlineView setTarget: self]; - [mOutlineView setDoubleAction: @selector(openBookmark:)]; - [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; - [mOutlineView reloadData]; + if (mBookmarks) + return; + + mBookmarks = new BookmarksService(self); + mBookmarks->AddObserver(); + + [mOutlineView setTarget: self]; + [mOutlineView setDoubleAction: @selector(openBookmark:)]; + [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; + [mOutlineView reloadData]; } -(IBAction)addBookmark:(id)aSender @@ -530,20 +530,10 @@ //Get the cell of the text attachment. attachmentAttrStringCell = (NSCell *)[(NSTextAttachment *)[attachmentAttrString attribute:NSAttachmentAttributeName atIndex:0 effectiveRange:nil] attachmentCell]; - //Figure out which image to add, and set the cell's image. - // Use the bookmark groups image for groups. - if ([self outlineView:outlineView isItemExpandable:item]) { - nsIContent* content = [item contentNode]; - nsCOMPtr elt(do_QueryInterface(content)); - nsAutoString group; - content->GetAttr(kNameSpaceID_None, BookmarksService::gGroupAtom, group); - if (!group.IsEmpty()) - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"groupbookmark"]]; - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"folder"]]; - } - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"smallbookmark"]]; + + nsCOMPtr elt(do_QueryInterface(content)); + NSImage* bookmarkImage = mBookmarks->CreateIconForBookmark(elt); + [attachmentAttrStringCell setImage:bookmarkImage]; //Insert the image [cellValue replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attachmentAttrString]; @@ -855,7 +845,16 @@ @end +#pragma mark - + @implementation BookmarkItem + +-(void)dealloc +{ + [mSiteIcon release]; + [super dealloc]; +} + -(nsIContent*)contentNode { return mContentNode; @@ -887,6 +886,18 @@ return [NSString stringWith_nsAString: href]; } +- (void)setSiteIcon:(NSImage*)image +{ + //NSLog(@"Setting site icon for %@", [self url]); + [mSiteIcon autorelease]; + mSiteIcon = [image retain]; +} + +- (NSImage*)siteIcon +{ + return mSiteIcon; +} + -(void)setContentNode: (nsIContent*)aContentNode { mContentNode = aContentNode; @@ -896,6 +907,7 @@ { BookmarkItem* copy = [[[self class] allocWithZone: aZone] init]; [copy setContentNode: mContentNode]; + [copy setSiteIcon: mSiteIcon]; return copy; } diff --git a/camino/BookmarksService.h b/camino/BookmarksService.h index c03f27353565..411935e25172 100644 --- a/camino/BookmarksService.h +++ b/camino/BookmarksService.h @@ -53,6 +53,9 @@ class nsIDOMHTMLDocument; @class BookmarksDataSource; @class BookmarkItem; +// despite appearances, BookmarksService is not a singleton. We make one for the bookmarks menu, +// one each per BookmarksDataSource, and one per bookmarks toolbar. It relies on a bunch of global +// variables, which is evil. class BookmarksService { public: @@ -63,6 +66,7 @@ public: void AddObserver(); void RemoveObserver(); +public: static void BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); static void BookmarkChanged(nsIContent* aItem, bool shouldFlush = true); static void BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); @@ -71,7 +75,6 @@ public: static void MoveBookmarkToFolder(nsIDOMElement* aBookmark, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt); static void DeleteBookmark(nsIDOMElement* aBookmark); -public: static void GetRootContent(nsIContent** aResult); static BookmarkItem* GetRootItem(); static BookmarkItem* GetWrapperFor(nsIContent* aItem); @@ -87,6 +90,8 @@ public: static void ConstructAddBookmarkFolderList(NSPopUpButton* aPopup, BookmarkItem* aItem); + static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); + static void EnsureToolbarRoot(); static void ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc); @@ -96,8 +101,6 @@ public: static NSString* ResolveKeyword(NSString* aKeyword); - static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); - static BOOL DoAncestorsIncludeNode(BookmarkItem* bookmark, BookmarkItem* searchItem); static bool IsBookmarkDropValid(BookmarkItem* proposedParent, int index, NSArray* draggedIDs); static bool PerformBookmarkDrop(BookmarkItem* parent, int index, NSArray* draggedIDs); @@ -139,3 +142,23 @@ private: CHBookmarksToolbar* mToolbar; BookmarksDataSource* mDataSource; }; + + + +// singleton bookmarks manager object + +@interface BookmarksManager : NSObject +{ + + + +} + ++ (BookmarksManager*)sharedBookmarksManager; + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString; + + +@end + + diff --git a/camino/BookmarksService.mm b/camino/BookmarksService.mm index baa4588e3003..6df65543da71 100644 --- a/camino/BookmarksService.mm +++ b/camino/BookmarksService.mm @@ -37,12 +37,13 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserView.h" #import "BookmarksService.h" #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" #import "CHIconTabViewItem.h" -#import "StringUtils.h" +#import "SiteIconProvider.h" #include "nsCRT.h" #include "nsString.h" @@ -106,6 +107,7 @@ NSMutableDictionary* BookmarksService::gDictionary = nil; MainController* BookmarksService::gMainController = nil; NSMenu* BookmarksService::gBookmarksMenu = nil; nsIDOMElement* BookmarksService::gToolbarRoot = nsnull; + nsIAtom* BookmarksService::gBookmarkAtom = nsnull; nsIAtom* BookmarksService::gDescriptionAtom = nsnull; nsIAtom* BookmarksService::gFolderAtom = nsnull; @@ -114,8 +116,11 @@ nsIAtom* BookmarksService::gHrefAtom = nsnull; nsIAtom* BookmarksService::gKeywordAtom = nsnull; nsIAtom* BookmarksService::gNameAtom = nsnull; nsIAtom* BookmarksService::gOpenAtom = nsnull; + nsVoidArray* BookmarksService::gInstances = nsnull; + BOOL BookmarksService::gBookmarksFileReadOK = NO; + int BookmarksService::CHInsertNone = 0; int BookmarksService::CHInsertInto = 1; int BookmarksService::CHInsertBefore = 2; @@ -137,6 +142,54 @@ BookmarksService::~BookmarksService() { } +void +BookmarksService::AddObserver() +{ + gRefCnt++; + if (gRefCnt == 1) { + gBookmarkAtom = NS_NewAtom("bookmark"); + gFolderAtom = NS_NewAtom("folder"); + gNameAtom = NS_NewAtom("name"); + gHrefAtom = NS_NewAtom("href"); + gOpenAtom = NS_NewAtom("open"); + gKeywordAtom = NS_NewAtom("id"); + gDescriptionAtom = NS_NewAtom("description"); + gGroupAtom = NS_NewAtom("group"); + gInstances = new nsVoidArray(); + + ReadBookmarks(); + } + + gInstances->AppendElement(this); +} + +void +BookmarksService::RemoveObserver() +{ + if (gRefCnt == 0) + return; + + gInstances->RemoveElement(this); + + gRefCnt--; + if (gRefCnt == 0) { + // Flush Bookmarks before shutting down as some changes are not flushed when + // they are performed (folder open/closed) as writing a whole bookmark file for + // that type of operation seems excessive. + FlushBookmarks(); + + NS_IF_RELEASE(gBookmarks); + NS_RELEASE(gBookmarkAtom); + NS_RELEASE(gFolderAtom); + NS_RELEASE(gNameAtom); + NS_RELEASE(gHrefAtom); + NS_RELEASE(gOpenAtom); + [gDictionary release]; + } +} + +#pragma mark - + void BookmarksService::GetRootContent(nsIContent** aResult) { @@ -211,7 +264,7 @@ BookmarksService::LocateMenu(nsIContent* aContent) } void -BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -258,7 +311,7 @@ BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool } void -BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) +BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -289,6 +342,10 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) aItem->GetAttr(kNameSpaceID_None, gNameAtom, name); NSString* bookmarkTitle = [[NSString stringWith_nsAString: name] stringByTruncatingTo:80 at:kTruncateAtMiddle]; [childItem setTitle: bookmarkTitle]; + + // and reset the image + BookmarkItem* item = GetWrapperFor(aItem); + [childItem setImage: [item siteIcon]]; } } @@ -298,7 +355,7 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) } void -BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances) return; @@ -343,51 +400,6 @@ BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bo FlushBookmarks(); } -void -BookmarksService::AddObserver() -{ - gRefCnt++; - if (gRefCnt == 1) { - gBookmarkAtom = NS_NewAtom("bookmark"); - gFolderAtom = NS_NewAtom("folder"); - gNameAtom = NS_NewAtom("name"); - gHrefAtom = NS_NewAtom("href"); - gOpenAtom = NS_NewAtom("open"); - gKeywordAtom = NS_NewAtom("id"); - gDescriptionAtom = NS_NewAtom("description"); - gGroupAtom = NS_NewAtom("group"); - gInstances = new nsVoidArray(); - - ReadBookmarks(); - } - - gInstances->AppendElement(this); -} - -void -BookmarksService::RemoveObserver() -{ - if (gRefCnt == 0) - return; - - gInstances->RemoveElement(this); - - gRefCnt--; - if (gRefCnt == 0) { - // Flush Bookmarks before shutting down as some changes are not flushed when - // they are performed (folder open/closed) as writing a whole bookmark file for - // that type of operation seems excessive. - FlushBookmarks(); - - NS_IF_RELEASE(gBookmarks); - NS_RELEASE(gBookmarkAtom); - NS_RELEASE(gFolderAtom); - NS_RELEASE(gNameAtom); - NS_RELEASE(gHrefAtom); - NS_RELEASE(gOpenAtom); - [gDictionary release]; - } -} void BookmarksService::AddBookmarkToFolder(nsString& aURL, nsString& aTitle, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt) @@ -601,6 +613,40 @@ BookmarksService::FlushBookmarks() domSerializer->SerializeToStream(domDoc, outputStream, nsnull); } +NSImage* +BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) +{ + nsCOMPtr tagName; + nsCOMPtr content = do_QueryInterface(aElement); + content->GetTag(*getter_AddRefs(tagName)); + + nsAutoString group; + content->GetAttr(kNameSpaceID_None, gGroupAtom, group); + if (!group.IsEmpty()) + return [NSImage imageNamed:@"groupbookmark"]; + + if (tagName == BookmarksService::gFolderAtom) + return [NSImage imageNamed:@"folder"]; + + // fire off a proxy icon load + if ([[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" withSuccess:NULL]) + { + nsAutoString href; + content->GetAttr(kNameSpaceID_None, gHrefAtom, href); + if (href.Length() > 0) + { + BookmarkItem* contentItem = BookmarksService::GetWrapperFor(content); + if ([contentItem siteIcon]) + return [contentItem siteIcon]; + + if (contentItem && ![contentItem siteIcon]) + [[BookmarksManager sharedBookmarksManager] loadProxyImageFor:contentItem withURI:[NSString stringWith_nsAString:href]]; + } + } + + return [NSImage imageNamed:@"smallbookmark"]; +} + void BookmarksService::EnsureToolbarRoot() { if (gToolbarRoot) @@ -763,18 +809,21 @@ BookmarksService::AddMenuBookmark(NSMenu* aMenu, nsIContent* aParent, nsIContent nsAutoString group; aChild->GetAttr(kNameSpaceID_None, gGroupAtom, group); + nsCOMPtr elt(do_QueryInterface(aChild)); + NSImage* menuItemImage = BookmarksService::CreateIconForBookmark(elt); + if (group.IsEmpty() && tagName == gFolderAtom) { NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease]; [aMenu setSubmenu: menu forItem: menuItem]; [menu setAutoenablesItems: NO]; - [menuItem setImage: [NSImage imageNamed:@"folder"]]; + [menuItem setImage: menuItemImage]; ConstructBookmarksMenu(menu, aChild); } else { if (group.IsEmpty()) - [menuItem setImage: [NSImage imageNamed:@"smallbookmark"]]; + [menuItem setImage: menuItemImage]; else - [menuItem setImage: [NSImage imageNamed:@"groupbookmark"]]; + [menuItem setImage: menuItemImage]; [menuItem setTarget: gMainController]; [menuItem setAction: @selector(openMenuBookmark:)]; @@ -997,10 +1046,12 @@ BookmarksService::ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc) nsCOMPtr parentContent(do_QueryInterface(bookmarksRoot)); nsCOMPtr childContent(do_QueryInterface(importedRootElement)); +#if 0 // XXX testing if (gDictionary) [gDictionary removeAllObjects]; - +#endif + // this will save the file BookmarkAdded(parentContent, childContent, true /* flush */); } @@ -1072,24 +1123,7 @@ BookmarksService::ResolveKeyword(NSString* aKeyword) content->GetAttr(kNameSpaceID_None, gHrefAtom, url); return [NSString stringWith_nsAString: url]; } - return [NSString stringWithCString:""]; -} - -NSImage* -BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) -{ - nsCOMPtr tagName; - nsCOMPtr content = do_QueryInterface(aElement); - content->GetTag(*getter_AddRefs(tagName)); - if (tagName == BookmarksService::gFolderAtom) - return [NSImage imageNamed:@"folder"]; - - nsAutoString group; - content->GetAttr(kNameSpaceID_None, gGroupAtom, group); - if (!group.IsEmpty()) - return [NSImage imageNamed:@"smallgroup"]; - - return [NSImage imageNamed:@"groupbookmark"]; + return [NSString string]; } // Is searchItem equal to bookmark or bookmark's parent, grandparent, etc? @@ -1292,3 +1326,78 @@ BookmarksService::PerformURLDrop(BookmarkItem* parentItem, BookmarkItem* beforeI return YES; } +#pragma mark - + +@interface BookmarksManager(Private) + +- (void)registerNotificationListener; +- (void)imageLoadedNotification:(NSNotification*)notification; + +@end + + +@implementation BookmarksManager + ++ (BookmarksManager*)sharedBookmarksManager; +{ + static BookmarksManager* sBookmarksManager = nil; + + if (!sBookmarksManager) + sBookmarksManager = [[BookmarksManager alloc] init]; + + return sBookmarksManager; +} + +- (id)init +{ + if ((self = [super init])) + { + [self registerNotificationListener]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString +{ + [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon:self + forURI:inURIString withUserData:requestor allowNetwork:NO]; +} + + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// callback for [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] +- (void)imageLoadedNotification:(NSNotification*)notification +{ + //NSLog(@"BookmarksManager imageLoadedNotification"); + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + id requestor = [userInfo objectForKey:SiteIconLoadUserDataKey]; // requestor is a BookmarkItem + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + + if (iconImage && [requestor isMemberOfClass:[BookmarkItem class]]) + { + [requestor setSiteIcon:iconImage]; + BookmarksService::BookmarkChanged([requestor contentNode], FALSE); + } + + } +} + + +@end + diff --git a/camino/BrowserWindowController.h b/camino/BrowserWindowController.h index ecc80d2755c6..b11edfacbefd 100644 --- a/camino/BrowserWindowController.h +++ b/camino/BrowserWindowController.h @@ -78,6 +78,7 @@ class nsIDOMNode; @class BookmarksDataSource; @class CHHistoryDataSource; @class CHExtendedTabView; +@class CHPageProxyIcon; @interface BrowserWindowController : NSWindowController { @@ -93,6 +94,7 @@ class nsIDOMNode; IBOutlet NSWindow* mLocationSheetWindow; IBOutlet NSTextField* mLocationSheetURLField; IBOutlet NSView* mStatusBar; // contains the status text, progress bar, and lock + IBOutlet CHPageProxyIcon* mProxyIcon; IBOutlet id mSidebarBrowserView; // currently unused IBOutlet BookmarksDataSource* mSidebarBookmarksDataSource; @@ -162,6 +164,7 @@ class nsIDOMNode; - (void)loadURL:(NSString*)aURLSpec referrer:(NSString*)aReferrer activate:(BOOL)activate; - (void)updateLocationFields:(NSString *)locationString; +- (void)updateSiteIcons:(NSImage *)siteIconImage; - (void)updateToolbarItems; - (void)focusURLBar; diff --git a/camino/BrowserWindowController.mm b/camino/BrowserWindowController.mm index 92a275afd009..82f3d33886af 100644 --- a/camino/BrowserWindowController.mm +++ b/camino/BrowserWindowController.mm @@ -46,6 +46,7 @@ #import "CHHistoryDataSource.h" #import "CHExtendedTabView.h" #import "CHUserDefaults.h" +#import "CHPageProxyIcon.h" #include "nsIWebNavigation.h" #include "nsIDOMElement.h" @@ -947,6 +948,13 @@ static NSArray* sToolbarDefaults = nil; // [[self window] display]; } +- (void)updateSiteIcons:(NSImage *)siteIconImage +{ + if (siteIconImage == nil) + siteIconImage = [NSImage imageNamed:@"globe_ico"]; + [mProxyIcon setImage:siteIconImage]; +} + -(void)newTab:(BOOL)allowHomepage { CHIconTabViewItem* newTab = [[[CHIconTabViewItem alloc] initWithIdentifier: nil] autorelease]; diff --git a/camino/CHBookmarksButton.h b/camino/CHBookmarksButton.h index 7570360b23c9..27d5623352ef 100644 --- a/camino/CHBookmarksButton.h +++ b/camino/CHBookmarksButton.h @@ -25,15 +25,20 @@ #import class nsIDOMElement; +class BookmarksService; + @class BookmarkItem; -@interface CHBookmarksButton : NSButton { - - nsIDOMElement* mElement; - BookmarkItem* mBookmarkItem; - BOOL mIsFolder; +@interface CHBookmarksButton : NSButton +{ + nsIDOMElement* mElement; + BookmarkItem* mBookmarkItem; + BookmarksService* mBookmarksService; + BOOL mIsFolder; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService; + -(void)setElement: (nsIDOMElement*)aElt; -(nsIDOMElement*)element; diff --git a/camino/CHBookmarksButton.mm b/camino/CHBookmarksButton.mm index ec374db86769..abf785435fd6 100644 --- a/camino/CHBookmarksButton.mm +++ b/camino/CHBookmarksButton.mm @@ -39,9 +39,9 @@ @implementation CHBookmarksButton -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { - mElement = nsnull; [self setBezelStyle: NSRegularSquareBezelStyle]; [self setButtonType: NSMomentaryChangeButton]; [self setBordered: NO]; @@ -52,6 +52,15 @@ return self; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService +{ + if ( (self = [self initWithFrame:frame]) ) { + mBookmarksService = bookmarksService; + [self setElement:element]; + } + return self; +} + -(IBAction)openBookmark:(id)aSender { // See if we're a group. @@ -203,22 +212,24 @@ nsAutoString tag; mElement->GetLocalName(tag); + NSImage* bookmarkImage = mBookmarksService->CreateIconForBookmark(aElt); + nsAutoString group; mElement->GetAttribute(NS_LITERAL_STRING("group"), group); - + if (!group.IsEmpty()) { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"groupbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; } else if (tag.Equals(NS_LITERAL_STRING("folder"))) { - [self setImage: [NSImage imageNamed: @"folder"]]; + [self setImage: bookmarkImage]; mIsFolder = YES; } else { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"smallbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; nsAutoString href; diff --git a/camino/CHBookmarksToolbar.h b/camino/CHBookmarksToolbar.h index 895a1bd8005c..8f164fdaf8ac 100644 --- a/camino/CHBookmarksToolbar.h +++ b/camino/CHBookmarksToolbar.h @@ -28,12 +28,13 @@ class nsIDOMElement; class BookmarksService; class CHBookmarksButton; -@interface CHBookmarksToolbar : NSView { - BookmarksService* mBookmarks; - NSMutableArray* mButtons; +@interface CHBookmarksToolbar : NSView +{ + BookmarksService* mBookmarks; + NSMutableArray* mButtons; CHBookmarksButton* mDragInsertionButton; - int mDragInsertionPosition; - BOOL mIsShowing; + int mDragInsertionPosition; + BOOL mIsShowing; } -(void)initializeToolbar; diff --git a/camino/CHBookmarksToolbar.mm b/camino/CHBookmarksToolbar.mm index 2a1cee3573c5..c65d4d90c180 100644 --- a/camino/CHBookmarksToolbar.mm +++ b/camino/CHBookmarksToolbar.mm @@ -31,9 +31,14 @@ #include "nsIDOMElement.h" #include "nsIContent.h" +@interface CHBookmarksToolbar(Private) +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element; +@end + @implementation CHBookmarksToolbar -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { mBookmarks = nsnull; mButtons = [[NSMutableArray alloc] init]; @@ -95,8 +100,7 @@ while (child) { nsCOMPtr childElt(do_QueryInterface(child)); if (childElt) { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: childElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:childElt]; [self addSubview: button]; [mButtons addObject: button]; } @@ -111,8 +115,7 @@ -(void)addButton: (nsIDOMElement*)aElt atIndex: (int)aIndex { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: aElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:aElt]; [self addSubview: button]; [mButtons insertObject: button atIndex: aIndex]; if ([self isShown]) @@ -453,4 +456,9 @@ } } +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element +{ + return [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17) element:element bookmarksService:mBookmarks] autorelease]; +} + @end diff --git a/camino/CHBrowserWrapper.h b/camino/CHBrowserWrapper.h index 801a4d97a8a2..830af8077b31 100644 --- a/camino/CHBrowserWrapper.h +++ b/camino/CHBrowserWrapper.h @@ -51,6 +51,9 @@ NSTabViewItem* mTab; NSWindow* mWindow; + NSImage* mSiteIconImage; // current proxy icon image, which may be a site icon (favicon). + NSString* mSiteIconURI; // uri from which we loaded the site icon + // the secure state of this browser. We need to hold it so that we can set // the global lock icon whenever we become the primary. Value is one of // security enums in nsIWebProgressListener. @@ -60,9 +63,9 @@ NSString* mTitle; CHBrowserView* mBrowserView; - NSString* defaultStatus; - NSString* loadingStatus; - ToolTip* toolTip; + NSString* mDefaultStatusString; + NSString* mLoadingStatusString; + ToolTip* mToolTip; BOOL mIsPrimary; BOOL mIsBusy; diff --git a/camino/CHBrowserWrapper.mm b/camino/CHBrowserWrapper.mm index d3011dacdd26..01c9b87587a7 100644 --- a/camino/CHBrowserWrapper.mm +++ b/camino/CHBrowserWrapper.mm @@ -37,9 +37,12 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserWrapper.h" #import "BrowserWindowController.h" #import "BookmarksService.h" +#import "SiteIconProvider.h" +#import "CHIconTabViewItem.h" #import "ToolTip.h" #include "nsCOMPtr.h" @@ -64,8 +67,18 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; +const NSString* kOfflineNotificationName = @"offlineModeChanged"; + @interface CHBrowserWrapper(Private) - -(void) setPendingActive:(BOOL)active; + +- (void)setPendingActive:(BOOL)active; +- (void)registerNotificationListener; + +- (void)setSiteIconImage:(NSImage*)inSiteIcon; +- (void)setSiteIconURI:(NSString*)inSiteIconURI; + +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI; + @end @implementation CHBrowserWrapper @@ -85,10 +98,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; #endif [[NSNotificationCenter defaultCenter] removeObserver: self]; - - [defaultStatus release]; - [loadingStatus release]; - [toolTip release]; + + [mSiteIconImage release]; + [mSiteIconURI release]; + [mDefaultStatusString release]; + [mLoadingStatusString release]; + [mToolTip release]; [super dealloc]; } @@ -105,7 +120,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; progress = nil; progressSuper = nil; mIsPrimary = NO; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kOfflineNotificationName object:nil]; [mBrowserView setActive: NO]; } @@ -133,7 +148,13 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mIsBusy = NO; mListenersAttached = NO; mSecureState = nsIWebProgressListener::STATE_IS_INSECURE; - toolTip = [[ToolTip alloc] init]; + + mToolTip = [[ToolTip alloc] init]; + + //[self setSiteIconImage:[NSImage imageNamed:@"globe_ico"]]; + //[self setSiteIconURI: [NSString string]]; + + [self registerNotificationListener]; } return self; } @@ -155,9 +176,9 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (!mIsBusy) [progress removeFromSuperview]; - defaultStatus = NULL; - loadingStatus = DOCUMENT_DONE_STRING; - [status setStringValue:loadingStatus]; + mDefaultStatusString = NULL; + mLoadingStatusString = DOCUMENT_DONE_STRING; + [status setStringValue:mLoadingStatusString]; mIsPrimary = YES; @@ -181,11 +202,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (mWindowController) // Only register if we're the content area. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlineModeChanged:) - name:@"offlineModeChanged" + name:kOfflineNotificationName object:nil]; // Update the URL bar. [mWindowController updateLocationFields:[self getCurrentURLSpec]]; + [mWindowController updateSiteIcons:mSiteIconImage]; if (mWindowController && !mListenersAttached) { mListenersAttached = YES; @@ -209,7 +231,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; return [mBrowserView getCurrentURLSpec]; } -- (void)awakeFromNib +- (void)awakeFromNib { } @@ -239,17 +261,17 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLoadingStarted { - if (defaultStatus) { - [defaultStatus release]; - defaultStatus = NULL; + if (mDefaultStatusString) { + [mDefaultStatusString release]; + mDefaultStatusString = NULL; } [progressSuper addSubview:progress]; [progress setIndeterminate:YES]; [progress startAnimation:self]; - loadingStatus = NSLocalizedString(@"TabLoading", @""); - [status setStringValue:loadingStatus]; + mLoadingStatusString = NSLocalizedString(@"TabLoading", @""); + [status setStringValue:mLoadingStatusString]; mIsBusy = YES; [mTab setLabel: NSLocalizedString(@"TabLoading", @"")]; @@ -271,12 +293,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [progress stopAnimation:self]; [progress removeFromSuperview]; - loadingStatus = DOCUMENT_DONE_STRING; - if (defaultStatus) { - [status setStringValue:defaultStatus]; + mLoadingStatusString = DOCUMENT_DONE_STRING; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } mIsBusy = NO; @@ -316,8 +338,32 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLocationChange:(NSString*)urlSpec { + BOOL useSiteIcons = [[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" 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 withUserData:nil allowNetwork:YES]; + } + else + { + if ([urlSpec isEqualToString:@"about:blank"]) + faviconURI = urlSpec; + else + faviconURI = @""; + } + + if (!siteIconLoadInitiated) + [self updateSiteIconImage:nil withURI:faviconURI]; + if (mIsPrimary) - [mWindowController updateLocationFields:urlSpec]; + [mWindowController updateLocationFields:urlSpec]; } - (void)onStatusChange:(NSString*)aStatusString @@ -342,20 +388,20 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)setStatus:(NSString *)statusString ofType:(NSStatusType)type { if (type == NSStatusTypeScriptDefault) { - if (defaultStatus) { - [defaultStatus release]; + if (mDefaultStatusString) { + [mDefaultStatusString release]; } - defaultStatus = statusString; - if (defaultStatus) { - [defaultStatus retain]; + mDefaultStatusString = statusString; + if (mDefaultStatusString) { + [mDefaultStatusString retain]; } } else if (!statusString) { - if (defaultStatus) { - [status setStringValue:defaultStatus]; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } } else { @@ -418,12 +464,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onShowTooltip:(NSPoint)where withText:(NSString*)text { NSPoint point = [[self window] convertBaseToScreen:[self convertPoint: where toView:nil]]; - [toolTip showToolTipAtPoint: point withString: text]; + [mToolTip showToolTipAtPoint: point withString: text]; } - (void)onHideTooltip { - [toolTip closeToolTip]; + [mToolTip closeToolTip]; } // Called when a context menu should be shown. @@ -547,4 +593,77 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mActivateOnLoad = active; } +- (void)setSiteIconImage:(NSImage*)inSiteIcon +{ + [mSiteIconImage autorelease]; + mSiteIconImage = [inSiteIcon retain]; +} + +- (void)setSiteIconURI:(NSString*)inSiteIconURI +{ + [mSiteIconURI autorelease]; + mSiteIconURI = [inSiteIconURI retain]; +} + +// A nil inSiteIcon image indicates that we should use the default icon +// If inSiteIconURI is "about:blank", we don't show any icon +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI +{ + BOOL resetTabIcon = NO; + + if (![mSiteIconURI isEqualToString:inSiteIconURI]) + { + if (!inSiteIcon) + { + if (![inSiteIconURI isEqualToString:@"about:blank"]) + inSiteIcon = [NSImage imageNamed:@"globe_ico"]; + } + + [self setSiteIconImage: inSiteIcon]; + [self setSiteIconURI: inSiteIconURI]; + + // update the proxy icon + if (mIsPrimary) + [mWindowController updateSiteIcons:mSiteIconImage]; + + resetTabIcon = YES; + } + + // update the tab icon + if ([mTab isMemberOfClass:[CHIconTabViewItem class]]) + { + CHIconTabViewItem* tabItem = (CHIconTabViewItem*)mTab; + if (resetTabIcon || ![tabItem tabIcon]) + [tabItem setTabIcon:mSiteIconImage]; + } + +} + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// called when [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] completes +- (void)imageLoadedNotification:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + NSString* siteIconURI = [userInfo objectForKey:SiteIconLoadURIKey]; + + // NSLog(@"CHBrowserWrapper imageLoadedNotification got image %@ and uri %@", iconImage, proxyImageURI); + if (iconImage == nil) + siteIconURI = @""; // go back to default image + + [self updateSiteIconImage:iconImage withURI:siteIconURI]; + } +} + + @end diff --git a/camino/CHIconTabViewItem.h b/camino/CHIconTabViewItem.h index 13a37120dc8e..b1ee2fc0e26b 100644 --- a/camino/CHIconTabViewItem.h +++ b/camino/CHIconTabViewItem.h @@ -40,7 +40,8 @@ #import -@interface CHIconTabViewItem : NSTabViewItem { +@interface CHIconTabViewItem : NSTabViewItem +{ NSImage *mTabIcon; NSDictionary* mLabelAttributes; } diff --git a/camino/CHIconTabViewItem.mm b/camino/CHIconTabViewItem.mm index cf659548c4c0..a7a19c588bb1 100644 --- a/camino/CHIconTabViewItem.mm +++ b/camino/CHIconTabViewItem.mm @@ -39,8 +39,9 @@ * * ***** END LICENSE BLOCK ***** */ -#import "CHIconTabViewItem.h" +#import "NSString+Utils.h" +#import "CHIconTabViewItem.h" // // NSParagraphStyle has a line break mode which will automatically @@ -91,7 +92,7 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s [labelParagraphStyle setLineBreakMode:NSLineBreakByTruncatingMiddle]; #endif - [labelParagraphStyle setAlignment:NSCenterTextAlignment]; + [labelParagraphStyle setAlignment:NSNaturalTextAlignment]; NSFont *labelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; mLabelAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -139,9 +140,9 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s if ([self tabIcon]) { NSPoint drawPoint = NSMakePoint( (tabRect.origin.x), (tabRect.origin.y + 15.0) ); [[self tabIcon] compositeToPoint:drawPoint operation:NSCompositeSourceOver]; - tabRect = NSMakeRect(NSMinX(tabRect)+15.0, + tabRect = NSMakeRect(NSMinX(tabRect) + 18.0, NSMinY(tabRect), - NSWidth(tabRect)-15.0, + NSWidth(tabRect) - 18.0, NSHeight(tabRect)); } diff --git a/camino/CHPageProxyIcon.h b/camino/CHPageProxyIcon.h index 9dfc66212c3f..4e8b1e17ce8e 100644 --- a/camino/CHPageProxyIcon.h +++ b/camino/CHPageProxyIcon.h @@ -25,7 +25,7 @@ @interface CHPageProxyIcon : NSImageView { - } + @end diff --git a/camino/CHPageProxyIcon.mm b/camino/CHPageProxyIcon.mm index 80544d58280b..51ab9d59f31b 100644 --- a/camino/CHPageProxyIcon.mm +++ b/camino/CHPageProxyIcon.mm @@ -24,13 +24,25 @@ #import "NSString+Utils.h" #import "CHPageProxyIcon.h" + #import "BookmarksService.h" #import "MainController.h" #include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsString.h" @implementation CHPageProxyIcon +- (void)awakeFromNib +{ +} + +- (void)dealloc +{ + [super dealloc]; +} + - (void) resetCursorRects { NSCursor* cursor; @@ -77,4 +89,5 @@ event: event pasteboard: pboard source: self slideBack: YES]; } + @end diff --git a/camino/CHPreferenceManager.h b/camino/CHPreferenceManager.h index 3063fc19b835..a682a4c80e4c 100644 --- a/camino/CHPreferenceManager.h +++ b/camino/CHPreferenceManager.h @@ -35,12 +35,16 @@ * * ***** END LICENSE BLOCK ***** */ -#import +#import #import -@interface CHPreferenceManager : NSObject { +class nsIPref; + +@interface CHPreferenceManager : NSObject +{ NSUserDefaults* mDefaults; ICInstance mInternetConfig; + nsIPref* mPrefs; } + (CHPreferenceManager *)sharedInstance; @@ -54,4 +58,9 @@ - (NSString *) getICStringPref:(ConstStr255Param) prefKey; - (NSString *) homePage:(BOOL) checkStartupPagePref; +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; + @end diff --git a/camino/CHPreferenceManager.mm b/camino/CHPreferenceManager.mm index 51228e415557..ef8e2cd0eaa4 100644 --- a/camino/CHPreferenceManager.mm +++ b/camino/CHPreferenceManager.mm @@ -86,8 +86,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (void) dealloc { + ::ICStop(mInternetConfig); + NS_IF_RELEASE(mPrefs); + nsresult rv; - ICStop (mInternetConfig); nsCOMPtr pref(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { //NSLog(@"Saving prefs file"); @@ -100,7 +102,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (BOOL) initInternetConfig { OSStatus error; - error = ICStart (&mInternetConfig, 'CHIM'); + error = ::ICStart(&mInternetConfig, 'CHIM'); if (error != noErr) { // XXX throw here? NSLog(@"Error initializing IC"); @@ -178,6 +180,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); return NO; } + nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); + mPrefs = prefs; + NS_IF_ADDREF(mPrefs); + [self syncMozillaPrefs]; return YES; } @@ -191,9 +197,8 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); char strbuf[1024]; int numbuf; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) { - // XXXw. throw? + if (!mPrefs) { + NSLog(@"Mozilla prefs not set up successfully"); return; } @@ -201,42 +206,42 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // something that chimera can deal with. PRInt32 acceptCookies = 0; static const char* kCookieBehaviorPref = "network.cookie.cookieBehavior"; - prefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); + mPrefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); if ( acceptCookies == 1 ) { // accept foreign cookies, assume off acceptCookies = 2; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } else if ( acceptCookies == 3 ) { // p3p, assume all cookies on acceptCookies = 0; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } // get proxies from SystemConfiguration - prefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies - prefs->ClearUserPref("network.proxy.http"); - prefs->ClearUserPref("network.proxy.http_port"); - prefs->ClearUserPref("network.proxy.ssl"); - prefs->ClearUserPref("network.proxy.ssl_port"); - prefs->ClearUserPref("network.proxy.ftp"); - prefs->ClearUserPref("network.proxy.ftp_port"); - prefs->ClearUserPref("network.proxy.gopher"); - prefs->ClearUserPref("network.proxy.gopher_port"); - prefs->ClearUserPref("network.proxy.socks"); - prefs->ClearUserPref("network.proxy.socks_port"); - prefs->ClearUserPref("network.proxy.no_proxies_on"); + mPrefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies + mPrefs->ClearUserPref("network.proxy.http"); + mPrefs->ClearUserPref("network.proxy.http_port"); + mPrefs->ClearUserPref("network.proxy.ssl"); + mPrefs->ClearUserPref("network.proxy.ssl_port"); + mPrefs->ClearUserPref("network.proxy.ftp"); + mPrefs->ClearUserPref("network.proxy.ftp_port"); + mPrefs->ClearUserPref("network.proxy.gopher"); + mPrefs->ClearUserPref("network.proxy.gopher_port"); + mPrefs->ClearUserPref("network.proxy.socks"); + mPrefs->ClearUserPref("network.proxy.socks_port"); + mPrefs->ClearUserPref("network.proxy.no_proxies_on"); if ((cfDictionary = SCDynamicStoreCopyProxies (NULL)) != NULL) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPEnable, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.http", strbuf); + mPrefs->SetCharPref("network.proxy.http", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.http_port", numbuf); + mPrefs->SetIntPref("network.proxy.http_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -245,13 +250,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ssl", strbuf); + mPrefs->SetCharPref("network.proxy.ssl", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ssl_port", numbuf); + mPrefs->SetIntPref("network.proxy.ssl_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -260,13 +265,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ftp", strbuf); + mPrefs->SetCharPref("network.proxy.ftp", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ftp_port", numbuf); + mPrefs->SetIntPref("network.proxy.ftp_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -275,13 +280,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.gopher", strbuf); + mPrefs->SetCharPref("network.proxy.gopher", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.gopher_port", numbuf); + mPrefs->SetIntPref("network.proxy.gopher_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -290,13 +295,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.socks", strbuf); + mPrefs->SetCharPref("network.proxy.socks", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.socks_port", numbuf); + mPrefs->SetIntPref("network.proxy.socks_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -305,7 +310,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); cfString = CFStringCreateByCombiningStrings (NULL, cfArray, CFSTR(", ")); if (CFStringGetLength (cfString) > 0) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.no_proxies_on", strbuf); + mPrefs->SetCharPref("network.proxy.no_proxies_on", strbuf); } } } @@ -313,24 +318,73 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); } } -// convenience routines for mozilla prefs -- (NSString*)getMozillaPrefString: (const char*)prefName +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess { - NSMutableString *prefValue = [[[NSMutableString alloc] init] autorelease]; + NSString *prefValue = @""; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (prefs) { - char *buf = nsnull; - nsresult rv = prefs->GetCharPref(prefName, &buf); - if (NS_SUCCEEDED(rv) && buf) { - [prefValue setString:[NSString stringWithCString:buf]]; - free(buf); - } + char *buf = nsnull; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetCharPref(prefName, &buf); + + if (NS_SUCCEEDED(rv) && buf) { + // prefs are UTF-8 + prefValue = [NSString stringWithUTF8String:buf]; + free(buf); + if (outSuccess) *outSuccess = YES; + } else { + if (outSuccess) *outSuccess = NO; } return prefValue; } +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + // colors are stored in HTML-like #FFFFFF strings + NSString* colorString = [self getStringPref:prefName withSuccess:outSuccess]; + NSColor* returnColor = [NSColor blackColor]; + + if ([colorString hasPrefix:@"#"] && [colorString length] == 7) + { + unsigned int redInt, greenInt, blueInt; + sscanf([colorString cString], "#%02x%02x%02x", &redInt, &greenInt, &blueInt); + + float redFloat = ((float)redInt / 255.0); + float greenFloat = ((float)greenInt / 255.0); + float blueFloat = ((float)blueInt / 255.0); + + returnColor = [NSColor colorWithCalibratedRed:redFloat green:greenFloat blue:blueFloat alpha:1.0f]; + } + + return returnColor; +} + +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRBool boolPref = PR_FALSE; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + rv = mPrefs->GetBoolPref(prefName, &boolPref); + + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + + return boolPref ? YES : NO; +} + +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRInt32 intPref = 0; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetIntPref(prefName, &intPref); + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + return intPref; +} + + //- (BOOL) getICBoolPref:(ConstStr255Param) prefKey; //{ @@ -381,8 +435,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (NSString *) homePage:(BOOL)checkStartupPagePref { - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) + if (!mPrefs) return @"about:blank"; PRInt32 mode = 1; @@ -394,14 +447,14 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // is true. nsresult rv = NS_OK; if ( checkStartupPagePref ) - rv = prefs->GetIntPref("browser.startup.page", &mode); + rv = mPrefs->GetIntPref("browser.startup.page", &mode); if (NS_FAILED(rv) || mode == 1) { // see which home page to use PRBool boolPref; - if (NS_SUCCEEDED(prefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) + if (NS_SUCCEEDED(mPrefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) return [self getICStringPref:kICWWWHomePage]; - nsCOMPtr prefBranch = do_QueryInterface(prefs); + nsCOMPtr prefBranch = do_QueryInterface(mPrefs); if (!prefBranch) return @"about:blank"; NSString* homepagePref = nil; @@ -411,10 +464,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); homepagePref = NSLocalizedStringFromTable( @"HomePageDefault", @"WebsiteDefaults", nil); // and let's copy this into the homepage pref if it's not bad if (![homepagePref isEqualToString:@"HomePageDefault"]) - prefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); + mPrefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); } else { - homepagePref = [self getMozillaPrefString:"browser.startup.homepage"]; + homepagePref = [self getStringPref:"browser.startup.homepage" withSuccess:NULL]; } if (homepagePref && [homepagePref length] > 0 && ![homepagePref isEqualToString:@"HomePageDefault"]) diff --git a/camino/MainController.h b/camino/MainController.h index ccbf1a099f70..f06bf2127a5a 100644 --- a/camino/MainController.h +++ b/camino/MainController.h @@ -75,7 +75,7 @@ class BookmarksService; FindDlgController* mFindDialog; - MVPreferencesController* preferencesController; + MVPreferencesController* mPreferencesController; NSString* mStartURL; } diff --git a/camino/MainController.mm b/camino/MainController.mm index 2206223df253..c603e099d260 100644 --- a/camino/MainController.mm +++ b/camino/MainController.mm @@ -132,7 +132,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [mBookmarksMenu setAutoenablesItems: NO]; mMenuBookmarks = new BookmarksService((BookmarksDataSource*)nil); mMenuBookmarks->AddObserver(); - mMenuBookmarks->ConstructBookmarksMenu(mBookmarksMenu, nsnull); + BookmarksService::ConstructBookmarksMenu(mBookmarksMenu, nsnull); BookmarksService::gMainController = self; // Initialize offline mode. @@ -152,6 +152,29 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; */ } +-(void)applicationWillTerminate: (NSNotification*)aNotification +{ +#if DEBUG + NSLog(@"Termination notification"); +#endif + + // Autosave one of the windows. + [[[mApplication mainWindow] windowController] autosaveWindowFrame]; + + mMenuBookmarks->RemoveObserver(); + delete mMenuBookmarks; + mMenuBookmarks = nsnull; + + // Release before calling TermEmbedding since we need to access XPCOM + // to save preferences + [mPreferencesController release]; + [mPreferenceManager release]; + + nsCocoaBrowserService::TermEmbedding(); + + [self autorelease]; +} + -(IBAction)newWindow:(id)aSender { // If we have a key window, have it autosave its dimensions before @@ -455,29 +478,6 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [[[controller getBrowserWrapper] getBrowserView] setActive: YES]; } - --(void)applicationWillTerminate: (NSNotification*)aNotification -{ -#if DEBUG - NSLog(@"Termination notification"); -#endif - - // Autosave one of the windows. - [[[mApplication mainWindow] windowController] autosaveWindowFrame]; - - mMenuBookmarks->RemoveObserver(); - delete mMenuBookmarks; - mMenuBookmarks = nsnull; - - // Release before calling TermEmbedding since we need to access XPCOM - // to save preferences - [mPreferenceManager release]; - - nsCocoaBrowserService::TermEmbedding(); - - [self autorelease]; -} - // Bookmarks menu actions. -(IBAction) importBookmarks:(id)aSender { @@ -558,10 +558,10 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (MVPreferencesController *)preferencesController { - if (!preferencesController) { - preferencesController = [[MVPreferencesController sharedInstance] retain]; + if (!mPreferencesController) { + mPreferencesController = [[MVPreferencesController sharedInstance] retain]; } - return preferencesController; + return mPreferencesController; } - (void)displayPreferencesWindow:sender diff --git a/camino/RemoteDataProvider.h b/camino/RemoteDataProvider.h new file mode 100644 index 000000000000..13ff9a3af4ae --- /dev/null +++ b/camino/RemoteDataProvider.h @@ -0,0 +1,90 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + + +extern NSString* RemoteDataLoadRequestNotificationName; +extern NSString* RemoteDataLoadRequestURIKey; +extern NSString* RemoteDataLoadRequestDataKey; +extern NSString* RemoteDataLoadRequestUserDataKey; +extern NSString* 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 +// callback in NSData. +// +// Clients can either implement the RemoteLoadListener protocol and call +// loadURI directly, or they can register with the [NSNotification defaultCenter] +// for 'RemoteDataLoadRequestNotificationName' notifications, and catch all loads +// that happen that way. + +@protocol RemoteLoadListener +// called when the load completes, or fails. If the status code is a failure code, +// data may be nil. +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status; +@end + + +class RemoteURILoadManager; + +@interface RemoteDataProvider : NSObject +{ + RemoteURILoadManager* mLoadManager; +} + ++ (RemoteDataProvider*)sharedRemoteDataProvider; + +// generic method. You can load any URI asynchronously with this selector, +// and the listener will get the contents of the URI in an NSData. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +// specific request to load a remote file. The sender (or any other object), if +// registered with the notification center, will receive a notification when +// the load completes. The 'target' becomes the 'object' of the notification. +// The notification name is given by NSString* RemoteDataLoadRequestNotificationName above. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +@end diff --git a/camino/RemoteDataProvider.mm b/camino/RemoteDataProvider.mm new file mode 100644 index 000000000000..b869d6739170 --- /dev/null +++ b/camino/RemoteDataProvider.mm @@ -0,0 +1,298 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "RemoteDataProvider.h" + +#include "nsISupports.h" +#include "nsHashtable.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.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"; + + +// this has to retain the load listener, to ensure that the listener lives long +// enough to receive notifications. We have to be careful to avoid ref cycles. +class StreamLoaderContext : public nsISupports +{ +public: + StreamLoaderContext(id inLoadListener, id inUserData, id inTarget, const nsAString& inURI) + : mLoadListener(inLoadListener) + , mTarget(inTarget) + , mUserData(inUserData) + , mURI(inURI) + { + NS_INIT_ISUPPORTS(); + [mLoadListener retain]; + } + + virtual ~StreamLoaderContext() + { + [mLoadListener release]; + } + + NS_DECL_ISUPPORTS + + void LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength); + const nsAString& GetURI() { return mURI; } + +protected: + + id mLoadListener; // retained + id mTarget; // not retained + id mUserData; // not retained + nsString mURI; + +}; + + +NS_IMPL_ISUPPORTS1(StreamLoaderContext, nsISupports) + +void StreamLoaderContext::LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength) +{ + if (mLoadListener) + { + NSData* loadData = nil; + if (NS_SUCCEEDED(inLoadStatus)) + loadData = [NSData dataWithBytes:inData length:inDataLength]; + + [mLoadListener doneRemoteLoad:[NSString stringWith_nsAString:mURI] forTarget:mTarget withUserData:mUserData data:loadData status:inLoadStatus]; + } +} + + +class RemoteURILoadManager : public nsIStreamLoaderObserver +{ +public: + + RemoteURILoadManager(); + virtual ~RemoteURILoadManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLOADEROBSERVER + + nsresult Init(); + nsresult RequestURILoad(const nsAString& inURI, id loadListener, id userData, id target, PRBool allowNetworking); + +protected: + + nsSupportsHashtable mStreamLoaderHash; // hash of active stream loads, keyed on URI + nsCOMPtr mCacheSession; + +}; + +RemoteURILoadManager::RemoteURILoadManager() +{ + NS_INIT_ISUPPORTS(); +} + +RemoteURILoadManager::~RemoteURILoadManager() +{ +} + +NS_IMPL_ISUPPORTS1(RemoteURILoadManager, nsIStreamLoaderObserver) + +NS_IMETHODIMP RemoteURILoadManager::OnStreamComplete(nsIStreamLoader *loader, nsISupports *ctxt, nsresult status, PRUint32 resultLength, const char *result) +{ + StreamLoaderContext* loaderContext = NS_STATIC_CAST(StreamLoaderContext*, ctxt); + if (loaderContext) + { + loaderContext->LoadComplete(status, (const void*)result, resultLength); + + // remove the stream loader from the hash table + nsStringKey uriKey(loaderContext->GetURI()); + PRBool removed = mStreamLoaderHash.Remove(&uriKey); + } + + return NS_OK; +} + +static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID); + +nsresult RemoteURILoadManager::Init() +{ + nsresult rv; + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession("HTTP", nsICache::STORE_ANYWHERE, + nsICache::STREAM_BASED, getter_AddRefs(mCacheSession)); + + return rv; +} + +nsresult RemoteURILoadManager::RequestURILoad(const nsAString& inURI, id loadListener, + id userData, id target, PRBool allowNetworking) +{ + nsresult rv; + +#if 0 + // if no networking is allowed, make sure it's in the cache + if (!allowNetworking) + { + if (!mCacheSession) + return NS_ERROR_FAILURE; + + nsCOMPtr entryDesc; + rv = mCacheSession->OpenCacheEntry(NS_ConvertUCS2toUTF8(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + if (NS_FAILED(rv) || !entryDesc) + return NS_ERROR_FAILURE; + } +#endif + + nsStringKey uriKey(inURI); + + // first make sure that there isn't another entry in the hash for this + nsCOMPtr foundStreamSupports = mStreamLoaderHash.Get(&uriKey); + if (foundStreamSupports) + return NS_OK; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), inURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr loaderContext = new StreamLoaderContext(loadListener, userData, target, inURI); + + nsLoadFlags loadFlags = (allowNetworking) ? nsIRequest::LOAD_NORMAL : nsIRequest::LOAD_FROM_CACHE; + nsCOMPtr streamLoader; + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), uri, this, loaderContext, nsnull, nsnull, loadFlags); + if (NS_FAILED(rv)) + { + NSLog(@"NS_NewStreamLoader for favicon failed"); + return rv; + } + +#ifdef DEBUG_smfr + NSLog(@"RequestURILoad called for %@", [NSString stringWith_nsAString: inURI]); +#endif + + // put the stream loader into the hash table + nsCOMPtr streamLoaderAsSupports = do_QueryInterface(streamLoader); + mStreamLoaderHash.Put(&uriKey, streamLoaderAsSupports); + + return NS_OK; +} + + +#pragma mark - + + +@implementation RemoteDataProvider + + ++ (RemoteDataProvider*)sharedRemoteDataProvider +{ + static RemoteDataProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[RemoteDataProvider alloc] init]; + + // we probably need to register for NSApplicationWillTerminateNotification notifications + // and delete this then. + } + + return sIconProvider; +} + +- (id)init +{ + if ((self = [super init])) + { + mLoadManager = new RemoteURILoadManager; + NS_ADDREF(mLoadManager); + } + + return self; +} + +- (void)dealloc +{ + NS_IF_RELEASE(mLoadManager); + [super dealloc]; +} + +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + //NSLog(@"loadURI called with %@", inURI); + if (mLoadManager && [inURI length] > 0) + { + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsresult rv = mLoadManager->RequestURILoad(uriString, inListener, userData, target, (PRBool)inNetworkOK); + if (NS_FAILED(rv)) + { + NSLog(@"RequestURILoad failed for @%", inURI); + return NO; + } + } + + return YES; +} + +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + return [self loadURI:inURI forTarget:target withListener:self withUserData:userData allowNetworking:inNetworkOK]; +} + +// our own load listener callback +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, RemoteDataLoadRequestURIKey, + data, RemoteDataLoadRequestDataKey, + userData, RemoteDataLoadRequestUserDataKey, + [NSNumber numberWithInt:status], RemoteDataLoadRequestResultKey, + nil]; + + NSLog(@"remoteLoadDone with status %d and length %d", status, [data length]); + [[NSNotificationCenter defaultCenter] postNotificationName: RemoteDataLoadRequestNotificationName + object:target userInfo:notificationData]; +} + +@end diff --git a/camino/SiteIconProvider.h b/camino/SiteIconProvider.h new file mode 100644 index 000000000000..9cb0209c4558 --- /dev/null +++ b/camino/SiteIconProvider.h @@ -0,0 +1,69 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +#import "RemoteDataProvider.h" + +extern NSString* SiteIconLoadNotificationName; +extern NSString* SiteIconLoadImageKey; +extern NSString* SiteIconLoadURIKey; +extern NSString* SiteIconLoadUserDataKey; + +class NeckoCacheHelper; + +@interface SiteIconProvider : NSObject +{ + NeckoCacheHelper* mMissedIconsCacheHelper; +} + ++ (SiteIconProvider*)sharedFavoriteIconProvider; + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI; + +// Start a favicon.ico load for the given URI, which can be any URI. +// The caller will get a 'SiteIconLoadNotificationName' notification +// when the load is done, with the image at the 'SiteIconLoadImageKey' key +// in the notifcation userInfo. The caller will have had to register with the +// NSNotifcationCenter in order to receive this notifcation. The notification +// is dispatched with 'sender' as the object. +// This method returns YES if the uri request was dispatched (i.e. if we know +// that we've looked for, and failed to find, this icon before). If it returns +// YES, then the 'SiteIconLoadNotificationName' notification will be sent out. +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData allowNetwork:(BOOL)inAllowNetwork; + +@end diff --git a/camino/SiteIconProvider.mm b/camino/SiteIconProvider.mm new file mode 100644 index 000000000000..b5f5d93705d8 --- /dev/null +++ b/camino/SiteIconProvider.mm @@ -0,0 +1,330 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "SiteIconProvider.h" + +#include "prtime.h" +#include "nsString.h" +#include "nsISupports.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.h" + + +NSString* SiteIconLoadNotificationName = @"siteicon_load_notification"; +NSString* SiteIconLoadImageKey = @"siteicon_load_image"; +NSString* SiteIconLoadURIKey = @"siteicon_load_uri"; +NSString* SiteIconLoadUserDataKey = @"siteicon_load_user_data"; + + +static inline PRUint32 PRTimeToSeconds(PRTime t_usec) +{ + PRTime usec_per_sec; + PRUint32 t_sec; + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_DIV(t_usec, t_usec, usec_per_sec); + LL_L2I(t_sec, t_usec); + return t_sec; +} + +class NeckoCacheHelper +{ +public: + + NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue); + ~NeckoCacheHelper() {} + + nsresult Init(const char* inCacheSessionName); + nsresult ExistsInCache(const nsACString& inURI, PRBool* outExists); + nsresult PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds); + + nsresult ClearCache(); + +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) +{ +} + +nsresult NeckoCacheHelper::Init(const char* inCacheSessionName) +{ + nsresult rv; + + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession(inCacheSessionName, + nsICache::STORE_ANYWHERE, nsICache::STREAM_BASED, + getter_AddRefs(mCacheSession)); + if (NS_FAILED(rv)) + return rv; + + return NS_OK; +} + + +nsresult NeckoCacheHelper::ExistsInCache(const nsACString& inURI, PRBool* outExists) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + + *outExists = NS_SUCCEEDED(rv) && (entryDesc != NULL); + return NS_OK; +} + +nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), 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(mMetaElement, mMetaValue); // just set a bit of meta data. + entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds); + + entryDesc->MarkValid(); + entryDesc->Close(); + + return NS_OK; +} + +nsresult NeckoCacheHelper::ClearCache() +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + return mCacheSession->EvictEntries(); +} + + +#pragma mark - + +static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) +{ + outFaviconURI.Truncate(0); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), inURIString); + if (NS_FAILED(rv)) + return rv; + + // check for http/https + PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; + uri->SchemeIs("http", &isHTTP); + uri->SchemeIs("https", &isHTTPS); + if (!isHTTP && !isHTTPS) + return NS_OK; + + PRInt32 port; + uri->GetPort(&port); + + nsXPIDLCString scheme; + uri->GetScheme(scheme); + + nsXPIDLCString host; + uri->GetHost(host); + + nsCAutoString faviconURI = scheme; + faviconURI.Append("://"); + faviconURI.Append(host); + if (port != -1) { + faviconURI.Append(':'); + faviconURI.AppendInt(port); + } + faviconURI.Append("/favicon.ico"); + + outFaviconURI.Assign(NS_ConvertUTF8toUCS2(faviconURI)); + return NS_OK; +} + + +@interface SiteIconProvider(Private) + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds; +- (BOOL)inMissedIconsCache:(const nsAString&)inURI; + +@end + + +@implementation SiteIconProvider + +- (id)init +{ + if ((self = [super init])) + { + mMissedIconsCacheHelper = new NeckoCacheHelper("Favicon", "Missed"); + nsresult rv = mMissedIconsCacheHelper->Init("MissedIconsCache"); + if (NS_FAILED(rv)) { + delete mMissedIconsCacheHelper; + mMissedIconsCacheHelper = NULL; + } + } + + return self; +} + +- (void)dealloc +{ + delete mMissedIconsCacheHelper; + [super dealloc]; +} + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds +{ + if (mMissedIconsCacheHelper) + { + nsresult rv = mMissedIconsCacheHelper->PutInCache(NS_ConvertUCS2toUTF8(inURI), inExpSeconds); + //NSLog(@"Putting %@ in missed icon cache", [NSString stringWith_nsAString:inURI]); + } + +} + +- (BOOL)inMissedIconsCache:(const nsAString&)inURI +{ + PRBool inCache = PR_FALSE; + + if (mMissedIconsCacheHelper) + mMissedIconsCacheHelper->ExistsInCache(NS_ConvertUCS2toUTF8(inURI), &inCache); + + //NSLog(@"%@ in missed icon cache: %d", [NSString stringWith_nsAString:inURI], inCache); + return inCache; +} + + +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData 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; + + RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider]; + return [dataProvider loadURI:faviconString forTarget:sender withListener:self withUserData:userData allowNetworking:inAllowNetwork]; +} + +#define SITE_ICON_EXPIRATION_SECONDS (60 * 60 * 24 * 7) // 1 week + +// this is called on the main thread +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + BOOL loadOK = NS_SUCCEEDED(status) && (data != nil); + // it's hard to tell if the favicon load succeeded or not. Even if the file + // does not exist, servers will send back a 404 page with a 0 status. + // So we just go ahead and try to make the image; it will return nil on + // failure. + NSImage* faviconImage = [[NSImage alloc] initWithData:data]; + BOOL gotImageData = loadOK && (faviconImage != nil); + if (!gotImageData) + [self addToMissedIconsCache:uriString withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS]; + + [faviconImage setScalesWhenResized:YES]; + [faviconImage setSize:NSMakeSize(16, 16)]; + + // we always send out the notification, so that clients know + // about failed requests + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, SiteIconLoadURIKey, + faviconImage, SiteIconLoadImageKey, // may be nil + userData, SiteIconLoadUserDataKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName: SiteIconLoadNotificationName + object:target userInfo:notificationData]; +} + +#pragma mark - + ++ (SiteIconProvider*)sharedFavoriteIconProvider +{ + static SiteIconProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[SiteIconProvider alloc] init]; + } + + return sIconProvider; +} + + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsAutoString faviconURIString; + MakeFaviconURIFromURI(uriString, faviconURIString); + return [NSString stringWith_nsAString:faviconURIString]; +} + +@end diff --git a/camino/src/application/MainController.h b/camino/src/application/MainController.h index ccbf1a099f70..f06bf2127a5a 100644 --- a/camino/src/application/MainController.h +++ b/camino/src/application/MainController.h @@ -75,7 +75,7 @@ class BookmarksService; FindDlgController* mFindDialog; - MVPreferencesController* preferencesController; + MVPreferencesController* mPreferencesController; NSString* mStartURL; } diff --git a/camino/src/application/MainController.mm b/camino/src/application/MainController.mm index 2206223df253..c603e099d260 100644 --- a/camino/src/application/MainController.mm +++ b/camino/src/application/MainController.mm @@ -132,7 +132,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [mBookmarksMenu setAutoenablesItems: NO]; mMenuBookmarks = new BookmarksService((BookmarksDataSource*)nil); mMenuBookmarks->AddObserver(); - mMenuBookmarks->ConstructBookmarksMenu(mBookmarksMenu, nsnull); + BookmarksService::ConstructBookmarksMenu(mBookmarksMenu, nsnull); BookmarksService::gMainController = self; // Initialize offline mode. @@ -152,6 +152,29 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; */ } +-(void)applicationWillTerminate: (NSNotification*)aNotification +{ +#if DEBUG + NSLog(@"Termination notification"); +#endif + + // Autosave one of the windows. + [[[mApplication mainWindow] windowController] autosaveWindowFrame]; + + mMenuBookmarks->RemoveObserver(); + delete mMenuBookmarks; + mMenuBookmarks = nsnull; + + // Release before calling TermEmbedding since we need to access XPCOM + // to save preferences + [mPreferencesController release]; + [mPreferenceManager release]; + + nsCocoaBrowserService::TermEmbedding(); + + [self autorelease]; +} + -(IBAction)newWindow:(id)aSender { // If we have a key window, have it autosave its dimensions before @@ -455,29 +478,6 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [[[controller getBrowserWrapper] getBrowserView] setActive: YES]; } - --(void)applicationWillTerminate: (NSNotification*)aNotification -{ -#if DEBUG - NSLog(@"Termination notification"); -#endif - - // Autosave one of the windows. - [[[mApplication mainWindow] windowController] autosaveWindowFrame]; - - mMenuBookmarks->RemoveObserver(); - delete mMenuBookmarks; - mMenuBookmarks = nsnull; - - // Release before calling TermEmbedding since we need to access XPCOM - // to save preferences - [mPreferenceManager release]; - - nsCocoaBrowserService::TermEmbedding(); - - [self autorelease]; -} - // Bookmarks menu actions. -(IBAction) importBookmarks:(id)aSender { @@ -558,10 +558,10 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (MVPreferencesController *)preferencesController { - if (!preferencesController) { - preferencesController = [[MVPreferencesController sharedInstance] retain]; + if (!mPreferencesController) { + mPreferencesController = [[MVPreferencesController sharedInstance] retain]; } - return preferencesController; + return mPreferencesController; } - (void)displayPreferencesWindow:sender diff --git a/camino/src/bookmarks/BookmarksButton.h b/camino/src/bookmarks/BookmarksButton.h index 7570360b23c9..27d5623352ef 100644 --- a/camino/src/bookmarks/BookmarksButton.h +++ b/camino/src/bookmarks/BookmarksButton.h @@ -25,15 +25,20 @@ #import class nsIDOMElement; +class BookmarksService; + @class BookmarkItem; -@interface CHBookmarksButton : NSButton { - - nsIDOMElement* mElement; - BookmarkItem* mBookmarkItem; - BOOL mIsFolder; +@interface CHBookmarksButton : NSButton +{ + nsIDOMElement* mElement; + BookmarkItem* mBookmarkItem; + BookmarksService* mBookmarksService; + BOOL mIsFolder; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService; + -(void)setElement: (nsIDOMElement*)aElt; -(nsIDOMElement*)element; diff --git a/camino/src/bookmarks/BookmarksButton.mm b/camino/src/bookmarks/BookmarksButton.mm index ec374db86769..abf785435fd6 100644 --- a/camino/src/bookmarks/BookmarksButton.mm +++ b/camino/src/bookmarks/BookmarksButton.mm @@ -39,9 +39,9 @@ @implementation CHBookmarksButton -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { - mElement = nsnull; [self setBezelStyle: NSRegularSquareBezelStyle]; [self setButtonType: NSMomentaryChangeButton]; [self setBordered: NO]; @@ -52,6 +52,15 @@ return self; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService +{ + if ( (self = [self initWithFrame:frame]) ) { + mBookmarksService = bookmarksService; + [self setElement:element]; + } + return self; +} + -(IBAction)openBookmark:(id)aSender { // See if we're a group. @@ -203,22 +212,24 @@ nsAutoString tag; mElement->GetLocalName(tag); + NSImage* bookmarkImage = mBookmarksService->CreateIconForBookmark(aElt); + nsAutoString group; mElement->GetAttribute(NS_LITERAL_STRING("group"), group); - + if (!group.IsEmpty()) { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"groupbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; } else if (tag.Equals(NS_LITERAL_STRING("folder"))) { - [self setImage: [NSImage imageNamed: @"folder"]]; + [self setImage: bookmarkImage]; mIsFolder = YES; } else { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"smallbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; nsAutoString href; diff --git a/camino/src/bookmarks/BookmarksDataSource.h b/camino/src/bookmarks/BookmarksDataSource.h index f05a76b7a544..f0a280af1f29 100644 --- a/camino/src/bookmarks/BookmarksDataSource.h +++ b/camino/src/bookmarks/BookmarksDataSource.h @@ -49,6 +49,7 @@ class BookmarksService; @class BookmarkInfoController; +// data source for the bookmarks sidebar. We make one per browser window. @interface BookmarksDataSource : NSObject { BookmarksService* mBookmarks; @@ -106,14 +107,16 @@ class BookmarksService; @interface BookmarkItem : NSObject { nsIContent* mContentNode; + NSImage* mSiteIcon; } - (nsIContent*)contentNode; - (void)setContentNode: (nsIContent*)aContentNode; +- (void)setSiteIcon:(NSImage*)image; - (NSString*)url; +- (NSImage*)siteIcon; - (NSNumber*)contentID; - (id)copyWithZone:(NSZone *)aZone; - (BOOL)isFolder; @end - diff --git a/camino/src/bookmarks/BookmarksDataSource.mm b/camino/src/bookmarks/BookmarksDataSource.mm index 77cdc6c36687..9b5915765329 100644 --- a/camino/src/bookmarks/BookmarksDataSource.mm +++ b/camino/src/bookmarks/BookmarksDataSource.mm @@ -41,6 +41,7 @@ #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" +#import "SiteIconProvider.h" #include "nsCOMPtr.h" #include "nsIContent.h" @@ -55,17 +56,16 @@ #include "nsVoidArray.h" #import "BookmarksService.h" -#import "StringUtils.h" @implementation BookmarksDataSource -(id) init { - if ( (self = [super init]) ) { - mBookmarks = nsnull; - mCachedHref = nil; - } - return self; + if ( (self = [super init]) ) { + mBookmarks = nsnull; + mCachedHref = nil; + } + return self; } -(void) awakeFromNib @@ -86,16 +86,16 @@ -(void) ensureBookmarks { - if (mBookmarks) - return; - - mBookmarks = new BookmarksService(self); - mBookmarks->AddObserver(); - - [mOutlineView setTarget: self]; - [mOutlineView setDoubleAction: @selector(openBookmark:)]; - [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; - [mOutlineView reloadData]; + if (mBookmarks) + return; + + mBookmarks = new BookmarksService(self); + mBookmarks->AddObserver(); + + [mOutlineView setTarget: self]; + [mOutlineView setDoubleAction: @selector(openBookmark:)]; + [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; + [mOutlineView reloadData]; } -(IBAction)addBookmark:(id)aSender @@ -530,20 +530,10 @@ //Get the cell of the text attachment. attachmentAttrStringCell = (NSCell *)[(NSTextAttachment *)[attachmentAttrString attribute:NSAttachmentAttributeName atIndex:0 effectiveRange:nil] attachmentCell]; - //Figure out which image to add, and set the cell's image. - // Use the bookmark groups image for groups. - if ([self outlineView:outlineView isItemExpandable:item]) { - nsIContent* content = [item contentNode]; - nsCOMPtr elt(do_QueryInterface(content)); - nsAutoString group; - content->GetAttr(kNameSpaceID_None, BookmarksService::gGroupAtom, group); - if (!group.IsEmpty()) - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"groupbookmark"]]; - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"folder"]]; - } - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"smallbookmark"]]; + + nsCOMPtr elt(do_QueryInterface(content)); + NSImage* bookmarkImage = mBookmarks->CreateIconForBookmark(elt); + [attachmentAttrStringCell setImage:bookmarkImage]; //Insert the image [cellValue replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attachmentAttrString]; @@ -855,7 +845,16 @@ @end +#pragma mark - + @implementation BookmarkItem + +-(void)dealloc +{ + [mSiteIcon release]; + [super dealloc]; +} + -(nsIContent*)contentNode { return mContentNode; @@ -887,6 +886,18 @@ return [NSString stringWith_nsAString: href]; } +- (void)setSiteIcon:(NSImage*)image +{ + //NSLog(@"Setting site icon for %@", [self url]); + [mSiteIcon autorelease]; + mSiteIcon = [image retain]; +} + +- (NSImage*)siteIcon +{ + return mSiteIcon; +} + -(void)setContentNode: (nsIContent*)aContentNode { mContentNode = aContentNode; @@ -896,6 +907,7 @@ { BookmarkItem* copy = [[[self class] allocWithZone: aZone] init]; [copy setContentNode: mContentNode]; + [copy setSiteIcon: mSiteIcon]; return copy; } diff --git a/camino/src/bookmarks/BookmarksService.h b/camino/src/bookmarks/BookmarksService.h index c03f27353565..411935e25172 100644 --- a/camino/src/bookmarks/BookmarksService.h +++ b/camino/src/bookmarks/BookmarksService.h @@ -53,6 +53,9 @@ class nsIDOMHTMLDocument; @class BookmarksDataSource; @class BookmarkItem; +// despite appearances, BookmarksService is not a singleton. We make one for the bookmarks menu, +// one each per BookmarksDataSource, and one per bookmarks toolbar. It relies on a bunch of global +// variables, which is evil. class BookmarksService { public: @@ -63,6 +66,7 @@ public: void AddObserver(); void RemoveObserver(); +public: static void BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); static void BookmarkChanged(nsIContent* aItem, bool shouldFlush = true); static void BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); @@ -71,7 +75,6 @@ public: static void MoveBookmarkToFolder(nsIDOMElement* aBookmark, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt); static void DeleteBookmark(nsIDOMElement* aBookmark); -public: static void GetRootContent(nsIContent** aResult); static BookmarkItem* GetRootItem(); static BookmarkItem* GetWrapperFor(nsIContent* aItem); @@ -87,6 +90,8 @@ public: static void ConstructAddBookmarkFolderList(NSPopUpButton* aPopup, BookmarkItem* aItem); + static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); + static void EnsureToolbarRoot(); static void ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc); @@ -96,8 +101,6 @@ public: static NSString* ResolveKeyword(NSString* aKeyword); - static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); - static BOOL DoAncestorsIncludeNode(BookmarkItem* bookmark, BookmarkItem* searchItem); static bool IsBookmarkDropValid(BookmarkItem* proposedParent, int index, NSArray* draggedIDs); static bool PerformBookmarkDrop(BookmarkItem* parent, int index, NSArray* draggedIDs); @@ -139,3 +142,23 @@ private: CHBookmarksToolbar* mToolbar; BookmarksDataSource* mDataSource; }; + + + +// singleton bookmarks manager object + +@interface BookmarksManager : NSObject +{ + + + +} + ++ (BookmarksManager*)sharedBookmarksManager; + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString; + + +@end + + diff --git a/camino/src/bookmarks/BookmarksService.mm b/camino/src/bookmarks/BookmarksService.mm index baa4588e3003..6df65543da71 100644 --- a/camino/src/bookmarks/BookmarksService.mm +++ b/camino/src/bookmarks/BookmarksService.mm @@ -37,12 +37,13 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserView.h" #import "BookmarksService.h" #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" #import "CHIconTabViewItem.h" -#import "StringUtils.h" +#import "SiteIconProvider.h" #include "nsCRT.h" #include "nsString.h" @@ -106,6 +107,7 @@ NSMutableDictionary* BookmarksService::gDictionary = nil; MainController* BookmarksService::gMainController = nil; NSMenu* BookmarksService::gBookmarksMenu = nil; nsIDOMElement* BookmarksService::gToolbarRoot = nsnull; + nsIAtom* BookmarksService::gBookmarkAtom = nsnull; nsIAtom* BookmarksService::gDescriptionAtom = nsnull; nsIAtom* BookmarksService::gFolderAtom = nsnull; @@ -114,8 +116,11 @@ nsIAtom* BookmarksService::gHrefAtom = nsnull; nsIAtom* BookmarksService::gKeywordAtom = nsnull; nsIAtom* BookmarksService::gNameAtom = nsnull; nsIAtom* BookmarksService::gOpenAtom = nsnull; + nsVoidArray* BookmarksService::gInstances = nsnull; + BOOL BookmarksService::gBookmarksFileReadOK = NO; + int BookmarksService::CHInsertNone = 0; int BookmarksService::CHInsertInto = 1; int BookmarksService::CHInsertBefore = 2; @@ -137,6 +142,54 @@ BookmarksService::~BookmarksService() { } +void +BookmarksService::AddObserver() +{ + gRefCnt++; + if (gRefCnt == 1) { + gBookmarkAtom = NS_NewAtom("bookmark"); + gFolderAtom = NS_NewAtom("folder"); + gNameAtom = NS_NewAtom("name"); + gHrefAtom = NS_NewAtom("href"); + gOpenAtom = NS_NewAtom("open"); + gKeywordAtom = NS_NewAtom("id"); + gDescriptionAtom = NS_NewAtom("description"); + gGroupAtom = NS_NewAtom("group"); + gInstances = new nsVoidArray(); + + ReadBookmarks(); + } + + gInstances->AppendElement(this); +} + +void +BookmarksService::RemoveObserver() +{ + if (gRefCnt == 0) + return; + + gInstances->RemoveElement(this); + + gRefCnt--; + if (gRefCnt == 0) { + // Flush Bookmarks before shutting down as some changes are not flushed when + // they are performed (folder open/closed) as writing a whole bookmark file for + // that type of operation seems excessive. + FlushBookmarks(); + + NS_IF_RELEASE(gBookmarks); + NS_RELEASE(gBookmarkAtom); + NS_RELEASE(gFolderAtom); + NS_RELEASE(gNameAtom); + NS_RELEASE(gHrefAtom); + NS_RELEASE(gOpenAtom); + [gDictionary release]; + } +} + +#pragma mark - + void BookmarksService::GetRootContent(nsIContent** aResult) { @@ -211,7 +264,7 @@ BookmarksService::LocateMenu(nsIContent* aContent) } void -BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -258,7 +311,7 @@ BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool } void -BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) +BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -289,6 +342,10 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) aItem->GetAttr(kNameSpaceID_None, gNameAtom, name); NSString* bookmarkTitle = [[NSString stringWith_nsAString: name] stringByTruncatingTo:80 at:kTruncateAtMiddle]; [childItem setTitle: bookmarkTitle]; + + // and reset the image + BookmarkItem* item = GetWrapperFor(aItem); + [childItem setImage: [item siteIcon]]; } } @@ -298,7 +355,7 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) } void -BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances) return; @@ -343,51 +400,6 @@ BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bo FlushBookmarks(); } -void -BookmarksService::AddObserver() -{ - gRefCnt++; - if (gRefCnt == 1) { - gBookmarkAtom = NS_NewAtom("bookmark"); - gFolderAtom = NS_NewAtom("folder"); - gNameAtom = NS_NewAtom("name"); - gHrefAtom = NS_NewAtom("href"); - gOpenAtom = NS_NewAtom("open"); - gKeywordAtom = NS_NewAtom("id"); - gDescriptionAtom = NS_NewAtom("description"); - gGroupAtom = NS_NewAtom("group"); - gInstances = new nsVoidArray(); - - ReadBookmarks(); - } - - gInstances->AppendElement(this); -} - -void -BookmarksService::RemoveObserver() -{ - if (gRefCnt == 0) - return; - - gInstances->RemoveElement(this); - - gRefCnt--; - if (gRefCnt == 0) { - // Flush Bookmarks before shutting down as some changes are not flushed when - // they are performed (folder open/closed) as writing a whole bookmark file for - // that type of operation seems excessive. - FlushBookmarks(); - - NS_IF_RELEASE(gBookmarks); - NS_RELEASE(gBookmarkAtom); - NS_RELEASE(gFolderAtom); - NS_RELEASE(gNameAtom); - NS_RELEASE(gHrefAtom); - NS_RELEASE(gOpenAtom); - [gDictionary release]; - } -} void BookmarksService::AddBookmarkToFolder(nsString& aURL, nsString& aTitle, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt) @@ -601,6 +613,40 @@ BookmarksService::FlushBookmarks() domSerializer->SerializeToStream(domDoc, outputStream, nsnull); } +NSImage* +BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) +{ + nsCOMPtr tagName; + nsCOMPtr content = do_QueryInterface(aElement); + content->GetTag(*getter_AddRefs(tagName)); + + nsAutoString group; + content->GetAttr(kNameSpaceID_None, gGroupAtom, group); + if (!group.IsEmpty()) + return [NSImage imageNamed:@"groupbookmark"]; + + if (tagName == BookmarksService::gFolderAtom) + return [NSImage imageNamed:@"folder"]; + + // fire off a proxy icon load + if ([[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" withSuccess:NULL]) + { + nsAutoString href; + content->GetAttr(kNameSpaceID_None, gHrefAtom, href); + if (href.Length() > 0) + { + BookmarkItem* contentItem = BookmarksService::GetWrapperFor(content); + if ([contentItem siteIcon]) + return [contentItem siteIcon]; + + if (contentItem && ![contentItem siteIcon]) + [[BookmarksManager sharedBookmarksManager] loadProxyImageFor:contentItem withURI:[NSString stringWith_nsAString:href]]; + } + } + + return [NSImage imageNamed:@"smallbookmark"]; +} + void BookmarksService::EnsureToolbarRoot() { if (gToolbarRoot) @@ -763,18 +809,21 @@ BookmarksService::AddMenuBookmark(NSMenu* aMenu, nsIContent* aParent, nsIContent nsAutoString group; aChild->GetAttr(kNameSpaceID_None, gGroupAtom, group); + nsCOMPtr elt(do_QueryInterface(aChild)); + NSImage* menuItemImage = BookmarksService::CreateIconForBookmark(elt); + if (group.IsEmpty() && tagName == gFolderAtom) { NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease]; [aMenu setSubmenu: menu forItem: menuItem]; [menu setAutoenablesItems: NO]; - [menuItem setImage: [NSImage imageNamed:@"folder"]]; + [menuItem setImage: menuItemImage]; ConstructBookmarksMenu(menu, aChild); } else { if (group.IsEmpty()) - [menuItem setImage: [NSImage imageNamed:@"smallbookmark"]]; + [menuItem setImage: menuItemImage]; else - [menuItem setImage: [NSImage imageNamed:@"groupbookmark"]]; + [menuItem setImage: menuItemImage]; [menuItem setTarget: gMainController]; [menuItem setAction: @selector(openMenuBookmark:)]; @@ -997,10 +1046,12 @@ BookmarksService::ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc) nsCOMPtr parentContent(do_QueryInterface(bookmarksRoot)); nsCOMPtr childContent(do_QueryInterface(importedRootElement)); +#if 0 // XXX testing if (gDictionary) [gDictionary removeAllObjects]; - +#endif + // this will save the file BookmarkAdded(parentContent, childContent, true /* flush */); } @@ -1072,24 +1123,7 @@ BookmarksService::ResolveKeyword(NSString* aKeyword) content->GetAttr(kNameSpaceID_None, gHrefAtom, url); return [NSString stringWith_nsAString: url]; } - return [NSString stringWithCString:""]; -} - -NSImage* -BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) -{ - nsCOMPtr tagName; - nsCOMPtr content = do_QueryInterface(aElement); - content->GetTag(*getter_AddRefs(tagName)); - if (tagName == BookmarksService::gFolderAtom) - return [NSImage imageNamed:@"folder"]; - - nsAutoString group; - content->GetAttr(kNameSpaceID_None, gGroupAtom, group); - if (!group.IsEmpty()) - return [NSImage imageNamed:@"smallgroup"]; - - return [NSImage imageNamed:@"groupbookmark"]; + return [NSString string]; } // Is searchItem equal to bookmark or bookmark's parent, grandparent, etc? @@ -1292,3 +1326,78 @@ BookmarksService::PerformURLDrop(BookmarkItem* parentItem, BookmarkItem* beforeI return YES; } +#pragma mark - + +@interface BookmarksManager(Private) + +- (void)registerNotificationListener; +- (void)imageLoadedNotification:(NSNotification*)notification; + +@end + + +@implementation BookmarksManager + ++ (BookmarksManager*)sharedBookmarksManager; +{ + static BookmarksManager* sBookmarksManager = nil; + + if (!sBookmarksManager) + sBookmarksManager = [[BookmarksManager alloc] init]; + + return sBookmarksManager; +} + +- (id)init +{ + if ((self = [super init])) + { + [self registerNotificationListener]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString +{ + [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon:self + forURI:inURIString withUserData:requestor allowNetwork:NO]; +} + + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// callback for [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] +- (void)imageLoadedNotification:(NSNotification*)notification +{ + //NSLog(@"BookmarksManager imageLoadedNotification"); + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + id requestor = [userInfo objectForKey:SiteIconLoadUserDataKey]; // requestor is a BookmarkItem + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + + if (iconImage && [requestor isMemberOfClass:[BookmarkItem class]]) + { + [requestor setSiteIcon:iconImage]; + BookmarksService::BookmarkChanged([requestor contentNode], FALSE); + } + + } +} + + +@end + diff --git a/camino/src/bookmarks/BookmarksToolbar.h b/camino/src/bookmarks/BookmarksToolbar.h index 895a1bd8005c..8f164fdaf8ac 100644 --- a/camino/src/bookmarks/BookmarksToolbar.h +++ b/camino/src/bookmarks/BookmarksToolbar.h @@ -28,12 +28,13 @@ class nsIDOMElement; class BookmarksService; class CHBookmarksButton; -@interface CHBookmarksToolbar : NSView { - BookmarksService* mBookmarks; - NSMutableArray* mButtons; +@interface CHBookmarksToolbar : NSView +{ + BookmarksService* mBookmarks; + NSMutableArray* mButtons; CHBookmarksButton* mDragInsertionButton; - int mDragInsertionPosition; - BOOL mIsShowing; + int mDragInsertionPosition; + BOOL mIsShowing; } -(void)initializeToolbar; diff --git a/camino/src/bookmarks/BookmarksToolbar.mm b/camino/src/bookmarks/BookmarksToolbar.mm index 2a1cee3573c5..c65d4d90c180 100644 --- a/camino/src/bookmarks/BookmarksToolbar.mm +++ b/camino/src/bookmarks/BookmarksToolbar.mm @@ -31,9 +31,14 @@ #include "nsIDOMElement.h" #include "nsIContent.h" +@interface CHBookmarksToolbar(Private) +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element; +@end + @implementation CHBookmarksToolbar -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { mBookmarks = nsnull; mButtons = [[NSMutableArray alloc] init]; @@ -95,8 +100,7 @@ while (child) { nsCOMPtr childElt(do_QueryInterface(child)); if (childElt) { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: childElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:childElt]; [self addSubview: button]; [mButtons addObject: button]; } @@ -111,8 +115,7 @@ -(void)addButton: (nsIDOMElement*)aElt atIndex: (int)aIndex { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: aElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:aElt]; [self addSubview: button]; [mButtons insertObject: button atIndex: aIndex]; if ([self isShown]) @@ -453,4 +456,9 @@ } } +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element +{ + return [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17) element:element bookmarksService:mBookmarks] autorelease]; +} + @end diff --git a/camino/src/browser/BrowserWindowController.h b/camino/src/browser/BrowserWindowController.h index ecc80d2755c6..b11edfacbefd 100644 --- a/camino/src/browser/BrowserWindowController.h +++ b/camino/src/browser/BrowserWindowController.h @@ -78,6 +78,7 @@ class nsIDOMNode; @class BookmarksDataSource; @class CHHistoryDataSource; @class CHExtendedTabView; +@class CHPageProxyIcon; @interface BrowserWindowController : NSWindowController { @@ -93,6 +94,7 @@ class nsIDOMNode; IBOutlet NSWindow* mLocationSheetWindow; IBOutlet NSTextField* mLocationSheetURLField; IBOutlet NSView* mStatusBar; // contains the status text, progress bar, and lock + IBOutlet CHPageProxyIcon* mProxyIcon; IBOutlet id mSidebarBrowserView; // currently unused IBOutlet BookmarksDataSource* mSidebarBookmarksDataSource; @@ -162,6 +164,7 @@ class nsIDOMNode; - (void)loadURL:(NSString*)aURLSpec referrer:(NSString*)aReferrer activate:(BOOL)activate; - (void)updateLocationFields:(NSString *)locationString; +- (void)updateSiteIcons:(NSImage *)siteIconImage; - (void)updateToolbarItems; - (void)focusURLBar; diff --git a/camino/src/browser/BrowserWindowController.mm b/camino/src/browser/BrowserWindowController.mm index 92a275afd009..82f3d33886af 100644 --- a/camino/src/browser/BrowserWindowController.mm +++ b/camino/src/browser/BrowserWindowController.mm @@ -46,6 +46,7 @@ #import "CHHistoryDataSource.h" #import "CHExtendedTabView.h" #import "CHUserDefaults.h" +#import "CHPageProxyIcon.h" #include "nsIWebNavigation.h" #include "nsIDOMElement.h" @@ -947,6 +948,13 @@ static NSArray* sToolbarDefaults = nil; // [[self window] display]; } +- (void)updateSiteIcons:(NSImage *)siteIconImage +{ + if (siteIconImage == nil) + siteIconImage = [NSImage imageNamed:@"globe_ico"]; + [mProxyIcon setImage:siteIconImage]; +} + -(void)newTab:(BOOL)allowHomepage { CHIconTabViewItem* newTab = [[[CHIconTabViewItem alloc] initWithIdentifier: nil] autorelease]; diff --git a/camino/src/browser/BrowserWrapper.h b/camino/src/browser/BrowserWrapper.h index 801a4d97a8a2..830af8077b31 100644 --- a/camino/src/browser/BrowserWrapper.h +++ b/camino/src/browser/BrowserWrapper.h @@ -51,6 +51,9 @@ NSTabViewItem* mTab; NSWindow* mWindow; + NSImage* mSiteIconImage; // current proxy icon image, which may be a site icon (favicon). + NSString* mSiteIconURI; // uri from which we loaded the site icon + // the secure state of this browser. We need to hold it so that we can set // the global lock icon whenever we become the primary. Value is one of // security enums in nsIWebProgressListener. @@ -60,9 +63,9 @@ NSString* mTitle; CHBrowserView* mBrowserView; - NSString* defaultStatus; - NSString* loadingStatus; - ToolTip* toolTip; + NSString* mDefaultStatusString; + NSString* mLoadingStatusString; + ToolTip* mToolTip; BOOL mIsPrimary; BOOL mIsBusy; diff --git a/camino/src/browser/BrowserWrapper.mm b/camino/src/browser/BrowserWrapper.mm index d3011dacdd26..01c9b87587a7 100644 --- a/camino/src/browser/BrowserWrapper.mm +++ b/camino/src/browser/BrowserWrapper.mm @@ -37,9 +37,12 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserWrapper.h" #import "BrowserWindowController.h" #import "BookmarksService.h" +#import "SiteIconProvider.h" +#import "CHIconTabViewItem.h" #import "ToolTip.h" #include "nsCOMPtr.h" @@ -64,8 +67,18 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; +const NSString* kOfflineNotificationName = @"offlineModeChanged"; + @interface CHBrowserWrapper(Private) - -(void) setPendingActive:(BOOL)active; + +- (void)setPendingActive:(BOOL)active; +- (void)registerNotificationListener; + +- (void)setSiteIconImage:(NSImage*)inSiteIcon; +- (void)setSiteIconURI:(NSString*)inSiteIconURI; + +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI; + @end @implementation CHBrowserWrapper @@ -85,10 +98,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; #endif [[NSNotificationCenter defaultCenter] removeObserver: self]; - - [defaultStatus release]; - [loadingStatus release]; - [toolTip release]; + + [mSiteIconImage release]; + [mSiteIconURI release]; + [mDefaultStatusString release]; + [mLoadingStatusString release]; + [mToolTip release]; [super dealloc]; } @@ -105,7 +120,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; progress = nil; progressSuper = nil; mIsPrimary = NO; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kOfflineNotificationName object:nil]; [mBrowserView setActive: NO]; } @@ -133,7 +148,13 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mIsBusy = NO; mListenersAttached = NO; mSecureState = nsIWebProgressListener::STATE_IS_INSECURE; - toolTip = [[ToolTip alloc] init]; + + mToolTip = [[ToolTip alloc] init]; + + //[self setSiteIconImage:[NSImage imageNamed:@"globe_ico"]]; + //[self setSiteIconURI: [NSString string]]; + + [self registerNotificationListener]; } return self; } @@ -155,9 +176,9 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (!mIsBusy) [progress removeFromSuperview]; - defaultStatus = NULL; - loadingStatus = DOCUMENT_DONE_STRING; - [status setStringValue:loadingStatus]; + mDefaultStatusString = NULL; + mLoadingStatusString = DOCUMENT_DONE_STRING; + [status setStringValue:mLoadingStatusString]; mIsPrimary = YES; @@ -181,11 +202,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (mWindowController) // Only register if we're the content area. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlineModeChanged:) - name:@"offlineModeChanged" + name:kOfflineNotificationName object:nil]; // Update the URL bar. [mWindowController updateLocationFields:[self getCurrentURLSpec]]; + [mWindowController updateSiteIcons:mSiteIconImage]; if (mWindowController && !mListenersAttached) { mListenersAttached = YES; @@ -209,7 +231,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; return [mBrowserView getCurrentURLSpec]; } -- (void)awakeFromNib +- (void)awakeFromNib { } @@ -239,17 +261,17 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLoadingStarted { - if (defaultStatus) { - [defaultStatus release]; - defaultStatus = NULL; + if (mDefaultStatusString) { + [mDefaultStatusString release]; + mDefaultStatusString = NULL; } [progressSuper addSubview:progress]; [progress setIndeterminate:YES]; [progress startAnimation:self]; - loadingStatus = NSLocalizedString(@"TabLoading", @""); - [status setStringValue:loadingStatus]; + mLoadingStatusString = NSLocalizedString(@"TabLoading", @""); + [status setStringValue:mLoadingStatusString]; mIsBusy = YES; [mTab setLabel: NSLocalizedString(@"TabLoading", @"")]; @@ -271,12 +293,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [progress stopAnimation:self]; [progress removeFromSuperview]; - loadingStatus = DOCUMENT_DONE_STRING; - if (defaultStatus) { - [status setStringValue:defaultStatus]; + mLoadingStatusString = DOCUMENT_DONE_STRING; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } mIsBusy = NO; @@ -316,8 +338,32 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLocationChange:(NSString*)urlSpec { + BOOL useSiteIcons = [[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" 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 withUserData:nil allowNetwork:YES]; + } + else + { + if ([urlSpec isEqualToString:@"about:blank"]) + faviconURI = urlSpec; + else + faviconURI = @""; + } + + if (!siteIconLoadInitiated) + [self updateSiteIconImage:nil withURI:faviconURI]; + if (mIsPrimary) - [mWindowController updateLocationFields:urlSpec]; + [mWindowController updateLocationFields:urlSpec]; } - (void)onStatusChange:(NSString*)aStatusString @@ -342,20 +388,20 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)setStatus:(NSString *)statusString ofType:(NSStatusType)type { if (type == NSStatusTypeScriptDefault) { - if (defaultStatus) { - [defaultStatus release]; + if (mDefaultStatusString) { + [mDefaultStatusString release]; } - defaultStatus = statusString; - if (defaultStatus) { - [defaultStatus retain]; + mDefaultStatusString = statusString; + if (mDefaultStatusString) { + [mDefaultStatusString retain]; } } else if (!statusString) { - if (defaultStatus) { - [status setStringValue:defaultStatus]; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } } else { @@ -418,12 +464,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onShowTooltip:(NSPoint)where withText:(NSString*)text { NSPoint point = [[self window] convertBaseToScreen:[self convertPoint: where toView:nil]]; - [toolTip showToolTipAtPoint: point withString: text]; + [mToolTip showToolTipAtPoint: point withString: text]; } - (void)onHideTooltip { - [toolTip closeToolTip]; + [mToolTip closeToolTip]; } // Called when a context menu should be shown. @@ -547,4 +593,77 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mActivateOnLoad = active; } +- (void)setSiteIconImage:(NSImage*)inSiteIcon +{ + [mSiteIconImage autorelease]; + mSiteIconImage = [inSiteIcon retain]; +} + +- (void)setSiteIconURI:(NSString*)inSiteIconURI +{ + [mSiteIconURI autorelease]; + mSiteIconURI = [inSiteIconURI retain]; +} + +// A nil inSiteIcon image indicates that we should use the default icon +// If inSiteIconURI is "about:blank", we don't show any icon +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI +{ + BOOL resetTabIcon = NO; + + if (![mSiteIconURI isEqualToString:inSiteIconURI]) + { + if (!inSiteIcon) + { + if (![inSiteIconURI isEqualToString:@"about:blank"]) + inSiteIcon = [NSImage imageNamed:@"globe_ico"]; + } + + [self setSiteIconImage: inSiteIcon]; + [self setSiteIconURI: inSiteIconURI]; + + // update the proxy icon + if (mIsPrimary) + [mWindowController updateSiteIcons:mSiteIconImage]; + + resetTabIcon = YES; + } + + // update the tab icon + if ([mTab isMemberOfClass:[CHIconTabViewItem class]]) + { + CHIconTabViewItem* tabItem = (CHIconTabViewItem*)mTab; + if (resetTabIcon || ![tabItem tabIcon]) + [tabItem setTabIcon:mSiteIconImage]; + } + +} + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// called when [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] completes +- (void)imageLoadedNotification:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + NSString* siteIconURI = [userInfo objectForKey:SiteIconLoadURIKey]; + + // NSLog(@"CHBrowserWrapper imageLoadedNotification got image %@ and uri %@", iconImage, proxyImageURI); + if (iconImage == nil) + siteIconURI = @""; // go back to default image + + [self updateSiteIconImage:iconImage withURI:siteIconURI]; + } +} + + @end diff --git a/camino/src/browser/PageProxyIcon.h b/camino/src/browser/PageProxyIcon.h index 9dfc66212c3f..4e8b1e17ce8e 100644 --- a/camino/src/browser/PageProxyIcon.h +++ b/camino/src/browser/PageProxyIcon.h @@ -25,7 +25,7 @@ @interface CHPageProxyIcon : NSImageView { - } + @end diff --git a/camino/src/browser/PageProxyIcon.mm b/camino/src/browser/PageProxyIcon.mm index 80544d58280b..51ab9d59f31b 100644 --- a/camino/src/browser/PageProxyIcon.mm +++ b/camino/src/browser/PageProxyIcon.mm @@ -24,13 +24,25 @@ #import "NSString+Utils.h" #import "CHPageProxyIcon.h" + #import "BookmarksService.h" #import "MainController.h" #include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsString.h" @implementation CHPageProxyIcon +- (void)awakeFromNib +{ +} + +- (void)dealloc +{ + [super dealloc]; +} + - (void) resetCursorRects { NSCursor* cursor; @@ -77,4 +89,5 @@ event: event pasteboard: pboard source: self slideBack: YES]; } + @end diff --git a/camino/src/browser/RemoteDataProvider.h b/camino/src/browser/RemoteDataProvider.h new file mode 100644 index 000000000000..13ff9a3af4ae --- /dev/null +++ b/camino/src/browser/RemoteDataProvider.h @@ -0,0 +1,90 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + + +extern NSString* RemoteDataLoadRequestNotificationName; +extern NSString* RemoteDataLoadRequestURIKey; +extern NSString* RemoteDataLoadRequestDataKey; +extern NSString* RemoteDataLoadRequestUserDataKey; +extern NSString* 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 +// callback in NSData. +// +// Clients can either implement the RemoteLoadListener protocol and call +// loadURI directly, or they can register with the [NSNotification defaultCenter] +// for 'RemoteDataLoadRequestNotificationName' notifications, and catch all loads +// that happen that way. + +@protocol RemoteLoadListener +// called when the load completes, or fails. If the status code is a failure code, +// data may be nil. +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status; +@end + + +class RemoteURILoadManager; + +@interface RemoteDataProvider : NSObject +{ + RemoteURILoadManager* mLoadManager; +} + ++ (RemoteDataProvider*)sharedRemoteDataProvider; + +// generic method. You can load any URI asynchronously with this selector, +// and the listener will get the contents of the URI in an NSData. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +// specific request to load a remote file. The sender (or any other object), if +// registered with the notification center, will receive a notification when +// the load completes. The 'target' becomes the 'object' of the notification. +// The notification name is given by NSString* RemoteDataLoadRequestNotificationName above. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +@end diff --git a/camino/src/browser/RemoteDataProvider.mm b/camino/src/browser/RemoteDataProvider.mm new file mode 100644 index 000000000000..b869d6739170 --- /dev/null +++ b/camino/src/browser/RemoteDataProvider.mm @@ -0,0 +1,298 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "RemoteDataProvider.h" + +#include "nsISupports.h" +#include "nsHashtable.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.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"; + + +// this has to retain the load listener, to ensure that the listener lives long +// enough to receive notifications. We have to be careful to avoid ref cycles. +class StreamLoaderContext : public nsISupports +{ +public: + StreamLoaderContext(id inLoadListener, id inUserData, id inTarget, const nsAString& inURI) + : mLoadListener(inLoadListener) + , mTarget(inTarget) + , mUserData(inUserData) + , mURI(inURI) + { + NS_INIT_ISUPPORTS(); + [mLoadListener retain]; + } + + virtual ~StreamLoaderContext() + { + [mLoadListener release]; + } + + NS_DECL_ISUPPORTS + + void LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength); + const nsAString& GetURI() { return mURI; } + +protected: + + id mLoadListener; // retained + id mTarget; // not retained + id mUserData; // not retained + nsString mURI; + +}; + + +NS_IMPL_ISUPPORTS1(StreamLoaderContext, nsISupports) + +void StreamLoaderContext::LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength) +{ + if (mLoadListener) + { + NSData* loadData = nil; + if (NS_SUCCEEDED(inLoadStatus)) + loadData = [NSData dataWithBytes:inData length:inDataLength]; + + [mLoadListener doneRemoteLoad:[NSString stringWith_nsAString:mURI] forTarget:mTarget withUserData:mUserData data:loadData status:inLoadStatus]; + } +} + + +class RemoteURILoadManager : public nsIStreamLoaderObserver +{ +public: + + RemoteURILoadManager(); + virtual ~RemoteURILoadManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLOADEROBSERVER + + nsresult Init(); + nsresult RequestURILoad(const nsAString& inURI, id loadListener, id userData, id target, PRBool allowNetworking); + +protected: + + nsSupportsHashtable mStreamLoaderHash; // hash of active stream loads, keyed on URI + nsCOMPtr mCacheSession; + +}; + +RemoteURILoadManager::RemoteURILoadManager() +{ + NS_INIT_ISUPPORTS(); +} + +RemoteURILoadManager::~RemoteURILoadManager() +{ +} + +NS_IMPL_ISUPPORTS1(RemoteURILoadManager, nsIStreamLoaderObserver) + +NS_IMETHODIMP RemoteURILoadManager::OnStreamComplete(nsIStreamLoader *loader, nsISupports *ctxt, nsresult status, PRUint32 resultLength, const char *result) +{ + StreamLoaderContext* loaderContext = NS_STATIC_CAST(StreamLoaderContext*, ctxt); + if (loaderContext) + { + loaderContext->LoadComplete(status, (const void*)result, resultLength); + + // remove the stream loader from the hash table + nsStringKey uriKey(loaderContext->GetURI()); + PRBool removed = mStreamLoaderHash.Remove(&uriKey); + } + + return NS_OK; +} + +static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID); + +nsresult RemoteURILoadManager::Init() +{ + nsresult rv; + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession("HTTP", nsICache::STORE_ANYWHERE, + nsICache::STREAM_BASED, getter_AddRefs(mCacheSession)); + + return rv; +} + +nsresult RemoteURILoadManager::RequestURILoad(const nsAString& inURI, id loadListener, + id userData, id target, PRBool allowNetworking) +{ + nsresult rv; + +#if 0 + // if no networking is allowed, make sure it's in the cache + if (!allowNetworking) + { + if (!mCacheSession) + return NS_ERROR_FAILURE; + + nsCOMPtr entryDesc; + rv = mCacheSession->OpenCacheEntry(NS_ConvertUCS2toUTF8(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + if (NS_FAILED(rv) || !entryDesc) + return NS_ERROR_FAILURE; + } +#endif + + nsStringKey uriKey(inURI); + + // first make sure that there isn't another entry in the hash for this + nsCOMPtr foundStreamSupports = mStreamLoaderHash.Get(&uriKey); + if (foundStreamSupports) + return NS_OK; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), inURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr loaderContext = new StreamLoaderContext(loadListener, userData, target, inURI); + + nsLoadFlags loadFlags = (allowNetworking) ? nsIRequest::LOAD_NORMAL : nsIRequest::LOAD_FROM_CACHE; + nsCOMPtr streamLoader; + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), uri, this, loaderContext, nsnull, nsnull, loadFlags); + if (NS_FAILED(rv)) + { + NSLog(@"NS_NewStreamLoader for favicon failed"); + return rv; + } + +#ifdef DEBUG_smfr + NSLog(@"RequestURILoad called for %@", [NSString stringWith_nsAString: inURI]); +#endif + + // put the stream loader into the hash table + nsCOMPtr streamLoaderAsSupports = do_QueryInterface(streamLoader); + mStreamLoaderHash.Put(&uriKey, streamLoaderAsSupports); + + return NS_OK; +} + + +#pragma mark - + + +@implementation RemoteDataProvider + + ++ (RemoteDataProvider*)sharedRemoteDataProvider +{ + static RemoteDataProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[RemoteDataProvider alloc] init]; + + // we probably need to register for NSApplicationWillTerminateNotification notifications + // and delete this then. + } + + return sIconProvider; +} + +- (id)init +{ + if ((self = [super init])) + { + mLoadManager = new RemoteURILoadManager; + NS_ADDREF(mLoadManager); + } + + return self; +} + +- (void)dealloc +{ + NS_IF_RELEASE(mLoadManager); + [super dealloc]; +} + +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + //NSLog(@"loadURI called with %@", inURI); + if (mLoadManager && [inURI length] > 0) + { + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsresult rv = mLoadManager->RequestURILoad(uriString, inListener, userData, target, (PRBool)inNetworkOK); + if (NS_FAILED(rv)) + { + NSLog(@"RequestURILoad failed for @%", inURI); + return NO; + } + } + + return YES; +} + +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + return [self loadURI:inURI forTarget:target withListener:self withUserData:userData allowNetworking:inNetworkOK]; +} + +// our own load listener callback +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, RemoteDataLoadRequestURIKey, + data, RemoteDataLoadRequestDataKey, + userData, RemoteDataLoadRequestUserDataKey, + [NSNumber numberWithInt:status], RemoteDataLoadRequestResultKey, + nil]; + + NSLog(@"remoteLoadDone with status %d and length %d", status, [data length]); + [[NSNotificationCenter defaultCenter] postNotificationName: RemoteDataLoadRequestNotificationName + object:target userInfo:notificationData]; +} + +@end diff --git a/camino/src/browser/SiteIconProvider.h b/camino/src/browser/SiteIconProvider.h new file mode 100644 index 000000000000..9cb0209c4558 --- /dev/null +++ b/camino/src/browser/SiteIconProvider.h @@ -0,0 +1,69 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +#import "RemoteDataProvider.h" + +extern NSString* SiteIconLoadNotificationName; +extern NSString* SiteIconLoadImageKey; +extern NSString* SiteIconLoadURIKey; +extern NSString* SiteIconLoadUserDataKey; + +class NeckoCacheHelper; + +@interface SiteIconProvider : NSObject +{ + NeckoCacheHelper* mMissedIconsCacheHelper; +} + ++ (SiteIconProvider*)sharedFavoriteIconProvider; + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI; + +// Start a favicon.ico load for the given URI, which can be any URI. +// The caller will get a 'SiteIconLoadNotificationName' notification +// when the load is done, with the image at the 'SiteIconLoadImageKey' key +// in the notifcation userInfo. The caller will have had to register with the +// NSNotifcationCenter in order to receive this notifcation. The notification +// is dispatched with 'sender' as the object. +// This method returns YES if the uri request was dispatched (i.e. if we know +// that we've looked for, and failed to find, this icon before). If it returns +// YES, then the 'SiteIconLoadNotificationName' notification will be sent out. +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData allowNetwork:(BOOL)inAllowNetwork; + +@end diff --git a/camino/src/browser/SiteIconProvider.mm b/camino/src/browser/SiteIconProvider.mm new file mode 100644 index 000000000000..b5f5d93705d8 --- /dev/null +++ b/camino/src/browser/SiteIconProvider.mm @@ -0,0 +1,330 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "SiteIconProvider.h" + +#include "prtime.h" +#include "nsString.h" +#include "nsISupports.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.h" + + +NSString* SiteIconLoadNotificationName = @"siteicon_load_notification"; +NSString* SiteIconLoadImageKey = @"siteicon_load_image"; +NSString* SiteIconLoadURIKey = @"siteicon_load_uri"; +NSString* SiteIconLoadUserDataKey = @"siteicon_load_user_data"; + + +static inline PRUint32 PRTimeToSeconds(PRTime t_usec) +{ + PRTime usec_per_sec; + PRUint32 t_sec; + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_DIV(t_usec, t_usec, usec_per_sec); + LL_L2I(t_sec, t_usec); + return t_sec; +} + +class NeckoCacheHelper +{ +public: + + NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue); + ~NeckoCacheHelper() {} + + nsresult Init(const char* inCacheSessionName); + nsresult ExistsInCache(const nsACString& inURI, PRBool* outExists); + nsresult PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds); + + nsresult ClearCache(); + +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) +{ +} + +nsresult NeckoCacheHelper::Init(const char* inCacheSessionName) +{ + nsresult rv; + + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession(inCacheSessionName, + nsICache::STORE_ANYWHERE, nsICache::STREAM_BASED, + getter_AddRefs(mCacheSession)); + if (NS_FAILED(rv)) + return rv; + + return NS_OK; +} + + +nsresult NeckoCacheHelper::ExistsInCache(const nsACString& inURI, PRBool* outExists) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + + *outExists = NS_SUCCEEDED(rv) && (entryDesc != NULL); + return NS_OK; +} + +nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), 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(mMetaElement, mMetaValue); // just set a bit of meta data. + entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds); + + entryDesc->MarkValid(); + entryDesc->Close(); + + return NS_OK; +} + +nsresult NeckoCacheHelper::ClearCache() +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + return mCacheSession->EvictEntries(); +} + + +#pragma mark - + +static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) +{ + outFaviconURI.Truncate(0); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), inURIString); + if (NS_FAILED(rv)) + return rv; + + // check for http/https + PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; + uri->SchemeIs("http", &isHTTP); + uri->SchemeIs("https", &isHTTPS); + if (!isHTTP && !isHTTPS) + return NS_OK; + + PRInt32 port; + uri->GetPort(&port); + + nsXPIDLCString scheme; + uri->GetScheme(scheme); + + nsXPIDLCString host; + uri->GetHost(host); + + nsCAutoString faviconURI = scheme; + faviconURI.Append("://"); + faviconURI.Append(host); + if (port != -1) { + faviconURI.Append(':'); + faviconURI.AppendInt(port); + } + faviconURI.Append("/favicon.ico"); + + outFaviconURI.Assign(NS_ConvertUTF8toUCS2(faviconURI)); + return NS_OK; +} + + +@interface SiteIconProvider(Private) + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds; +- (BOOL)inMissedIconsCache:(const nsAString&)inURI; + +@end + + +@implementation SiteIconProvider + +- (id)init +{ + if ((self = [super init])) + { + mMissedIconsCacheHelper = new NeckoCacheHelper("Favicon", "Missed"); + nsresult rv = mMissedIconsCacheHelper->Init("MissedIconsCache"); + if (NS_FAILED(rv)) { + delete mMissedIconsCacheHelper; + mMissedIconsCacheHelper = NULL; + } + } + + return self; +} + +- (void)dealloc +{ + delete mMissedIconsCacheHelper; + [super dealloc]; +} + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds +{ + if (mMissedIconsCacheHelper) + { + nsresult rv = mMissedIconsCacheHelper->PutInCache(NS_ConvertUCS2toUTF8(inURI), inExpSeconds); + //NSLog(@"Putting %@ in missed icon cache", [NSString stringWith_nsAString:inURI]); + } + +} + +- (BOOL)inMissedIconsCache:(const nsAString&)inURI +{ + PRBool inCache = PR_FALSE; + + if (mMissedIconsCacheHelper) + mMissedIconsCacheHelper->ExistsInCache(NS_ConvertUCS2toUTF8(inURI), &inCache); + + //NSLog(@"%@ in missed icon cache: %d", [NSString stringWith_nsAString:inURI], inCache); + return inCache; +} + + +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData 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; + + RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider]; + return [dataProvider loadURI:faviconString forTarget:sender withListener:self withUserData:userData allowNetworking:inAllowNetwork]; +} + +#define SITE_ICON_EXPIRATION_SECONDS (60 * 60 * 24 * 7) // 1 week + +// this is called on the main thread +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + BOOL loadOK = NS_SUCCEEDED(status) && (data != nil); + // it's hard to tell if the favicon load succeeded or not. Even if the file + // does not exist, servers will send back a 404 page with a 0 status. + // So we just go ahead and try to make the image; it will return nil on + // failure. + NSImage* faviconImage = [[NSImage alloc] initWithData:data]; + BOOL gotImageData = loadOK && (faviconImage != nil); + if (!gotImageData) + [self addToMissedIconsCache:uriString withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS]; + + [faviconImage setScalesWhenResized:YES]; + [faviconImage setSize:NSMakeSize(16, 16)]; + + // we always send out the notification, so that clients know + // about failed requests + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, SiteIconLoadURIKey, + faviconImage, SiteIconLoadImageKey, // may be nil + userData, SiteIconLoadUserDataKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName: SiteIconLoadNotificationName + object:target userInfo:notificationData]; +} + +#pragma mark - + ++ (SiteIconProvider*)sharedFavoriteIconProvider +{ + static SiteIconProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[SiteIconProvider alloc] init]; + } + + return sIconProvider; +} + + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsAutoString faviconURIString; + MakeFaviconURIFromURI(uriString, faviconURIString); + return [NSString stringWith_nsAString:faviconURIString]; +} + +@end diff --git a/camino/src/extensions/IconTabViewItem.h b/camino/src/extensions/IconTabViewItem.h index 13a37120dc8e..b1ee2fc0e26b 100644 --- a/camino/src/extensions/IconTabViewItem.h +++ b/camino/src/extensions/IconTabViewItem.h @@ -40,7 +40,8 @@ #import -@interface CHIconTabViewItem : NSTabViewItem { +@interface CHIconTabViewItem : NSTabViewItem +{ NSImage *mTabIcon; NSDictionary* mLabelAttributes; } diff --git a/camino/src/extensions/IconTabViewItem.mm b/camino/src/extensions/IconTabViewItem.mm index cf659548c4c0..a7a19c588bb1 100644 --- a/camino/src/extensions/IconTabViewItem.mm +++ b/camino/src/extensions/IconTabViewItem.mm @@ -39,8 +39,9 @@ * * ***** END LICENSE BLOCK ***** */ -#import "CHIconTabViewItem.h" +#import "NSString+Utils.h" +#import "CHIconTabViewItem.h" // // NSParagraphStyle has a line break mode which will automatically @@ -91,7 +92,7 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s [labelParagraphStyle setLineBreakMode:NSLineBreakByTruncatingMiddle]; #endif - [labelParagraphStyle setAlignment:NSCenterTextAlignment]; + [labelParagraphStyle setAlignment:NSNaturalTextAlignment]; NSFont *labelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; mLabelAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -139,9 +140,9 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s if ([self tabIcon]) { NSPoint drawPoint = NSMakePoint( (tabRect.origin.x), (tabRect.origin.y + 15.0) ); [[self tabIcon] compositeToPoint:drawPoint operation:NSCompositeSourceOver]; - tabRect = NSMakeRect(NSMinX(tabRect)+15.0, + tabRect = NSMakeRect(NSMinX(tabRect) + 18.0, NSMinY(tabRect), - NSWidth(tabRect)-15.0, + NSWidth(tabRect) - 18.0, NSHeight(tabRect)); } diff --git a/camino/src/preferences/PreferenceManager.h b/camino/src/preferences/PreferenceManager.h index 3063fc19b835..a682a4c80e4c 100644 --- a/camino/src/preferences/PreferenceManager.h +++ b/camino/src/preferences/PreferenceManager.h @@ -35,12 +35,16 @@ * * ***** END LICENSE BLOCK ***** */ -#import +#import #import -@interface CHPreferenceManager : NSObject { +class nsIPref; + +@interface CHPreferenceManager : NSObject +{ NSUserDefaults* mDefaults; ICInstance mInternetConfig; + nsIPref* mPrefs; } + (CHPreferenceManager *)sharedInstance; @@ -54,4 +58,9 @@ - (NSString *) getICStringPref:(ConstStr255Param) prefKey; - (NSString *) homePage:(BOOL) checkStartupPagePref; +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; + @end diff --git a/camino/src/preferences/PreferenceManager.mm b/camino/src/preferences/PreferenceManager.mm index 51228e415557..ef8e2cd0eaa4 100644 --- a/camino/src/preferences/PreferenceManager.mm +++ b/camino/src/preferences/PreferenceManager.mm @@ -86,8 +86,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (void) dealloc { + ::ICStop(mInternetConfig); + NS_IF_RELEASE(mPrefs); + nsresult rv; - ICStop (mInternetConfig); nsCOMPtr pref(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { //NSLog(@"Saving prefs file"); @@ -100,7 +102,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (BOOL) initInternetConfig { OSStatus error; - error = ICStart (&mInternetConfig, 'CHIM'); + error = ::ICStart(&mInternetConfig, 'CHIM'); if (error != noErr) { // XXX throw here? NSLog(@"Error initializing IC"); @@ -178,6 +180,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); return NO; } + nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); + mPrefs = prefs; + NS_IF_ADDREF(mPrefs); + [self syncMozillaPrefs]; return YES; } @@ -191,9 +197,8 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); char strbuf[1024]; int numbuf; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) { - // XXXw. throw? + if (!mPrefs) { + NSLog(@"Mozilla prefs not set up successfully"); return; } @@ -201,42 +206,42 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // something that chimera can deal with. PRInt32 acceptCookies = 0; static const char* kCookieBehaviorPref = "network.cookie.cookieBehavior"; - prefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); + mPrefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); if ( acceptCookies == 1 ) { // accept foreign cookies, assume off acceptCookies = 2; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } else if ( acceptCookies == 3 ) { // p3p, assume all cookies on acceptCookies = 0; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } // get proxies from SystemConfiguration - prefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies - prefs->ClearUserPref("network.proxy.http"); - prefs->ClearUserPref("network.proxy.http_port"); - prefs->ClearUserPref("network.proxy.ssl"); - prefs->ClearUserPref("network.proxy.ssl_port"); - prefs->ClearUserPref("network.proxy.ftp"); - prefs->ClearUserPref("network.proxy.ftp_port"); - prefs->ClearUserPref("network.proxy.gopher"); - prefs->ClearUserPref("network.proxy.gopher_port"); - prefs->ClearUserPref("network.proxy.socks"); - prefs->ClearUserPref("network.proxy.socks_port"); - prefs->ClearUserPref("network.proxy.no_proxies_on"); + mPrefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies + mPrefs->ClearUserPref("network.proxy.http"); + mPrefs->ClearUserPref("network.proxy.http_port"); + mPrefs->ClearUserPref("network.proxy.ssl"); + mPrefs->ClearUserPref("network.proxy.ssl_port"); + mPrefs->ClearUserPref("network.proxy.ftp"); + mPrefs->ClearUserPref("network.proxy.ftp_port"); + mPrefs->ClearUserPref("network.proxy.gopher"); + mPrefs->ClearUserPref("network.proxy.gopher_port"); + mPrefs->ClearUserPref("network.proxy.socks"); + mPrefs->ClearUserPref("network.proxy.socks_port"); + mPrefs->ClearUserPref("network.proxy.no_proxies_on"); if ((cfDictionary = SCDynamicStoreCopyProxies (NULL)) != NULL) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPEnable, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.http", strbuf); + mPrefs->SetCharPref("network.proxy.http", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.http_port", numbuf); + mPrefs->SetIntPref("network.proxy.http_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -245,13 +250,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ssl", strbuf); + mPrefs->SetCharPref("network.proxy.ssl", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ssl_port", numbuf); + mPrefs->SetIntPref("network.proxy.ssl_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -260,13 +265,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ftp", strbuf); + mPrefs->SetCharPref("network.proxy.ftp", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ftp_port", numbuf); + mPrefs->SetIntPref("network.proxy.ftp_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -275,13 +280,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.gopher", strbuf); + mPrefs->SetCharPref("network.proxy.gopher", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.gopher_port", numbuf); + mPrefs->SetIntPref("network.proxy.gopher_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -290,13 +295,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.socks", strbuf); + mPrefs->SetCharPref("network.proxy.socks", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.socks_port", numbuf); + mPrefs->SetIntPref("network.proxy.socks_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -305,7 +310,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); cfString = CFStringCreateByCombiningStrings (NULL, cfArray, CFSTR(", ")); if (CFStringGetLength (cfString) > 0) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.no_proxies_on", strbuf); + mPrefs->SetCharPref("network.proxy.no_proxies_on", strbuf); } } } @@ -313,24 +318,73 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); } } -// convenience routines for mozilla prefs -- (NSString*)getMozillaPrefString: (const char*)prefName +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess { - NSMutableString *prefValue = [[[NSMutableString alloc] init] autorelease]; + NSString *prefValue = @""; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (prefs) { - char *buf = nsnull; - nsresult rv = prefs->GetCharPref(prefName, &buf); - if (NS_SUCCEEDED(rv) && buf) { - [prefValue setString:[NSString stringWithCString:buf]]; - free(buf); - } + char *buf = nsnull; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetCharPref(prefName, &buf); + + if (NS_SUCCEEDED(rv) && buf) { + // prefs are UTF-8 + prefValue = [NSString stringWithUTF8String:buf]; + free(buf); + if (outSuccess) *outSuccess = YES; + } else { + if (outSuccess) *outSuccess = NO; } return prefValue; } +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + // colors are stored in HTML-like #FFFFFF strings + NSString* colorString = [self getStringPref:prefName withSuccess:outSuccess]; + NSColor* returnColor = [NSColor blackColor]; + + if ([colorString hasPrefix:@"#"] && [colorString length] == 7) + { + unsigned int redInt, greenInt, blueInt; + sscanf([colorString cString], "#%02x%02x%02x", &redInt, &greenInt, &blueInt); + + float redFloat = ((float)redInt / 255.0); + float greenFloat = ((float)greenInt / 255.0); + float blueFloat = ((float)blueInt / 255.0); + + returnColor = [NSColor colorWithCalibratedRed:redFloat green:greenFloat blue:blueFloat alpha:1.0f]; + } + + return returnColor; +} + +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRBool boolPref = PR_FALSE; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + rv = mPrefs->GetBoolPref(prefName, &boolPref); + + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + + return boolPref ? YES : NO; +} + +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRInt32 intPref = 0; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetIntPref(prefName, &intPref); + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + return intPref; +} + + //- (BOOL) getICBoolPref:(ConstStr255Param) prefKey; //{ @@ -381,8 +435,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (NSString *) homePage:(BOOL)checkStartupPagePref { - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) + if (!mPrefs) return @"about:blank"; PRInt32 mode = 1; @@ -394,14 +447,14 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // is true. nsresult rv = NS_OK; if ( checkStartupPagePref ) - rv = prefs->GetIntPref("browser.startup.page", &mode); + rv = mPrefs->GetIntPref("browser.startup.page", &mode); if (NS_FAILED(rv) || mode == 1) { // see which home page to use PRBool boolPref; - if (NS_SUCCEEDED(prefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) + if (NS_SUCCEEDED(mPrefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) return [self getICStringPref:kICWWWHomePage]; - nsCOMPtr prefBranch = do_QueryInterface(prefs); + nsCOMPtr prefBranch = do_QueryInterface(mPrefs); if (!prefBranch) return @"about:blank"; NSString* homepagePref = nil; @@ -411,10 +464,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); homepagePref = NSLocalizedStringFromTable( @"HomePageDefault", @"WebsiteDefaults", nil); // and let's copy this into the homepage pref if it's not bad if (![homepagePref isEqualToString:@"HomePageDefault"]) - prefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); + mPrefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); } else { - homepagePref = [self getMozillaPrefString:"browser.startup.homepage"]; + homepagePref = [self getStringPref:"browser.startup.homepage" withSuccess:NULL]; } if (homepagePref && [homepagePref length] > 0 && ![homepagePref isEqualToString:@"HomePageDefault"]) diff --git a/chimera/BookmarksDataSource.h b/chimera/BookmarksDataSource.h index f05a76b7a544..f0a280af1f29 100644 --- a/chimera/BookmarksDataSource.h +++ b/chimera/BookmarksDataSource.h @@ -49,6 +49,7 @@ class BookmarksService; @class BookmarkInfoController; +// data source for the bookmarks sidebar. We make one per browser window. @interface BookmarksDataSource : NSObject { BookmarksService* mBookmarks; @@ -106,14 +107,16 @@ class BookmarksService; @interface BookmarkItem : NSObject { nsIContent* mContentNode; + NSImage* mSiteIcon; } - (nsIContent*)contentNode; - (void)setContentNode: (nsIContent*)aContentNode; +- (void)setSiteIcon:(NSImage*)image; - (NSString*)url; +- (NSImage*)siteIcon; - (NSNumber*)contentID; - (id)copyWithZone:(NSZone *)aZone; - (BOOL)isFolder; @end - diff --git a/chimera/BookmarksDataSource.mm b/chimera/BookmarksDataSource.mm index 77cdc6c36687..9b5915765329 100644 --- a/chimera/BookmarksDataSource.mm +++ b/chimera/BookmarksDataSource.mm @@ -41,6 +41,7 @@ #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" +#import "SiteIconProvider.h" #include "nsCOMPtr.h" #include "nsIContent.h" @@ -55,17 +56,16 @@ #include "nsVoidArray.h" #import "BookmarksService.h" -#import "StringUtils.h" @implementation BookmarksDataSource -(id) init { - if ( (self = [super init]) ) { - mBookmarks = nsnull; - mCachedHref = nil; - } - return self; + if ( (self = [super init]) ) { + mBookmarks = nsnull; + mCachedHref = nil; + } + return self; } -(void) awakeFromNib @@ -86,16 +86,16 @@ -(void) ensureBookmarks { - if (mBookmarks) - return; - - mBookmarks = new BookmarksService(self); - mBookmarks->AddObserver(); - - [mOutlineView setTarget: self]; - [mOutlineView setDoubleAction: @selector(openBookmark:)]; - [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; - [mOutlineView reloadData]; + if (mBookmarks) + return; + + mBookmarks = new BookmarksService(self); + mBookmarks->AddObserver(); + + [mOutlineView setTarget: self]; + [mOutlineView setDoubleAction: @selector(openBookmark:)]; + [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; + [mOutlineView reloadData]; } -(IBAction)addBookmark:(id)aSender @@ -530,20 +530,10 @@ //Get the cell of the text attachment. attachmentAttrStringCell = (NSCell *)[(NSTextAttachment *)[attachmentAttrString attribute:NSAttachmentAttributeName atIndex:0 effectiveRange:nil] attachmentCell]; - //Figure out which image to add, and set the cell's image. - // Use the bookmark groups image for groups. - if ([self outlineView:outlineView isItemExpandable:item]) { - nsIContent* content = [item contentNode]; - nsCOMPtr elt(do_QueryInterface(content)); - nsAutoString group; - content->GetAttr(kNameSpaceID_None, BookmarksService::gGroupAtom, group); - if (!group.IsEmpty()) - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"groupbookmark"]]; - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"folder"]]; - } - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"smallbookmark"]]; + + nsCOMPtr elt(do_QueryInterface(content)); + NSImage* bookmarkImage = mBookmarks->CreateIconForBookmark(elt); + [attachmentAttrStringCell setImage:bookmarkImage]; //Insert the image [cellValue replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attachmentAttrString]; @@ -855,7 +845,16 @@ @end +#pragma mark - + @implementation BookmarkItem + +-(void)dealloc +{ + [mSiteIcon release]; + [super dealloc]; +} + -(nsIContent*)contentNode { return mContentNode; @@ -887,6 +886,18 @@ return [NSString stringWith_nsAString: href]; } +- (void)setSiteIcon:(NSImage*)image +{ + //NSLog(@"Setting site icon for %@", [self url]); + [mSiteIcon autorelease]; + mSiteIcon = [image retain]; +} + +- (NSImage*)siteIcon +{ + return mSiteIcon; +} + -(void)setContentNode: (nsIContent*)aContentNode { mContentNode = aContentNode; @@ -896,6 +907,7 @@ { BookmarkItem* copy = [[[self class] allocWithZone: aZone] init]; [copy setContentNode: mContentNode]; + [copy setSiteIcon: mSiteIcon]; return copy; } diff --git a/chimera/BookmarksService.h b/chimera/BookmarksService.h index c03f27353565..411935e25172 100644 --- a/chimera/BookmarksService.h +++ b/chimera/BookmarksService.h @@ -53,6 +53,9 @@ class nsIDOMHTMLDocument; @class BookmarksDataSource; @class BookmarkItem; +// despite appearances, BookmarksService is not a singleton. We make one for the bookmarks menu, +// one each per BookmarksDataSource, and one per bookmarks toolbar. It relies on a bunch of global +// variables, which is evil. class BookmarksService { public: @@ -63,6 +66,7 @@ public: void AddObserver(); void RemoveObserver(); +public: static void BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); static void BookmarkChanged(nsIContent* aItem, bool shouldFlush = true); static void BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); @@ -71,7 +75,6 @@ public: static void MoveBookmarkToFolder(nsIDOMElement* aBookmark, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt); static void DeleteBookmark(nsIDOMElement* aBookmark); -public: static void GetRootContent(nsIContent** aResult); static BookmarkItem* GetRootItem(); static BookmarkItem* GetWrapperFor(nsIContent* aItem); @@ -87,6 +90,8 @@ public: static void ConstructAddBookmarkFolderList(NSPopUpButton* aPopup, BookmarkItem* aItem); + static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); + static void EnsureToolbarRoot(); static void ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc); @@ -96,8 +101,6 @@ public: static NSString* ResolveKeyword(NSString* aKeyword); - static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); - static BOOL DoAncestorsIncludeNode(BookmarkItem* bookmark, BookmarkItem* searchItem); static bool IsBookmarkDropValid(BookmarkItem* proposedParent, int index, NSArray* draggedIDs); static bool PerformBookmarkDrop(BookmarkItem* parent, int index, NSArray* draggedIDs); @@ -139,3 +142,23 @@ private: CHBookmarksToolbar* mToolbar; BookmarksDataSource* mDataSource; }; + + + +// singleton bookmarks manager object + +@interface BookmarksManager : NSObject +{ + + + +} + ++ (BookmarksManager*)sharedBookmarksManager; + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString; + + +@end + + diff --git a/chimera/BookmarksService.mm b/chimera/BookmarksService.mm index baa4588e3003..6df65543da71 100644 --- a/chimera/BookmarksService.mm +++ b/chimera/BookmarksService.mm @@ -37,12 +37,13 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserView.h" #import "BookmarksService.h" #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" #import "CHIconTabViewItem.h" -#import "StringUtils.h" +#import "SiteIconProvider.h" #include "nsCRT.h" #include "nsString.h" @@ -106,6 +107,7 @@ NSMutableDictionary* BookmarksService::gDictionary = nil; MainController* BookmarksService::gMainController = nil; NSMenu* BookmarksService::gBookmarksMenu = nil; nsIDOMElement* BookmarksService::gToolbarRoot = nsnull; + nsIAtom* BookmarksService::gBookmarkAtom = nsnull; nsIAtom* BookmarksService::gDescriptionAtom = nsnull; nsIAtom* BookmarksService::gFolderAtom = nsnull; @@ -114,8 +116,11 @@ nsIAtom* BookmarksService::gHrefAtom = nsnull; nsIAtom* BookmarksService::gKeywordAtom = nsnull; nsIAtom* BookmarksService::gNameAtom = nsnull; nsIAtom* BookmarksService::gOpenAtom = nsnull; + nsVoidArray* BookmarksService::gInstances = nsnull; + BOOL BookmarksService::gBookmarksFileReadOK = NO; + int BookmarksService::CHInsertNone = 0; int BookmarksService::CHInsertInto = 1; int BookmarksService::CHInsertBefore = 2; @@ -137,6 +142,54 @@ BookmarksService::~BookmarksService() { } +void +BookmarksService::AddObserver() +{ + gRefCnt++; + if (gRefCnt == 1) { + gBookmarkAtom = NS_NewAtom("bookmark"); + gFolderAtom = NS_NewAtom("folder"); + gNameAtom = NS_NewAtom("name"); + gHrefAtom = NS_NewAtom("href"); + gOpenAtom = NS_NewAtom("open"); + gKeywordAtom = NS_NewAtom("id"); + gDescriptionAtom = NS_NewAtom("description"); + gGroupAtom = NS_NewAtom("group"); + gInstances = new nsVoidArray(); + + ReadBookmarks(); + } + + gInstances->AppendElement(this); +} + +void +BookmarksService::RemoveObserver() +{ + if (gRefCnt == 0) + return; + + gInstances->RemoveElement(this); + + gRefCnt--; + if (gRefCnt == 0) { + // Flush Bookmarks before shutting down as some changes are not flushed when + // they are performed (folder open/closed) as writing a whole bookmark file for + // that type of operation seems excessive. + FlushBookmarks(); + + NS_IF_RELEASE(gBookmarks); + NS_RELEASE(gBookmarkAtom); + NS_RELEASE(gFolderAtom); + NS_RELEASE(gNameAtom); + NS_RELEASE(gHrefAtom); + NS_RELEASE(gOpenAtom); + [gDictionary release]; + } +} + +#pragma mark - + void BookmarksService::GetRootContent(nsIContent** aResult) { @@ -211,7 +264,7 @@ BookmarksService::LocateMenu(nsIContent* aContent) } void -BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -258,7 +311,7 @@ BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool } void -BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) +BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -289,6 +342,10 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) aItem->GetAttr(kNameSpaceID_None, gNameAtom, name); NSString* bookmarkTitle = [[NSString stringWith_nsAString: name] stringByTruncatingTo:80 at:kTruncateAtMiddle]; [childItem setTitle: bookmarkTitle]; + + // and reset the image + BookmarkItem* item = GetWrapperFor(aItem); + [childItem setImage: [item siteIcon]]; } } @@ -298,7 +355,7 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) } void -BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances) return; @@ -343,51 +400,6 @@ BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bo FlushBookmarks(); } -void -BookmarksService::AddObserver() -{ - gRefCnt++; - if (gRefCnt == 1) { - gBookmarkAtom = NS_NewAtom("bookmark"); - gFolderAtom = NS_NewAtom("folder"); - gNameAtom = NS_NewAtom("name"); - gHrefAtom = NS_NewAtom("href"); - gOpenAtom = NS_NewAtom("open"); - gKeywordAtom = NS_NewAtom("id"); - gDescriptionAtom = NS_NewAtom("description"); - gGroupAtom = NS_NewAtom("group"); - gInstances = new nsVoidArray(); - - ReadBookmarks(); - } - - gInstances->AppendElement(this); -} - -void -BookmarksService::RemoveObserver() -{ - if (gRefCnt == 0) - return; - - gInstances->RemoveElement(this); - - gRefCnt--; - if (gRefCnt == 0) { - // Flush Bookmarks before shutting down as some changes are not flushed when - // they are performed (folder open/closed) as writing a whole bookmark file for - // that type of operation seems excessive. - FlushBookmarks(); - - NS_IF_RELEASE(gBookmarks); - NS_RELEASE(gBookmarkAtom); - NS_RELEASE(gFolderAtom); - NS_RELEASE(gNameAtom); - NS_RELEASE(gHrefAtom); - NS_RELEASE(gOpenAtom); - [gDictionary release]; - } -} void BookmarksService::AddBookmarkToFolder(nsString& aURL, nsString& aTitle, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt) @@ -601,6 +613,40 @@ BookmarksService::FlushBookmarks() domSerializer->SerializeToStream(domDoc, outputStream, nsnull); } +NSImage* +BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) +{ + nsCOMPtr tagName; + nsCOMPtr content = do_QueryInterface(aElement); + content->GetTag(*getter_AddRefs(tagName)); + + nsAutoString group; + content->GetAttr(kNameSpaceID_None, gGroupAtom, group); + if (!group.IsEmpty()) + return [NSImage imageNamed:@"groupbookmark"]; + + if (tagName == BookmarksService::gFolderAtom) + return [NSImage imageNamed:@"folder"]; + + // fire off a proxy icon load + if ([[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" withSuccess:NULL]) + { + nsAutoString href; + content->GetAttr(kNameSpaceID_None, gHrefAtom, href); + if (href.Length() > 0) + { + BookmarkItem* contentItem = BookmarksService::GetWrapperFor(content); + if ([contentItem siteIcon]) + return [contentItem siteIcon]; + + if (contentItem && ![contentItem siteIcon]) + [[BookmarksManager sharedBookmarksManager] loadProxyImageFor:contentItem withURI:[NSString stringWith_nsAString:href]]; + } + } + + return [NSImage imageNamed:@"smallbookmark"]; +} + void BookmarksService::EnsureToolbarRoot() { if (gToolbarRoot) @@ -763,18 +809,21 @@ BookmarksService::AddMenuBookmark(NSMenu* aMenu, nsIContent* aParent, nsIContent nsAutoString group; aChild->GetAttr(kNameSpaceID_None, gGroupAtom, group); + nsCOMPtr elt(do_QueryInterface(aChild)); + NSImage* menuItemImage = BookmarksService::CreateIconForBookmark(elt); + if (group.IsEmpty() && tagName == gFolderAtom) { NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease]; [aMenu setSubmenu: menu forItem: menuItem]; [menu setAutoenablesItems: NO]; - [menuItem setImage: [NSImage imageNamed:@"folder"]]; + [menuItem setImage: menuItemImage]; ConstructBookmarksMenu(menu, aChild); } else { if (group.IsEmpty()) - [menuItem setImage: [NSImage imageNamed:@"smallbookmark"]]; + [menuItem setImage: menuItemImage]; else - [menuItem setImage: [NSImage imageNamed:@"groupbookmark"]]; + [menuItem setImage: menuItemImage]; [menuItem setTarget: gMainController]; [menuItem setAction: @selector(openMenuBookmark:)]; @@ -997,10 +1046,12 @@ BookmarksService::ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc) nsCOMPtr parentContent(do_QueryInterface(bookmarksRoot)); nsCOMPtr childContent(do_QueryInterface(importedRootElement)); +#if 0 // XXX testing if (gDictionary) [gDictionary removeAllObjects]; - +#endif + // this will save the file BookmarkAdded(parentContent, childContent, true /* flush */); } @@ -1072,24 +1123,7 @@ BookmarksService::ResolveKeyword(NSString* aKeyword) content->GetAttr(kNameSpaceID_None, gHrefAtom, url); return [NSString stringWith_nsAString: url]; } - return [NSString stringWithCString:""]; -} - -NSImage* -BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) -{ - nsCOMPtr tagName; - nsCOMPtr content = do_QueryInterface(aElement); - content->GetTag(*getter_AddRefs(tagName)); - if (tagName == BookmarksService::gFolderAtom) - return [NSImage imageNamed:@"folder"]; - - nsAutoString group; - content->GetAttr(kNameSpaceID_None, gGroupAtom, group); - if (!group.IsEmpty()) - return [NSImage imageNamed:@"smallgroup"]; - - return [NSImage imageNamed:@"groupbookmark"]; + return [NSString string]; } // Is searchItem equal to bookmark or bookmark's parent, grandparent, etc? @@ -1292,3 +1326,78 @@ BookmarksService::PerformURLDrop(BookmarkItem* parentItem, BookmarkItem* beforeI return YES; } +#pragma mark - + +@interface BookmarksManager(Private) + +- (void)registerNotificationListener; +- (void)imageLoadedNotification:(NSNotification*)notification; + +@end + + +@implementation BookmarksManager + ++ (BookmarksManager*)sharedBookmarksManager; +{ + static BookmarksManager* sBookmarksManager = nil; + + if (!sBookmarksManager) + sBookmarksManager = [[BookmarksManager alloc] init]; + + return sBookmarksManager; +} + +- (id)init +{ + if ((self = [super init])) + { + [self registerNotificationListener]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString +{ + [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon:self + forURI:inURIString withUserData:requestor allowNetwork:NO]; +} + + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// callback for [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] +- (void)imageLoadedNotification:(NSNotification*)notification +{ + //NSLog(@"BookmarksManager imageLoadedNotification"); + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + id requestor = [userInfo objectForKey:SiteIconLoadUserDataKey]; // requestor is a BookmarkItem + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + + if (iconImage && [requestor isMemberOfClass:[BookmarkItem class]]) + { + [requestor setSiteIcon:iconImage]; + BookmarksService::BookmarkChanged([requestor contentNode], FALSE); + } + + } +} + + +@end + diff --git a/chimera/BrowserWindowController.h b/chimera/BrowserWindowController.h index ecc80d2755c6..b11edfacbefd 100644 --- a/chimera/BrowserWindowController.h +++ b/chimera/BrowserWindowController.h @@ -78,6 +78,7 @@ class nsIDOMNode; @class BookmarksDataSource; @class CHHistoryDataSource; @class CHExtendedTabView; +@class CHPageProxyIcon; @interface BrowserWindowController : NSWindowController { @@ -93,6 +94,7 @@ class nsIDOMNode; IBOutlet NSWindow* mLocationSheetWindow; IBOutlet NSTextField* mLocationSheetURLField; IBOutlet NSView* mStatusBar; // contains the status text, progress bar, and lock + IBOutlet CHPageProxyIcon* mProxyIcon; IBOutlet id mSidebarBrowserView; // currently unused IBOutlet BookmarksDataSource* mSidebarBookmarksDataSource; @@ -162,6 +164,7 @@ class nsIDOMNode; - (void)loadURL:(NSString*)aURLSpec referrer:(NSString*)aReferrer activate:(BOOL)activate; - (void)updateLocationFields:(NSString *)locationString; +- (void)updateSiteIcons:(NSImage *)siteIconImage; - (void)updateToolbarItems; - (void)focusURLBar; diff --git a/chimera/BrowserWindowController.mm b/chimera/BrowserWindowController.mm index 92a275afd009..82f3d33886af 100644 --- a/chimera/BrowserWindowController.mm +++ b/chimera/BrowserWindowController.mm @@ -46,6 +46,7 @@ #import "CHHistoryDataSource.h" #import "CHExtendedTabView.h" #import "CHUserDefaults.h" +#import "CHPageProxyIcon.h" #include "nsIWebNavigation.h" #include "nsIDOMElement.h" @@ -947,6 +948,13 @@ static NSArray* sToolbarDefaults = nil; // [[self window] display]; } +- (void)updateSiteIcons:(NSImage *)siteIconImage +{ + if (siteIconImage == nil) + siteIconImage = [NSImage imageNamed:@"globe_ico"]; + [mProxyIcon setImage:siteIconImage]; +} + -(void)newTab:(BOOL)allowHomepage { CHIconTabViewItem* newTab = [[[CHIconTabViewItem alloc] initWithIdentifier: nil] autorelease]; diff --git a/chimera/CHBookmarksButton.h b/chimera/CHBookmarksButton.h index 7570360b23c9..27d5623352ef 100644 --- a/chimera/CHBookmarksButton.h +++ b/chimera/CHBookmarksButton.h @@ -25,15 +25,20 @@ #import class nsIDOMElement; +class BookmarksService; + @class BookmarkItem; -@interface CHBookmarksButton : NSButton { - - nsIDOMElement* mElement; - BookmarkItem* mBookmarkItem; - BOOL mIsFolder; +@interface CHBookmarksButton : NSButton +{ + nsIDOMElement* mElement; + BookmarkItem* mBookmarkItem; + BookmarksService* mBookmarksService; + BOOL mIsFolder; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService; + -(void)setElement: (nsIDOMElement*)aElt; -(nsIDOMElement*)element; diff --git a/chimera/CHBookmarksButton.mm b/chimera/CHBookmarksButton.mm index ec374db86769..abf785435fd6 100644 --- a/chimera/CHBookmarksButton.mm +++ b/chimera/CHBookmarksButton.mm @@ -39,9 +39,9 @@ @implementation CHBookmarksButton -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { - mElement = nsnull; [self setBezelStyle: NSRegularSquareBezelStyle]; [self setButtonType: NSMomentaryChangeButton]; [self setBordered: NO]; @@ -52,6 +52,15 @@ return self; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService +{ + if ( (self = [self initWithFrame:frame]) ) { + mBookmarksService = bookmarksService; + [self setElement:element]; + } + return self; +} + -(IBAction)openBookmark:(id)aSender { // See if we're a group. @@ -203,22 +212,24 @@ nsAutoString tag; mElement->GetLocalName(tag); + NSImage* bookmarkImage = mBookmarksService->CreateIconForBookmark(aElt); + nsAutoString group; mElement->GetAttribute(NS_LITERAL_STRING("group"), group); - + if (!group.IsEmpty()) { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"groupbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; } else if (tag.Equals(NS_LITERAL_STRING("folder"))) { - [self setImage: [NSImage imageNamed: @"folder"]]; + [self setImage: bookmarkImage]; mIsFolder = YES; } else { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"smallbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; nsAutoString href; diff --git a/chimera/CHBookmarksToolbar.h b/chimera/CHBookmarksToolbar.h index 895a1bd8005c..8f164fdaf8ac 100644 --- a/chimera/CHBookmarksToolbar.h +++ b/chimera/CHBookmarksToolbar.h @@ -28,12 +28,13 @@ class nsIDOMElement; class BookmarksService; class CHBookmarksButton; -@interface CHBookmarksToolbar : NSView { - BookmarksService* mBookmarks; - NSMutableArray* mButtons; +@interface CHBookmarksToolbar : NSView +{ + BookmarksService* mBookmarks; + NSMutableArray* mButtons; CHBookmarksButton* mDragInsertionButton; - int mDragInsertionPosition; - BOOL mIsShowing; + int mDragInsertionPosition; + BOOL mIsShowing; } -(void)initializeToolbar; diff --git a/chimera/CHBookmarksToolbar.mm b/chimera/CHBookmarksToolbar.mm index 2a1cee3573c5..c65d4d90c180 100644 --- a/chimera/CHBookmarksToolbar.mm +++ b/chimera/CHBookmarksToolbar.mm @@ -31,9 +31,14 @@ #include "nsIDOMElement.h" #include "nsIContent.h" +@interface CHBookmarksToolbar(Private) +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element; +@end + @implementation CHBookmarksToolbar -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { mBookmarks = nsnull; mButtons = [[NSMutableArray alloc] init]; @@ -95,8 +100,7 @@ while (child) { nsCOMPtr childElt(do_QueryInterface(child)); if (childElt) { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: childElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:childElt]; [self addSubview: button]; [mButtons addObject: button]; } @@ -111,8 +115,7 @@ -(void)addButton: (nsIDOMElement*)aElt atIndex: (int)aIndex { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: aElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:aElt]; [self addSubview: button]; [mButtons insertObject: button atIndex: aIndex]; if ([self isShown]) @@ -453,4 +456,9 @@ } } +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element +{ + return [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17) element:element bookmarksService:mBookmarks] autorelease]; +} + @end diff --git a/chimera/CHBrowserWrapper.h b/chimera/CHBrowserWrapper.h index 801a4d97a8a2..830af8077b31 100644 --- a/chimera/CHBrowserWrapper.h +++ b/chimera/CHBrowserWrapper.h @@ -51,6 +51,9 @@ NSTabViewItem* mTab; NSWindow* mWindow; + NSImage* mSiteIconImage; // current proxy icon image, which may be a site icon (favicon). + NSString* mSiteIconURI; // uri from which we loaded the site icon + // the secure state of this browser. We need to hold it so that we can set // the global lock icon whenever we become the primary. Value is one of // security enums in nsIWebProgressListener. @@ -60,9 +63,9 @@ NSString* mTitle; CHBrowserView* mBrowserView; - NSString* defaultStatus; - NSString* loadingStatus; - ToolTip* toolTip; + NSString* mDefaultStatusString; + NSString* mLoadingStatusString; + ToolTip* mToolTip; BOOL mIsPrimary; BOOL mIsBusy; diff --git a/chimera/CHBrowserWrapper.mm b/chimera/CHBrowserWrapper.mm index d3011dacdd26..01c9b87587a7 100644 --- a/chimera/CHBrowserWrapper.mm +++ b/chimera/CHBrowserWrapper.mm @@ -37,9 +37,12 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserWrapper.h" #import "BrowserWindowController.h" #import "BookmarksService.h" +#import "SiteIconProvider.h" +#import "CHIconTabViewItem.h" #import "ToolTip.h" #include "nsCOMPtr.h" @@ -64,8 +67,18 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; +const NSString* kOfflineNotificationName = @"offlineModeChanged"; + @interface CHBrowserWrapper(Private) - -(void) setPendingActive:(BOOL)active; + +- (void)setPendingActive:(BOOL)active; +- (void)registerNotificationListener; + +- (void)setSiteIconImage:(NSImage*)inSiteIcon; +- (void)setSiteIconURI:(NSString*)inSiteIconURI; + +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI; + @end @implementation CHBrowserWrapper @@ -85,10 +98,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; #endif [[NSNotificationCenter defaultCenter] removeObserver: self]; - - [defaultStatus release]; - [loadingStatus release]; - [toolTip release]; + + [mSiteIconImage release]; + [mSiteIconURI release]; + [mDefaultStatusString release]; + [mLoadingStatusString release]; + [mToolTip release]; [super dealloc]; } @@ -105,7 +120,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; progress = nil; progressSuper = nil; mIsPrimary = NO; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kOfflineNotificationName object:nil]; [mBrowserView setActive: NO]; } @@ -133,7 +148,13 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mIsBusy = NO; mListenersAttached = NO; mSecureState = nsIWebProgressListener::STATE_IS_INSECURE; - toolTip = [[ToolTip alloc] init]; + + mToolTip = [[ToolTip alloc] init]; + + //[self setSiteIconImage:[NSImage imageNamed:@"globe_ico"]]; + //[self setSiteIconURI: [NSString string]]; + + [self registerNotificationListener]; } return self; } @@ -155,9 +176,9 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (!mIsBusy) [progress removeFromSuperview]; - defaultStatus = NULL; - loadingStatus = DOCUMENT_DONE_STRING; - [status setStringValue:loadingStatus]; + mDefaultStatusString = NULL; + mLoadingStatusString = DOCUMENT_DONE_STRING; + [status setStringValue:mLoadingStatusString]; mIsPrimary = YES; @@ -181,11 +202,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (mWindowController) // Only register if we're the content area. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlineModeChanged:) - name:@"offlineModeChanged" + name:kOfflineNotificationName object:nil]; // Update the URL bar. [mWindowController updateLocationFields:[self getCurrentURLSpec]]; + [mWindowController updateSiteIcons:mSiteIconImage]; if (mWindowController && !mListenersAttached) { mListenersAttached = YES; @@ -209,7 +231,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; return [mBrowserView getCurrentURLSpec]; } -- (void)awakeFromNib +- (void)awakeFromNib { } @@ -239,17 +261,17 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLoadingStarted { - if (defaultStatus) { - [defaultStatus release]; - defaultStatus = NULL; + if (mDefaultStatusString) { + [mDefaultStatusString release]; + mDefaultStatusString = NULL; } [progressSuper addSubview:progress]; [progress setIndeterminate:YES]; [progress startAnimation:self]; - loadingStatus = NSLocalizedString(@"TabLoading", @""); - [status setStringValue:loadingStatus]; + mLoadingStatusString = NSLocalizedString(@"TabLoading", @""); + [status setStringValue:mLoadingStatusString]; mIsBusy = YES; [mTab setLabel: NSLocalizedString(@"TabLoading", @"")]; @@ -271,12 +293,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [progress stopAnimation:self]; [progress removeFromSuperview]; - loadingStatus = DOCUMENT_DONE_STRING; - if (defaultStatus) { - [status setStringValue:defaultStatus]; + mLoadingStatusString = DOCUMENT_DONE_STRING; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } mIsBusy = NO; @@ -316,8 +338,32 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLocationChange:(NSString*)urlSpec { + BOOL useSiteIcons = [[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" 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 withUserData:nil allowNetwork:YES]; + } + else + { + if ([urlSpec isEqualToString:@"about:blank"]) + faviconURI = urlSpec; + else + faviconURI = @""; + } + + if (!siteIconLoadInitiated) + [self updateSiteIconImage:nil withURI:faviconURI]; + if (mIsPrimary) - [mWindowController updateLocationFields:urlSpec]; + [mWindowController updateLocationFields:urlSpec]; } - (void)onStatusChange:(NSString*)aStatusString @@ -342,20 +388,20 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)setStatus:(NSString *)statusString ofType:(NSStatusType)type { if (type == NSStatusTypeScriptDefault) { - if (defaultStatus) { - [defaultStatus release]; + if (mDefaultStatusString) { + [mDefaultStatusString release]; } - defaultStatus = statusString; - if (defaultStatus) { - [defaultStatus retain]; + mDefaultStatusString = statusString; + if (mDefaultStatusString) { + [mDefaultStatusString retain]; } } else if (!statusString) { - if (defaultStatus) { - [status setStringValue:defaultStatus]; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } } else { @@ -418,12 +464,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onShowTooltip:(NSPoint)where withText:(NSString*)text { NSPoint point = [[self window] convertBaseToScreen:[self convertPoint: where toView:nil]]; - [toolTip showToolTipAtPoint: point withString: text]; + [mToolTip showToolTipAtPoint: point withString: text]; } - (void)onHideTooltip { - [toolTip closeToolTip]; + [mToolTip closeToolTip]; } // Called when a context menu should be shown. @@ -547,4 +593,77 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mActivateOnLoad = active; } +- (void)setSiteIconImage:(NSImage*)inSiteIcon +{ + [mSiteIconImage autorelease]; + mSiteIconImage = [inSiteIcon retain]; +} + +- (void)setSiteIconURI:(NSString*)inSiteIconURI +{ + [mSiteIconURI autorelease]; + mSiteIconURI = [inSiteIconURI retain]; +} + +// A nil inSiteIcon image indicates that we should use the default icon +// If inSiteIconURI is "about:blank", we don't show any icon +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI +{ + BOOL resetTabIcon = NO; + + if (![mSiteIconURI isEqualToString:inSiteIconURI]) + { + if (!inSiteIcon) + { + if (![inSiteIconURI isEqualToString:@"about:blank"]) + inSiteIcon = [NSImage imageNamed:@"globe_ico"]; + } + + [self setSiteIconImage: inSiteIcon]; + [self setSiteIconURI: inSiteIconURI]; + + // update the proxy icon + if (mIsPrimary) + [mWindowController updateSiteIcons:mSiteIconImage]; + + resetTabIcon = YES; + } + + // update the tab icon + if ([mTab isMemberOfClass:[CHIconTabViewItem class]]) + { + CHIconTabViewItem* tabItem = (CHIconTabViewItem*)mTab; + if (resetTabIcon || ![tabItem tabIcon]) + [tabItem setTabIcon:mSiteIconImage]; + } + +} + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// called when [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] completes +- (void)imageLoadedNotification:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + NSString* siteIconURI = [userInfo objectForKey:SiteIconLoadURIKey]; + + // NSLog(@"CHBrowserWrapper imageLoadedNotification got image %@ and uri %@", iconImage, proxyImageURI); + if (iconImage == nil) + siteIconURI = @""; // go back to default image + + [self updateSiteIconImage:iconImage withURI:siteIconURI]; + } +} + + @end diff --git a/chimera/CHIconTabViewItem.h b/chimera/CHIconTabViewItem.h index 13a37120dc8e..b1ee2fc0e26b 100644 --- a/chimera/CHIconTabViewItem.h +++ b/chimera/CHIconTabViewItem.h @@ -40,7 +40,8 @@ #import -@interface CHIconTabViewItem : NSTabViewItem { +@interface CHIconTabViewItem : NSTabViewItem +{ NSImage *mTabIcon; NSDictionary* mLabelAttributes; } diff --git a/chimera/CHIconTabViewItem.mm b/chimera/CHIconTabViewItem.mm index cf659548c4c0..a7a19c588bb1 100644 --- a/chimera/CHIconTabViewItem.mm +++ b/chimera/CHIconTabViewItem.mm @@ -39,8 +39,9 @@ * * ***** END LICENSE BLOCK ***** */ -#import "CHIconTabViewItem.h" +#import "NSString+Utils.h" +#import "CHIconTabViewItem.h" // // NSParagraphStyle has a line break mode which will automatically @@ -91,7 +92,7 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s [labelParagraphStyle setLineBreakMode:NSLineBreakByTruncatingMiddle]; #endif - [labelParagraphStyle setAlignment:NSCenterTextAlignment]; + [labelParagraphStyle setAlignment:NSNaturalTextAlignment]; NSFont *labelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; mLabelAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -139,9 +140,9 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s if ([self tabIcon]) { NSPoint drawPoint = NSMakePoint( (tabRect.origin.x), (tabRect.origin.y + 15.0) ); [[self tabIcon] compositeToPoint:drawPoint operation:NSCompositeSourceOver]; - tabRect = NSMakeRect(NSMinX(tabRect)+15.0, + tabRect = NSMakeRect(NSMinX(tabRect) + 18.0, NSMinY(tabRect), - NSWidth(tabRect)-15.0, + NSWidth(tabRect) - 18.0, NSHeight(tabRect)); } diff --git a/chimera/CHPageProxyIcon.h b/chimera/CHPageProxyIcon.h index 9dfc66212c3f..4e8b1e17ce8e 100644 --- a/chimera/CHPageProxyIcon.h +++ b/chimera/CHPageProxyIcon.h @@ -25,7 +25,7 @@ @interface CHPageProxyIcon : NSImageView { - } + @end diff --git a/chimera/CHPageProxyIcon.mm b/chimera/CHPageProxyIcon.mm index 80544d58280b..51ab9d59f31b 100644 --- a/chimera/CHPageProxyIcon.mm +++ b/chimera/CHPageProxyIcon.mm @@ -24,13 +24,25 @@ #import "NSString+Utils.h" #import "CHPageProxyIcon.h" + #import "BookmarksService.h" #import "MainController.h" #include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsString.h" @implementation CHPageProxyIcon +- (void)awakeFromNib +{ +} + +- (void)dealloc +{ + [super dealloc]; +} + - (void) resetCursorRects { NSCursor* cursor; @@ -77,4 +89,5 @@ event: event pasteboard: pboard source: self slideBack: YES]; } + @end diff --git a/chimera/CHPreferenceManager.h b/chimera/CHPreferenceManager.h index 3063fc19b835..a682a4c80e4c 100644 --- a/chimera/CHPreferenceManager.h +++ b/chimera/CHPreferenceManager.h @@ -35,12 +35,16 @@ * * ***** END LICENSE BLOCK ***** */ -#import +#import #import -@interface CHPreferenceManager : NSObject { +class nsIPref; + +@interface CHPreferenceManager : NSObject +{ NSUserDefaults* mDefaults; ICInstance mInternetConfig; + nsIPref* mPrefs; } + (CHPreferenceManager *)sharedInstance; @@ -54,4 +58,9 @@ - (NSString *) getICStringPref:(ConstStr255Param) prefKey; - (NSString *) homePage:(BOOL) checkStartupPagePref; +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; + @end diff --git a/chimera/CHPreferenceManager.mm b/chimera/CHPreferenceManager.mm index 51228e415557..ef8e2cd0eaa4 100644 --- a/chimera/CHPreferenceManager.mm +++ b/chimera/CHPreferenceManager.mm @@ -86,8 +86,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (void) dealloc { + ::ICStop(mInternetConfig); + NS_IF_RELEASE(mPrefs); + nsresult rv; - ICStop (mInternetConfig); nsCOMPtr pref(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { //NSLog(@"Saving prefs file"); @@ -100,7 +102,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (BOOL) initInternetConfig { OSStatus error; - error = ICStart (&mInternetConfig, 'CHIM'); + error = ::ICStart(&mInternetConfig, 'CHIM'); if (error != noErr) { // XXX throw here? NSLog(@"Error initializing IC"); @@ -178,6 +180,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); return NO; } + nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); + mPrefs = prefs; + NS_IF_ADDREF(mPrefs); + [self syncMozillaPrefs]; return YES; } @@ -191,9 +197,8 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); char strbuf[1024]; int numbuf; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) { - // XXXw. throw? + if (!mPrefs) { + NSLog(@"Mozilla prefs not set up successfully"); return; } @@ -201,42 +206,42 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // something that chimera can deal with. PRInt32 acceptCookies = 0; static const char* kCookieBehaviorPref = "network.cookie.cookieBehavior"; - prefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); + mPrefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); if ( acceptCookies == 1 ) { // accept foreign cookies, assume off acceptCookies = 2; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } else if ( acceptCookies == 3 ) { // p3p, assume all cookies on acceptCookies = 0; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } // get proxies from SystemConfiguration - prefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies - prefs->ClearUserPref("network.proxy.http"); - prefs->ClearUserPref("network.proxy.http_port"); - prefs->ClearUserPref("network.proxy.ssl"); - prefs->ClearUserPref("network.proxy.ssl_port"); - prefs->ClearUserPref("network.proxy.ftp"); - prefs->ClearUserPref("network.proxy.ftp_port"); - prefs->ClearUserPref("network.proxy.gopher"); - prefs->ClearUserPref("network.proxy.gopher_port"); - prefs->ClearUserPref("network.proxy.socks"); - prefs->ClearUserPref("network.proxy.socks_port"); - prefs->ClearUserPref("network.proxy.no_proxies_on"); + mPrefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies + mPrefs->ClearUserPref("network.proxy.http"); + mPrefs->ClearUserPref("network.proxy.http_port"); + mPrefs->ClearUserPref("network.proxy.ssl"); + mPrefs->ClearUserPref("network.proxy.ssl_port"); + mPrefs->ClearUserPref("network.proxy.ftp"); + mPrefs->ClearUserPref("network.proxy.ftp_port"); + mPrefs->ClearUserPref("network.proxy.gopher"); + mPrefs->ClearUserPref("network.proxy.gopher_port"); + mPrefs->ClearUserPref("network.proxy.socks"); + mPrefs->ClearUserPref("network.proxy.socks_port"); + mPrefs->ClearUserPref("network.proxy.no_proxies_on"); if ((cfDictionary = SCDynamicStoreCopyProxies (NULL)) != NULL) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPEnable, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.http", strbuf); + mPrefs->SetCharPref("network.proxy.http", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.http_port", numbuf); + mPrefs->SetIntPref("network.proxy.http_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -245,13 +250,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ssl", strbuf); + mPrefs->SetCharPref("network.proxy.ssl", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ssl_port", numbuf); + mPrefs->SetIntPref("network.proxy.ssl_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -260,13 +265,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ftp", strbuf); + mPrefs->SetCharPref("network.proxy.ftp", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ftp_port", numbuf); + mPrefs->SetIntPref("network.proxy.ftp_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -275,13 +280,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.gopher", strbuf); + mPrefs->SetCharPref("network.proxy.gopher", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.gopher_port", numbuf); + mPrefs->SetIntPref("network.proxy.gopher_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -290,13 +295,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.socks", strbuf); + mPrefs->SetCharPref("network.proxy.socks", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.socks_port", numbuf); + mPrefs->SetIntPref("network.proxy.socks_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -305,7 +310,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); cfString = CFStringCreateByCombiningStrings (NULL, cfArray, CFSTR(", ")); if (CFStringGetLength (cfString) > 0) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.no_proxies_on", strbuf); + mPrefs->SetCharPref("network.proxy.no_proxies_on", strbuf); } } } @@ -313,24 +318,73 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); } } -// convenience routines for mozilla prefs -- (NSString*)getMozillaPrefString: (const char*)prefName +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess { - NSMutableString *prefValue = [[[NSMutableString alloc] init] autorelease]; + NSString *prefValue = @""; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (prefs) { - char *buf = nsnull; - nsresult rv = prefs->GetCharPref(prefName, &buf); - if (NS_SUCCEEDED(rv) && buf) { - [prefValue setString:[NSString stringWithCString:buf]]; - free(buf); - } + char *buf = nsnull; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetCharPref(prefName, &buf); + + if (NS_SUCCEEDED(rv) && buf) { + // prefs are UTF-8 + prefValue = [NSString stringWithUTF8String:buf]; + free(buf); + if (outSuccess) *outSuccess = YES; + } else { + if (outSuccess) *outSuccess = NO; } return prefValue; } +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + // colors are stored in HTML-like #FFFFFF strings + NSString* colorString = [self getStringPref:prefName withSuccess:outSuccess]; + NSColor* returnColor = [NSColor blackColor]; + + if ([colorString hasPrefix:@"#"] && [colorString length] == 7) + { + unsigned int redInt, greenInt, blueInt; + sscanf([colorString cString], "#%02x%02x%02x", &redInt, &greenInt, &blueInt); + + float redFloat = ((float)redInt / 255.0); + float greenFloat = ((float)greenInt / 255.0); + float blueFloat = ((float)blueInt / 255.0); + + returnColor = [NSColor colorWithCalibratedRed:redFloat green:greenFloat blue:blueFloat alpha:1.0f]; + } + + return returnColor; +} + +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRBool boolPref = PR_FALSE; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + rv = mPrefs->GetBoolPref(prefName, &boolPref); + + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + + return boolPref ? YES : NO; +} + +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRInt32 intPref = 0; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetIntPref(prefName, &intPref); + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + return intPref; +} + + //- (BOOL) getICBoolPref:(ConstStr255Param) prefKey; //{ @@ -381,8 +435,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (NSString *) homePage:(BOOL)checkStartupPagePref { - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) + if (!mPrefs) return @"about:blank"; PRInt32 mode = 1; @@ -394,14 +447,14 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // is true. nsresult rv = NS_OK; if ( checkStartupPagePref ) - rv = prefs->GetIntPref("browser.startup.page", &mode); + rv = mPrefs->GetIntPref("browser.startup.page", &mode); if (NS_FAILED(rv) || mode == 1) { // see which home page to use PRBool boolPref; - if (NS_SUCCEEDED(prefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) + if (NS_SUCCEEDED(mPrefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) return [self getICStringPref:kICWWWHomePage]; - nsCOMPtr prefBranch = do_QueryInterface(prefs); + nsCOMPtr prefBranch = do_QueryInterface(mPrefs); if (!prefBranch) return @"about:blank"; NSString* homepagePref = nil; @@ -411,10 +464,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); homepagePref = NSLocalizedStringFromTable( @"HomePageDefault", @"WebsiteDefaults", nil); // and let's copy this into the homepage pref if it's not bad if (![homepagePref isEqualToString:@"HomePageDefault"]) - prefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); + mPrefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); } else { - homepagePref = [self getMozillaPrefString:"browser.startup.homepage"]; + homepagePref = [self getStringPref:"browser.startup.homepage" withSuccess:NULL]; } if (homepagePref && [homepagePref length] > 0 && ![homepagePref isEqualToString:@"HomePageDefault"]) diff --git a/chimera/MainController.h b/chimera/MainController.h index ccbf1a099f70..f06bf2127a5a 100644 --- a/chimera/MainController.h +++ b/chimera/MainController.h @@ -75,7 +75,7 @@ class BookmarksService; FindDlgController* mFindDialog; - MVPreferencesController* preferencesController; + MVPreferencesController* mPreferencesController; NSString* mStartURL; } diff --git a/chimera/MainController.mm b/chimera/MainController.mm index 2206223df253..c603e099d260 100644 --- a/chimera/MainController.mm +++ b/chimera/MainController.mm @@ -132,7 +132,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [mBookmarksMenu setAutoenablesItems: NO]; mMenuBookmarks = new BookmarksService((BookmarksDataSource*)nil); mMenuBookmarks->AddObserver(); - mMenuBookmarks->ConstructBookmarksMenu(mBookmarksMenu, nsnull); + BookmarksService::ConstructBookmarksMenu(mBookmarksMenu, nsnull); BookmarksService::gMainController = self; // Initialize offline mode. @@ -152,6 +152,29 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; */ } +-(void)applicationWillTerminate: (NSNotification*)aNotification +{ +#if DEBUG + NSLog(@"Termination notification"); +#endif + + // Autosave one of the windows. + [[[mApplication mainWindow] windowController] autosaveWindowFrame]; + + mMenuBookmarks->RemoveObserver(); + delete mMenuBookmarks; + mMenuBookmarks = nsnull; + + // Release before calling TermEmbedding since we need to access XPCOM + // to save preferences + [mPreferencesController release]; + [mPreferenceManager release]; + + nsCocoaBrowserService::TermEmbedding(); + + [self autorelease]; +} + -(IBAction)newWindow:(id)aSender { // If we have a key window, have it autosave its dimensions before @@ -455,29 +478,6 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [[[controller getBrowserWrapper] getBrowserView] setActive: YES]; } - --(void)applicationWillTerminate: (NSNotification*)aNotification -{ -#if DEBUG - NSLog(@"Termination notification"); -#endif - - // Autosave one of the windows. - [[[mApplication mainWindow] windowController] autosaveWindowFrame]; - - mMenuBookmarks->RemoveObserver(); - delete mMenuBookmarks; - mMenuBookmarks = nsnull; - - // Release before calling TermEmbedding since we need to access XPCOM - // to save preferences - [mPreferenceManager release]; - - nsCocoaBrowserService::TermEmbedding(); - - [self autorelease]; -} - // Bookmarks menu actions. -(IBAction) importBookmarks:(id)aSender { @@ -558,10 +558,10 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (MVPreferencesController *)preferencesController { - if (!preferencesController) { - preferencesController = [[MVPreferencesController sharedInstance] retain]; + if (!mPreferencesController) { + mPreferencesController = [[MVPreferencesController sharedInstance] retain]; } - return preferencesController; + return mPreferencesController; } - (void)displayPreferencesWindow:sender diff --git a/chimera/RemoteDataProvider.h b/chimera/RemoteDataProvider.h new file mode 100644 index 000000000000..13ff9a3af4ae --- /dev/null +++ b/chimera/RemoteDataProvider.h @@ -0,0 +1,90 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + + +extern NSString* RemoteDataLoadRequestNotificationName; +extern NSString* RemoteDataLoadRequestURIKey; +extern NSString* RemoteDataLoadRequestDataKey; +extern NSString* RemoteDataLoadRequestUserDataKey; +extern NSString* 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 +// callback in NSData. +// +// Clients can either implement the RemoteLoadListener protocol and call +// loadURI directly, or they can register with the [NSNotification defaultCenter] +// for 'RemoteDataLoadRequestNotificationName' notifications, and catch all loads +// that happen that way. + +@protocol RemoteLoadListener +// called when the load completes, or fails. If the status code is a failure code, +// data may be nil. +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status; +@end + + +class RemoteURILoadManager; + +@interface RemoteDataProvider : NSObject +{ + RemoteURILoadManager* mLoadManager; +} + ++ (RemoteDataProvider*)sharedRemoteDataProvider; + +// generic method. You can load any URI asynchronously with this selector, +// and the listener will get the contents of the URI in an NSData. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +// specific request to load a remote file. The sender (or any other object), if +// registered with the notification center, will receive a notification when +// the load completes. The 'target' becomes the 'object' of the notification. +// The notification name is given by NSString* RemoteDataLoadRequestNotificationName above. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +@end diff --git a/chimera/RemoteDataProvider.mm b/chimera/RemoteDataProvider.mm new file mode 100644 index 000000000000..b869d6739170 --- /dev/null +++ b/chimera/RemoteDataProvider.mm @@ -0,0 +1,298 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "RemoteDataProvider.h" + +#include "nsISupports.h" +#include "nsHashtable.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.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"; + + +// this has to retain the load listener, to ensure that the listener lives long +// enough to receive notifications. We have to be careful to avoid ref cycles. +class StreamLoaderContext : public nsISupports +{ +public: + StreamLoaderContext(id inLoadListener, id inUserData, id inTarget, const nsAString& inURI) + : mLoadListener(inLoadListener) + , mTarget(inTarget) + , mUserData(inUserData) + , mURI(inURI) + { + NS_INIT_ISUPPORTS(); + [mLoadListener retain]; + } + + virtual ~StreamLoaderContext() + { + [mLoadListener release]; + } + + NS_DECL_ISUPPORTS + + void LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength); + const nsAString& GetURI() { return mURI; } + +protected: + + id mLoadListener; // retained + id mTarget; // not retained + id mUserData; // not retained + nsString mURI; + +}; + + +NS_IMPL_ISUPPORTS1(StreamLoaderContext, nsISupports) + +void StreamLoaderContext::LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength) +{ + if (mLoadListener) + { + NSData* loadData = nil; + if (NS_SUCCEEDED(inLoadStatus)) + loadData = [NSData dataWithBytes:inData length:inDataLength]; + + [mLoadListener doneRemoteLoad:[NSString stringWith_nsAString:mURI] forTarget:mTarget withUserData:mUserData data:loadData status:inLoadStatus]; + } +} + + +class RemoteURILoadManager : public nsIStreamLoaderObserver +{ +public: + + RemoteURILoadManager(); + virtual ~RemoteURILoadManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLOADEROBSERVER + + nsresult Init(); + nsresult RequestURILoad(const nsAString& inURI, id loadListener, id userData, id target, PRBool allowNetworking); + +protected: + + nsSupportsHashtable mStreamLoaderHash; // hash of active stream loads, keyed on URI + nsCOMPtr mCacheSession; + +}; + +RemoteURILoadManager::RemoteURILoadManager() +{ + NS_INIT_ISUPPORTS(); +} + +RemoteURILoadManager::~RemoteURILoadManager() +{ +} + +NS_IMPL_ISUPPORTS1(RemoteURILoadManager, nsIStreamLoaderObserver) + +NS_IMETHODIMP RemoteURILoadManager::OnStreamComplete(nsIStreamLoader *loader, nsISupports *ctxt, nsresult status, PRUint32 resultLength, const char *result) +{ + StreamLoaderContext* loaderContext = NS_STATIC_CAST(StreamLoaderContext*, ctxt); + if (loaderContext) + { + loaderContext->LoadComplete(status, (const void*)result, resultLength); + + // remove the stream loader from the hash table + nsStringKey uriKey(loaderContext->GetURI()); + PRBool removed = mStreamLoaderHash.Remove(&uriKey); + } + + return NS_OK; +} + +static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID); + +nsresult RemoteURILoadManager::Init() +{ + nsresult rv; + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession("HTTP", nsICache::STORE_ANYWHERE, + nsICache::STREAM_BASED, getter_AddRefs(mCacheSession)); + + return rv; +} + +nsresult RemoteURILoadManager::RequestURILoad(const nsAString& inURI, id loadListener, + id userData, id target, PRBool allowNetworking) +{ + nsresult rv; + +#if 0 + // if no networking is allowed, make sure it's in the cache + if (!allowNetworking) + { + if (!mCacheSession) + return NS_ERROR_FAILURE; + + nsCOMPtr entryDesc; + rv = mCacheSession->OpenCacheEntry(NS_ConvertUCS2toUTF8(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + if (NS_FAILED(rv) || !entryDesc) + return NS_ERROR_FAILURE; + } +#endif + + nsStringKey uriKey(inURI); + + // first make sure that there isn't another entry in the hash for this + nsCOMPtr foundStreamSupports = mStreamLoaderHash.Get(&uriKey); + if (foundStreamSupports) + return NS_OK; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), inURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr loaderContext = new StreamLoaderContext(loadListener, userData, target, inURI); + + nsLoadFlags loadFlags = (allowNetworking) ? nsIRequest::LOAD_NORMAL : nsIRequest::LOAD_FROM_CACHE; + nsCOMPtr streamLoader; + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), uri, this, loaderContext, nsnull, nsnull, loadFlags); + if (NS_FAILED(rv)) + { + NSLog(@"NS_NewStreamLoader for favicon failed"); + return rv; + } + +#ifdef DEBUG_smfr + NSLog(@"RequestURILoad called for %@", [NSString stringWith_nsAString: inURI]); +#endif + + // put the stream loader into the hash table + nsCOMPtr streamLoaderAsSupports = do_QueryInterface(streamLoader); + mStreamLoaderHash.Put(&uriKey, streamLoaderAsSupports); + + return NS_OK; +} + + +#pragma mark - + + +@implementation RemoteDataProvider + + ++ (RemoteDataProvider*)sharedRemoteDataProvider +{ + static RemoteDataProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[RemoteDataProvider alloc] init]; + + // we probably need to register for NSApplicationWillTerminateNotification notifications + // and delete this then. + } + + return sIconProvider; +} + +- (id)init +{ + if ((self = [super init])) + { + mLoadManager = new RemoteURILoadManager; + NS_ADDREF(mLoadManager); + } + + return self; +} + +- (void)dealloc +{ + NS_IF_RELEASE(mLoadManager); + [super dealloc]; +} + +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + //NSLog(@"loadURI called with %@", inURI); + if (mLoadManager && [inURI length] > 0) + { + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsresult rv = mLoadManager->RequestURILoad(uriString, inListener, userData, target, (PRBool)inNetworkOK); + if (NS_FAILED(rv)) + { + NSLog(@"RequestURILoad failed for @%", inURI); + return NO; + } + } + + return YES; +} + +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + return [self loadURI:inURI forTarget:target withListener:self withUserData:userData allowNetworking:inNetworkOK]; +} + +// our own load listener callback +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, RemoteDataLoadRequestURIKey, + data, RemoteDataLoadRequestDataKey, + userData, RemoteDataLoadRequestUserDataKey, + [NSNumber numberWithInt:status], RemoteDataLoadRequestResultKey, + nil]; + + NSLog(@"remoteLoadDone with status %d and length %d", status, [data length]); + [[NSNotificationCenter defaultCenter] postNotificationName: RemoteDataLoadRequestNotificationName + object:target userInfo:notificationData]; +} + +@end diff --git a/chimera/SiteIconProvider.h b/chimera/SiteIconProvider.h new file mode 100644 index 000000000000..9cb0209c4558 --- /dev/null +++ b/chimera/SiteIconProvider.h @@ -0,0 +1,69 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +#import "RemoteDataProvider.h" + +extern NSString* SiteIconLoadNotificationName; +extern NSString* SiteIconLoadImageKey; +extern NSString* SiteIconLoadURIKey; +extern NSString* SiteIconLoadUserDataKey; + +class NeckoCacheHelper; + +@interface SiteIconProvider : NSObject +{ + NeckoCacheHelper* mMissedIconsCacheHelper; +} + ++ (SiteIconProvider*)sharedFavoriteIconProvider; + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI; + +// Start a favicon.ico load for the given URI, which can be any URI. +// The caller will get a 'SiteIconLoadNotificationName' notification +// when the load is done, with the image at the 'SiteIconLoadImageKey' key +// in the notifcation userInfo. The caller will have had to register with the +// NSNotifcationCenter in order to receive this notifcation. The notification +// is dispatched with 'sender' as the object. +// This method returns YES if the uri request was dispatched (i.e. if we know +// that we've looked for, and failed to find, this icon before). If it returns +// YES, then the 'SiteIconLoadNotificationName' notification will be sent out. +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData allowNetwork:(BOOL)inAllowNetwork; + +@end diff --git a/chimera/SiteIconProvider.mm b/chimera/SiteIconProvider.mm new file mode 100644 index 000000000000..b5f5d93705d8 --- /dev/null +++ b/chimera/SiteIconProvider.mm @@ -0,0 +1,330 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "SiteIconProvider.h" + +#include "prtime.h" +#include "nsString.h" +#include "nsISupports.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.h" + + +NSString* SiteIconLoadNotificationName = @"siteicon_load_notification"; +NSString* SiteIconLoadImageKey = @"siteicon_load_image"; +NSString* SiteIconLoadURIKey = @"siteicon_load_uri"; +NSString* SiteIconLoadUserDataKey = @"siteicon_load_user_data"; + + +static inline PRUint32 PRTimeToSeconds(PRTime t_usec) +{ + PRTime usec_per_sec; + PRUint32 t_sec; + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_DIV(t_usec, t_usec, usec_per_sec); + LL_L2I(t_sec, t_usec); + return t_sec; +} + +class NeckoCacheHelper +{ +public: + + NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue); + ~NeckoCacheHelper() {} + + nsresult Init(const char* inCacheSessionName); + nsresult ExistsInCache(const nsACString& inURI, PRBool* outExists); + nsresult PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds); + + nsresult ClearCache(); + +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) +{ +} + +nsresult NeckoCacheHelper::Init(const char* inCacheSessionName) +{ + nsresult rv; + + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession(inCacheSessionName, + nsICache::STORE_ANYWHERE, nsICache::STREAM_BASED, + getter_AddRefs(mCacheSession)); + if (NS_FAILED(rv)) + return rv; + + return NS_OK; +} + + +nsresult NeckoCacheHelper::ExistsInCache(const nsACString& inURI, PRBool* outExists) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + + *outExists = NS_SUCCEEDED(rv) && (entryDesc != NULL); + return NS_OK; +} + +nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), 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(mMetaElement, mMetaValue); // just set a bit of meta data. + entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds); + + entryDesc->MarkValid(); + entryDesc->Close(); + + return NS_OK; +} + +nsresult NeckoCacheHelper::ClearCache() +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + return mCacheSession->EvictEntries(); +} + + +#pragma mark - + +static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) +{ + outFaviconURI.Truncate(0); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), inURIString); + if (NS_FAILED(rv)) + return rv; + + // check for http/https + PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; + uri->SchemeIs("http", &isHTTP); + uri->SchemeIs("https", &isHTTPS); + if (!isHTTP && !isHTTPS) + return NS_OK; + + PRInt32 port; + uri->GetPort(&port); + + nsXPIDLCString scheme; + uri->GetScheme(scheme); + + nsXPIDLCString host; + uri->GetHost(host); + + nsCAutoString faviconURI = scheme; + faviconURI.Append("://"); + faviconURI.Append(host); + if (port != -1) { + faviconURI.Append(':'); + faviconURI.AppendInt(port); + } + faviconURI.Append("/favicon.ico"); + + outFaviconURI.Assign(NS_ConvertUTF8toUCS2(faviconURI)); + return NS_OK; +} + + +@interface SiteIconProvider(Private) + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds; +- (BOOL)inMissedIconsCache:(const nsAString&)inURI; + +@end + + +@implementation SiteIconProvider + +- (id)init +{ + if ((self = [super init])) + { + mMissedIconsCacheHelper = new NeckoCacheHelper("Favicon", "Missed"); + nsresult rv = mMissedIconsCacheHelper->Init("MissedIconsCache"); + if (NS_FAILED(rv)) { + delete mMissedIconsCacheHelper; + mMissedIconsCacheHelper = NULL; + } + } + + return self; +} + +- (void)dealloc +{ + delete mMissedIconsCacheHelper; + [super dealloc]; +} + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds +{ + if (mMissedIconsCacheHelper) + { + nsresult rv = mMissedIconsCacheHelper->PutInCache(NS_ConvertUCS2toUTF8(inURI), inExpSeconds); + //NSLog(@"Putting %@ in missed icon cache", [NSString stringWith_nsAString:inURI]); + } + +} + +- (BOOL)inMissedIconsCache:(const nsAString&)inURI +{ + PRBool inCache = PR_FALSE; + + if (mMissedIconsCacheHelper) + mMissedIconsCacheHelper->ExistsInCache(NS_ConvertUCS2toUTF8(inURI), &inCache); + + //NSLog(@"%@ in missed icon cache: %d", [NSString stringWith_nsAString:inURI], inCache); + return inCache; +} + + +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData 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; + + RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider]; + return [dataProvider loadURI:faviconString forTarget:sender withListener:self withUserData:userData allowNetworking:inAllowNetwork]; +} + +#define SITE_ICON_EXPIRATION_SECONDS (60 * 60 * 24 * 7) // 1 week + +// this is called on the main thread +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + BOOL loadOK = NS_SUCCEEDED(status) && (data != nil); + // it's hard to tell if the favicon load succeeded or not. Even if the file + // does not exist, servers will send back a 404 page with a 0 status. + // So we just go ahead and try to make the image; it will return nil on + // failure. + NSImage* faviconImage = [[NSImage alloc] initWithData:data]; + BOOL gotImageData = loadOK && (faviconImage != nil); + if (!gotImageData) + [self addToMissedIconsCache:uriString withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS]; + + [faviconImage setScalesWhenResized:YES]; + [faviconImage setSize:NSMakeSize(16, 16)]; + + // we always send out the notification, so that clients know + // about failed requests + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, SiteIconLoadURIKey, + faviconImage, SiteIconLoadImageKey, // may be nil + userData, SiteIconLoadUserDataKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName: SiteIconLoadNotificationName + object:target userInfo:notificationData]; +} + +#pragma mark - + ++ (SiteIconProvider*)sharedFavoriteIconProvider +{ + static SiteIconProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[SiteIconProvider alloc] init]; + } + + return sIconProvider; +} + + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsAutoString faviconURIString; + MakeFaviconURIFromURI(uriString, faviconURIString); + return [NSString stringWith_nsAString:faviconURIString]; +} + +@end diff --git a/chimera/src/application/MainController.h b/chimera/src/application/MainController.h index ccbf1a099f70..f06bf2127a5a 100644 --- a/chimera/src/application/MainController.h +++ b/chimera/src/application/MainController.h @@ -75,7 +75,7 @@ class BookmarksService; FindDlgController* mFindDialog; - MVPreferencesController* preferencesController; + MVPreferencesController* mPreferencesController; NSString* mStartURL; } diff --git a/chimera/src/application/MainController.mm b/chimera/src/application/MainController.mm index 2206223df253..c603e099d260 100644 --- a/chimera/src/application/MainController.mm +++ b/chimera/src/application/MainController.mm @@ -132,7 +132,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [mBookmarksMenu setAutoenablesItems: NO]; mMenuBookmarks = new BookmarksService((BookmarksDataSource*)nil); mMenuBookmarks->AddObserver(); - mMenuBookmarks->ConstructBookmarksMenu(mBookmarksMenu, nsnull); + BookmarksService::ConstructBookmarksMenu(mBookmarksMenu, nsnull); BookmarksService::gMainController = self; // Initialize offline mode. @@ -152,6 +152,29 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; */ } +-(void)applicationWillTerminate: (NSNotification*)aNotification +{ +#if DEBUG + NSLog(@"Termination notification"); +#endif + + // Autosave one of the windows. + [[[mApplication mainWindow] windowController] autosaveWindowFrame]; + + mMenuBookmarks->RemoveObserver(); + delete mMenuBookmarks; + mMenuBookmarks = nsnull; + + // Release before calling TermEmbedding since we need to access XPCOM + // to save preferences + [mPreferencesController release]; + [mPreferenceManager release]; + + nsCocoaBrowserService::TermEmbedding(); + + [self autorelease]; +} + -(IBAction)newWindow:(id)aSender { // If we have a key window, have it autosave its dimensions before @@ -455,29 +478,6 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [[[controller getBrowserWrapper] getBrowserView] setActive: YES]; } - --(void)applicationWillTerminate: (NSNotification*)aNotification -{ -#if DEBUG - NSLog(@"Termination notification"); -#endif - - // Autosave one of the windows. - [[[mApplication mainWindow] windowController] autosaveWindowFrame]; - - mMenuBookmarks->RemoveObserver(); - delete mMenuBookmarks; - mMenuBookmarks = nsnull; - - // Release before calling TermEmbedding since we need to access XPCOM - // to save preferences - [mPreferenceManager release]; - - nsCocoaBrowserService::TermEmbedding(); - - [self autorelease]; -} - // Bookmarks menu actions. -(IBAction) importBookmarks:(id)aSender { @@ -558,10 +558,10 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (MVPreferencesController *)preferencesController { - if (!preferencesController) { - preferencesController = [[MVPreferencesController sharedInstance] retain]; + if (!mPreferencesController) { + mPreferencesController = [[MVPreferencesController sharedInstance] retain]; } - return preferencesController; + return mPreferencesController; } - (void)displayPreferencesWindow:sender diff --git a/chimera/src/bookmarks/BookmarksButton.h b/chimera/src/bookmarks/BookmarksButton.h index 7570360b23c9..27d5623352ef 100644 --- a/chimera/src/bookmarks/BookmarksButton.h +++ b/chimera/src/bookmarks/BookmarksButton.h @@ -25,15 +25,20 @@ #import class nsIDOMElement; +class BookmarksService; + @class BookmarkItem; -@interface CHBookmarksButton : NSButton { - - nsIDOMElement* mElement; - BookmarkItem* mBookmarkItem; - BOOL mIsFolder; +@interface CHBookmarksButton : NSButton +{ + nsIDOMElement* mElement; + BookmarkItem* mBookmarkItem; + BookmarksService* mBookmarksService; + BOOL mIsFolder; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService; + -(void)setElement: (nsIDOMElement*)aElt; -(nsIDOMElement*)element; diff --git a/chimera/src/bookmarks/BookmarksButton.mm b/chimera/src/bookmarks/BookmarksButton.mm index ec374db86769..abf785435fd6 100644 --- a/chimera/src/bookmarks/BookmarksButton.mm +++ b/chimera/src/bookmarks/BookmarksButton.mm @@ -39,9 +39,9 @@ @implementation CHBookmarksButton -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { - mElement = nsnull; [self setBezelStyle: NSRegularSquareBezelStyle]; [self setButtonType: NSMomentaryChangeButton]; [self setBordered: NO]; @@ -52,6 +52,15 @@ return self; } +-(id)initWithFrame:(NSRect)frame element:(nsIDOMElement*)element bookmarksService:(BookmarksService*)bookmarksService +{ + if ( (self = [self initWithFrame:frame]) ) { + mBookmarksService = bookmarksService; + [self setElement:element]; + } + return self; +} + -(IBAction)openBookmark:(id)aSender { // See if we're a group. @@ -203,22 +212,24 @@ nsAutoString tag; mElement->GetLocalName(tag); + NSImage* bookmarkImage = mBookmarksService->CreateIconForBookmark(aElt); + nsAutoString group; mElement->GetAttribute(NS_LITERAL_STRING("group"), group); - + if (!group.IsEmpty()) { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"groupbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; } else if (tag.Equals(NS_LITERAL_STRING("folder"))) { - [self setImage: [NSImage imageNamed: @"folder"]]; + [self setImage: bookmarkImage]; mIsFolder = YES; } else { mIsFolder = NO; - [self setImage: [NSImage imageNamed: @"smallbookmark"]]; + [self setImage: bookmarkImage]; [self setAction: @selector(openBookmark:)]; [self setTarget: self]; nsAutoString href; diff --git a/chimera/src/bookmarks/BookmarksDataSource.h b/chimera/src/bookmarks/BookmarksDataSource.h index f05a76b7a544..f0a280af1f29 100644 --- a/chimera/src/bookmarks/BookmarksDataSource.h +++ b/chimera/src/bookmarks/BookmarksDataSource.h @@ -49,6 +49,7 @@ class BookmarksService; @class BookmarkInfoController; +// data source for the bookmarks sidebar. We make one per browser window. @interface BookmarksDataSource : NSObject { BookmarksService* mBookmarks; @@ -106,14 +107,16 @@ class BookmarksService; @interface BookmarkItem : NSObject { nsIContent* mContentNode; + NSImage* mSiteIcon; } - (nsIContent*)contentNode; - (void)setContentNode: (nsIContent*)aContentNode; +- (void)setSiteIcon:(NSImage*)image; - (NSString*)url; +- (NSImage*)siteIcon; - (NSNumber*)contentID; - (id)copyWithZone:(NSZone *)aZone; - (BOOL)isFolder; @end - diff --git a/chimera/src/bookmarks/BookmarksDataSource.mm b/chimera/src/bookmarks/BookmarksDataSource.mm index 77cdc6c36687..9b5915765329 100644 --- a/chimera/src/bookmarks/BookmarksDataSource.mm +++ b/chimera/src/bookmarks/BookmarksDataSource.mm @@ -41,6 +41,7 @@ #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" +#import "SiteIconProvider.h" #include "nsCOMPtr.h" #include "nsIContent.h" @@ -55,17 +56,16 @@ #include "nsVoidArray.h" #import "BookmarksService.h" -#import "StringUtils.h" @implementation BookmarksDataSource -(id) init { - if ( (self = [super init]) ) { - mBookmarks = nsnull; - mCachedHref = nil; - } - return self; + if ( (self = [super init]) ) { + mBookmarks = nsnull; + mCachedHref = nil; + } + return self; } -(void) awakeFromNib @@ -86,16 +86,16 @@ -(void) ensureBookmarks { - if (mBookmarks) - return; - - mBookmarks = new BookmarksService(self); - mBookmarks->AddObserver(); - - [mOutlineView setTarget: self]; - [mOutlineView setDoubleAction: @selector(openBookmark:)]; - [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; - [mOutlineView reloadData]; + if (mBookmarks) + return; + + mBookmarks = new BookmarksService(self); + mBookmarks->AddObserver(); + + [mOutlineView setTarget: self]; + [mOutlineView setDoubleAction: @selector(openBookmark:)]; + [mOutlineView setDeleteAction: @selector(deleteBookmarks:)]; + [mOutlineView reloadData]; } -(IBAction)addBookmark:(id)aSender @@ -530,20 +530,10 @@ //Get the cell of the text attachment. attachmentAttrStringCell = (NSCell *)[(NSTextAttachment *)[attachmentAttrString attribute:NSAttachmentAttributeName atIndex:0 effectiveRange:nil] attachmentCell]; - //Figure out which image to add, and set the cell's image. - // Use the bookmark groups image for groups. - if ([self outlineView:outlineView isItemExpandable:item]) { - nsIContent* content = [item contentNode]; - nsCOMPtr elt(do_QueryInterface(content)); - nsAutoString group; - content->GetAttr(kNameSpaceID_None, BookmarksService::gGroupAtom, group); - if (!group.IsEmpty()) - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"groupbookmark"]]; - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"folder"]]; - } - else - [attachmentAttrStringCell setImage:[NSImage imageNamed:@"smallbookmark"]]; + + nsCOMPtr elt(do_QueryInterface(content)); + NSImage* bookmarkImage = mBookmarks->CreateIconForBookmark(elt); + [attachmentAttrStringCell setImage:bookmarkImage]; //Insert the image [cellValue replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attachmentAttrString]; @@ -855,7 +845,16 @@ @end +#pragma mark - + @implementation BookmarkItem + +-(void)dealloc +{ + [mSiteIcon release]; + [super dealloc]; +} + -(nsIContent*)contentNode { return mContentNode; @@ -887,6 +886,18 @@ return [NSString stringWith_nsAString: href]; } +- (void)setSiteIcon:(NSImage*)image +{ + //NSLog(@"Setting site icon for %@", [self url]); + [mSiteIcon autorelease]; + mSiteIcon = [image retain]; +} + +- (NSImage*)siteIcon +{ + return mSiteIcon; +} + -(void)setContentNode: (nsIContent*)aContentNode { mContentNode = aContentNode; @@ -896,6 +907,7 @@ { BookmarkItem* copy = [[[self class] allocWithZone: aZone] init]; [copy setContentNode: mContentNode]; + [copy setSiteIcon: mSiteIcon]; return copy; } diff --git a/chimera/src/bookmarks/BookmarksService.h b/chimera/src/bookmarks/BookmarksService.h index c03f27353565..411935e25172 100644 --- a/chimera/src/bookmarks/BookmarksService.h +++ b/chimera/src/bookmarks/BookmarksService.h @@ -53,6 +53,9 @@ class nsIDOMHTMLDocument; @class BookmarksDataSource; @class BookmarkItem; +// despite appearances, BookmarksService is not a singleton. We make one for the bookmarks menu, +// one each per BookmarksDataSource, and one per bookmarks toolbar. It relies on a bunch of global +// variables, which is evil. class BookmarksService { public: @@ -63,6 +66,7 @@ public: void AddObserver(); void RemoveObserver(); +public: static void BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); static void BookmarkChanged(nsIContent* aItem, bool shouldFlush = true); static void BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true); @@ -71,7 +75,6 @@ public: static void MoveBookmarkToFolder(nsIDOMElement* aBookmark, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt); static void DeleteBookmark(nsIDOMElement* aBookmark); -public: static void GetRootContent(nsIContent** aResult); static BookmarkItem* GetRootItem(); static BookmarkItem* GetWrapperFor(nsIContent* aItem); @@ -87,6 +90,8 @@ public: static void ConstructAddBookmarkFolderList(NSPopUpButton* aPopup, BookmarkItem* aItem); + static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); + static void EnsureToolbarRoot(); static void ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc); @@ -96,8 +101,6 @@ public: static NSString* ResolveKeyword(NSString* aKeyword); - static NSImage* CreateIconForBookmark(nsIDOMElement* aElement); - static BOOL DoAncestorsIncludeNode(BookmarkItem* bookmark, BookmarkItem* searchItem); static bool IsBookmarkDropValid(BookmarkItem* proposedParent, int index, NSArray* draggedIDs); static bool PerformBookmarkDrop(BookmarkItem* parent, int index, NSArray* draggedIDs); @@ -139,3 +142,23 @@ private: CHBookmarksToolbar* mToolbar; BookmarksDataSource* mDataSource; }; + + + +// singleton bookmarks manager object + +@interface BookmarksManager : NSObject +{ + + + +} + ++ (BookmarksManager*)sharedBookmarksManager; + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString; + + +@end + + diff --git a/chimera/src/bookmarks/BookmarksService.mm b/chimera/src/bookmarks/BookmarksService.mm index baa4588e3003..6df65543da71 100644 --- a/chimera/src/bookmarks/BookmarksService.mm +++ b/chimera/src/bookmarks/BookmarksService.mm @@ -37,12 +37,13 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserView.h" #import "BookmarksService.h" #import "BookmarksDataSource.h" #import "BookmarkInfoController.h" #import "CHIconTabViewItem.h" -#import "StringUtils.h" +#import "SiteIconProvider.h" #include "nsCRT.h" #include "nsString.h" @@ -106,6 +107,7 @@ NSMutableDictionary* BookmarksService::gDictionary = nil; MainController* BookmarksService::gMainController = nil; NSMenu* BookmarksService::gBookmarksMenu = nil; nsIDOMElement* BookmarksService::gToolbarRoot = nsnull; + nsIAtom* BookmarksService::gBookmarkAtom = nsnull; nsIAtom* BookmarksService::gDescriptionAtom = nsnull; nsIAtom* BookmarksService::gFolderAtom = nsnull; @@ -114,8 +116,11 @@ nsIAtom* BookmarksService::gHrefAtom = nsnull; nsIAtom* BookmarksService::gKeywordAtom = nsnull; nsIAtom* BookmarksService::gNameAtom = nsnull; nsIAtom* BookmarksService::gOpenAtom = nsnull; + nsVoidArray* BookmarksService::gInstances = nsnull; + BOOL BookmarksService::gBookmarksFileReadOK = NO; + int BookmarksService::CHInsertNone = 0; int BookmarksService::CHInsertInto = 1; int BookmarksService::CHInsertBefore = 2; @@ -137,6 +142,54 @@ BookmarksService::~BookmarksService() { } +void +BookmarksService::AddObserver() +{ + gRefCnt++; + if (gRefCnt == 1) { + gBookmarkAtom = NS_NewAtom("bookmark"); + gFolderAtom = NS_NewAtom("folder"); + gNameAtom = NS_NewAtom("name"); + gHrefAtom = NS_NewAtom("href"); + gOpenAtom = NS_NewAtom("open"); + gKeywordAtom = NS_NewAtom("id"); + gDescriptionAtom = NS_NewAtom("description"); + gGroupAtom = NS_NewAtom("group"); + gInstances = new nsVoidArray(); + + ReadBookmarks(); + } + + gInstances->AppendElement(this); +} + +void +BookmarksService::RemoveObserver() +{ + if (gRefCnt == 0) + return; + + gInstances->RemoveElement(this); + + gRefCnt--; + if (gRefCnt == 0) { + // Flush Bookmarks before shutting down as some changes are not flushed when + // they are performed (folder open/closed) as writing a whole bookmark file for + // that type of operation seems excessive. + FlushBookmarks(); + + NS_IF_RELEASE(gBookmarks); + NS_RELEASE(gBookmarkAtom); + NS_RELEASE(gFolderAtom); + NS_RELEASE(gNameAtom); + NS_RELEASE(gHrefAtom); + NS_RELEASE(gOpenAtom); + [gDictionary release]; + } +} + +#pragma mark - + void BookmarksService::GetRootContent(nsIContent** aResult) { @@ -211,7 +264,7 @@ BookmarksService::LocateMenu(nsIContent* aContent) } void -BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -258,7 +311,7 @@ BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild, bool } void -BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) +BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush) { if (!gInstances || !gDictionary) return; @@ -289,6 +342,10 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) aItem->GetAttr(kNameSpaceID_None, gNameAtom, name); NSString* bookmarkTitle = [[NSString stringWith_nsAString: name] stringByTruncatingTo:80 at:kTruncateAtMiddle]; [childItem setTitle: bookmarkTitle]; + + // and reset the image + BookmarkItem* item = GetWrapperFor(aItem); + [childItem setImage: [item siteIcon]]; } } @@ -298,7 +355,7 @@ BookmarksService::BookmarkChanged(nsIContent* aItem, bool shouldFlush = true) } void -BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush = true) +BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bool shouldFlush) { if (!gInstances) return; @@ -343,51 +400,6 @@ BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild, bo FlushBookmarks(); } -void -BookmarksService::AddObserver() -{ - gRefCnt++; - if (gRefCnt == 1) { - gBookmarkAtom = NS_NewAtom("bookmark"); - gFolderAtom = NS_NewAtom("folder"); - gNameAtom = NS_NewAtom("name"); - gHrefAtom = NS_NewAtom("href"); - gOpenAtom = NS_NewAtom("open"); - gKeywordAtom = NS_NewAtom("id"); - gDescriptionAtom = NS_NewAtom("description"); - gGroupAtom = NS_NewAtom("group"); - gInstances = new nsVoidArray(); - - ReadBookmarks(); - } - - gInstances->AppendElement(this); -} - -void -BookmarksService::RemoveObserver() -{ - if (gRefCnt == 0) - return; - - gInstances->RemoveElement(this); - - gRefCnt--; - if (gRefCnt == 0) { - // Flush Bookmarks before shutting down as some changes are not flushed when - // they are performed (folder open/closed) as writing a whole bookmark file for - // that type of operation seems excessive. - FlushBookmarks(); - - NS_IF_RELEASE(gBookmarks); - NS_RELEASE(gBookmarkAtom); - NS_RELEASE(gFolderAtom); - NS_RELEASE(gNameAtom); - NS_RELEASE(gHrefAtom); - NS_RELEASE(gOpenAtom); - [gDictionary release]; - } -} void BookmarksService::AddBookmarkToFolder(nsString& aURL, nsString& aTitle, nsIDOMElement* aFolder, nsIDOMElement* aBeforeElt) @@ -601,6 +613,40 @@ BookmarksService::FlushBookmarks() domSerializer->SerializeToStream(domDoc, outputStream, nsnull); } +NSImage* +BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) +{ + nsCOMPtr tagName; + nsCOMPtr content = do_QueryInterface(aElement); + content->GetTag(*getter_AddRefs(tagName)); + + nsAutoString group; + content->GetAttr(kNameSpaceID_None, gGroupAtom, group); + if (!group.IsEmpty()) + return [NSImage imageNamed:@"groupbookmark"]; + + if (tagName == BookmarksService::gFolderAtom) + return [NSImage imageNamed:@"folder"]; + + // fire off a proxy icon load + if ([[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" withSuccess:NULL]) + { + nsAutoString href; + content->GetAttr(kNameSpaceID_None, gHrefAtom, href); + if (href.Length() > 0) + { + BookmarkItem* contentItem = BookmarksService::GetWrapperFor(content); + if ([contentItem siteIcon]) + return [contentItem siteIcon]; + + if (contentItem && ![contentItem siteIcon]) + [[BookmarksManager sharedBookmarksManager] loadProxyImageFor:contentItem withURI:[NSString stringWith_nsAString:href]]; + } + } + + return [NSImage imageNamed:@"smallbookmark"]; +} + void BookmarksService::EnsureToolbarRoot() { if (gToolbarRoot) @@ -763,18 +809,21 @@ BookmarksService::AddMenuBookmark(NSMenu* aMenu, nsIContent* aParent, nsIContent nsAutoString group; aChild->GetAttr(kNameSpaceID_None, gGroupAtom, group); + nsCOMPtr elt(do_QueryInterface(aChild)); + NSImage* menuItemImage = BookmarksService::CreateIconForBookmark(elt); + if (group.IsEmpty() && tagName == gFolderAtom) { NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease]; [aMenu setSubmenu: menu forItem: menuItem]; [menu setAutoenablesItems: NO]; - [menuItem setImage: [NSImage imageNamed:@"folder"]]; + [menuItem setImage: menuItemImage]; ConstructBookmarksMenu(menu, aChild); } else { if (group.IsEmpty()) - [menuItem setImage: [NSImage imageNamed:@"smallbookmark"]]; + [menuItem setImage: menuItemImage]; else - [menuItem setImage: [NSImage imageNamed:@"groupbookmark"]]; + [menuItem setImage: menuItemImage]; [menuItem setTarget: gMainController]; [menuItem setAction: @selector(openMenuBookmark:)]; @@ -997,10 +1046,12 @@ BookmarksService::ImportBookmarks(nsIDOMHTMLDocument* aHTMLDoc) nsCOMPtr parentContent(do_QueryInterface(bookmarksRoot)); nsCOMPtr childContent(do_QueryInterface(importedRootElement)); +#if 0 // XXX testing if (gDictionary) [gDictionary removeAllObjects]; - +#endif + // this will save the file BookmarkAdded(parentContent, childContent, true /* flush */); } @@ -1072,24 +1123,7 @@ BookmarksService::ResolveKeyword(NSString* aKeyword) content->GetAttr(kNameSpaceID_None, gHrefAtom, url); return [NSString stringWith_nsAString: url]; } - return [NSString stringWithCString:""]; -} - -NSImage* -BookmarksService::CreateIconForBookmark(nsIDOMElement* aElement) -{ - nsCOMPtr tagName; - nsCOMPtr content = do_QueryInterface(aElement); - content->GetTag(*getter_AddRefs(tagName)); - if (tagName == BookmarksService::gFolderAtom) - return [NSImage imageNamed:@"folder"]; - - nsAutoString group; - content->GetAttr(kNameSpaceID_None, gGroupAtom, group); - if (!group.IsEmpty()) - return [NSImage imageNamed:@"smallgroup"]; - - return [NSImage imageNamed:@"groupbookmark"]; + return [NSString string]; } // Is searchItem equal to bookmark or bookmark's parent, grandparent, etc? @@ -1292,3 +1326,78 @@ BookmarksService::PerformURLDrop(BookmarkItem* parentItem, BookmarkItem* beforeI return YES; } +#pragma mark - + +@interface BookmarksManager(Private) + +- (void)registerNotificationListener; +- (void)imageLoadedNotification:(NSNotification*)notification; + +@end + + +@implementation BookmarksManager + ++ (BookmarksManager*)sharedBookmarksManager; +{ + static BookmarksManager* sBookmarksManager = nil; + + if (!sBookmarksManager) + sBookmarksManager = [[BookmarksManager alloc] init]; + + return sBookmarksManager; +} + +- (id)init +{ + if ((self = [super init])) + { + [self registerNotificationListener]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)loadProxyImageFor:(id)requestor withURI:(NSString*)inURIString +{ + [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon:self + forURI:inURIString withUserData:requestor allowNetwork:NO]; +} + + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// callback for [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] +- (void)imageLoadedNotification:(NSNotification*)notification +{ + //NSLog(@"BookmarksManager imageLoadedNotification"); + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + id requestor = [userInfo objectForKey:SiteIconLoadUserDataKey]; // requestor is a BookmarkItem + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + + if (iconImage && [requestor isMemberOfClass:[BookmarkItem class]]) + { + [requestor setSiteIcon:iconImage]; + BookmarksService::BookmarkChanged([requestor contentNode], FALSE); + } + + } +} + + +@end + diff --git a/chimera/src/bookmarks/BookmarksToolbar.h b/chimera/src/bookmarks/BookmarksToolbar.h index 895a1bd8005c..8f164fdaf8ac 100644 --- a/chimera/src/bookmarks/BookmarksToolbar.h +++ b/chimera/src/bookmarks/BookmarksToolbar.h @@ -28,12 +28,13 @@ class nsIDOMElement; class BookmarksService; class CHBookmarksButton; -@interface CHBookmarksToolbar : NSView { - BookmarksService* mBookmarks; - NSMutableArray* mButtons; +@interface CHBookmarksToolbar : NSView +{ + BookmarksService* mBookmarks; + NSMutableArray* mButtons; CHBookmarksButton* mDragInsertionButton; - int mDragInsertionPosition; - BOOL mIsShowing; + int mDragInsertionPosition; + BOOL mIsShowing; } -(void)initializeToolbar; diff --git a/chimera/src/bookmarks/BookmarksToolbar.mm b/chimera/src/bookmarks/BookmarksToolbar.mm index 2a1cee3573c5..c65d4d90c180 100644 --- a/chimera/src/bookmarks/BookmarksToolbar.mm +++ b/chimera/src/bookmarks/BookmarksToolbar.mm @@ -31,9 +31,14 @@ #include "nsIDOMElement.h" #include "nsIContent.h" +@interface CHBookmarksToolbar(Private) +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element; +@end + @implementation CHBookmarksToolbar -- (id)initWithFrame:(NSRect)frame { +- (id)initWithFrame:(NSRect)frame +{ if ( (self = [super initWithFrame:frame]) ) { mBookmarks = nsnull; mButtons = [[NSMutableArray alloc] init]; @@ -95,8 +100,7 @@ while (child) { nsCOMPtr childElt(do_QueryInterface(child)); if (childElt) { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: childElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:childElt]; [self addSubview: button]; [mButtons addObject: button]; } @@ -111,8 +115,7 @@ -(void)addButton: (nsIDOMElement*)aElt atIndex: (int)aIndex { - CHBookmarksButton* button = [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17)] autorelease]; - [button setElement: aElt]; + CHBookmarksButton* button = [self makeNewButtonWithElement:aElt]; [self addSubview: button]; [mButtons insertObject: button atIndex: aIndex]; if ([self isShown]) @@ -453,4 +456,9 @@ } } +- (CHBookmarksButton*)makeNewButtonWithElement:(nsIDOMElement*)element +{ + return [[[CHBookmarksButton alloc] initWithFrame: NSMakeRect(2, 1, 100, 17) element:element bookmarksService:mBookmarks] autorelease]; +} + @end diff --git a/chimera/src/browser/BrowserWindowController.h b/chimera/src/browser/BrowserWindowController.h index ecc80d2755c6..b11edfacbefd 100644 --- a/chimera/src/browser/BrowserWindowController.h +++ b/chimera/src/browser/BrowserWindowController.h @@ -78,6 +78,7 @@ class nsIDOMNode; @class BookmarksDataSource; @class CHHistoryDataSource; @class CHExtendedTabView; +@class CHPageProxyIcon; @interface BrowserWindowController : NSWindowController { @@ -93,6 +94,7 @@ class nsIDOMNode; IBOutlet NSWindow* mLocationSheetWindow; IBOutlet NSTextField* mLocationSheetURLField; IBOutlet NSView* mStatusBar; // contains the status text, progress bar, and lock + IBOutlet CHPageProxyIcon* mProxyIcon; IBOutlet id mSidebarBrowserView; // currently unused IBOutlet BookmarksDataSource* mSidebarBookmarksDataSource; @@ -162,6 +164,7 @@ class nsIDOMNode; - (void)loadURL:(NSString*)aURLSpec referrer:(NSString*)aReferrer activate:(BOOL)activate; - (void)updateLocationFields:(NSString *)locationString; +- (void)updateSiteIcons:(NSImage *)siteIconImage; - (void)updateToolbarItems; - (void)focusURLBar; diff --git a/chimera/src/browser/BrowserWindowController.mm b/chimera/src/browser/BrowserWindowController.mm index 92a275afd009..82f3d33886af 100644 --- a/chimera/src/browser/BrowserWindowController.mm +++ b/chimera/src/browser/BrowserWindowController.mm @@ -46,6 +46,7 @@ #import "CHHistoryDataSource.h" #import "CHExtendedTabView.h" #import "CHUserDefaults.h" +#import "CHPageProxyIcon.h" #include "nsIWebNavigation.h" #include "nsIDOMElement.h" @@ -947,6 +948,13 @@ static NSArray* sToolbarDefaults = nil; // [[self window] display]; } +- (void)updateSiteIcons:(NSImage *)siteIconImage +{ + if (siteIconImage == nil) + siteIconImage = [NSImage imageNamed:@"globe_ico"]; + [mProxyIcon setImage:siteIconImage]; +} + -(void)newTab:(BOOL)allowHomepage { CHIconTabViewItem* newTab = [[[CHIconTabViewItem alloc] initWithIdentifier: nil] autorelease]; diff --git a/chimera/src/browser/BrowserWrapper.h b/chimera/src/browser/BrowserWrapper.h index 801a4d97a8a2..830af8077b31 100644 --- a/chimera/src/browser/BrowserWrapper.h +++ b/chimera/src/browser/BrowserWrapper.h @@ -51,6 +51,9 @@ NSTabViewItem* mTab; NSWindow* mWindow; + NSImage* mSiteIconImage; // current proxy icon image, which may be a site icon (favicon). + NSString* mSiteIconURI; // uri from which we loaded the site icon + // the secure state of this browser. We need to hold it so that we can set // the global lock icon whenever we become the primary. Value is one of // security enums in nsIWebProgressListener. @@ -60,9 +63,9 @@ NSString* mTitle; CHBrowserView* mBrowserView; - NSString* defaultStatus; - NSString* loadingStatus; - ToolTip* toolTip; + NSString* mDefaultStatusString; + NSString* mLoadingStatusString; + ToolTip* mToolTip; BOOL mIsPrimary; BOOL mIsBusy; diff --git a/chimera/src/browser/BrowserWrapper.mm b/chimera/src/browser/BrowserWrapper.mm index d3011dacdd26..01c9b87587a7 100644 --- a/chimera/src/browser/BrowserWrapper.mm +++ b/chimera/src/browser/BrowserWrapper.mm @@ -37,9 +37,12 @@ #import "NSString+Utils.h" +#import "CHPreferenceManager.h" #import "CHBrowserWrapper.h" #import "BrowserWindowController.h" #import "BookmarksService.h" +#import "SiteIconProvider.h" +#import "CHIconTabViewItem.h" #import "ToolTip.h" #include "nsCOMPtr.h" @@ -64,8 +67,18 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; +const NSString* kOfflineNotificationName = @"offlineModeChanged"; + @interface CHBrowserWrapper(Private) - -(void) setPendingActive:(BOOL)active; + +- (void)setPendingActive:(BOOL)active; +- (void)registerNotificationListener; + +- (void)setSiteIconImage:(NSImage*)inSiteIcon; +- (void)setSiteIconURI:(NSString*)inSiteIconURI; + +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI; + @end @implementation CHBrowserWrapper @@ -85,10 +98,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; #endif [[NSNotificationCenter defaultCenter] removeObserver: self]; - - [defaultStatus release]; - [loadingStatus release]; - [toolTip release]; + + [mSiteIconImage release]; + [mSiteIconURI release]; + [mDefaultStatusString release]; + [mLoadingStatusString release]; + [mToolTip release]; [super dealloc]; } @@ -105,7 +120,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; progress = nil; progressSuper = nil; mIsPrimary = NO; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kOfflineNotificationName object:nil]; [mBrowserView setActive: NO]; } @@ -133,7 +148,13 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mIsBusy = NO; mListenersAttached = NO; mSecureState = nsIWebProgressListener::STATE_IS_INSECURE; - toolTip = [[ToolTip alloc] init]; + + mToolTip = [[ToolTip alloc] init]; + + //[self setSiteIconImage:[NSImage imageNamed:@"globe_ico"]]; + //[self setSiteIconURI: [NSString string]]; + + [self registerNotificationListener]; } return self; } @@ -155,9 +176,9 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (!mIsBusy) [progress removeFromSuperview]; - defaultStatus = NULL; - loadingStatus = DOCUMENT_DONE_STRING; - [status setStringValue:loadingStatus]; + mDefaultStatusString = NULL; + mLoadingStatusString = DOCUMENT_DONE_STRING; + [status setStringValue:mLoadingStatusString]; mIsPrimary = YES; @@ -181,11 +202,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; if (mWindowController) // Only register if we're the content area. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlineModeChanged:) - name:@"offlineModeChanged" + name:kOfflineNotificationName object:nil]; // Update the URL bar. [mWindowController updateLocationFields:[self getCurrentURLSpec]]; + [mWindowController updateSiteIcons:mSiteIconImage]; if (mWindowController && !mListenersAttached) { mListenersAttached = YES; @@ -209,7 +231,7 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; return [mBrowserView getCurrentURLSpec]; } -- (void)awakeFromNib +- (void)awakeFromNib { } @@ -239,17 +261,17 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLoadingStarted { - if (defaultStatus) { - [defaultStatus release]; - defaultStatus = NULL; + if (mDefaultStatusString) { + [mDefaultStatusString release]; + mDefaultStatusString = NULL; } [progressSuper addSubview:progress]; [progress setIndeterminate:YES]; [progress startAnimation:self]; - loadingStatus = NSLocalizedString(@"TabLoading", @""); - [status setStringValue:loadingStatus]; + mLoadingStatusString = NSLocalizedString(@"TabLoading", @""); + [status setStringValue:mLoadingStatusString]; mIsBusy = YES; [mTab setLabel: NSLocalizedString(@"TabLoading", @"")]; @@ -271,12 +293,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; [progress stopAnimation:self]; [progress removeFromSuperview]; - loadingStatus = DOCUMENT_DONE_STRING; - if (defaultStatus) { - [status setStringValue:defaultStatus]; + mLoadingStatusString = DOCUMENT_DONE_STRING; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } mIsBusy = NO; @@ -316,8 +338,32 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onLocationChange:(NSString*)urlSpec { + BOOL useSiteIcons = [[CHPreferenceManager sharedInstance] getBooleanPref:"browser.chrome.site_icons" 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 withUserData:nil allowNetwork:YES]; + } + else + { + if ([urlSpec isEqualToString:@"about:blank"]) + faviconURI = urlSpec; + else + faviconURI = @""; + } + + if (!siteIconLoadInitiated) + [self updateSiteIconImage:nil withURI:faviconURI]; + if (mIsPrimary) - [mWindowController updateLocationFields:urlSpec]; + [mWindowController updateLocationFields:urlSpec]; } - (void)onStatusChange:(NSString*)aStatusString @@ -342,20 +388,20 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)setStatus:(NSString *)statusString ofType:(NSStatusType)type { if (type == NSStatusTypeScriptDefault) { - if (defaultStatus) { - [defaultStatus release]; + if (mDefaultStatusString) { + [mDefaultStatusString release]; } - defaultStatus = statusString; - if (defaultStatus) { - [defaultStatus retain]; + mDefaultStatusString = statusString; + if (mDefaultStatusString) { + [mDefaultStatusString retain]; } } else if (!statusString) { - if (defaultStatus) { - [status setStringValue:defaultStatus]; + if (mDefaultStatusString) { + [status setStringValue:mDefaultStatusString]; } else { - [status setStringValue:loadingStatus]; + [status setStringValue:mLoadingStatusString]; } } else { @@ -418,12 +464,12 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; - (void)onShowTooltip:(NSPoint)where withText:(NSString*)text { NSPoint point = [[self window] convertBaseToScreen:[self convertPoint: where toView:nil]]; - [toolTip showToolTipAtPoint: point withString: text]; + [mToolTip showToolTipAtPoint: point withString: text]; } - (void)onHideTooltip { - [toolTip closeToolTip]; + [mToolTip closeToolTip]; } // Called when a context menu should be shown. @@ -547,4 +593,77 @@ static const char* ioServiceContractID = "@mozilla.org/network/io-service;1"; mActivateOnLoad = active; } +- (void)setSiteIconImage:(NSImage*)inSiteIcon +{ + [mSiteIconImage autorelease]; + mSiteIconImage = [inSiteIcon retain]; +} + +- (void)setSiteIconURI:(NSString*)inSiteIconURI +{ + [mSiteIconURI autorelease]; + mSiteIconURI = [inSiteIconURI retain]; +} + +// A nil inSiteIcon image indicates that we should use the default icon +// If inSiteIconURI is "about:blank", we don't show any icon +- (void)updateSiteIconImage:(NSImage*)inSiteIcon withURI:(NSString *)inSiteIconURI +{ + BOOL resetTabIcon = NO; + + if (![mSiteIconURI isEqualToString:inSiteIconURI]) + { + if (!inSiteIcon) + { + if (![inSiteIconURI isEqualToString:@"about:blank"]) + inSiteIcon = [NSImage imageNamed:@"globe_ico"]; + } + + [self setSiteIconImage: inSiteIcon]; + [self setSiteIconURI: inSiteIconURI]; + + // update the proxy icon + if (mIsPrimary) + [mWindowController updateSiteIcons:mSiteIconImage]; + + resetTabIcon = YES; + } + + // update the tab icon + if ([mTab isMemberOfClass:[CHIconTabViewItem class]]) + { + CHIconTabViewItem* tabItem = (CHIconTabViewItem*)mTab; + if (resetTabIcon || ![tabItem tabIcon]) + [tabItem setTabIcon:mSiteIconImage]; + } + +} + +- (void)registerNotificationListener +{ + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(imageLoadedNotification:) + name: SiteIconLoadNotificationName + object: self]; + +} + +// called when [[SiteIconProvider sharedFavoriteIconProvider] loadFavoriteIcon] completes +- (void)imageLoadedNotification:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + if (userInfo) + { + NSImage* iconImage = [userInfo objectForKey:SiteIconLoadImageKey]; + NSString* siteIconURI = [userInfo objectForKey:SiteIconLoadURIKey]; + + // NSLog(@"CHBrowserWrapper imageLoadedNotification got image %@ and uri %@", iconImage, proxyImageURI); + if (iconImage == nil) + siteIconURI = @""; // go back to default image + + [self updateSiteIconImage:iconImage withURI:siteIconURI]; + } +} + + @end diff --git a/chimera/src/browser/PageProxyIcon.h b/chimera/src/browser/PageProxyIcon.h index 9dfc66212c3f..4e8b1e17ce8e 100644 --- a/chimera/src/browser/PageProxyIcon.h +++ b/chimera/src/browser/PageProxyIcon.h @@ -25,7 +25,7 @@ @interface CHPageProxyIcon : NSImageView { - } + @end diff --git a/chimera/src/browser/PageProxyIcon.mm b/chimera/src/browser/PageProxyIcon.mm index 80544d58280b..51ab9d59f31b 100644 --- a/chimera/src/browser/PageProxyIcon.mm +++ b/chimera/src/browser/PageProxyIcon.mm @@ -24,13 +24,25 @@ #import "NSString+Utils.h" #import "CHPageProxyIcon.h" + #import "BookmarksService.h" #import "MainController.h" #include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsString.h" @implementation CHPageProxyIcon +- (void)awakeFromNib +{ +} + +- (void)dealloc +{ + [super dealloc]; +} + - (void) resetCursorRects { NSCursor* cursor; @@ -77,4 +89,5 @@ event: event pasteboard: pboard source: self slideBack: YES]; } + @end diff --git a/chimera/src/browser/RemoteDataProvider.h b/chimera/src/browser/RemoteDataProvider.h new file mode 100644 index 000000000000..13ff9a3af4ae --- /dev/null +++ b/chimera/src/browser/RemoteDataProvider.h @@ -0,0 +1,90 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + + +extern NSString* RemoteDataLoadRequestNotificationName; +extern NSString* RemoteDataLoadRequestURIKey; +extern NSString* RemoteDataLoadRequestDataKey; +extern NSString* RemoteDataLoadRequestUserDataKey; +extern NSString* 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 +// callback in NSData. +// +// Clients can either implement the RemoteLoadListener protocol and call +// loadURI directly, or they can register with the [NSNotification defaultCenter] +// for 'RemoteDataLoadRequestNotificationName' notifications, and catch all loads +// that happen that way. + +@protocol RemoteLoadListener +// called when the load completes, or fails. If the status code is a failure code, +// data may be nil. +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status; +@end + + +class RemoteURILoadManager; + +@interface RemoteDataProvider : NSObject +{ + RemoteURILoadManager* mLoadManager; +} + ++ (RemoteDataProvider*)sharedRemoteDataProvider; + +// generic method. You can load any URI asynchronously with this selector, +// and the listener will get the contents of the URI in an NSData. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +// specific request to load a remote file. The sender (or any other object), if +// registered with the notification center, will receive a notification when +// the load completes. The 'target' becomes the 'object' of the notification. +// The notification name is given by NSString* RemoteDataLoadRequestNotificationName above. +// If allowNetworking is NO, then this method will just check the cache, +// and not go to the network +// This method will return YES if the request was dispatched, or NO otherwise. +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK; + +@end diff --git a/chimera/src/browser/RemoteDataProvider.mm b/chimera/src/browser/RemoteDataProvider.mm new file mode 100644 index 000000000000..b869d6739170 --- /dev/null +++ b/chimera/src/browser/RemoteDataProvider.mm @@ -0,0 +1,298 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "RemoteDataProvider.h" + +#include "nsISupports.h" +#include "nsHashtable.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.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"; + + +// this has to retain the load listener, to ensure that the listener lives long +// enough to receive notifications. We have to be careful to avoid ref cycles. +class StreamLoaderContext : public nsISupports +{ +public: + StreamLoaderContext(id inLoadListener, id inUserData, id inTarget, const nsAString& inURI) + : mLoadListener(inLoadListener) + , mTarget(inTarget) + , mUserData(inUserData) + , mURI(inURI) + { + NS_INIT_ISUPPORTS(); + [mLoadListener retain]; + } + + virtual ~StreamLoaderContext() + { + [mLoadListener release]; + } + + NS_DECL_ISUPPORTS + + void LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength); + const nsAString& GetURI() { return mURI; } + +protected: + + id mLoadListener; // retained + id mTarget; // not retained + id mUserData; // not retained + nsString mURI; + +}; + + +NS_IMPL_ISUPPORTS1(StreamLoaderContext, nsISupports) + +void StreamLoaderContext::LoadComplete(nsresult inLoadStatus, const void* inData, unsigned int inDataLength) +{ + if (mLoadListener) + { + NSData* loadData = nil; + if (NS_SUCCEEDED(inLoadStatus)) + loadData = [NSData dataWithBytes:inData length:inDataLength]; + + [mLoadListener doneRemoteLoad:[NSString stringWith_nsAString:mURI] forTarget:mTarget withUserData:mUserData data:loadData status:inLoadStatus]; + } +} + + +class RemoteURILoadManager : public nsIStreamLoaderObserver +{ +public: + + RemoteURILoadManager(); + virtual ~RemoteURILoadManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLOADEROBSERVER + + nsresult Init(); + nsresult RequestURILoad(const nsAString& inURI, id loadListener, id userData, id target, PRBool allowNetworking); + +protected: + + nsSupportsHashtable mStreamLoaderHash; // hash of active stream loads, keyed on URI + nsCOMPtr mCacheSession; + +}; + +RemoteURILoadManager::RemoteURILoadManager() +{ + NS_INIT_ISUPPORTS(); +} + +RemoteURILoadManager::~RemoteURILoadManager() +{ +} + +NS_IMPL_ISUPPORTS1(RemoteURILoadManager, nsIStreamLoaderObserver) + +NS_IMETHODIMP RemoteURILoadManager::OnStreamComplete(nsIStreamLoader *loader, nsISupports *ctxt, nsresult status, PRUint32 resultLength, const char *result) +{ + StreamLoaderContext* loaderContext = NS_STATIC_CAST(StreamLoaderContext*, ctxt); + if (loaderContext) + { + loaderContext->LoadComplete(status, (const void*)result, resultLength); + + // remove the stream loader from the hash table + nsStringKey uriKey(loaderContext->GetURI()); + PRBool removed = mStreamLoaderHash.Remove(&uriKey); + } + + return NS_OK; +} + +static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID); + +nsresult RemoteURILoadManager::Init() +{ + nsresult rv; + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession("HTTP", nsICache::STORE_ANYWHERE, + nsICache::STREAM_BASED, getter_AddRefs(mCacheSession)); + + return rv; +} + +nsresult RemoteURILoadManager::RequestURILoad(const nsAString& inURI, id loadListener, + id userData, id target, PRBool allowNetworking) +{ + nsresult rv; + +#if 0 + // if no networking is allowed, make sure it's in the cache + if (!allowNetworking) + { + if (!mCacheSession) + return NS_ERROR_FAILURE; + + nsCOMPtr entryDesc; + rv = mCacheSession->OpenCacheEntry(NS_ConvertUCS2toUTF8(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + if (NS_FAILED(rv) || !entryDesc) + return NS_ERROR_FAILURE; + } +#endif + + nsStringKey uriKey(inURI); + + // first make sure that there isn't another entry in the hash for this + nsCOMPtr foundStreamSupports = mStreamLoaderHash.Get(&uriKey); + if (foundStreamSupports) + return NS_OK; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), inURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr loaderContext = new StreamLoaderContext(loadListener, userData, target, inURI); + + nsLoadFlags loadFlags = (allowNetworking) ? nsIRequest::LOAD_NORMAL : nsIRequest::LOAD_FROM_CACHE; + nsCOMPtr streamLoader; + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), uri, this, loaderContext, nsnull, nsnull, loadFlags); + if (NS_FAILED(rv)) + { + NSLog(@"NS_NewStreamLoader for favicon failed"); + return rv; + } + +#ifdef DEBUG_smfr + NSLog(@"RequestURILoad called for %@", [NSString stringWith_nsAString: inURI]); +#endif + + // put the stream loader into the hash table + nsCOMPtr streamLoaderAsSupports = do_QueryInterface(streamLoader); + mStreamLoaderHash.Put(&uriKey, streamLoaderAsSupports); + + return NS_OK; +} + + +#pragma mark - + + +@implementation RemoteDataProvider + + ++ (RemoteDataProvider*)sharedRemoteDataProvider +{ + static RemoteDataProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[RemoteDataProvider alloc] init]; + + // we probably need to register for NSApplicationWillTerminateNotification notifications + // and delete this then. + } + + return sIconProvider; +} + +- (id)init +{ + if ((self = [super init])) + { + mLoadManager = new RemoteURILoadManager; + NS_ADDREF(mLoadManager); + } + + return self; +} + +- (void)dealloc +{ + NS_IF_RELEASE(mLoadManager); + [super dealloc]; +} + +- (BOOL)loadURI:(NSString*)inURI forTarget:(id)target withListener:(id)inListener + withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + //NSLog(@"loadURI called with %@", inURI); + if (mLoadManager && [inURI length] > 0) + { + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsresult rv = mLoadManager->RequestURILoad(uriString, inListener, userData, target, (PRBool)inNetworkOK); + if (NS_FAILED(rv)) + { + NSLog(@"RequestURILoad failed for @%", inURI); + return NO; + } + } + + return YES; +} + +- (BOOL)postURILoadRequest:(NSString*)inURI forTarget:(id)target withUserData:(id)userData allowNetworking:(BOOL)inNetworkOK +{ + return [self loadURI:inURI forTarget:target withListener:self withUserData:userData allowNetworking:inNetworkOK]; +} + +// our own load listener callback +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, RemoteDataLoadRequestURIKey, + data, RemoteDataLoadRequestDataKey, + userData, RemoteDataLoadRequestUserDataKey, + [NSNumber numberWithInt:status], RemoteDataLoadRequestResultKey, + nil]; + + NSLog(@"remoteLoadDone with status %d and length %d", status, [data length]); + [[NSNotificationCenter defaultCenter] postNotificationName: RemoteDataLoadRequestNotificationName + object:target userInfo:notificationData]; +} + +@end diff --git a/chimera/src/browser/SiteIconProvider.h b/chimera/src/browser/SiteIconProvider.h new file mode 100644 index 000000000000..9cb0209c4558 --- /dev/null +++ b/chimera/src/browser/SiteIconProvider.h @@ -0,0 +1,69 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import + +#import "RemoteDataProvider.h" + +extern NSString* SiteIconLoadNotificationName; +extern NSString* SiteIconLoadImageKey; +extern NSString* SiteIconLoadURIKey; +extern NSString* SiteIconLoadUserDataKey; + +class NeckoCacheHelper; + +@interface SiteIconProvider : NSObject +{ + NeckoCacheHelper* mMissedIconsCacheHelper; +} + ++ (SiteIconProvider*)sharedFavoriteIconProvider; + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI; + +// Start a favicon.ico load for the given URI, which can be any URI. +// The caller will get a 'SiteIconLoadNotificationName' notification +// when the load is done, with the image at the 'SiteIconLoadImageKey' key +// in the notifcation userInfo. The caller will have had to register with the +// NSNotifcationCenter in order to receive this notifcation. The notification +// is dispatched with 'sender' as the object. +// This method returns YES if the uri request was dispatched (i.e. if we know +// that we've looked for, and failed to find, this icon before). If it returns +// YES, then the 'SiteIconLoadNotificationName' notification will be sent out. +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData allowNetwork:(BOOL)inAllowNetwork; + +@end diff --git a/chimera/src/browser/SiteIconProvider.mm b/chimera/src/browser/SiteIconProvider.mm new file mode 100644 index 000000000000..b5f5d93705d8 --- /dev/null +++ b/chimera/src/browser/SiteIconProvider.mm @@ -0,0 +1,330 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSString+Utils.h" + +#import "SiteIconProvider.h" + +#include "prtime.h" +#include "nsString.h" +#include "nsISupports.h" +#include "nsNetUtil.h" +#include "nsICacheSession.h" +#include "nsICacheService.h" +#include "nsICacheEntryDescriptor.h" + + +NSString* SiteIconLoadNotificationName = @"siteicon_load_notification"; +NSString* SiteIconLoadImageKey = @"siteicon_load_image"; +NSString* SiteIconLoadURIKey = @"siteicon_load_uri"; +NSString* SiteIconLoadUserDataKey = @"siteicon_load_user_data"; + + +static inline PRUint32 PRTimeToSeconds(PRTime t_usec) +{ + PRTime usec_per_sec; + PRUint32 t_sec; + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_DIV(t_usec, t_usec, usec_per_sec); + LL_L2I(t_sec, t_usec); + return t_sec; +} + +class NeckoCacheHelper +{ +public: + + NeckoCacheHelper(const char* inMetaElement, const char* inMetaValue); + ~NeckoCacheHelper() {} + + nsresult Init(const char* inCacheSessionName); + nsresult ExistsInCache(const nsACString& inURI, PRBool* outExists); + nsresult PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds); + + nsresult ClearCache(); + +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) +{ +} + +nsresult NeckoCacheHelper::Init(const char* inCacheSessionName) +{ + nsresult rv; + + nsCOMPtr cacheService = do_GetService(kCacheServiceCID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = cacheService->CreateSession(inCacheSessionName, + nsICache::STORE_ANYWHERE, nsICache::STREAM_BASED, + getter_AddRefs(mCacheSession)); + if (NS_FAILED(rv)) + return rv; + + return NS_OK; +} + + +nsresult NeckoCacheHelper::ExistsInCache(const nsACString& inURI, PRBool* outExists) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), nsICache::ACCESS_READ, nsICache::NON_BLOCKING, getter_AddRefs(entryDesc)); + + *outExists = NS_SUCCEEDED(rv) && (entryDesc != NULL); + return NS_OK; +} + +nsresult NeckoCacheHelper::PutInCache(const nsACString& inURI, PRUint32 inExpirationTimeSeconds) +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + nsCOMPtr entryDesc; + nsresult rv = mCacheSession->OpenCacheEntry(PromiseFlatCString(inURI).get(), 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(mMetaElement, mMetaValue); // just set a bit of meta data. + entryDesc->SetExpirationTime(PRTimeToSeconds(PR_Now()) + inExpirationTimeSeconds); + + entryDesc->MarkValid(); + entryDesc->Close(); + + return NS_OK; +} + +nsresult NeckoCacheHelper::ClearCache() +{ + NS_ASSERTION(mCacheSession, "No cache session"); + + return mCacheSession->EvictEntries(); +} + + +#pragma mark - + +static nsresult MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) +{ + outFaviconURI.Truncate(0); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), inURIString); + if (NS_FAILED(rv)) + return rv; + + // check for http/https + PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; + uri->SchemeIs("http", &isHTTP); + uri->SchemeIs("https", &isHTTPS); + if (!isHTTP && !isHTTPS) + return NS_OK; + + PRInt32 port; + uri->GetPort(&port); + + nsXPIDLCString scheme; + uri->GetScheme(scheme); + + nsXPIDLCString host; + uri->GetHost(host); + + nsCAutoString faviconURI = scheme; + faviconURI.Append("://"); + faviconURI.Append(host); + if (port != -1) { + faviconURI.Append(':'); + faviconURI.AppendInt(port); + } + faviconURI.Append("/favicon.ico"); + + outFaviconURI.Assign(NS_ConvertUTF8toUCS2(faviconURI)); + return NS_OK; +} + + +@interface SiteIconProvider(Private) + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds; +- (BOOL)inMissedIconsCache:(const nsAString&)inURI; + +@end + + +@implementation SiteIconProvider + +- (id)init +{ + if ((self = [super init])) + { + mMissedIconsCacheHelper = new NeckoCacheHelper("Favicon", "Missed"); + nsresult rv = mMissedIconsCacheHelper->Init("MissedIconsCache"); + if (NS_FAILED(rv)) { + delete mMissedIconsCacheHelper; + mMissedIconsCacheHelper = NULL; + } + } + + return self; +} + +- (void)dealloc +{ + delete mMissedIconsCacheHelper; + [super dealloc]; +} + +- (void)addToMissedIconsCache:(const nsAString&)inURI withExpirationSeconds:(unsigned int)inExpSeconds +{ + if (mMissedIconsCacheHelper) + { + nsresult rv = mMissedIconsCacheHelper->PutInCache(NS_ConvertUCS2toUTF8(inURI), inExpSeconds); + //NSLog(@"Putting %@ in missed icon cache", [NSString stringWith_nsAString:inURI]); + } + +} + +- (BOOL)inMissedIconsCache:(const nsAString&)inURI +{ + PRBool inCache = PR_FALSE; + + if (mMissedIconsCacheHelper) + mMissedIconsCacheHelper->ExistsInCache(NS_ConvertUCS2toUTF8(inURI), &inCache); + + //NSLog(@"%@ in missed icon cache: %d", [NSString stringWith_nsAString:inURI], inCache); + return inCache; +} + + +- (BOOL)loadFavoriteIcon:(id)sender forURI:(NSString *)inURI withUserData:(id)userData 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; + + RemoteDataProvider* dataProvider = [RemoteDataProvider sharedRemoteDataProvider]; + return [dataProvider loadURI:faviconString forTarget:sender withListener:self withUserData:userData allowNetworking:inAllowNetwork]; +} + +#define SITE_ICON_EXPIRATION_SECONDS (60 * 60 * 24 * 7) // 1 week + +// this is called on the main thread +- (void)doneRemoteLoad:(NSString*)inURI forTarget:(id)target withUserData:(id)userData data:(NSData*)data status:(nsresult)status +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + BOOL loadOK = NS_SUCCEEDED(status) && (data != nil); + // it's hard to tell if the favicon load succeeded or not. Even if the file + // does not exist, servers will send back a 404 page with a 0 status. + // So we just go ahead and try to make the image; it will return nil on + // failure. + NSImage* faviconImage = [[NSImage alloc] initWithData:data]; + BOOL gotImageData = loadOK && (faviconImage != nil); + if (!gotImageData) + [self addToMissedIconsCache:uriString withExpirationSeconds:SITE_ICON_EXPIRATION_SECONDS]; + + [faviconImage setScalesWhenResized:YES]; + [faviconImage setSize:NSMakeSize(16, 16)]; + + // we always send out the notification, so that clients know + // about failed requests + NSDictionary* notificationData = [NSDictionary dictionaryWithObjectsAndKeys: + inURI, SiteIconLoadURIKey, + faviconImage, SiteIconLoadImageKey, // may be nil + userData, SiteIconLoadUserDataKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName: SiteIconLoadNotificationName + object:target userInfo:notificationData]; +} + +#pragma mark - + ++ (SiteIconProvider*)sharedFavoriteIconProvider +{ + static SiteIconProvider* sIconProvider = nil; + if (!sIconProvider) + { + sIconProvider = [[SiteIconProvider alloc] init]; + } + + return sIconProvider; +} + + ++ (NSString*)faviconLocationStringFromURI:(NSString*)inURI +{ + nsAutoString uriString; + [inURI assignTo_nsAString:uriString]; + + nsAutoString faviconURIString; + MakeFaviconURIFromURI(uriString, faviconURIString); + return [NSString stringWith_nsAString:faviconURIString]; +} + +@end diff --git a/chimera/src/extensions/IconTabViewItem.h b/chimera/src/extensions/IconTabViewItem.h index 13a37120dc8e..b1ee2fc0e26b 100644 --- a/chimera/src/extensions/IconTabViewItem.h +++ b/chimera/src/extensions/IconTabViewItem.h @@ -40,7 +40,8 @@ #import -@interface CHIconTabViewItem : NSTabViewItem { +@interface CHIconTabViewItem : NSTabViewItem +{ NSImage *mTabIcon; NSDictionary* mLabelAttributes; } diff --git a/chimera/src/extensions/IconTabViewItem.mm b/chimera/src/extensions/IconTabViewItem.mm index cf659548c4c0..a7a19c588bb1 100644 --- a/chimera/src/extensions/IconTabViewItem.mm +++ b/chimera/src/extensions/IconTabViewItem.mm @@ -39,8 +39,9 @@ * * ***** END LICENSE BLOCK ***** */ -#import "CHIconTabViewItem.h" +#import "NSString+Utils.h" +#import "CHIconTabViewItem.h" // // NSParagraphStyle has a line break mode which will automatically @@ -91,7 +92,7 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s [labelParagraphStyle setLineBreakMode:NSLineBreakByTruncatingMiddle]; #endif - [labelParagraphStyle setAlignment:NSCenterTextAlignment]; + [labelParagraphStyle setAlignment:NSNaturalTextAlignment]; NSFont *labelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; mLabelAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -139,9 +140,9 @@ static const int kEllipseSpaces = 4; //yes, i know it's 3 ...'s if ([self tabIcon]) { NSPoint drawPoint = NSMakePoint( (tabRect.origin.x), (tabRect.origin.y + 15.0) ); [[self tabIcon] compositeToPoint:drawPoint operation:NSCompositeSourceOver]; - tabRect = NSMakeRect(NSMinX(tabRect)+15.0, + tabRect = NSMakeRect(NSMinX(tabRect) + 18.0, NSMinY(tabRect), - NSWidth(tabRect)-15.0, + NSWidth(tabRect) - 18.0, NSHeight(tabRect)); } diff --git a/chimera/src/preferences/PreferenceManager.h b/chimera/src/preferences/PreferenceManager.h index 3063fc19b835..a682a4c80e4c 100644 --- a/chimera/src/preferences/PreferenceManager.h +++ b/chimera/src/preferences/PreferenceManager.h @@ -35,12 +35,16 @@ * * ***** END LICENSE BLOCK ***** */ -#import +#import #import -@interface CHPreferenceManager : NSObject { +class nsIPref; + +@interface CHPreferenceManager : NSObject +{ NSUserDefaults* mDefaults; ICInstance mInternetConfig; + nsIPref* mPrefs; } + (CHPreferenceManager *)sharedInstance; @@ -54,4 +58,9 @@ - (NSString *) getICStringPref:(ConstStr255Param) prefKey; - (NSString *) homePage:(BOOL) checkStartupPagePref; +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess; + @end diff --git a/chimera/src/preferences/PreferenceManager.mm b/chimera/src/preferences/PreferenceManager.mm index 51228e415557..ef8e2cd0eaa4 100644 --- a/chimera/src/preferences/PreferenceManager.mm +++ b/chimera/src/preferences/PreferenceManager.mm @@ -86,8 +86,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (void) dealloc { + ::ICStop(mInternetConfig); + NS_IF_RELEASE(mPrefs); + nsresult rv; - ICStop (mInternetConfig); nsCOMPtr pref(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { //NSLog(@"Saving prefs file"); @@ -100,7 +102,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (BOOL) initInternetConfig { OSStatus error; - error = ICStart (&mInternetConfig, 'CHIM'); + error = ::ICStart(&mInternetConfig, 'CHIM'); if (error != noErr) { // XXX throw here? NSLog(@"Error initializing IC"); @@ -178,6 +180,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); return NO; } + nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); + mPrefs = prefs; + NS_IF_ADDREF(mPrefs); + [self syncMozillaPrefs]; return YES; } @@ -191,9 +197,8 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); char strbuf[1024]; int numbuf; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) { - // XXXw. throw? + if (!mPrefs) { + NSLog(@"Mozilla prefs not set up successfully"); return; } @@ -201,42 +206,42 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // something that chimera can deal with. PRInt32 acceptCookies = 0; static const char* kCookieBehaviorPref = "network.cookie.cookieBehavior"; - prefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); + mPrefs->GetIntPref(kCookieBehaviorPref, &acceptCookies); if ( acceptCookies == 1 ) { // accept foreign cookies, assume off acceptCookies = 2; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } else if ( acceptCookies == 3 ) { // p3p, assume all cookies on acceptCookies = 0; - prefs->SetIntPref(kCookieBehaviorPref, acceptCookies); + mPrefs->SetIntPref(kCookieBehaviorPref, acceptCookies); } // get proxies from SystemConfiguration - prefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies - prefs->ClearUserPref("network.proxy.http"); - prefs->ClearUserPref("network.proxy.http_port"); - prefs->ClearUserPref("network.proxy.ssl"); - prefs->ClearUserPref("network.proxy.ssl_port"); - prefs->ClearUserPref("network.proxy.ftp"); - prefs->ClearUserPref("network.proxy.ftp_port"); - prefs->ClearUserPref("network.proxy.gopher"); - prefs->ClearUserPref("network.proxy.gopher_port"); - prefs->ClearUserPref("network.proxy.socks"); - prefs->ClearUserPref("network.proxy.socks_port"); - prefs->ClearUserPref("network.proxy.no_proxies_on"); + mPrefs->SetIntPref("network.proxy.type", 0); // 0 == no proxies + mPrefs->ClearUserPref("network.proxy.http"); + mPrefs->ClearUserPref("network.proxy.http_port"); + mPrefs->ClearUserPref("network.proxy.ssl"); + mPrefs->ClearUserPref("network.proxy.ssl_port"); + mPrefs->ClearUserPref("network.proxy.ftp"); + mPrefs->ClearUserPref("network.proxy.ftp_port"); + mPrefs->ClearUserPref("network.proxy.gopher"); + mPrefs->ClearUserPref("network.proxy.gopher_port"); + mPrefs->ClearUserPref("network.proxy.socks"); + mPrefs->ClearUserPref("network.proxy.socks_port"); + mPrefs->ClearUserPref("network.proxy.no_proxies_on"); if ((cfDictionary = SCDynamicStoreCopyProxies (NULL)) != NULL) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPEnable, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.http", strbuf); + mPrefs->SetCharPref("network.proxy.http", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.http_port", numbuf); + mPrefs->SetIntPref("network.proxy.http_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -245,13 +250,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ssl", strbuf); + mPrefs->SetCharPref("network.proxy.ssl", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesHTTPSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ssl_port", numbuf); + mPrefs->SetIntPref("network.proxy.ssl_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -260,13 +265,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.ftp", strbuf); + mPrefs->SetCharPref("network.proxy.ftp", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesFTPPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.ftp_port", numbuf); + mPrefs->SetIntPref("network.proxy.ftp_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -275,13 +280,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.gopher", strbuf); + mPrefs->SetCharPref("network.proxy.gopher", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesGopherPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.gopher_port", numbuf); + mPrefs->SetIntPref("network.proxy.gopher_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -290,13 +295,13 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE && numbuf == 1) { if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSProxy, (const void **)&cfString) == TRUE) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.socks", strbuf); + mPrefs->SetCharPref("network.proxy.socks", strbuf); } if (CFDictionaryGetValueIfPresent (cfDictionary, kSCPropNetProxiesSOCKSPort, (const void **)&cfNumber) == TRUE) { if (CFNumberGetValue (cfNumber, kCFNumberIntType, &numbuf) == TRUE) { - prefs->SetIntPref("network.proxy.socks_port", numbuf); + mPrefs->SetIntPref("network.proxy.socks_port", numbuf); } - prefs->SetIntPref("network.proxy.type", 1); + mPrefs->SetIntPref("network.proxy.type", 1); } } } @@ -305,7 +310,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); cfString = CFStringCreateByCombiningStrings (NULL, cfArray, CFSTR(", ")); if (CFStringGetLength (cfString) > 0) { if (CFStringGetCString (cfString, strbuf, sizeof(strbuf)-1, kCFStringEncodingASCII) == TRUE) { - prefs->SetCharPref("network.proxy.no_proxies_on", strbuf); + mPrefs->SetCharPref("network.proxy.no_proxies_on", strbuf); } } } @@ -313,24 +318,73 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); } } -// convenience routines for mozilla prefs -- (NSString*)getMozillaPrefString: (const char*)prefName +- (NSString*)getStringPref: (const char*)prefName withSuccess:(BOOL*)outSuccess { - NSMutableString *prefValue = [[[NSMutableString alloc] init] autorelease]; + NSString *prefValue = @""; - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (prefs) { - char *buf = nsnull; - nsresult rv = prefs->GetCharPref(prefName, &buf); - if (NS_SUCCEEDED(rv) && buf) { - [prefValue setString:[NSString stringWithCString:buf]]; - free(buf); - } + char *buf = nsnull; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetCharPref(prefName, &buf); + + if (NS_SUCCEEDED(rv) && buf) { + // prefs are UTF-8 + prefValue = [NSString stringWithUTF8String:buf]; + free(buf); + if (outSuccess) *outSuccess = YES; + } else { + if (outSuccess) *outSuccess = NO; } return prefValue; } +- (NSColor*)getColorPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + // colors are stored in HTML-like #FFFFFF strings + NSString* colorString = [self getStringPref:prefName withSuccess:outSuccess]; + NSColor* returnColor = [NSColor blackColor]; + + if ([colorString hasPrefix:@"#"] && [colorString length] == 7) + { + unsigned int redInt, greenInt, blueInt; + sscanf([colorString cString], "#%02x%02x%02x", &redInt, &greenInt, &blueInt); + + float redFloat = ((float)redInt / 255.0); + float greenFloat = ((float)greenInt / 255.0); + float blueFloat = ((float)blueInt / 255.0); + + returnColor = [NSColor colorWithCalibratedRed:redFloat green:greenFloat blue:blueFloat alpha:1.0f]; + } + + return returnColor; +} + +- (BOOL)getBooleanPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRBool boolPref = PR_FALSE; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + rv = mPrefs->GetBoolPref(prefName, &boolPref); + + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + + return boolPref ? YES : NO; +} + +- (int)getIntPref: (const char*)prefName withSuccess:(BOOL*)outSuccess +{ + PRInt32 intPref = 0; + nsresult rv = NS_ERROR_FAILURE; + if (mPrefs) + mPrefs->GetIntPref(prefName, &intPref); + if (outSuccess) + *outSuccess = NS_SUCCEEDED(rv); + return intPref; +} + + //- (BOOL) getICBoolPref:(ConstStr255Param) prefKey; //{ @@ -381,8 +435,7 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); - (NSString *) homePage:(BOOL)checkStartupPagePref { - nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); - if (!prefs) + if (!mPrefs) return @"about:blank"; PRInt32 mode = 1; @@ -394,14 +447,14 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); // is true. nsresult rv = NS_OK; if ( checkStartupPagePref ) - rv = prefs->GetIntPref("browser.startup.page", &mode); + rv = mPrefs->GetIntPref("browser.startup.page", &mode); if (NS_FAILED(rv) || mode == 1) { // see which home page to use PRBool boolPref; - if (NS_SUCCEEDED(prefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) + if (NS_SUCCEEDED(mPrefs->GetBoolPref("chimera.use_system_home_page", &boolPref)) && boolPref) return [self getICStringPref:kICWWWHomePage]; - nsCOMPtr prefBranch = do_QueryInterface(prefs); + nsCOMPtr prefBranch = do_QueryInterface(mPrefs); if (!prefBranch) return @"about:blank"; NSString* homepagePref = nil; @@ -411,10 +464,10 @@ app_getModuleInfo(nsStaticModuleInfo **info, PRUint32 *count); homepagePref = NSLocalizedStringFromTable( @"HomePageDefault", @"WebsiteDefaults", nil); // and let's copy this into the homepage pref if it's not bad if (![homepagePref isEqualToString:@"HomePageDefault"]) - prefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); + mPrefs->SetCharPref("browser.startup.homepage", [homepagePref UTF8String]); } else { - homepagePref = [self getMozillaPrefString:"browser.startup.homepage"]; + homepagePref = [self getStringPref:"browser.startup.homepage" withSuccess:NULL]; } if (homepagePref && [homepagePref length] > 0 && ![homepagePref isEqualToString:@"HomePageDefault"])