Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Favicons #388

Merged
merged 46 commits into from
Jan 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5fb5732
Favicons Xcode folder group
tomasstrba Dec 7, 2021
228cb29
Refactored to FaviconManager
tomasstrba Dec 7, 2021
4854a57
Merge branch 'develop' into tom/favicons
tomasstrba Dec 8, 2021
5459425
In progress
tomasstrba Dec 9, 2021
af45ce4
Merge branch 'develop' into tom/favicons
tomasstrba Dec 16, 2021
24b8ed4
Using 16x16 icon for tab bar favicons as the last option
tomasstrba Dec 26, 2021
86e2611
Merge branch 'develop' into tom/favicons
tomasstrba Dec 26, 2021
bdcdc96
Using small favicon for homepage view
tomasstrba Dec 26, 2021
0de47bf
Queue bariers + Cache invalidation
tomasstrba Dec 28, 2021
a5d079b
Removing in general
tomasstrba Jan 2, 2022
4a50afa
Burning
tomasstrba Jan 2, 2022
249c666
Burning exceptions - bookmarks
tomasstrba Jan 2, 2022
9e8d982
Regular cleaning of old favicons
tomasstrba Jan 3, 2022
d3e83d9
Logs corrected
tomasstrba Jan 3, 2022
48730a2
Unit tests compilation fixed
tomasstrba Jan 3, 2022
ab6fd70
Code corrections
tomasstrba Jan 3, 2022
5b46c47
Prevent initial content setting to trigger storing of the state
tomasstrba Jan 5, 2022
da607ff
AppStateChangePublisherTests corrected
tomasstrba Jan 5, 2022
3c46782
Fire tests fixed
tomasstrba Jan 5, 2022
4d3c601
Custom queue usage refactored. Currently, the queue is usd just for t…
tomasstrba Jan 5, 2022
71fad17
Unit tests compilation fixed
tomasstrba Jan 5, 2022
9e00a45
Loading of favicons after app finished launching. + Unit tests of bur…
tomasstrba Jan 5, 2022
f366f08
Unnecessary unit test removed
tomasstrba Jan 5, 2022
2afb3ff
Fixed issue of outdated favicon not being refreshed due to the optimi…
tomasstrba Jan 6, 2022
6a76ac9
Long method refactored into multiple ones
tomasstrba Jan 6, 2022
2f42876
Filtering out huge favicons that slowed down or used too much of the …
tomasstrba Jan 6, 2022
a78a085
Hompage favicons resized if necessary to avoid the blurry artefacts
tomasstrba Jan 7, 2022
b93dd5e
Merge branch 'develop' into tom/favicons
tomasstrba Jan 7, 2022
8efa496
Raising the bar for filtering out large favicons (twitter.com)
tomasstrba Jan 9, 2022
af9a9f5
asyncAfter() replaced with .dropFirst()
tomasstrba Jan 9, 2022
2a88dd5
Refreshing favicons in bookmark views
tomasstrba Jan 10, 2022
3f4661b
Small cropping of the favicon on homepage disabled
tomasstrba Jan 10, 2022
ffa5089
Making sure small images are used if medium aren't available
tomasstrba Jan 10, 2022
f0c0ee0
Removing Retina resizing logic to keep the homepage experience unchanged
tomasstrba Jan 11, 2022
9e75ede
Merge branch 'develop' into tom/favicons
tomasstrba Jan 12, 2022
2bb6dc8
Using URLSession.dataTask instead of NSImage(contentsOf:)
tomasstrba Jan 12, 2022
10c0b25
PR comments
tomasstrba Jan 13, 2022
4e5684b
Burning exception of bookmarked domains
tomasstrba Jan 14, 2022
52cbfa6
Favicons of Logins added to burning exceptions
tomasstrba Jan 14, 2022
0ada8f4
Unit tests compilation fixed
tomasstrba Jan 14, 2022
e1c957f
SwiftLint issue
tomasstrba Jan 14, 2022
46e2e16
Import of BSK corrected
tomasstrba Jan 14, 2022
e2118a4
Using favicon manager mock in unit tests
tomasstrba Jan 14, 2022
edc29da
SecureVault exceptions removed
tomasstrba Jan 16, 2022
59dc721
Unit tests adjusted
tomasstrba Jan 16, 2022
d2ed224
Unnecessary assertionFailure removed
tomasstrba Jan 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 77 additions & 11 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions DuckDuckGo/AppDelegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

