B4A Library WebViewExtras

Hi all.

WebViewExtras is my latest library.
It's a much updated version of JSInterface.

WebViewExtras exposes more of the available native Android WebView methods to your B4A application:

addJavascriptInterface(webView1 As WebView, interfaceName As String)

Add a javascript interface to webView1, methods of the interface can be accessed using javascript with the interfaceName as the javascript namespace.

The interface contains just a single overloaded method CallSub().
The CallSub method signatures are:

CallSub(subName As String, callUIThread As boolean)
CallSub(subName As String, callUIThread As boolean, parameter1 As String)
CallSub(subName As String, callUIThread As boolean, parameter1 As String, parameter2 As String)
CallSub(subName As String, callUIThread As boolean, parameter1 As String, parameter2 As String, parameter3 As String)


So if you have added the interface to your webView with the interfaceName of "B4A" then you can write javascript such as:

B4X:
B4A.CallSub('MySubName', true)

The callUIThread parameter is an important update - it's not available with JSInterface.

Does the Sub called by your javascript modify your activity UI?
If the answer is yes then you need to pass boolean true as callUIThread otherwise you can pass false.
If you pass false and then the Sub tries to modify your activity UI you will get an exception.

Does your javascript require a return value from your Sub?
If the answer is yes then the Sub MUST NOT modify the activity UI.
If CallSub is excuted with callUIThread set to true then no values will be returned from your Sub to the javascript.

You will need to structure your B4A code so that Subs that return values to javascript do not modify the activity UI.

addWebChromeClient(webView1 As WebView, EventName As String)

Add a WebChromeClient to webView1.

The default B4A WebView has no WebChromeClient.
A WebChromeClient handles many things, the WebChromeClient that WebViewExtras adds to your WebView enables:

Version 1.30 of WebViewExtras requires that an additional EventName parameter is passed to the addWebChromeClient method, see this post: http://www.b4x.com/forum/additional-libraries-official-updates/12453-webviewextras-2.html#post102448

clearCache(webView1 As WebView, includeDiskFiles As boolean)

Clear the WebView cache.
Note that the cache is per-application, so this will clear the cache for all WebViews used in an application.

boolean includeDiskFiles - If false, only the RAM cache is cleared.

executeJavascript(webView1 As WebView, javascriptStatement As String)

Executes a string of one or more javascript statements in webView1.
javascriptStatement - A string of one or more (semi-colon seperated) javascript statements.

flingScroll(webView1 As WebView, vx As Int, vy As Int)

flingScroll is a poorly documented method of the WebView.
It's included in WebViewExtras as it may be useful but i can find no documentation for it or it's parameters.

vx and vy do not seem to be pixel values - i suspect they are velocity values for the kinetic/fling scroll.

pageDown(webView1 As WebView, scrollToBottom As boolean)

Scroll the contents of webView1 down by half the page size.

scrollToBottom - If true then webView1 will be scrolled to the bottom of the page.

Returns a Boolean value to indicate the success or failure of the scroll.

pageUp(webView1 As WebView, scrollToTop As boolean)

Scroll the contents of webView1 up by half the page size.

scrollToTop - If true then webView1 will be scrolled to the top of the page.

Returns a Boolean value to indicate the success or failure of the scroll.

zoomIn(webView1 As WebView)

Perform zoom in on webView1.

Returns a Boolean value to indicate the success or failure of the zoom.

zoomOut(webView1 As WebView)

Perform zoom out on webView1.

Returns a Boolean value to indicate the success or failure of the zoom.

Up to date documentation/reference for this library can be found here: http://www.b4x.com/forum/additional-libraries-official-updates/12453-webviewextras-3.html#post106486.

Library and demo code is attached to this post.

The demo is a bit brief - sorry but i don't have time to write demo code for all the new methods.
The demo displays two WebViews - the top WebView has a JavascriptInterface and WebChromeClient whereas the lower WebView has neither - it is the default B4A WebView.

Martin.

Edit by Erel:
- There is a security issue related to AddJavascriptInterface in older versions of Android. See this link: https://www.b4x.com/android/forum/t...ascriptinterface-vulnerability.85032/#content
- v2.20 is attached. This is the latest version.
 

