Observe backForwardList changes of WKWebview - ios

To explain my problem I would like to start with the fundamentals of my project.
We are building an app which employs a web view which loads a lot of different websites (web apps) to form our app package. Those web apps are made with different web tech like ruby or ember/react (single page web app). The later change urls via push state which is problematic for the webviews WKNavigationDelegate as it doesn't recognise any of those url changes. If you load normal http requests (like old rails pages) then everything is fine and well.
In order to know on which page the user is at any time I created a user script which tapped into the pushsstate js prototype and messaged back the url change to the iOS app and even though this is A solution its in my eyes incredibly hacky. So I have been looking for alternatives and I came across the WKWebView.backForwardList which actually records all those push state changes.
The issue I have now is how do I monitor/observe the backForwardList for changes in lets say the currentItem? You can't use KVO to do so as these properties don't support it.
I did however found a possible solution by observing the webview.scrollView.contentSize which for some reason will trigger every time something changes on the screen. It's odd that this observer is fired for every single animation which is running on the screen its almost as if its called on pixel changes of the scrollview. Our web apps are animating all the time as they build with canvas elements for games which means the observer is called a lot and don't feel comfortable to have this running all the time.
Do you know a nicer/neater way to keep track of the changes WKWebView.backForwardList items.
Cheers Thomas

I found the solution which seems obvious but wasn't at the time
// Add observation.
urlObservation = webView?.observe(\.url, changeHandler: { (webView, change) in
print("Web view URL changed to \(webView.backForwardList.currentItem?.url.absoluteString ?? "Empty")", webView.scrollView.contentSize.width, webView.scrollView.contentSize.height)
})
This seems to track the url changes even though the navigation delegate are NOT triggered in any way.

Related

Keeping a WKWebView and it's UIViewController in the background running and accessible from multiple ViewControllers

Background: In order to make web requests to an API endpoint, I need to scrape a website and retrieve a token every 25-30 seconds. I'm doing this with a WKWebView and injecting some custom JavaScript using WKUserScript to retrieve AJAX response headers containing the token. Please focus on the question specifically and not on this background information - I'm attempting this entirely for my own educational purposes.
Goal
I will have different 'model' classes, or even just other UIViewControllers, that may need to call the shared UIViewController to retrieve this token to make an authenticated request.
Maybe I might abstract this into one "Sdk" class. Regardless, this 'model' SDK class could be instantiated and used by any other ViewController.
More info
I would like to be able to call the UIViewController of the WKWebView and retrieve some data. Unless I re-create it every 25 seconds, I need to run it in the background or share it. I would like to be able to run a UIViewController 'in the background' and receive some information from it once WKWebView has done it's thing.
I know there are multiple ways of communicating with another ViewController including delegation and segueing. However, I'm not sure that these help me keep the view containing the WKWebView existing in the background so I can call it's ViewController and have it re-perform the scrape. Delegation may work for normal code, but what about one that must have the view existing? Would I have to re-create this WKWebView dynamically each time a different model, or view controller, were to try and get this token?
One post suggests utilising ContainerViewControllers. From this, I gather that in the 'master' ViewController (the one containing the other ones), I could place the hidden WKWebView to do it's thing and communicate to the child view controllers that way via delegation.
Another post suggests using AppDelegate and making it a shared service. I'm completely against using a Singleton as it is widely considered an anti-pattern. There must be another way, even if a little more complex, that helps me do what I want without resorting to this 'cheat'.
This post talks about communicating between multiple ViewControllers, but I can't figure out how this would be useful when something needs to stay running and executing things.
How about any other ways to do this? Run something in a background thread with a strong pointer so it doesn't get discarded? I'm using Xcode 9.2, Swift 4, and iOS 11. As I'm very new to iOS programming, any small code examples on this would be appreciated.
Unfortunately, WKWebView must be in the view hierarchy to use it. You must have added it as a sub view of an on-screen view controller.
This was fine for me. I added this off-screen so it was not visible. Hidden attribute might have worked as well. Either way you must call addSubview with it to make it work.
There are some other questions and answers here which verify this.
Here is a way if you don't wish to use a singleton.
1- In the DidFinishlaunchingWithOptions, Make a timer that runs in the background and call a method inside the app delegate Called FetchNewToken.
2- In FetchNewToken, make the call needed and retrieve the new token (you can use alamofire or any 3rd library to make the call easier for you).
Up on successfully retrieving the token, save it in NSUserDefaults under the name upToDateToken
You can access this token anywhere from the application using NSUserDefaults and it will always be up to date.

How to warn user if he/she accidentally close/leave session at website?

