Migrating from 2017 r2.1 to 2018 r4

(Note: I originally posted this in General but it was suggested iOS might be a more appropriate home)

I have an inherited app that I need to migrate from Xojo 2017 r2.1/iOS9.3/Xcode 7 to Xojo 2018 r4/iOS SDK 12.1/Xcode 10/. Code assembles and runs, but blows up on the first reference to an ExecuteJavascript emulation method (ExecuteJavascript) using UIKit

[code]    EvaluateJavascript(extends web as iOSHTMLViewer, JS as Text) as Text
Declare Function stringByEvaluatingJavaScriptFromString Lib "UIKit" Selector     "stringByEvaluatingJavaScriptFromString:" (id As Ptr, js As CFStringRef) As     CFStringRef

Dim jsResponse As Text
jsResponse = stringByEvaluatingJavascriptFromString(web.Handle, JS)

Return jsResponse[/code]

Greg O’Lone responded:

Right. That method is from UIWebView and doesn’t work with WKWebView. See the following link for more info:

https://developer.apple.com/documentation/uikit/uiwebview/1617963-stringbyevaluatingjavascriptfrom?language=occ

Would anyone have a pointer to an example using WKWebView using Xojo syntax? I am not Obj-C adept…

Go grab iOSDesignExtensions on Git and add it to your project. Then you should be able to use the ExecuteJavascriptXC method to run your JS.

ExecuteJavascriptXC in place of ExecuteJavascript?

Or EvaluateJavascript?

I have this currently:

// Get current browser title Dim currentTitle As Text // currentTitle = webViewer.EvaluateJavascript("getBrowserTitle();") currentTitle = webViewer.ExecuteJavascriptXC("getBrowserTitle();") // which should return "fred"; calls from iOSDesignExtensions Function ExecuteJavascriptXC(Extends viewer As iOSHTMLViewer, script As Text, callback As ExecuteJavascriptDelegateXC = nil) As Text Declare Sub execJavaScript Lib "WebKit.framework" selector "evaluateJavaScript:completionHandler:" (obj_id As ptr, script As CFStringRef, block As ptr) // callback is Nil If callback <> Nil Then ExecuteJavascriptCallback = callback Dim block As New iOSBlock(AddressOf ExecuteJavascript_Result) execJavaScript(viewer.Handle, script, block.Handle) Else execJavaScript(viewer.Handle, script, Nil) // which returns nothing, unlike the original EvaluateJavascript in 2017 r2.1 End If // so the parent Sub receives no result and continues looping waiting for a result to be returned... which never occurs

So what I see is that the original (2017 r2.1) EvaluateJavascript was supposed to return a response, and (2018 r4) ExecuteJavascriptXC does not.

ExecuteJavascriptXC is the wrapper method in iOSDesignExtensions so you don’t have to write the declare.

I copied all ExecuteJavascript* from within HTMLViewerExtensionsXC in iOSDesignExtensions.

I also added more description above. But while my older EvaluateJavascript returned a value, ExecuteJavascriptXC does not.

ExecuteJavascriptXC will return a value via the callback parameter that you pass when you call it. So create a method that accepts the same parameters as the delegate and that where your result will be.

Do you have a code example using Execute JavascriptXC to interact with a web viewer on iOS?

I tried

dim fred as ExecuteJavascriptDelegateXC webViewer.ExecuteJavascriptXC("getBrowserTitle();", fred)
even an alert I planted in getBrowserTitle() in the JS no longer triggers.

No you don’t want to create a variable of type delegate. You want to create a method that matches the delegate parameters. Take a look at Delegates in the Xojo docs. Then have a look at the vHTMLViewer view in iOSDesignExtensions. It has a “Pi” button that uses JS to calculate pi and that demonstrates how to use a callback to receive the result.

Thanks - I have more to go on now.

I’d like to revive this thread - I have just installed 2019r2b81, as Apple updated my Xcode toolkit to 11.0. After a few tweaks, at least the code compiles. As Jason suggested above, I installed the iOSDesignExtensions, and am emulating some of the code of the vHTMLViewer view example.

My problem is I am unable to execute a simple function call to javascript to retrieve the document.title; whether I embed my code in a text string or pull up my library file, which reads successfully. Here’s what I’m doing: (I tried to simplify it as much as possible)

  var getBrowserTitle as text = var newTitle=document.title; newTitle;
  dim js as text = getBrowserTitle
  Call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)

   // (jn another method)
   cmdResult(value As auto, error As RuntimeException)
     name = value  // name is a global text variable