Attachments

  • WebViewExtras_v1_42.zip
    7.8 KB · Views: 9,052
  • v2.20.zip
    41.1 KB · Views: 404
Last edited by a moderator:

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,
Further thought. If I'm going to go down the Type or wrapper class approach with multiple instances how do I get from a WebView to its related WebViewExtras and/or the other "related" classes? I could try and make sure they each have a common key in their tags, which is essentially what I'm doing now in the old version of my code, but it's a bit clumsy. Any ideas?
Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
@andrewj

The default android WebView is just that - a WebView.
It has no WebViewClient or WebChromeClient set.
These are optional components that a developer can set if required.
For the most basic use case neither of these two components would be required but for anything but the most basic use case you'll need one or both components.

Look at the official documentation for both WebViewClient and WebChromeClient.
If you don't set a WebViewClient then your app will not be able to take action when a page is loaded or an SSL error is received etc.
And if you don't set a WebChromeClient your application will not be able to take action when a webpage requests geolocation or a javascript error occurs etc.

They are just optional components that more often than not you will want to use.
Each handles different types of WebView events.

A WebViewClient can handle one or more of the public methods listed in it's documentation and a WebChromeClient can handle one or more of the public methods listed in it's documentation.

The b4a WebView has a minimal WebViewClient set that handles just a few events:
  • PageFinished (Url As String)
  • OverrideUrl (Url As String) As Boolean
  • UserAndPasswordRequired (Host As String, Realm As String) As String()
The b4a WebView has no WebChromeClient set.

The WebViewExtras2 DefaultWebViewClient and DefaultWebChromeClient are implementations of those classes that handle some but not all possible events.

Hope that explains what these objects/classes are and what they do.

You're probably right - for your project it'd be much better to merge both libraries.
I'll look at that a bit later and post again.

Martin.

(PS Thanks for the donation :))
 

andrewj

Active Member
Licensed User
Longtime User
Dear Martin,

Thanks for the reply. The "Client" objects' roles are much clearer now - that's the problem of coming to development via B4A rather than Java, I "don't know what I don't know"... :)

I really see FlingableWebView+ as a "one stop shop" so that if you use this as your primary web view object, you should be able to access the majority of functionality directly without having to worry too much about the implementation detail. That's closer to the VB style of doing things, but maybe a bit further away from the Java style.

In terms of the "calling" side, I think it would be fine to expose WebViewExtras and the two "clients" as "public members" of your FlingableWebView object, as it would be straightforward to access them via the syntax I outlined in post #100. However I think it would be better to raise all events against the core FlingableWebView object (as you already do for PageFinished() and OverRideURL()), so that the user of your library doesn't have to worry about dimming and handling instances of other objects just to catch one or two events.

I started setting up a wrapper B4A class with this structure and it looked promising, but obviously it would be much better if you handled this in your library.

Does that make sense?

Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
Does that make sense?

Perfect sense!
Stop creating your wrapper class and i'll have the two libraries merged pretty shortly.
I've already merged them and am just looking at some edits to make - then i can upload the new single library for you to look at.

Martin.
 

andrewj

Active Member
Licensed User
Longtime User
That's brilliant. I wan't going to go any further on my wrapper until we'd exchanged views anyway, and it was a useful bit of learning anyway!

Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
@andrewj

Right i've been busy and have something for you to test.

I've merged FlingableWebView and WebViewExtras2.
  • The WebViewExtras object no longer exists, all of it's methods have been moved to the FlingableWebView.
  • The FlingableWebView has a new field named WebKitConstants and WebKitConstants has fields:
    • CacheMode
      Constants used to set the WebSettings CacheMode.
    • GeoLocationPermission
      Constants returned by the WebChromeClient GeoLocationPermissionsRequest event.
    • MessageLevel
      Constants that define the type of a ConsoleMessage.
    • ReceivedErrorType
      Constants used to identify the type of ErrorCode passed to the WebViewClient ReceivedError event.
    • SslErrorType
      Constants used to identify types of SSL errors.
    These four fields contain all constants previously found in WebSettings, DefaultWebChromeClient, DefaultWebViewClient and SslError objects.
    With a reference to the FlingableWebView you can access all constants.
  • The FlingableWebView has a new method:
    Initialize2 (EventName As String, InterfaceName As String)
    Convenience method:
    Initialize the FlingableWebView, set a DefaultWebChromeClient and DefaultWebViewClient and add a DefaultJavascriptInterface.
    EventName - The EventName will be used to initialize the FlingableWebView, DefaultWebChromeClient and DefaultWebViewClient.
    InterfaceName - Used as the DefaultJavascriptInterface InterfaceName

