Swift Headless WKWebView; running XMLHttpRequest - javascript

How can I run an XMLHttpRequest from within a WKWebView that I have created in Swift?
Currently, I use the WKWebView to access a page on a server I own. This page contains my Javascript. However, when I attempt to make an XMLHttpRequest by using WKWebView.evaluateJavaScript (i.e. call a javascript function that makes an XMLHttpRequest) the Javascript code executes successfully but the Safari debugger shows "Failed to load resource".
However, if I 'manually' make an XMLHttpRequest by using the console in the Safari debugger and then call my request function from my app, it works fine - but only once!
I'm at a bit of a loss as to why this is...
As mentioned in the comments, a better solution than bundling the Javascript with my swift library is to host the scripts on a page that is accessed via the WKWebView. Previous information maintained below;
Some background: I have been tasked with producing a Swift library that wraps our current Javascript API. I had hoped to use a WKWebView to simply load and execute our existing javascript code, however any XMLHttpRequests that I run simply return error (fire the onerror callback) instantly. The error object returned to onerror is {"position":0,"totalSize":0}
This thread seems to suggest that my WKWebView needs a 'parent' in order to execute javascript correctly. I have attempted to reproduce their code in Swift, but to no effect (see below)
let webConf = WKWebViewConfiguration()
WKWebView webView = WKWebView(frame: CGRectZero, configuration: webConf)
UIApplication.sharedApplication().keyWindow?.addSubview(webView)
In order to get to this point, I have had to set 'App Transport Security Settings -> Allow Arbitrary Loads' to 'YES' in my info.plist file, in order to enable non-https requests.

Related

Webextension inline install chrome.runtime.connect issues

