diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f6bbf59ad9..24acdb46c7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -323,6 +323,7 @@ 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; + 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -509,7 +510,7 @@ 85F0E97329952D7A003D5181 /* DuckDuckGo Recovery Document.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 85F0E97229952D7A003D5181 /* DuckDuckGo Recovery Document.pdf */; }; 85F200002215C17B006BB258 /* FindInPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F2FFFF2215C17B006BB258 /* FindInPage.swift */; }; 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F200032216F5D8006BB258 /* FindInPageView.swift */; }; - 85F200072217032E006BB258 /* OmniBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F20005221702F7006BB258 /* OmniBarTests.swift */; }; + 85F200072217032E006BB258 /* AddressDisplayHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F20005221702F7006BB258 /* AddressDisplayHelperTests.swift */; }; 85F21DB0210F5E32002631A6 /* AtbIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F21DAF210F5E32002631A6 /* AtbIntegrationTests.swift */; }; 85F21DC021123B03002631A6 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F143C2E41E4A4CD400CFDE3A /* Core.framework */; }; 85F21DC621145DD5002631A6 /* global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512BCBF2061B6110085E862 /* global.swift */; }; @@ -1404,6 +1405,7 @@ 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; + 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelper.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -1595,7 +1597,7 @@ 85EE7F58224673C5000FE757 /* WebContainerNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebContainerNavigationController.swift; sourceTree = ""; }; 85F0E97229952D7A003D5181 /* DuckDuckGo Recovery Document.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "DuckDuckGo Recovery Document.pdf"; sourceTree = ""; }; 85F200032216F5D8006BB258 /* FindInPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInPageView.swift; sourceTree = ""; }; - 85F20005221702F7006BB258 /* OmniBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarTests.swift; sourceTree = ""; }; + 85F20005221702F7006BB258 /* AddressDisplayHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelperTests.swift; sourceTree = ""; }; 85F21DAD210F5E32002631A6 /* AtbUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AtbUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 85F21DAF210F5E32002631A6 /* AtbIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtbIntegrationTests.swift; sourceTree = ""; }; 85F21DB1210F5E32002631A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -5355,6 +5357,7 @@ children = ( F114C55A1E66EB020018F95F /* NibLoading.swift */, F1C4A70D1E57725800A6CA1B /* OmniBar.swift */, + 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */, 98D16975250CE707009513CC /* OmniBar.xib */, F130D7391E5776C500C45811 /* OmniBarDelegate.swift */, F1D477C51F2126CC0031ED49 /* OmniBarState.swift */, @@ -5449,7 +5452,7 @@ isa = PBXGroup; children = ( 8588026424E4209900C24AB6 /* LargeOmniBarStateTests.swift */, - 85F20005221702F7006BB258 /* OmniBarTests.swift */, + 85F20005221702F7006BB258 /* AddressDisplayHelperTests.swift */, F1D477C81F2139410031ED49 /* SmallOmniBarStateTests.swift */, 1E8146A628C8AAF500D1AF63 /* PrivacyIconAndTrackers */, ); @@ -6879,6 +6882,7 @@ F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */, 020108AE29A7F91600644F9D /* AppTPTrackerCell.swift in Sources */, 983D71B12A286E810072E26D /* SyncDebugViewController.swift in Sources */, + 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */, F103073B1E7C91330059FEC7 /* BookmarksDataSource.swift in Sources */, EE0153E62A6FE106002A8B26 /* NetworkProtectionRootViewModel.swift in Sources */, 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, @@ -7072,7 +7076,7 @@ 85480CB429226B3B007E8F13 /* CrashCollectionExtensionTests.swift in Sources */, 4B6484FC27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift in Sources */, 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */, - 85F200072217032E006BB258 /* OmniBarTests.swift in Sources */, + 85F200072217032E006BB258 /* AddressDisplayHelperTests.swift in Sources */, B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */, C14882E427F20D9A00D59F0C /* BookmarksImporterTests.swift in Sources */, 8588026A24E424EE00C24AB6 /* AppWidthObserverTests.swift in Sources */, diff --git a/DuckDuckGo/AddressDisplayHelper.swift b/DuckDuckGo/AddressDisplayHelper.swift new file mode 100644 index 0000000000..16b508bdeb --- /dev/null +++ b/DuckDuckGo/AddressDisplayHelper.swift @@ -0,0 +1,46 @@ +// +// AddressDisplayHelper.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension OmniBar { + + struct AddressDisplayHelper { + + static func addressForDisplay(url: URL, showsFullURL: Bool) -> String { + guard !showsFullURL, let shortAddress = shortURLString(url) else { + return url.absoluteString + } + + return shortAddress + } + + /// Creates a string containing a short version the http(s) URL. + /// + /// - returns: URL's host without `www` component. `nil` if no host present or scheme does not match http(s). + static func shortURLString(_ url: URL) -> String? { + + guard !url.isCustomURLScheme() else { + return nil + } + + return url.host?.droppingWwwPrefix() + } + } +} diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 47528d7e39..b112492b87 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1511,6 +1511,10 @@ extension MainViewController: OmniBarDelegate { } func onTextFieldWillBeginEditing(_ omniBar: OmniBar) { + if let currentTab { + viewCoordinator.omniBar.refreshText(forUrl: currentTab.url, forceFullURL: true) + } + guard homeController == nil else { return } if !skipSERPFlow, isSERPPresented, let query = omniBar.textField.text { @@ -1521,6 +1525,7 @@ extension MainViewController: OmniBarDelegate { } func onTextFieldDidBeginEditing(_ omniBar: OmniBar) -> Bool { + let selectQueryText = !(isSERPPresented && !skipSERPFlow) skipSERPFlow = false diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 2d0cc8c45b..4a3bc54fd0 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -384,11 +384,7 @@ class OmniBar: UIView { omniDelegate?.onOmniQueryUpdated("") } - func refreshText(forUrl url: URL?) { - - if textField.isEditing { - return - } + func refreshText(forUrl url: URL?, forceFullURL: Bool = false) { guard let url = url else { textField.text = nil @@ -398,37 +394,10 @@ class OmniBar: UIView { if let query = url.searchQuery { textField.text = query } else { - textField.attributedText = OmniBar.demphasisePath(forUrl: url) + textField.text = AddressDisplayHelper.addressForDisplay(url: url, showsFullURL: textField.isEditing || forceFullURL) } } - public class func demphasisePath(forUrl url: URL) -> NSAttributedString? { - - let s = url.absoluteString - let attributedString = NSMutableAttributedString(string: s) - guard let c = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - return attributedString - } - - let theme = ThemeManager.shared.currentTheme - - if let pathStart = c.rangeOfPath?.lowerBound { - let urlEnd = s.endIndex - - let pathRange = NSRange(pathStart ..< urlEnd, in: s) - attributedString.addAttribute(.foregroundColor, value: theme.searchBarTextDeemphasisColor, range: pathRange) - - let domainRange = NSRange(s.startIndex ..< pathStart, in: s) - attributedString.addAttribute(.foregroundColor, value: theme.searchBarTextColor, range: domainRange) - - } else { - let range = NSRange(s.startIndex ..< s.endIndex, in: s) - attributedString.addAttribute(.foregroundColor, value: theme.searchBarTextColor, range: range) - } - - return attributedString - } - @IBAction func onTextEntered(_ sender: Any) { onQuerySubmitted() } @@ -517,7 +486,7 @@ class OmniBar: UIView { super.layoutSubviews() NotificationCenter.default.post(name: OmniBar.didLayoutNotification, object: self) } - + } // swiftlint:enable type_body_length @@ -571,7 +540,7 @@ extension OmniBar: Themable { searchStackContainer?.tintColor = theme.barTintColor if let url = textField.text.flatMap({ URL(trimmedAddressBarString: $0.trimmingWhitespace()) }) { - textField.attributedText = OmniBar.demphasisePath(forUrl: url) + textField.text = AddressDisplayHelper.addressForDisplay(url: url, showsFullURL: textField.isEditing) } textField.textColor = theme.searchBarTextColor textField.tintColor = UIColor(designSystemColor: .accent) diff --git a/DuckDuckGoTests/AddressDisplayHelperTests.swift b/DuckDuckGoTests/AddressDisplayHelperTests.swift new file mode 100644 index 0000000000..c4122c4f20 --- /dev/null +++ b/DuckDuckGoTests/AddressDisplayHelperTests.swift @@ -0,0 +1,61 @@ +// +// AddressDisplayHelperTests.swift +// DuckDuckGo +// +// Copyright © 2019 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +import XCTest +@testable import DuckDuckGo + +class AddressDisplayHelperTests: XCTestCase { + + private typealias AddressHelper = OmniBar.AddressDisplayHelper + + func testShortURL() { + + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com/some/path")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.subdomain.duckduckgo.com/some/path")!), "subdomain.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://m.duckduckgo.com/some/path")!), "m.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://some-other.sub.domain.duck.eu/with/path")!), "some-other.sub.domain.duck.eu") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://duckduckgo.com:1234")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://192.168.0.1:1234")!), "192.168.0.1") + + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.com")!), "com") // This is an exception we are ok with) + + XCTAssertNil(AddressHelper.shortURLString(URL(string: "file:///some/path")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "somescheme:///some/path")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "blob:https://www.my.com/111-222-333-444")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "data:text/plain;charset=UTF-8;page=21,the%20data:12345")!)) + } + + func testShortensURLWhenShortVersionExpected() { + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: false) + + XCTAssertEqual(addressForDisplay, "some.domain.eu") + } + + func testDoesNotShortenURLWhenFullVersionExpected() { + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: true) + + XCTAssertEqual(addressForDisplay, "http://some.domain.eu/with/path") + } + + func testFallsBackToLongURLWhenCannotProduceShortURL() { + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "file:///some/path")!, showsFullURL: false) + + XCTAssertEqual(addressForDisplay, "file:///some/path") + } +} diff --git a/DuckDuckGoTests/OmniBarTests.swift b/DuckDuckGoTests/OmniBarTests.swift deleted file mode 100644 index ea9fb56af6..0000000000 --- a/DuckDuckGoTests/OmniBarTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// OmniBarTests.swift -// DuckDuckGo -// -// Copyright © 2019 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -import XCTest -@testable import DuckDuckGo - -class OmniBarTests: XCTestCase { - - func testDeemphasisePathDoesNotCrash() { - - _ = OmniBar.demphasisePath(forUrl: URL(string: "example.com")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "a/b")!) - - testWith(prefix: "http:///") // crashes but we don't allow it anyway - testWith(prefix: "http://localhost") - testWith(prefix: "http://localhost/") - testWith(prefix: "http://example.com") - testWith(prefix: "http://example.com/") - testWith(prefix: "http://example.com/path") - testWith(prefix: "http://example.com/path/") - testWith(prefix: "http://user:password@example.com/path/") - - testWith(prefix: "http://localhost:8080") - testWith(prefix: "http://localhost:8080/") - testWith(prefix: "http://example.com:8080") - testWith(prefix: "http://example.com:8080/") - testWith(prefix: "http://example.com:8080/path") - testWith(prefix: "http://example.com:8080/path/") - testWith(prefix: "http://user:password@example.com:8080/path/") - - } - - private func testWith(prefix: String) { - - _ = OmniBar.demphasisePath(forUrl: URL(string: prefix)!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)#")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)#/fragment")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)?")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)?x=1")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)?x=1&")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)?x=1&y=1")!) - _ = OmniBar.demphasisePath(forUrl: URL(string: "\(prefix)?x=1&y=1,2")!) - - } - -}