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

Assorted fixes and updates to HTML New Tab Page #3748

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15385,8 +15385,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 225.0.0;
branch = dominik/customizer;
kind = branch;
};
};
9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "20e6eaf0b1e423d9a270e2d460cae284c08f73d8",
"version" : "225.0.0"
"branch" : "dominik/customizer",
"revision" : "b951930902a499af162f985fbe408949bd0cb599"
}
},
{
"identity" : "content-scope-scripts",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/content-scope-scripts",
"state" : {
"revision" : "7958ddab724c26326333cae13fe81478290607fa",
"version" : "7.6.0"
"revision" : "0ac30560ec969a321caea321f95537120416c323",
"version" : "7.7.0"
}
},
{
Expand Down
22 changes: 10 additions & 12 deletions DuckDuckGo/NewTabPage/DefaultsFavoritesActionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ final class DefaultFavoritesActionsHandler: FavoritesActionsHandling {
bookmarkManager.moveFavorites(with: [bookmarkID], toIndex: index) { _ in }
}

@MainActor
func onFaviconMissing() {
faviconsFetcherOnboarding?.presentOnboardingIfNeeded()
}

@MainActor
private var window: NSWindow? {
WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.view.window
Expand All @@ -86,14 +81,17 @@ final class DefaultFavoritesActionsHandler: FavoritesActionsHandling {
private var tabCollectionViewModel: TabCollectionViewModel? {
WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel
}
}

extension Bookmark: NewTabPageFavorite {
private enum Const {
static let wwwPrefix = "www."
}

private lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding? = {
guard let syncService = NSApp.delegateTyped.syncService, let syncBookmarksAdapter = NSApp.delegateTyped.syncDataProviders?.bookmarksAdapter else {
assertionFailure("SyncService and/or SyncBookmarksAdapter is nil")
var etldPlusOne: String? {
guard let domain = urlObject?.host else {
return nil
}
return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter)
}()
return ContentBlocking.shared.tld.eTLDplus1(domain)?.dropping(prefix: Const.wwwPrefix)
}
}

extension Bookmark: NewTabPageFavorite {}
19 changes: 19 additions & 0 deletions DuckDuckGo/NewTabPage/NewTabPageCustomizationProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ final class NewTabPageCustomizationProvider: NewTabPageCustomBackgroundProviding
}
homePageSettingsModel.customImagesManager?.deleteImage(image)
}

@MainActor
func showContextMenu(for imageID: String, using presenter: any NewTabPageContextMenuPresenting) async {
let menu = NSMenu()

menu.buildItems {
NSMenuItem(title: UserText.deleteBackground, action: #selector(deleteBackground(_:)), target: self, representedObject: imageID)
.withAccessibilityIdentifier("HomePage.Views.deleteBackground")
}

presenter.showContextMenu(menu)
}

@objc public func deleteBackground(_ sender: NSMenuItem) {
Task {
guard let imageID = sender.representedObject as? String else { return }
await deleteImage(with: imageID)
}
}
}

extension NewTabPageDataModel.Background {
Expand Down
13 changes: 13 additions & 0 deletions DuckDuckGo/Tab/Navigation/DuckURLSchemeHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ final class DuckURLSchemeHandler: NSObject, WKURLSchemeHandler {
}

func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {}

private lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding? = {
guard let syncService = NSApp.delegateTyped.syncService, let syncBookmarksAdapter = NSApp.delegateTyped.syncDataProviders?.bookmarksAdapter else {
assertionFailure("SyncService and/or SyncBookmarksAdapter is nil")
return nil
}
return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter)
}()
}

// MARK: - Native UI Paged
Expand Down Expand Up @@ -177,11 +185,16 @@ private extension DuckURLSchemeHandler {
guard let response = HTTPURLResponse(url: requestURL, statusCode: 404, httpVersion: "HTTP/1.1", headerFields: nil) else {
return nil
}
onFaviconMissing()
return (response, Data())
}
let response = URLResponse(url: requestURL, mimeType: "image/png", expectedContentLength: imagePNGData.count, textEncodingName: nil)
return (response, imagePNGData)
}

private func onFaviconMissing() {
faviconsFetcherOnboarding?.presentOnboardingIfNeeded()
}
}

