evaluateJavaScript in WebView only works once

Hi there,

I’m trying to share an item from within a WebView. As Scriptable internally uses a WKWebView, the Web Share API is unavailable (because Scriptable would have to implement the prompt).

To work around this, I’m using evaluateJavaScript, and binding this to an event handler, which then calls the completion handler. Scriptable then shows a share sheet, then re-evaluates the javascript (via a recursive function).

At least that’s what should happen. But, for some reason, the second call to evaluateJavaScript just doesn’t seem to work, no matter what I do. I’ve tried using timers to no avail.

Here’s my code:

let wb = new WebView()

function log(text) {
	console.log("[scriptable] " + text)
}

let html = `<!DOCTYPE html>
<html>
<head>
	<style>
		button {
			font-size: 2em;
		}
		
		textarea {
			height: 300em;
			width: 100%;
			font-size: 2em;
			font-family: monospace;
		}
	</style>
</head>
<body>
	<h1>JS has not initialised yet.</h1>
	<button id="share">Share text</button>
	<h2>Log:</h2>
	<textarea disabled id="logger"></textarea>
</body>
</html>`

wb.loadHTML(html).then(() => {
	function applyShareCode() {
		log("evaluating javascript");
		wb.evaluateJavaScript(`
			function log(text) {
				document.getElementById("logger").value += "[browser] " + text + "\\n";
			}
		
			log("initialised")
		
			if (window._count === 1) document.body.innerHTML = "Counted!";
			if (!window._count) window._count = 0;
			window._count++;
			document.querySelector("h1").innerText = window._count;
			log("set count")
			
			let button = document.getElementById("share");
			button.addEventListener("click", function h(){
				button.removeEventListener("click", h);
				log("removed old event handler")
				completion();
				log("completion called")
			});
			log("set event handler")
		`, true).then(_ => {
				log("received completion handler")
				ShareSheet.present(["It works... once."])
				log("re-evaluating javascript")
				applyShareCode();				
		});
	}
	applyShareCode();
});

wb.present(true);

Can anyone help shed some light on what’s happening?

This is a very tricky case if you don’t know the nuances of Scriptable.

It generally appears that Scriptable has some problems catching errors in an asynchronous context and therefore doesn’t log them. So if something doesn’t work as expected, doesn’t log any error and runs in an async context, then I wrap everything in a try…catch and log the catched error (and throw it again to stop execution).

I did exactly this in your applyShareCode and there really was an error. It said:

Error: Failed evaluating JavaScript with error on line 0: SyntaxError: Can't create duplicate variable: 'button'

You declare button in the WebView again when evaluating a second time. Either you change the declaration from let to var or wrap that code in curly braces:

{
  // your code for the button here
}
1 Like

Thank you!! I’ve noticed that behaviour too in other places. It seems like Scriptable doesn’t error if a promise is rejected without a reject handler.

I’ve added the below code to my script, which also catches the error…

.catch(e => {
	console.error(e);
});

Unfortunately it doesn’t seem possible to apply this globally (in a browser we can use window.onunhandledrejection), so you’ve got to add this to every single promise that might reject.

Thank you again!