JavascriptCore doesn't come with any support for setTimeout (which is an API on the window/DOM object not provide din JavaScriptCore). So you need to implement a version of this yourself - no worries on that (there are examples to refer to out there, so it is fairly easy to set this up).
But I have come across an issue with this that I am not sure how to handle properly. It relates to when the timer callback fires and runs code in the JavaScript engine itself.
Consider this code snippet (assume I have provided an implementation of setTimeout):
console.log('Hello - here we go');
setTimeout(() => {
console.log('Hi from setTimeout callback ...');
}, 0);
Promise.resolve().then(() => {
console.log('Hi from promise');
});
console.log('Hi from main block');
In Node.js or say Safari, I would see this output:
Hello - here we go
Hi from main block
Hi from promise
Hi from setTimeout callback ...
So the promise then() is handled before the settimeout callback is handled. I think this is basically because Promise then() handlers are pushed onto something like a microtask queue, and the setttimeout callbacks on a separate queue, and the microtask queue is emptied before any other queue is processed (after completing the current event loop of course).
But when I implement this in JavaScript core, I don't always see the above - instead I can have:
Hello - here we go
Hi from main block
Hi from setTimeout callback ...
Hi from promise
So the timeout callback can be run BEFORE the promise handler. This obviously is different from Node or Safari.
Now I assume that is because the timeout callback is triggered from Swift native code that uses the call() API on a JSValue object that is provided when the settimeout is given to the native layer to process. And it seems that when native code attempts to execute JavaScript code (via call() or similar) then this is just executed as soon as possible - obviously not interrupting the Javascript core when executing any current event loop code, but potentially running between the point when the Javascript core finishes a normal event loop cycle and then starts processing the queued promise handlers.
This means that code that runs nicely in Node (for example) might not work the same way due to this behaviour.
Also, I also notice another thing: if JavaScript code makes a call to a native-provided method (e.g. by calling the setTimeout I show above, which I implement via a native-side handler) then during that call from JavaScript, it is possible for the native side to execute a call() and run Javascript code it wants. Again this is not what would happen in Node or Safari: it is not possible for timeouts (or network completions) to interrupt any 'builtin' function call, but in JavascriptCore it certainly is (to get around this I set a flag on the JavaScript side indicating a native call is being made, and if any native-triggered callback occurs on the javascript side when this flag is set, I have to 'queue' it via a promise handler for execution AFTER the current event loop is complete).
Are these known issues with Javascript core and are there ways to get around them?
thanks
Post
Replies
Boosts
Views
Activity
hi
I have been using WKWebView embedded in a UIViewRepresentable for displaying inside a SwiftUI View hierarchy, but when I try the same code on 17,5 beta (simulator) the code fails.
In fact, the code runs (no exceptions raised or anything) but the web view does not render. In the console logs I see:
Warning: -[BETextInput attributedMarkedText] is unimplemented
Error launching process, description 'The operation couldn’t be completed. (OSStatus error -10814.)', reason ''
The code I am using to present the view is:
struct MyWebView: UIViewRepresentable {
let content: String
func makeUIView(context: Context) -> WKWebView {
// Javascript that disables pinch-to-zoom by inserting the HTML viewport meta tag into <head>
let source: String = """
var meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '*:focus{outline:none}body{margin:0;padding:0}';
var head = document.getElementsByTagName('head')[0];
head.appendChild(meta);
head.appendChild(style);
"""
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let userContentController: WKUserContentController = WKUserContentController()
let conf = WKWebViewConfiguration()
conf.userContentController = userContentController
userContentController.addUserScript(script)
let webView = WKWebView(frame: CGRect.zero /*CGRect(x: 0, y: 0, width: 1000, height: 1000)*/, configuration: conf)
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
webView.scrollView.backgroundColor = UIColor.clear
webView.scrollView.isScrollEnabled = false
webView.scrollView.isMultipleTouchEnabled = false
if #available(iOS 16.4, *) {
webView.isInspectable = true
}
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.loadHTMLString(content, baseURL: nil)
}
}
This has been working for ages and ages (back to at least ios 15) - something changed. Maybe it is just a problem with the beta 17.5 release?
I can create a new JSContext and use this to perform JavaScript operations. But if I want to shut down the JS Core after I have finished with it (without killing the app this is running in) I can find no means of doing that.
Is there a way to get the iOS system to release all memory/resources associated with the JSCore after I have finished with it?
The Apple docs at https://developer.apple.com/documentation/javascriptcore/jscontext don't provide any guidance for how to remove the JS Core after use. Maybe I am missing something.
I did also look at the underlying JSVirtualMemory instance, but again there is no obvious way to shut this down and release all memory.
If I simply dereference the JSContext instance, I would have hoped that this might trigger cleanup but it does not.