The new library is named AJWebKit to avoid confusion with either FlingableWebView or WebViewExtras2.
Be sure to uncheck both FlingableWebView and WebViewExtras2 in the b4a IDE when you use the new AJWebKit library.

I've updated the GeoLocation demo project too to show basic syntax of using AJWebKit and attached it along with the new library files.

Martin.
 
Last edited:

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,

Just seen your email. This sounds perfect. I'll get testing later today or tomorrow am.

Andrew
 

chrjak

Active Member
Licensed User
Longtime User
Hey Martin,
Thanks for your support. With the longclick sub it works now on android 2 (didn't tested on android 4 yet)
Regards
 

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,

It's looking very good, but one problem so far. I'm getting a lot of errors like:
B4X:
java.lang.ClassCastException: uk.co.martinpearman.b4a.webkit.DefaultWebViewClient cannot be cast to uk.co.martinpearman.android.webkit.FlingableWebView

The problem is that the events PageFinished and OverrideUrl used to be raised by the WebView itself, but are now raised by the WebViewClient. This causes a problem because unless I'm missing a trick, there's no way to get to the related WebView, which I need to access for related processing (e.g. to call GetTitle). We either need to raise these events against the WebView (which would probably be the best solution to allow drop-in compatibility with other WebView components), or provide an additional method on the "client" objects which returns the related WebView.

If you can fix this then we're looking good.
Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
Are you using the Sender object in the event Subs, expecting it to be the FlingableWebview but it's a different object?

Martin
 

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,
Yes, that's how I've written all my code. As I have an array it's the only way to identify which view raises the events.
Andrew
 

warwound

Expert
Licensed User
Longtime User
Morning Andrew.

We have two options then:
  • I update the code so that the Sender object is the FlingableWebView.
  • I update the signature of the Event and pass the FlingableWebView as a parameter to event sub.
    This more closely matches the underlying methods of the android WebViewClient and WebChromeClient, for example look here and you'll see that the WebViewClient method signatures pass the WebView as a parameter.
    The current event PageFinished(Url As String) would become PageFinished(FlingableWebView1 As FlingableWebView, Url As String).
    This option will save you having to Dim a FlingableWebView in your event subs but break your existing b4a code.

What do you think - is does either option appeal to you more than the other?

Martin.
 

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,

Thanks for the quick response. I'm happy with either approach, I don't have that much code to change now we have such a streamlined design overall.

If you want to position FlingableWebView as a "full function, drop-in replacement" for the standard WebView then the first option might be better, but it's up to you.

One other question. I notice you can return a read-only property IsPrivateBrowsingEnabled, but there doesn't seem to be a way to set this. I've also had a look at the WebViewSettings library (which works fine with your new library to get/set the user agent string), and there doesn't seem to be anything there either.

Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
Ok i'm not sure which of those two options is 'best'.
Personally i'd go with option 2 and pass the WebView as a parameter to the event sub.
Let me experiment a little and see what seems best.

The DefaultJavascriptInterface will need modifying so it can pass the WebView/FlingableWebView to b4a as either the Sender or as an event parameter so i'll see how to modify the DefaultJavascriptInterface and choose an option that can be implemented for all objects that raise events.

http://developer.android.com/refere...ext, android.util.AttributeSet, int, boolean)

PrivateBrowsing can only be enabled in the WebView constructor.
In the FlingableWebView library i do not use the constructor which offers to enable PrivateBrowsing.
Though i could update FlingableWebView so that it's Initialize or Initialize2 methods accept a Boolean PrivateBrowsing parameter.

You can see that PrivateBrowsing is not a setting that can be enabled/disabled in a WebView - it can only be set when the WebView is first created.
To enable or disable PrivateBrowsing you'd need to re-create the WebView.

But note the documentation:
This constructor was deprecated in API level 17.
Private browsing is no longer supported directly via WebView and will be removed in a future release.
Prefer using WebSettings, WebViewDatabase, CookieManager and WebStorage for fine-grained control of privacy data.

Rather than implement a deprecated constructor it'd be better and more future proof for you to use the suggested other techniques to control user privacy.
I'd guess these other techniques also allow you to enable/disable control of user privacy without needing to re-create the WebView.
Google will be your friend here as i have no experience of adjusting settings to control user privacy...

Google have a habit of deprecating WebView and related class methods with android updates and then removing deprecated methods with subsequent updates.
Relying on a deprecated method or constructor could leave you with broken code if/when a future android update removes that deprecated method or constructor.

Martin.
 

andrewj

Active Member
Licensed User
Longtime User
Thanks Martin. I look forward to testing the modified version, and I agree with your advice on how to implement private browsing.
Andrew
 

warwound

Expert
Licensed User
Longtime User
@andrewj

Here's another update for you to take a look at...

Open the AJWebKit.html document in a browser and look at the signatures of the events raised.
Where (easily) possible i've updated events to pass a FlingableWebView as an event parameter.

Some events don't pass a FlingableWebView, look at the DefaultWebChromeClient events ConsoleMessage and GeolocationPermissionsShowPrompt for example.
I think the logic behind this is that you don't need to know which WebView raised the event in order to take action.

Events raised by the FlingableWebView itself should pass the FlingableWebView as the event Sender.

Have a look at the demo project - i've added a couple of new events to the DefaultWebChromeClient:
  • ReceivedIcon (FlingableWebView1 As FlingableWebView, Icon As Bitmap)
  • ReceivedTitle (FlingableWebView1 As FlingableWebView, Title As String)

I've also renamed the GeoLocationPermissionsRequest event to GeolocationPermissionsShowPrompt and added a new event GeolocationPermissionsHidePrompt.
The change of event name better matches the underlying android method name and is logically better when looking at the new GeolocationPermissionsHidePrompt event i think.

If you can now decide whether any other remaining events should ideally pass a FlingableWebView parameter then i'll see if that can be implemented.

Martin.
 
Last edited:

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,

Looks like it's working brilliantly. I'll proceed with a bit more testing, but so far so good.

Thanks for all your efforts. Hopefully this will bring you in some further donations!

Andrew
 

andrewj

Active Member
Licensed User
Longtime User
Hi Martin,

Further question, if you don't mind. This seems to work for interacting with the page text, but I'd also like to be able to interact with images on a page, specifically to allow users to download them. This needs to be general-purpose, not just on my own web pages.

Over here you wrote some sample javascript code using getElementsByTagName which I use to return the matching URL when a user clicks on a link (for my "open in new tab" option), and I could do something similar to get a list of <img> tags in the document, but I still somehow need to get some information about the image which the user is clicking on.

Any ideas?
Thanks
Andrew
 

warwound

Expert
Licensed User
Longtime User
You have two options:
  • Inject javascript into each and every webpage once the webpage has loaded.
    The javascript will have to contain functions to perform whatever tasks are required of it - detecting clicks on images etc - and the javascript will have to communicate with your b4a code using the JavascriptInterface.
    Your b4a code can then take whatever action is required.
    Not a very elegant solution.
  • Try to detect these user events directly in your b4a code.
    For that i've updated AJWebKit:If you can get this to work it is by far the more elegant solution.

I've got little idea how the HitTestResult stuff works.
I'd guess you detect a click or long click on the WebView - an HTML element may now have the WebView focus - and the HitTestResult object returned by GetHitTestResult will let you determine what the user has clicked or long clicked on.
That's a guess on my part - you might find it useful to google for articles about the HitTestResult.

Library update attached.

Martin.
 
Last edited:
Top