name always comes back empty, and triggers a Runtime Exception of “A JavaScript exception occurred”

Ideas?

Aargh - typos above; first pass code too late to edit. the code s/b

Dim html As Text = "<html><body></body></html><script>function getBrowserTitle(){var newTitle = document.title}newTitle;</script></body></html>"
Declare Sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)
dim js as text = getBrowserTitle
  Call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)

   // (jn another method)
   cmdResult(value As auto, error As RuntimeException)
     name = value  // name is a global text variable
 function getBrowserTitle(){var newTitle = document.title}newTitle;

I think this should be:

 function getBrowserTitle(){var newTitle = document.title; return newTitle;}

Either way in Greg’s reply works for the javascript; if I define js as text = “getBrowserTitle()” I no longer get “A JavaScript exception occurred” and instead get “TypeMismatchException (Expected Text but got Ptr)” since value is returned as &h0000000… instead of text.

Clearly the document.title is not being read correctly.

Anyone see a way to get this property to read correctly?

Also, is there any property under Variables->self (ViewWeb.ViewWeb) that should reveal the document.title?

This should get you the document title

[code]dim js as text = “document.title”

Call HTMLViewer1.ExecuteJavascriptXC(js, weakAddressOf cmdResult)[/code]

And If you are loading the HTML and executing the javascript in the same Xojo function it will not work because the page isn’t loaded yet.

I followed your (Jeremie’s) suggestions. Still no result value.

  1. The HTML view (webViewer) is activated via loadHTML; my html initialization text string displays a message to confirm this.
  2. I have a timer-triggered method that performs the ExecuteJavascriptXC, and tests to see if cmdResult has been run
  3. if it has, it should have a result. My value (title) comes back empty now.

This gets me a fresh window, displaying “hi!”

// load test html to window referenced by webViewer
Dim html As Text = "<html><body></body></html>hi!</body></html>"
Declare Sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)

This is the timed routine, firing every second until satisfied

// Get current browser title
dim currentTitle As text
dim js as text = "document.title"

// try to get the document.title property via js, once
// reqDocTitle is a switch initialized to false
if (not(reqDocTitle)) then
  call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)
  reqDocTitle = true
end if

// if cmdResult has not yet executed, drop out
// cmdResultDone indicated cmdResult has fired; initialized as false
if (cmdResultDone) then
   break    // I stop here to check the title
  // Determine if currentTitle is "title" which means the page loaded
  if (currentTitle = "title") then
    // do stuff...
  end if
end if
cmdResultDone = false

and here’s cmdResult:

If error <> nil Then
  dim reason as text
  reason = error.Reason
  break
  return
end if
dim res as text = value
cmdResultDone = true

How do I get a result back from (js) document.title, other than nil, using webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult) ?

[quote=455494:@Michael Gehl]This gets me a fresh window, displaying “hi!”

// load test html to window referenced by webViewer
Dim html As Text = “hi!”
Declare Sub loadHTML Lib “WebKit.Framework” Selector “loadHTMLString:baseURL:” (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)[/quote]

I don’t know if this is causing the error but your html is wrong.
You don’t need to declare loadHTML function as it is already included in iOSDesignExtensions

You should pause the debugger when you get nil and move up the function stack to see what error the iOS framework is returning.

Should have mentioned I had fixed the html; I’m a dyslexic typist :wink:

loadHTML in HTMLExtensionsXC uses UIKit - is that right? I’m redeclaring it locally with WebKit.

Next up the stack is ExecuteJavascript_Result with a result pointer and a zeroed error pointer, which tries to invokes the callback (cmdResult) with a blank value and error of Nil, and returns Nil.

dim html As text = "<html><body>hi!</body></html>"
// from vHTMLViewer example
declare sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)

this succeeds, as the “hi!” is displayed.
in another method, triggered by a timer, and checked to ensure the code above had been executed:

// Get current browser title
dim js as text = "document.title"
// try to get the document.title property via js, once
call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)

ExecuteJavascriptXC runs ExecuteJavascript_Result, which executes cmdResult as a callback

// cmdResult(value as auto, error as RuntimeException)
if error <> Nil Then
  dim reason As text
  reason = error.Reason
  break
  return
end If
// break here, step ahead one, but value is blank
dim res as text = value