HTTPSUpgrade.shared.loadDataAsync()
LocalBookmarkManager.shared.loadBookmarks()
FaviconManager.shared.loadFavicons()
_=ConfigurationManager.shared
_=DownloadListCoordinator.shared

Expand Down
20 changes: 14 additions & 6 deletions DuckDuckGo/Bookmarks/Model/Bookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ internal class BaseBookmarkEntity {
return Bookmark(id: id,
url: url,
title: title,
favicon: managedObject.faviconEncrypted as? NSImage,
oldFavicon: managedObject.faviconEncrypted as? NSImage,
isFavorite: managedObject.isFavorite,
parentFolderUUID: parentFolderUUID)
}
Expand Down Expand Up @@ -134,21 +134,29 @@ final class Bookmark: BaseBookmarkEntity {
}

let url: URL
var favicon: NSImage?
var isFavorite: Bool
var parentFolderUUID: UUID?

// Property oldFavicon can be removed in future updates when favicon cache is built
var oldFavicon: NSImage?
let faviconManagement: FaviconManagement
func favicon(_ sizeCategory: Favicon.SizeCategory) -> NSImage? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion here would be to add a default value (_ sizeCategory: Favicon.SizeCategory = .small) since most places calling this are requesting the .small version.
But there's also an argument to be made that always requiring the category is better to telegraph the intention on the call site.
I don't have strong opinions on this, LGTM either way, but I'm curious to know your opinion :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a hard choice :D I believe a developer should always think what size of favicon is required. Once there is a default argument Xcode usually autocompletes the method without the argument. In this case, I think .small or .medium as an argument isn't a lot of work and there is no risk of bad favicon usage.

Agree? :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it makes sense. Thanks for explaining your opinion :)

return faviconManagement.getCachedFavicon(for: url, sizeCategory: sizeCategory)?.image ?? oldFavicon
}