// MARK: - Custom Background Images
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ extension FeatureFlag: FeatureFlagDescribing {
case .networkProtectionEnforceRoutes:
return .remoteDevelopment(.subfeature(NetworkProtectionSubfeature.enforceRoutes))
case .htmlNewTabPage:
return .disabled
return .remoteReleasable(.subfeature(HTMLNewTabPageSubfeature.isLaunched))
case .autofillPartialFormSaves:
return .remoteReleasable(.subfeature(AutofillSubfeature.partialFormSaves))
case .autcompleteTabs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,24 @@ public protocol NewTabPageCustomBackgroundProviding: AnyObject {

@MainActor func presentUploadDialog() async
func deleteImage(with imageID: String) async

@MainActor func showContextMenu(for imageID: String, using presenter: NewTabPageContextMenuPresenting) async
}

public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient {

let model: NewTabPageCustomBackgroundProviding
let contextMenuPresenter: NewTabPageContextMenuPresenting
public weak var userScriptsSource: NewTabPageUserScriptsSource?

private var cancellables: Set<AnyCancellable> = []

public init(model: NewTabPageCustomBackgroundProviding) {
public init(
model: NewTabPageCustomBackgroundProviding,
contextMenuPresenter: NewTabPageContextMenuPresenting = DefaultNewTabPageContextMenuPresenter()
) {
self.model = model
self.contextMenuPresenter = contextMenuPresenter

model.backgroundPublisher
.sink { [weak self] background in
Expand Down Expand Up @@ -82,6 +89,7 @@ public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient {

enum MessageName: String, CaseIterable {
case autoOpen = "customizer_autoOpen"
case contextMenu = "customizer_contextMenu"
case deleteImage = "customizer_deleteImage"
case onBackgroundUpdate = "customizer_onBackgroundUpdate"
case onImagesUpdate = "customizer_onImagesUpdate"
Expand All @@ -93,13 +101,23 @@ public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient {

public func registerMessageHandlers(for userScript: any SubfeatureWithExternalMessageHandling) {
userScript.registerMessageHandlers([
MessageName.contextMenu.rawValue: { [weak self] in try await self?.showContextMenu(params: $0, original: $1) },
MessageName.deleteImage.rawValue: { [weak self] in try await self?.deleteImage(params: $0, original: $1) },
MessageName.setBackground.rawValue: { [weak self] in try await self?.setBackground(params: $0, original: $1) },
MessageName.setTheme.rawValue: { [weak self] in try await self?.setTheme(params: $0, original: $1) },
MessageName.upload.rawValue: { [weak self] in try await self?.upload(params: $0, original: $1) },
])
}

@MainActor
func showContextMenu(params: Any, original: WKScriptMessage) async throws -> Encodable? {
guard let contextMenu: NewTabPageDataModel.UserImageContextMenu = DecodableHelper.decode(from: params) else {
return nil
}
await model.showContextMenu(for: contextMenu.id, using: contextMenuPresenter)
return nil
}

@MainActor
func deleteImage(params: Any, original: WKScriptMessage) async throws -> Encodable? {
guard let data: NewTabPageDataModel.DeleteImageData = DecodableHelper.decode(from: params) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,13 @@ extension NewTabPageDataModel {
struct DeleteImageData: Codable, Equatable {
let id: String
}

struct UserImageContextMenu: Codable, Equatable {
let target: Target
let id: String

enum Target: String, Codable, Equatable {
case userImage
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,30 @@ extension NewTabPageDataModel {
}

struct Favorite: Encodable, Equatable {
let etldPlusOne: String?
let favicon: FavoriteFavicon?
let id: String
let title: String
let url: String

init(id: String, title: String, url: String, favicon: NewTabPageDataModel.FavoriteFavicon? = nil) {
init(id: String, title: String, url: String, etldPlusOne: String?, favicon: NewTabPageDataModel.FavoriteFavicon? = nil) {
self.id = id
self.title = title
self.url = url
self.favicon = favicon
self.etldPlusOne = etldPlusOne
}

@MainActor
init(_ bookmark: NewTabPageFavorite, preferredFaviconSize: Int, onFaviconMissing: () -> Void) {
init(_ bookmark: NewTabPageFavorite, preferredFaviconSize: Int) {
id = bookmark.id
title = bookmark.title
url = bookmark.url
etldPlusOne = bookmark.etldPlusOne

if let url = bookmark.urlObject, let duckFaviconURL = URL.duckFavicon(for: url) {
favicon = FavoriteFavicon(maxAvailableSize: preferredFaviconSize, src: duckFaviconURL.absoluteString)
} else {
onFaviconMissing()
favicon = nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public protocol NewTabPageFavorite {
var title: String { get }
var url: String { get }
var urlObject: URL? { get }
var etldPlusOne: String? { get }
}

public protocol FavoritesActionsHandling {
Expand All @@ -31,7 +32,6 @@ public protocol FavoritesActionsHandling {
@MainActor func open(_ url: URL, target: FavoriteOpenTarget)
@MainActor func addNewFavorite()
@MainActor func edit(_ favorite: FavoriteType)
@MainActor func onFaviconMissing()

func removeFavorite(_ favorite: FavoriteType)
func deleteBookmark(for favorite: FavoriteType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ public final class NewTabPageFavoritesClient<FavoriteType, ActionHandler>: NewTa
@MainActor
private func getData(params: Any, original: WKScriptMessage) async throws -> Encodable? {
let favorites = favoritesModel.favorites.map {
NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize, onFaviconMissing: favoritesModel.onFaviconMissing)
NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize)
}
return NewTabPageDataModel.FavoritesData(favorites: favorites)
}

@MainActor
private func notifyDataUpdated(_ favorites: [NewTabPageFavorite]) {
let favorites = favoritesModel.favorites.map {
NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize, onFaviconMissing: favoritesModel.onFaviconMissing)
NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize)
}
pushMessage(named: MessageName.onDataUpdate.rawValue, params: NewTabPageDataModel.FavoritesData(favorites: favorites))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,6 @@ public final class NewTabPageFavoritesModel<FavoriteType, ActionHandler>: NSObje
actionsHandler.addNewFavorite()
}

@MainActor
func onFaviconMissing() {
actionsHandler.onFaviconMissing()
}

// MARK: Context Menu

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ final class CapturingNewTabPageCustomBackgroundProvider: NewTabPageCustomBackgro
deleteImageCalls.append(imageID)
}

func showContextMenu(for imageID: String, using presenter: any NewTabPage.NewTabPageContextMenuPresenting) async {
showContextMenuCalls.append(imageID)
}

var presentUploadDialogCallsCount: Int = 0
var deleteImageCalls: [String] = []
var showContextMenuCalls: [String] = []
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ final class CapturingNewTabPageFavoritesActionsHandler: FavoritesActionsHandling
editCalls.append(favorite)
}

func onFaviconMissing() {
onFaviconMissingCallCount += 1
}

func removeFavorite(_ favorite: MockNewTabPageFavorite) {
removeFavoriteCalls.append(favorite)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ struct MockNewTabPageFavorite: NewTabPageFavorite, Equatable {
var urlObject: URL? {
URL(string: url)
}

var etldPlusOne: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,29 @@ import XCTest
final class NewTabPageCustomBackgroundClientTests: XCTestCase {
private var client: NewTabPageCustomBackgroundClient!
private var model: CapturingNewTabPageCustomBackgroundProvider!
private var contextMenuPresenter: CapturingNewTabPageContextMenuPresenter!
private var userScript: NewTabPageUserScript!
private var messageHelper: MessageHelper<NewTabPageCustomBackgroundClient.MessageName>!

override func setUpWithError() throws {
try super.setUpWithError()
model = CapturingNewTabPageCustomBackgroundProvider()
client = NewTabPageCustomBackgroundClient(model: model)
contextMenuPresenter = CapturingNewTabPageContextMenuPresenter()
client = NewTabPageCustomBackgroundClient(model: model, contextMenuPresenter: contextMenuPresenter)

userScript = NewTabPageUserScript()
messageHelper = .init(userScript: userScript)
client.registerMessageHandlers(for: userScript)
}

// MARK: - contextMenu

func testThatContextMenuActionIsForwardedToTheModel() async throws {
let action = NewTabPageDataModel.UserImageContextMenu(target: .userImage, id: "abcd.jpg")
try await messageHelper.handleMessageExpectingNilResponse(named: .contextMenu, parameters: action)
XCTAssertEqual(model.showContextMenuCalls, ["abcd.jpg"])
}

// MARK: - deleteImage

func testThatDeleteImageCallsModel() async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ final class NewTabPageFavoritesClientTests: XCTestCase {
]
let data: NewTabPageDataModel.FavoritesData = try await messageHelper.handleMessage(named: .getData)
XCTAssertEqual(data.favorites, [
.init(id: "1", title: "A", url: "https://a.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//a.com")),
.init(id: "10", title: "B", url: "https://b.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//b.com")),
.init(id: "5", title: "C", url: "https://c.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//c.com")),
.init(id: "2", title: "D", url: "https://d.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//d.com")),
.init(id: "3", title: "E", url: "https://e.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//e.com"))
.init(id: "1", title: "A", url: "https://a.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//a.com")),
.init(id: "10", title: "B", url: "https://b.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//b.com")),
.init(id: "5", title: "C", url: "https://c.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//c.com")),
.init(id: "2", title: "D", url: "https://d.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//d.com")),
.init(id: "3", title: "E", url: "https://e.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//e.com"))
])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ public struct LetterIconView: View {
public var characterCount: Int
private var paddingModifier: CGFloat
private var font: Font
private static let wwwPreffix = "www."
private static let wwwPrefix = "www."

private var characters: String {
if let prefferedFirstCharacters = prefferedFirstCharacters,
prefferedFirstCharacters != "" {
return String(prefferedFirstCharacters.prefix(characterCount))
}
return String(title.replacingOccurrences(of: Self.wwwPreffix, with: "").prefix(characterCount))
return String(title.replacingOccurrences(of: Self.wwwPrefix, with: "").prefix(characterCount))
}

/// Initializes a `LetterIconView`
Expand Down
Loading
Loading