diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index 679f3ea5c3..c326a203d5 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -206,9 +206,9 @@ final class AddressBarContentComponent: Component { self.activated(true) if let textField = self.textField { textField.becomeFirstResponder() - Queue.mainQueue().justDispatch { + Queue.mainQueue().after(0.3, { textField.selectAll(nil) - } + }) } } @@ -238,7 +238,9 @@ final class AddressBarContentComponent: Component { public func textFieldShouldReturn(_ textField: UITextField) -> Bool { if let component = self.component { - component.performAction.invoke(.navigateTo(explicitUrl(textField.text ?? ""))) + let finalUrl = explicitUrl(textField.text ?? "") +// finalUrl = finalUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? finalUrl + component.performAction.invoke(.navigateTo(finalUrl)) } textField.endEditing(true) return false @@ -273,6 +275,10 @@ final class AddressBarContentComponent: Component { var title: String = "" if let parsedUrl = URL(string: component.url) { title = parsedUrl.host ?? component.url + if title.hasPrefix("www.") { + title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4)) + } + title = title.idnaDecoded ?? title } self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, transition: transition) @@ -333,6 +339,7 @@ final class AddressBarContentComponent: Component { transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) + self.cancelButton.isUserInteractionEnabled = isActiveWithText let textX: CGFloat = backgroundFrame.minX + sideInset let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index b33621c4ee..34c91738e7 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -148,7 +148,7 @@ final class BrowserAddressListComponent: Component { } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - self.endEditing(true) + self.window?.endEditing(true) } private func updateScrolling(transition: ComponentTransition) { diff --git a/submodules/BrowserUI/Sources/BrowserContent.swift b/submodules/BrowserUI/Sources/BrowserContent.swift index 7096b83042..f9f1687d93 100644 --- a/submodules/BrowserUI/Sources/BrowserContent.swift +++ b/submodules/BrowserUI/Sources/BrowserContent.swift @@ -162,12 +162,14 @@ protocol BrowserContent: UIView { var present: (ViewController, Any?) -> Void { get set } var presentInGlobalOverlay: (ViewController) -> Void { get set } var getNavigationController: () -> NavigationController? { get set } + var openAppUrl: (String) -> Void { get set } var minimize: () -> Void { get set } var close: () -> Void { get set } var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set } - + func resetScrolling() + func reload() func stop() diff --git a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift index 26b1d11e7f..1fae1b7ad3 100644 --- a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift +++ b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift @@ -17,7 +17,6 @@ import ShareController import UndoUI import UrlEscaping - final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData @@ -37,6 +36,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate } var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -360,6 +360,10 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate } } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { // self.currentError = nil self.updateFontState(self.currentFontState, force: true) diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index 29bed73a96..a9972dbe90 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -66,6 +66,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var currentAccessibilityAreas: [AccessibilityAreaNode] = [] var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -766,6 +767,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.readingProgress.set(readingProgress) } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint { var contentOffset = CGPoint() for (_, itemNode) in self.visibleItemsWithNodes { diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index d31d7fd6a1..e153a02294 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -35,6 +35,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { let readingProgress: CGFloat let loadingProgress: Double? let collapseFraction: CGFloat + let activate: () -> Void init( backgroundColor: UIColor, @@ -50,7 +51,8 @@ final class BrowserNavigationBarComponent: CombinedComponent { centerItem: AnyComponentWithIdentity?, readingProgress: CGFloat, loadingProgress: Double?, - collapseFraction: CGFloat + collapseFraction: CGFloat, + activate: @escaping () -> Void ) { self.backgroundColor = backgroundColor self.separatorColor = separatorColor @@ -66,6 +68,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { self.readingProgress = readingProgress self.loadingProgress = loadingProgress self.collapseFraction = collapseFraction + self.activate = activate } static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool { @@ -122,6 +125,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self) + let activate = Child(Button.self) return { context in var availableWidth = context.availableSize.width @@ -266,6 +270,23 @@ final class BrowserNavigationBarComponent: CombinedComponent { ) } + if context.component.collapseFraction == 1.0 { + let activateAction = context.component.activate + let activate = activate.update( + component: Button( + content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))), + action: { + activateAction() + } + ), + availableSize: size, + transition: .immediate + ) + context.add(activate + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + ) + } + return size } } diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index 3bd36c1cf6..e037f45663 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -38,6 +38,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU } var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -352,6 +353,10 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { // self.currentError = nil self.updateFontState(self.currentFontState, force: true) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index daaad7f05c..b4ca2e6f30 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -188,7 +188,10 @@ private final class BrowserScreenComponent: CombinedComponent { centerItem: navigationContent, readingProgress: context.component.contentState?.readingProgress ?? 0.0, loadingProgress: context.component.contentState?.estimatedProgress, - collapseFraction: collapseFraction + collapseFraction: collapseFraction, + activate: { + performAction.invoke(.expand) + } ), availableSize: context.availableSize, transition: context.transition @@ -267,6 +270,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) context.add(addressList .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0)) + .clipsToBounds(true) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) @@ -314,6 +318,7 @@ public class BrowserScreen: ViewController, MinimizableController { case openAddressBar case closeAddressBar case navigateTo(String) + case expand } fileprivate final class Node: ViewControllerTracingNode { @@ -568,6 +573,10 @@ public class BrowserScreen: ViewController, MinimizableController { updatedState.addressFocused = false return updatedState }) + case .expand: + if let content = self.content.last { + content.resetScrolling() + } } } @@ -626,6 +635,14 @@ public class BrowserScreen: ViewController, MinimizableController { } self.pushContent(content, transition: .spring(duration: 0.4)) } + browserContent.openAppUrl = { [weak self] url in + guard let self else { + return + } + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: { [weak self] in + self?.view.window?.endEditing(true) + }) + } browserContent.present = { [weak self] c, a in guard let self, let controller = self.controller else { return @@ -989,6 +1006,10 @@ public class BrowserScreen: ViewController, MinimizableController { } } + if update.isReset { + scrollingPanelOffsetFraction = 0.0 + } + if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction { self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction self.requestLayout(transition: transition) diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index ba252fbbf7..58e21e3b0c 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -18,6 +18,7 @@ import UndoUI import LottieComponent import MultilineTextComponent import UrlEscaping +import UrlHandling private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { private final class PendingTask { @@ -139,6 +140,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU private let faviconDisposable = MetaDisposable() var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -201,6 +203,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU super.init(frame: .zero) + self.backgroundColor = presentationData.theme.list.plainBackgroundColor + self.webView.backgroundColor = presentationData.theme.list.plainBackgroundColor + self.webView.allowsBackForwardNavigationGestures = true self.webView.scrollView.delegate = self self.webView.scrollView.clipsToBounds = false @@ -214,7 +219,6 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward), options: [], context: nil) self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.hasOnlySecureContent), options: [], context: nil) if #available(iOS 15.0, *) { - self.backgroundColor = presentationData.theme.list.plainBackgroundColor self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } if #available(iOS 16.4, *) { @@ -460,11 +464,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU ErrorComponent( theme: self.presentationData.theme, title: self.presentationData.strings.Browser_ErrorTitle, - text: error.localizedDescription + text: error.localizedDescription, + insets: insets ) ), environment: {}, - containerSize: CGSize(width: size.width - insets.left - insets.right - 72.0, height: size.height) + containerSize: CGSize(width: size.width, height: size.height) ) if self.errorView.superview == nil { self.addSubview(self.errorView) @@ -521,14 +526,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.snapScrollingOffsetToInsets() + + if self.ignoreUpdatesUntilScrollingStopped { + self.ignoreUpdatesUntilScrollingStopped = false + } } } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.snapScrollingOffsetToInsets() + + if self.ignoreUpdatesUntilScrollingStopped { + self.ignoreUpdatesUntilScrollingStopped = false + } } private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) { + guard !self.ignoreUpdatesUntilScrollingStopped else { + return + } let scrollView = self.webView.scrollView let isInteracting = scrollView.isDragging || scrollView.isDecelerating if let previousScrollingOffsetValue = self.previousScrollingOffset { @@ -558,6 +574,30 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } + private var ignoreUpdatesUntilScrollingStopped = false + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + self.ignoreUpdatesUntilScrollingStopped = true + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url?.absoluteString { + if isTelegramMeLink(url) || isTelegraPhLink(url) { + decisionHandler(.cancel) + self.minimize() + self.openAppUrl(url) + } else { + if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { + decisionHandler(.download) + } else { + decisionHandler(.allow) + } + } + } else { + decisionHandler(.allow) + } + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { self.currentError = nil self.updateFontState(self.currentFontState, force: true) @@ -881,15 +921,18 @@ private final class ErrorComponent: CombinedComponent { let theme: PresentationTheme let title: String let text: String + let insets: UIEdgeInsets init( theme: PresentationTheme, title: String, - text: String + text: String, + insets: UIEdgeInsets ) { self.theme = theme self.title = title self.text = text + self.insets = insets } static func ==(lhs: ErrorComponent, rhs: ErrorComponent) -> Bool { @@ -902,10 +945,14 @@ private final class ErrorComponent: CombinedComponent { if lhs.text != rhs.text { return false } + if lhs.insets != rhs.insets { + return false + } return true } static var body: Body { + let background = Child(Rectangle.self) let animation = Child(LottieComponent.self) let title = Child(MultilineTextComponent.self) let text = Child(MultilineTextComponent.self) @@ -916,6 +963,17 @@ private final class ErrorComponent: CombinedComponent { let animationSpacing: CGFloat = 8.0 let textSpacing: CGFloat = 8.0 + let constrainedWidth = context.availableSize.width - 76.0 - context.component.insets.left - context.component.insets.right + + let background = background.update( + component: Rectangle(color: context.component.theme.list.plainBackgroundColor), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + let animation = animation.update( component: LottieComponent( content: LottieComponent.AppBundleContent(name: "ChatListNoResults") @@ -924,9 +982,6 @@ private final class ErrorComponent: CombinedComponent { availableSize: CGSize(width: animationSize, height: animationSize), transition: .immediate ) - context.add(animation - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + animation.size.height / 2.0)) - ) contentHeight += animation.size.height + animationSpacing let title = title.update( @@ -939,12 +994,9 @@ private final class ErrorComponent: CombinedComponent { horizontalAlignment: .center ), environment: {}, - availableSize: context.availableSize, + availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height), transition: .immediate ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + title.size.height / 2.0)) - ) contentHeight += title.size.height + textSpacing let text = text.update( @@ -958,15 +1010,27 @@ private final class ErrorComponent: CombinedComponent { maximumNumberOfLines: 0 ), environment: {}, - availableSize: context.availableSize, + availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height), transition: .immediate ) - context.add(text - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + text.size.height / 2.0)) - ) contentHeight += text.size.height - - return CGSize(width: context.availableSize.width, height: contentHeight) + + var originY = floor((context.availableSize.height - contentHeight) / 2.0) + context.add(animation + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + animation.size.height / 2.0)) + ) + originY += animation.size.height + animationSpacing + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + title.size.height / 2.0)) + ) + originY += title.size.height + textSpacing + + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + text.size.height / 2.0)) + ) + + return context.availableSize } } } diff --git a/submodules/BrowserUI/Sources/Punycode.swift b/submodules/BrowserUI/Sources/Punycode.swift new file mode 100644 index 0000000000..2edbbb22e3 --- /dev/null +++ b/submodules/BrowserUI/Sources/Punycode.swift @@ -0,0 +1,317 @@ +// +// Created by kojirof on 2018-11-19. +// Copyright (c) 2018 Gumob. All rights reserved. +// + +//MIT License +// +//Copyright (c) 2018 Gumob +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +import Foundation + +public class Punycode { + + /// Punycode RFC 3492 + /// See https://www.ietf.org/rfc/rfc3492.txt for standard details + + private let base: Int = 36 + private let tMin: Int = 1 + private let tMax: Int = 26 + private let skew: Int = 38 + private let damp: Int = 700 + private let initialBias: Int = 72 + private let initialN: Int = 128 + + /// RFC 3492 specific + private let delimiter: Character = "-" + private let lowercase: ClosedRange = "a"..."z" + private let digits: ClosedRange = "0"..."9" + private let lettersBase: UInt32 = Character("a").unicodeScalars.first!.value + private let digitsBase: UInt32 = Character("0").unicodeScalars.first!.value + + /// IDNA + private let ace: String = "xn--" + + private func adaptBias(_ delta: Int, _ numberOfPoints: Int, _ firstTime: Bool) -> Int { + var delta: Int = delta + if firstTime { + delta /= damp + } else { + delta /= 2 + } + delta += delta / numberOfPoints + var k: Int = 0 + while delta > ((base - tMin) * tMax) / 2 { + delta /= base - tMin + k += base + } + return k + ((base - tMin + 1) * delta) / (delta + skew) + } + + /// Maps a punycode character to index + private func punycodeIndex(for character: Character) -> Int? { + if lowercase.contains(character) { + return Int(character.unicodeScalars.first!.value - lettersBase) + } else if digits.contains(character) { + return Int(character.unicodeScalars.first!.value - digitsBase) + 26 /// count of lowercase letters range + } else { + return nil + } + } + + /// Maps an index to corresponding punycode character + private func punycodeValue(for digit: Int) -> Character? { + guard digit < base else { return nil } + if digit < 26 { + return Character(UnicodeScalar(lettersBase.advanced(by: digit))!) + } else { + return Character(UnicodeScalar(digitsBase.advanced(by: digit - 26))!) + } + } + + /// Decodes punycode encoded string to original representation + /// + /// - Parameter punycode: Punycode encoding (RFC 3492) + /// - Returns: Decoded string or nil if the input cannot be decoded + public func decodePunycode(_ punycode: Substring) -> String? { + var n: Int = initialN + var i: Int = 0 + var bias: Int = initialBias + var output: [Character] = [] + var inputPosition = punycode.startIndex + + let delimiterPosition: Substring.Index = punycode.lastIndex(of: delimiter) ?? punycode.startIndex + if delimiterPosition > punycode.startIndex { + output.append(contentsOf: punycode[..= bias + tMax ? tMax : k - bias) + if digit < t { + break + } + w *= base - t + k += base + } while !punycodeInput.isEmpty + bias = adaptBias(i - oldI, output.count + 1, oldI == 0) + n += i / (output.count + 1) + i %= (output.count + 1) + guard n >= 0x80, let scalar = UnicodeScalar(n) else { + return nil + } + output.insert(Character(scalar), at: i) + i += 1 + } + + return String(output) + } + + /// Encodes string to punycode (RFC 3492) + /// + /// - Parameter input: Input string + /// - Returns: Punycode encoded string + public func encodePunycode(_ input: Substring) -> String? { + var n: Int = initialN + var delta: Int = 0 + var bias: Int = initialBias + var output: String = "" + for scalar in input.unicodeScalars { + if scalar.isASCII { + let char = Character(scalar) + output.append(char) + } else if !scalar.isValid { + return nil /// Encountered a scalar out of acceptable range + } + } + var handled: Int = output.count + let basic: Int = handled + if basic > 0 { + output.append(delimiter) + } + while handled < input.unicodeScalars.count { + var minimumCodepoint: Int = 0x10FFFF + for scalar: Unicode.Scalar in input.unicodeScalars { + if scalar.value < minimumCodepoint && scalar.value >= n { + minimumCodepoint = Int(scalar.value) + } + } + delta += (minimumCodepoint - n) * (handled + 1) + n = minimumCodepoint + for scalar: Unicode.Scalar in input.unicodeScalars { + if scalar.value < n { + delta += 1 + } else if scalar.value == n { + var q: Int = delta + var k: Int = base + while true { + let t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias) + if q < t { + break + } + guard let character: Character = punycodeValue(for: t + ((q - t) % (base - t))) else { return nil } + output.append(character) + q = (q - t) / (base - t) + k += base + } + guard let character: Character = punycodeValue(for: q) else { return nil } + output.append(character) + bias = adaptBias(delta, handled + 1, handled == basic) + delta = 0 + handled += 1 + } + } + delta += 1 + n += 1 + } + + return output + } + + /// Returns new string containing IDNA-encoded hostname + /// + /// - Returns: IDNA encoded hostname or nil if the string can't be encoded + public func encodeIDNA(_ input: Substring) -> String? { + let parts: [Substring] = input.split(separator: ".") + var output: String = "" + for part: Substring in parts { + if output.count > 0 { + output.append(".") + } + if part.rangeOfCharacter(from: CharacterSet.urlHostAllowed.inverted) != nil { + guard let encoded: String = part.lowercased().punycodeEncoded else { return nil } + output += ace + encoded + } else { + output += part + } + } + return output + } + + /// Returns new string containing hostname decoded from IDNA representation + /// + /// - Returns: Original hostname or nil if the string doesn't contain correct encoding + public func decodedIDNA(_ input: Substring) -> String? { + let parts: [Substring] = input.split(separator: ".") + var output: String = "" + for part: Substring in parts { + if output.count > 0 { + output.append(".") + } + if part.hasPrefix(ace) { + guard let decoded: String = part.dropFirst(ace.count).punycodeDecoded else { return nil } + output += decoded + } else { + output += part + } + } + return output + } +} + +private extension Substring { + func lastIndex(of element: Character) -> String.Index? { + var position: Index = endIndex + while position > startIndex { + position = self.index(before: position) + if self[position] == element { + return position + } + } + return nil + } +} + +private extension UnicodeScalar { + var isValid: Bool { + return value < 0xD880 || (value >= 0xE000 && value <= 0x1FFFFF) + } +} + +public extension Substring { + /// Returns new string in punycode encoding (RFC 3492) + /// + /// - Returns: Punycode encoded string or nil if the string can't be encoded + var punycodeEncoded: String? { + return Punycode().encodePunycode(self) + } + + /// Returns new string decoded from punycode representation (RFC 3492) + /// + /// - Returns: Original string or nil if the string doesn't contain correct encoding + var punycodeDecoded: String? { + return Punycode().decodePunycode(self) + } + + /// Returns new string containing IDNA-encoded hostname + /// + /// - Returns: IDNA encoded hostname or nil if the string can't be encoded + var idnaEncoded: String? { + return Punycode().encodeIDNA(self) + } + + /// Returns new string containing hostname decoded from IDNA representation + /// + /// - Returns: Original hostname or nil if the string doesn't contain correct encoding + var idnaDecoded: String? { + return Punycode().decodedIDNA(self) + } +} + +public extension String { + + /// Returns new string in punycode encoding (RFC 3492) + /// + /// - Returns: Punycode encoded string or nil if the string can't be encoded + var punycodeEncoded: String? { + return self[..=0; i--) { uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword); } diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index c25368c74b..4897df2846 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1008,11 +1008,12 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur return } + let urlScheme = (parsedUrl.scheme ?? "").lowercased() var isInternetUrl = false - if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" { + if ["http", "https"].contains(urlScheme) { isInternetUrl = true } - if parsedUrl.scheme == "tonsite" { + if urlScheme == "tonsite" { isInternetUrl = true } @@ -1032,7 +1033,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur settings = .defaultSettings } if accessChallengeData.data.isLockable { - if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == nil { + if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == "inApp" { settings = WebBrowserSettings(defaultWebBrowser: "safari", exceptions: []) } }