init(id: UUID,
url: URL,
title: String,
favicon: NSImage? = nil,
oldFavicon: NSImage? = nil,
isFavorite: Bool,
parentFolderUUID: UUID? = nil) {
parentFolderUUID: UUID? = nil,
faviconManagement: FaviconManagement = FaviconManager.shared) {

self.url = url
self.favicon = favicon
self.oldFavicon = oldFavicon
self.isFavorite = isFavorite
self.parentFolderUUID = parentFolderUUID
self.faviconManagement = faviconManagement

super.init(id: id, title: title, isFolder: false)
}
Expand All @@ -157,7 +165,7 @@ final class Bookmark: BaseBookmarkEntity {
self.init(id: bookmark.id,
url: newUrl,
title: bookmark.title,
favicon: nil,
oldFavicon: nil,
isFavorite: bookmark.isFavorite)
}

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/Model/BookmarkList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct BookmarkList {

var topLevelEntities: [BaseBookmarkEntity] = []

private var allBookmarkURLsOrdered: [URL]
private(set) var allBookmarkURLsOrdered: [URL]
private var favoriteBookmarkURLsOrdered: [URL]
private var itemsDict: [URL: Bookmark]

Expand Down
46 changes: 13 additions & 33 deletions DuckDuckGo/Bookmarks/Model/BookmarkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Combine
protocol BookmarkManager: AnyObject {

func isUrlBookmarked(url: URL) -> Bool
func isHostInBookmarks(host: String) -> Bool
func getBookmark(for url: URL) -> Bookmark?
@discardableResult func makeBookmark(for url: URL, title: String, isFavorite: Bool) -> Bookmark?
@discardableResult func makeFolder(for title: String, parent: BookmarkFolder?) -> BookmarkFolder
Expand All @@ -45,22 +46,18 @@ final class LocalBookmarkManager: BookmarkManager {

static let shared = LocalBookmarkManager()

private init() {
subscribeToCachedFavicons()
}
private init() {}

init(bookmarkStore: BookmarkStore, faviconService: FaviconService) {
init(bookmarkStore: BookmarkStore, faviconManagement: FaviconManagement) {
self.bookmarkStore = bookmarkStore
self.faviconService = faviconService

subscribeToCachedFavicons()
self.faviconManagement = faviconManagement
}

@Published private(set) var list: BookmarkList?
var listPublisher: Published<BookmarkList?>.Publisher { $list }

private lazy var bookmarkStore: BookmarkStore = LocalBookmarkStore()
private lazy var faviconService: FaviconService = LocalFaviconService.shared
private lazy var faviconManagement: FaviconManagement = FaviconManager.shared

// MARK: - Bookmarks

Expand All @@ -86,6 +83,12 @@ final class LocalBookmarkManager: BookmarkManager {
return list?[url] != nil
}

func isHostInBookmarks(host: String) -> Bool {
return list?.allBookmarkURLsOrdered.contains(where: { url in
url.host == host
}) ?? false
}

func getBookmark(for url: URL) -> Bookmark? {
return list?[url]
}
Expand All @@ -99,7 +102,7 @@ final class LocalBookmarkManager: BookmarkManager {
}

let id = UUID()
let bookmark = Bookmark(id: id, url: url, title: title, favicon: favicon(for: url.host), isFavorite: isFavorite)
let bookmark = Bookmark(id: id, url: url, title: title, isFavorite: isFavorite)

list?.insert(bookmark)
bookmarkStore.save(bookmark: bookmark, parent: nil) { [weak self] success, _ in
Expand Down Expand Up @@ -201,32 +204,9 @@ final class LocalBookmarkManager: BookmarkManager {

// MARK: - Favicons

private var faviconCancellable: AnyCancellable?

private func subscribeToCachedFavicons() {
faviconCancellable = faviconService.cachedFaviconsPublisher
.sink(receiveValue: { [weak self] (host, favicon) in
self?.update(favicon: favicon, for: host)
})
}

private func update(favicon: NSImage, for host: String) {
guard let bookmarks = list?.bookmarks() else { return }

bookmarks
.filter { $0.url.host == host &&
$0.favicon?.size.isSmaller(than: favicon.size) ?? true
}
.forEach {
let bookmark = $0
bookmark.favicon = favicon
update(bookmark: bookmark)
}
}

private func favicon(for host: String?) -> NSImage? {
if let host = host {
return faviconService.getCachedFavicon(for: host, mustBeFromUserScript: false)
return faviconManagement.getCachedFavicon(for: host, sizeCategory: .small)?.image
}

return nil
Expand Down
2 changes: 0 additions & 2 deletions DuckDuckGo/Bookmarks/Services/BookmarkStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ final class LocalBookmarkStore: BookmarkStore {
bookmarkMO.id = bookmark.id
bookmarkMO.urlEncrypted = bookmark.url as NSURL?
bookmarkMO.titleEncrypted = bookmark.title as NSString
bookmarkMO.faviconEncrypted = bookmark.favicon
bookmarkMO.isFavorite = bookmark.isFavorite
bookmarkMO.isFolder = bookmark.isFolder
bookmarkMO.dateAdded = NSDate.now
Expand Down Expand Up @@ -485,7 +484,6 @@ fileprivate extension BookmarkManagedObject {
id = bookmark.id
urlEncrypted = bookmark.url as NSURL?
titleEncrypted = bookmark.title as NSString
faviconEncrypted = bookmark.favicon
isFavorite = bookmark.isFavorite
isFolder = false
}
Expand Down
6 changes: 6 additions & 0 deletions DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ final class BookmarkListViewController: NSViewController {
}.store(in: &cancellables)
}

override func viewWillAppear() {
super.viewWillAppear()

reloadData()
}

private func reloadData() {
let selectedNodes = self.selectedNodes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ final class BookmarkManagementSidebarViewController: NSViewController {

override func viewWillAppear() {
super.viewWillAppear()
reloadData()

tabSwitcherButton.select(tabType: .bookmarks)
}

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class BookmarkOutlineViewCell: NSTableCellView {
private func commonInit() {}

func update(from bookmark: Bookmark) {
faviconImageView.image = bookmark.favicon ?? Self.defaultBookmarkFavicon
faviconImageView.image = bookmark.favicon(.small) ?? Self.defaultBookmarkFavicon
titleLabel.stringValue = bookmark.title
countLabel.stringValue = ""
}
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ final class BookmarkTableCellView: NSTableCellView, NibLoadable {
func update(from bookmark: Bookmark) {
self.entity = bookmark

faviconImageView.image = bookmark.favicon ?? Self.defaultBookmarkFavicon
faviconImageView.image = bookmark.favicon(.small)
accessoryImageView.image = bookmark.isFavorite ? Self.favoriteAccessoryViewImage : nil
favoriteButton.image = bookmark.isFavorite ? Self.favoriteFilledAccessoryViewImage : Self.favoriteAccessoryViewImage
primaryTitleLabelValue = bookmark.title
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/ViewModel/BookmarkViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct BookmarkViewModel {
// bookmark.isFavorite ? bookmark.favicon?.makeFavoriteOverlay() : bookmark.favicon

if let bookmark = entity as? Bookmark {
let favicon = bookmark.favicon?.copy() as? NSImage
let favicon = bookmark.favicon(.small)?.copy() as? NSImage
favicon?.size = NSSize.faviconSize
return favicon
} else if entity is BookmarkFolder {
Expand Down
54 changes: 16 additions & 38 deletions DuckDuckGo/BrowserTab/Model/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ final class Tab: NSObject {
weak var delegate: TabDelegate?

init(content: TabContent,
faviconService: FaviconService = LocalFaviconService.shared,
faviconManagement: FaviconManagement = FaviconManager.shared,
webCacheManager: WebCacheManager = WebCacheManager.shared,
webViewConfiguration: WebViewConfiguration? = nil,
historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared,
Expand All @@ -105,7 +105,7 @@ final class Tab: NSObject {
canBeClosedWithBack: Bool = false) {

self.content = content
self.faviconService = faviconService
self.faviconManagement = faviconManagement
self.historyCoordinating = historyCoordinating
self.scriptsSource = scriptsSource
self.visitedDomains = visitedDomains
Expand All @@ -125,12 +125,6 @@ final class Tab: NSObject {
super.init()

setupWebView(shouldLoadInBackground: shouldLoadInBackground)

// cache session-restored favicon if present
if let favicon = favicon,
let host = content.url?.host {
faviconService.cacheIfNeeded(favicon: favicon, for: host, isFromUserScript: false)
}
}

deinit {
Expand Down Expand Up @@ -382,32 +376,22 @@ final class Tab: NSObject {
// MARK: - Favicon

@Published var favicon: NSImage?
let faviconService: FaviconService
let faviconManagement: FaviconManagement

private func handleFavicon(oldContent: TabContent) {
if !content.isUrl {
favicon = nil
}
if oldContent.url?.host != content.url?.host {
fetchFavicon(nil, for: content.url?.host, isFromUserScript: false)
}
}
guard faviconManagement.areFaviconsLoaded else { return }

private func fetchFavicon(_ faviconURL: URL?, for host: String?, isFromUserScript: Bool) {
if favicon != nil {
guard content.isUrl, let url = content.url else {
favicon = nil
}

guard let host = host else {
return
}

faviconService.fetchFavicon(faviconURL, for: host, isFromUserScript: isFromUserScript) { (image, error) in
guard error == nil, let image = image else {
return
if let cachedFavicon = faviconManagement.getCachedFavicon(for: url, sizeCategory: .small)?.image {
if cachedFavicon != favicon {
favicon = cachedFavicon
}

self.favicon = image
} else {
favicon = nil
}
}

Expand Down Expand Up @@ -588,20 +572,14 @@ extension Tab: ContextMenuDelegate {

extension Tab: FaviconUserScriptDelegate {

func faviconUserScript(_ faviconUserScript: FaviconUserScript, didFindFavicon faviconUrl: URL) {
guard let host = self.content.url?.host else {
return
}

faviconService.fetchFavicon(faviconUrl, for: host, isFromUserScript: true) { (image, error) in
guard host == self.content.url?.host else {
return
}
guard error == nil, let image = image else {
func faviconUserScript(_ faviconUserScript: FaviconUserScript,
didFindFaviconLinks faviconLinks: [FaviconUserScript.FaviconLink],
for documentUrl: URL) {
faviconManagement.handleFaviconLinks(faviconLinks, documentUrl: documentUrl) { favicon in
guard documentUrl == self.content.url, let favicon = favicon else {
return
}

self.favicon = image
self.favicon = favicon.image
}
}

Expand Down
Loading