I'm having a really weird issue, I've developped a webextension that uses messaging between content script and background script (using chrome.runtime.connect) and nativemessaging.
The issue i'm facing is that when I install the extension (manually from the store beforehand and then connect to my website, everything works as expected, the chrome.runtime.connect works and returns a valid port to the background script.
But when i do an inline install of the extension from my website to get around the fact to have to navigate to have the content script in the webpage, i manually inject the content script into my page using
function injectContentScript() {
var s = document.createElement("script");
s.setAttribute("src", "chrome-extension://<extensionid>/content.js");
document.head.appendChild(s);
}
and the exact same content script but manually injected doesn't behave the same. chrome.runtime.connect returns a null object and chrome.runtime.lastError gives me
Could not establish connection. Receiving end does not exist.
I'm calling on the sender side (content.js - manually injected content script) chrome.runtime.connect(extensionID) where extension id is the id of the extension generated by the chrome webstore. And on the receiving side (background.js - extension background script) chrome.runtime.onConnect.addListener(onPortConnected);
I'm not really sure how to debug this issue, maybe it's a timing issue?
The background script is well executed even with the inline install (i've added logs and debugged it through the background.html in chrome extension manager)
Any help would be greatly appreciated!
You have two scenarios.
Your content script content.js is executed as normal upon navigation, as a content script defined in the manifest.
In this case, it executes in a special JS context attached to the page and reserved for your content scripts. See Execution Environment docs section for explanation. It is isolated from the webpage and is considered part of the extension (albeit with lower privileges).
When you connect from a content script, chrome.runtime.connect() is treated as internal communication between parts of the extension. So while you can provide the extension ID, it is not needed.
More importantly, the event raised in this case is chrome.runtime.onConnect.
Your supposed "inject content script immediately" code called from the webpage does something completely different.
Instead of creating a new execution context, the code is instead added directly to the page; it is not considered part of the extension and has no access to extension API.
Normally, a call to chrome.runtime.connect() would simply fail, as this is not a function exposed to webpages; however, you also declared externally_connectable, so it is exposed specifically to your webpage.
In this case, passing the extension ID is mandatory for the connect. You were doing this already, so the call was succeeding.
However, and that's what made it fail: the corresponding event is no longer onConnect, but onConnectExternal!
What you should be doing is:
Not mixing code that is run in very different contexts.
If you need communication from the webpage to background, always do it from the webpage, not sometimes-from-content-sometimes-from-page.
That way you only have to listen to onConnectExternal and it cuts out the need for a content script (if it was its only function).
See the docs as well: Sending messages from web pages.
You don't have to source the code from chrome-extension://<extensionid>/; you can directly add this to your website's code and potentially avoid web_accessible_resources.
And if you actually want to inject content scripts on first run, see for example this answer.
Related reading: How to properly handle chrome extension updates from content scripts

Chrome extensions for silent print?

I have made a silent print web application that prints a PDF file. The key was to add JavaScript to the PDF file that silently print itself.
To do this I open the PDF with acrobat reader in chrome, that allow me to execute the script (with the proper permissions).
But as it was announced this solution won't work after chrome 45 because the npapi issue.
I guess a possible solution could be to use the recently release printProvider of chrome extensions.
Nevertheless I can't imagine how to fire any of the printProvider events.
So the question is: Is ok to think in chrome extensions to make a silent print web application, and how can I fire and handle a print job for an embedded PDF of a HTML Page.
Finally I reached an acceptable solution for this problem, as I couldn't find it out there, but read to many post with the same issue I will leave my solution here.
So first you need to add your printer to the Google Cloud Print and then you will need to add a proyect to the Google Developers Console
Then add this script and any time you need to print something execute the print() function. This method will print the document indicated in the content
The application will ask for your permission once to manage your printers.
function auth() {
gapi.auth.authorize({
'client_id': 'YOUR_GOOGLE_API_CLIENT_ID',
'scope': 'https://www.googleapis.com/auth/cloudprint',
'immediate': true
});
}
function print() {
var xhr = new XMLHttpRequest();
var q = new FormData()
q.append('xsrf', gapi.auth.getToken().access_token);
q.append('printerid', 'YOUR_GOOGLE_CLOUD_PRINTER_ID');
q.append('jobid', '');
q.append('title', 'silentPrintTest');
q.append('contentType', 'url');
q.append('content',"http://www.pdf995.com/samples/pdf.pdf");
q.append('ticket', '{ "version": "1.0", "print": {}}');
xhr.open('POST', 'https://www.google.com/cloudprint/submit');
xhr.setRequestHeader('Authorization', 'Bearer ' + gapi.auth.getToken().access_token);
xhr.onload = function () {
try {
var r = JSON.parse(xhr.responseText);
console.log(r.message)
} catch (e) {
console.log(xhr.responseText)
}
}
xhr.send(q)
}
window.addEventListener('load', auth);
<script src="https://apis.google.com/js/client.js"></script>
Anyway this script throw a 'Access-Control-Allow-Origin' error, even though this appears in the documentation... I couldn't make it work :(
Google APIs support requests and responses using Cross-origin Resource Sharing (CORS). You do not need to load the complete JavaScript client library to use CORS. If you want your application to access a user's personal information, however, it must still work with Google's OAuth 2.0 mechanism. To make this possible, Google provides the standalone auth client — a subset of the JavaScript client.
So to go throw this I had to install this chrome extension CORS. I'm sure that some one will improve this script to avoid this chrome extension.
You can register an Application to a URI Scheme to trigger the local application to print silently. The setting is pretty easy and straightforward. It's a seamless experience. I have posted the solution here with full example:
https://stackoverflow.com/a/37601807/409319
After the removal of npapi, I don't believe this is possible solely programmatically. The only current way I know to get chrome to print silently is using chrome kiosk mode, which is a flag (mode) you have to set when starting chrome.
Take a look at these SO posts:
Silent printing (direct) using KIOSK mode in Google Chrome
Running Chrome with extension in kiosk mode
This used to be possible using browser plugins (e.g. Java + NPAPI, ActiveX) but has been blacklisted by most browsers for several years.
If interested in modern solutions that use similar techniques, the architecture usually requires the following:
WebSocket, HTTP or Custom URI connection back to localhost
API that talks through web transport (JavaScript or custom URI scheme) to an app running locally.
A detail of projects (several of them are open source) that leverage these technologies are available here:
https://stackoverflow.com/a/28783269/3196753
Since the source code of these projects can vary (hundreds of lines to tens-of-thousands of lines), a code snippet would be too large unless a inquiring about a specific project's API.
Side note: Some technologies offer dedicated cloud resources, which add convenience at the expense of potential latency and privacy. At the time of writing this, the most popular "free" cloud solution -- Google Cloud Print -- is slated to be retired in December 2020.

Finding a strange Javascript function 'window.ueLogError' in a PhantomJS error message

I'm working on a project that involves retrieving multiple websites with PhantomJS and when I try to load Amazon.com, PhantomJS crashes while trying to evaluate this function with the error:
TypeError: 'undefined' is not a function (evaluating 'window.ueLogError')
The weird thing is that I can't seem to find any documentation or explanation of any kind for window.ueLogError. A google search gives a handful of websites with scripts that contain it, but it doesn't seem to be a part of any documentation I can find. Does anyone know anything about ueLogError?
The window object is the global sink in the browser. Every variable that you define globally (e.g. jQuery's $) is a property in window. ueLogError is not some kind of standardized JavaScript function. It is most likely some function for an advertisement script.
Not all resources that a page requests as part of the page load are actually successfully loaded. Some requests fail due to SSL problems. Some requests fail, because the user agent string is not supported. Some requests fail because of unknown reasons.
You can register to the onConsoleMessage, onError, onResourceError, onResourceTimeout events (Example) to see whether there are errors. Usually you will find that some JavaScript file wasn't loaded which would have contained ueLogError which another script depends on.

Fail fast when loading google maps asynchronously

I have an web app where some pages use the google maps API.
I'm using the async load with callback like explained in the documentation : https://developers.google.com/maps/documentation/javascript/tutorial?csw=1#asynch
It works great when the network connection is ok.
However, if for whatever reason, the browser fails to download the google maps script, it will hang for about 10 second before an unrecoverable connection an error. That network error stops the script execution and I cannot continue with my app process.
I want to recover from that error to continue the process of my webapp just with a info message telling the user that Google Maps isn't availaible for the moment.
I tried the following but it obviously doens't work because of cross domain xmlhttprequest access :
$.get("https://maps.googleapis.com/maps/api/js?key=.......",function(){
var googlemapsscript = document.createElement('script');
googlemapsscript.type = 'text/javascript';
googlemapsscript.src = 'https://maps.googleapis.com/maps/api/js?key=........&sensor=false&callback=successLoadGoogleMaps';
document.body.appendChild(googlemapsscript);
}).fail(function(){
errorLoadGoogleMaps();
});
When it has trouble accessing Google Maps API, the jquery $.get fails, thus running immediately errorLoadGoogleMaps(). However when it can access the API it throws the cross domain error.
I'm looking for the following behavior :
- Try to download Google Maps API
- Fail fast if it isn't availaible
- If available, continue with the success callback
Currently as a bad workaround I'm using a callback written in the script element :
googlemapsscript.onerror="errorLoadGoogleMaps()"
However it doesn't fail fast at all, the browser try to download the script for about 10 seconds before throwing the error and executing the callback.
How to fail immediately when trying to download an unavailable script (in particular Google Maps API here) ?
You might want to uses jQuery.getScript(), which should takes care a lot of errors handing, and browser supporting for you.

Execute javascript without webview in Android

I'm trying to execute a JS fonction in my Android app.
The function is in a .js file on a website.
I'm not using webview, I want to execute the JS function because it sends the request i want.
In the Console in my browser i just have to do question.vote(0);, how can I do it in my app ?
UPDATE 2018: AndroidJSCore has been superseded by LiquidCore, which is based on V8. Not only does it include the V8 engine, but all of Node.js is available as well.
You can execute JavaScript without a WebView. You can use AndroidJSCore. Here is a quick example how you might do it:
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet("http://your_website_here/file.js");
HttpResponse response = client.execute(request);
String js = EntityUtils.toString(response.getEntity());
JSContext context = new JSContext();
context.evaluateScript(js);
context.evaluateScript("question.vote(0);");
However, this most likely won't work outside of a WebView, because I presume you are not only relying on JavaScript, but AJAX, which is not part of pure JavaScript. It requires a browser implementation.
Is there a reason you don't use a hidden WebView and simply inject your code?
// Create a WebView and load a page that includes your JS file
webView.evaluateJavascript("question.vote(0);", null);
For the future reference, there is a library by square for this purpose.
https://github.com/square/duktape-android
This library is a wrapper for Duktape, embeddable JavaScript engine.
You can run javascript without toying with WebView.
Duktape is Ecmascript E5/E5.1 compliant, so basic stuff can be done with this.

Categories

Resources