We develop accounting system on a web with ASP.NET MVC and encountered this problem - if user is in the middle of the work and somehow manages to close browser all work is gone (and users are not geeks at all it did happen and will happen). Especially problematic at Chrome in Windows after they removed warning of closing tabs from philosophical reasons so now it will just shut down. We would like to be able to somehow catch this behavior. Also even when Firefox for example has warning that user is going to shut down his tabs - simple clicking the checkbox will remove it all again. So is it possible to keep track of this action to prevent user accidentally closing browser and lost all the work? Or is it even possible to do for example in Chrome? The solution would be simple warning window but it needs to show basically everytime if closing while our web application is alive. We don't want to keep session alive after closing browser for obvious security reasons. Also - it should work at Chrome, IE and Firefox. Thank you for your help.
you can try the below code:
window.onbeforeunload = function()
{
return "Are you sure you want to exit";
}
If your front end is using MVVM such as AngularJS or Knockout, or any of the other popular binding libraries, consider a different approach to preventing the user from quitting the page.
In a client-side interval, serialize your view model and store it in local storage.
When the save/exit condition for the current page is met, clear the local storage.
If the page is loaded again, and there is something in local storage, this means that on the previous session, the browser was closed before saving - so deserialize the local storage object back into the viewmodel (use a unique key per page) - you can combine this with some UI that tells the user that their previous state has been restored, click ok to continue or start over to start again (which would reset the viewmodel)
I should add that you'll have to watch out for multiple simultaneous tabs, so you may want to work around this by making the key unique somehow, e.g. creating two invoices simultaneously.
This essentially provides an auto-save but client-side only.
You can combine this technique with using window.onBeforeUnload as per Tejinder's answer, but as you cannot style the "unload confirmation" prompt, providing an auto-resume is a much better experience.

Is there a way to use Branch deeplinks while the app is already running?

As Branch documentation states and my current implementation goes, the branch links are detected in the AppDelegate, a view controller is registered accordingly and then launched from there.
Is there a way I can do this not in the AppDelegate but elsewhere?
My main issue is that if my app is already launched and someone clicks on a Branch Deeplink, it works as it should but takes me back to the launch screen of the app, rather than staying at the currently launched view.
I tried using
[branch initSessionWithLaunchOptions:launchOptions automaticallyDisplayDeepLinkController:NO];
and launching the view controller from elsewhere, but now I don't understand how the "deepLinkingCompletionDelegate" will be called to fetch the data accompanying the link.
Alex from Branch here:
These code snippets don't need to be in your AppDelegate, but they do need to be called in the correct app lifecycle methods. The typical place for these to be defined is the AppDelegate, and it would be a much more advanced implementation to put them anywhere else. Even if you did, the effect would be the same and you wouldn't avoid the issue you've identified.
It sounds like you are using our basic automatic deep link routing approach. What you want to do is build your own custom router, so that you can decide intelligently what to do with the link data. In other words, you'll still receive the link params right away, but you'll be able to decide to not open the launch screen if the app is already running.

Firefox content script memory consumption

I am developing one extension for Firefox using addon-sdk. That extension opens one tab and load one web page which is updated each N minutes. This web page is processed using one content script. The problem is that memory grows each time the content script gets executed. Do you know why? Is there any way to call any garbage collector in order to maintain memory consuption stable?
EDIT:
The web page contains bank account details and the Content Scripts look for new movements on in. It is a framed page and one of its frames (which contains movements list) is reloaded to see if any change occured. I use jquery to proccess the list.
When new movements appear, those are sent to the extension using port and the extension saves them in a remote web server using Response.
Trying check this instructions by mozilla:
https://developer.mozilla.org/en/XUL_School/JavaScript_Object_Management
https://developer.mozilla.org/en/Zombie_Compartments#Proactive_checking_of_add-ons
Depends on what are you using on your add-on... If you're using some oberserver for example, you need to unregister this observer so it won't leak... Can you give more descriptions about your addon? The code or exactly what it does...
Maybe you're not declaring variabled and you're using globals all the time, try also unset the variable after use it.
Are you using jquery?

Showing status of current request by AJAX

I'm trying to develop an application which modifies a couple of tasks of the famous Online-TODO List RememberTheMilk (rememberthemilk.com) using the REST API.
Unfortunately the modifying takes a lot of time, so I want to give a feedback to the users.
My idea was just to display a couple of text lines (e.g. modifying task 1 of n...).
Therefore I used the periodically_call_remote on my page and called a which reads a Singleton.
In the request I store the text that should be displayed in the same singleton. But I found out, that once I set up a request, the periodically_call_remote does not update the specified div.
My question to this:
1. is this a good way to implement this behaviour?
2. if it is, how do get the periodically_call_remote to work during a submit?
Using a Singleton is most definitely a bad idea. In an advanced production setup it isn't guaranteed that subsequent requests will go to the same process or to the same machine (and subsequently will have a different Singleton). Plus, if you have many users, I don't even want to think about what'll happen to those poor Singletons.
Does any of this stuff actually need to go through your Rails app? It seems like you can call the RTM API via Javascript from the page the user is on and then update the page when the XHR request is complete.

Resources