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

Move WebView init to main thread. #989

Merged

Conversation

jsligh
Copy link
Collaborator

@jsligh jsligh commented May 29, 2024

Closes #988


if let result = result, self.userAgent.isEmpty {
self.userAgent = "\(result)"
if let webView = self.webView {
Copy link
Contributor

Choose a reason for hiding this comment

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

If the webview has not initialized the completion block will not be called. Is that OK?

@weibel
Copy link
Contributor

weibel commented May 30, 2024

Since I initiated this issue myself I have reviewed the code. Here's my feedback

There are some potential areas where improvements can be made to ensure thread safety, efficiency, and better management of the user agent string. Here are my suggestions:

  • Use a synchronization mechanism like DispatchSemaphore to manage concurrent access to the user agent string.
  • Persist the user agent string, to minimize fetch time on subsequent app launches. The WKWebView init and javascript evaluation on app start can take several hundred ms depending on os and device.
  • Introduce a delay when initializing the WKWebView to ensure it's ready before evaluating JavaScript. (EDIT: This is probably not necessary. The registering of the webviews suggested below, seems to have solved this issue)

I have made a solution in a branch in our own fork Vivino#1 tell me if you want a PR

IMO it's a lot of work to fetch the string. Have you ever considered reverse engineering the string format and accept that it might not always reflect the string from WKWebView?

@YuriyVelichkoPI
Copy link
Contributor

Hi @weibel, thanks for the review!

I like the idea to persist the ua string. However, we still should update the value because the user can update the OS, and the actual string will be changed as well.

We want to avoid blocking operations since we had already faced deadlock solving this task with semaphores. So let @OlenaPostindustria propose the fix as well, and we will choose the most reliable variant.

@weibel
Copy link
Contributor

weibel commented May 30, 2024

@YuriyVelichkoPI We could e.g. persist the string with the OS version as key. I tried this in the solution I linked to. It's still in review on our side.

Did you consider the following?

IMO it's a lot of work to fetch the string. Have you ever considered reverse engineering the string format and accept that it might not always reflect the string from WKWebView?

@YuriyVelichkoPI
Copy link
Contributor

We could e.g. persist the string with the OS version as key. I tried this in the solution I linked to. It's still in review on our side.

Did you consider the following?

IMO it's a lot of work to fetch the string. Have you ever considered reverse engineering the string format and accept that it might not always reflect the string from WKWebView?

It should be considered on the committee. I'm not sure how sensitive this info is. In some cases it should be precies.

@@ -22,11 +22,13 @@ public class UserAgentService: NSObject {

public private(set) var userAgent: String = ""

private let webView = WKWebView()
private var webView: WKWebView?
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is no need to keep the webView instance the whole time, so I suggest deallocating it when the user agent is fetched. But as we use singleton it can be pretty tricky and can cause problems if we accidentally deallocate the web view instance when another fetchUserAgent is in progress. My solution here would be to create the web view registry. Check out the code below for more info. Please, let me know your thoughts about this.

@objc(PBMUserAgentService) @objcMembers
public class UserAgentService: NSObject {
    
    public static let shared = UserAgentService()
    
    public private(set) var userAgent: String = ""
    
    private var webViews = [WKWebView]()
    
    override init() {
        super.init()
        
        fetchUserAgent()
    }
    
    public func fetchUserAgent(completion: ((String) -> Void)? = nil) {
        // user agent has been already generated
        guard userAgent.isEmpty else {
            completion?(userAgent)
            return
        }
        
        DispatchQueue.main.async {
            let webView = WKWebView()
            self.webViews.append(webView)
            
            webView.evaluateJavaScript("navigator.userAgent") { [weak self] result, error in
                guard let self = self else { return }
                
                if let error {
                    Log.error(error.localizedDescription)
                }
                
                if let result = result, self.userAgent.isEmpty  {
                    self.userAgent = "\(result)"
                }
                
                self.webViews.removeAll(where: { $0 == webView })
                
                completion?(self.userAgent)
            }
        }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

It should work well since the registry and userAgent will be managed sequentially in the main thread. userAgent should be safe for reading from any thread. Alternatively, the value passed to fetchUserAgent completion handler should be used.

@weibel also mentioned the "warming up" of the WKWebView object before runing the JS code in it. Is it possible that the result of fetching the ua will be different depending on the js execution time in the WebView lifecycle?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@OlenaPostindustria thank you so much for your proposed solution! I like this method as it bypasses the proposed issues with singletons.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@YuriyVelichkoPI No, I don't think so, the result should be the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unsure about the warm up time. I need to run a few tests. The problems i have had might be becasue the webview was deallocated before the block could return as i did not think about the singleton issue that was solved with registering the views

Copy link
Collaborator

@OlenaPostindustria OlenaPostindustria left a comment

Choose a reason for hiding this comment

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

LGTM

@jsligh jsligh merged commit e34af8a into master May 30, 2024
4 checks passed
@jsligh jsligh deleted the 988-crashes-off-the-main-thread-in-line-25-in-useragentservice branch May 30, 2024 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Crashes off the main thread in line 25 in UserAgentService
4 participants