简体   繁体   中英

Intercepting and modifying the JavaScript code from PhoneGap Android apps

Context

I'm currently trying to design a model generator for hybrid Android apps. The goal is as follows: given any hybrid Android app developed using PhoneGap, generate a UI model that describes the UI states (in this case, I'm treating the UI state as equivalent to the DOM state) and the transitions to these UI states (denoted by actions - eg, click on DOM element X). The model is represented by a finite state machine, where the nodes are the UI states and the edges are the transitions.

Problem

My current task is to come up with a way to figure out whether a DOM element has an event handler registered to it (let's assume for now that we're only interested in the initial DOM state). With the help of this StackOverflow answer , I was able to come up with a solution that uses webView.loadURL("javascript:" + ....) to determine if an element has an event registered to it either by using, for example, element.onclick, or by using methods provided by libraries like jQuery and Prototype. However, I am currently unable to determine if an element has an event registered to it via addEventListener(), for reasons specified in that same StackOverflow answer. (Again, for simplicity, let's assume we're only interested in events registered right after the page loads, prior to any user interactions).

Questions

  1. The approach I'm planning to take is as follows: I plan to intercept the JavaScript code from the PhoneGap app. Once intercepted, I will instrument the code (eg, using Rhino) such that after each call to addEventListener(), I will place a "marker" for the DOM element involved in that call. I will then pass this instrumented code to the WebView so that it is this instrumented JavaScript code that gets loaded instead of the original JavaScript code. By doing so, I can determine which elements addEventListener() was called on. This seems pretty straightforward, but the problem is, I can't figure out a way to intercept the JavaScript code for instrumentation, and to pass the instrumented code so that it gets loaded in lieu of the original. Are there any tools that would allow me to do this intercepting and passing, as described? By the way, I'm using the Android emulator to run the PhoneGap app.
  2. Are there any other approaches that are perhaps simpler (or more elegant) and I should consider?

The solution I came up with is as follows. I created a new CordovaWebViewClient (passing the same activity and webView object to the CordovaWebViewClient constructor) in which the onPageStarted() method is overriden. The onPageStarted() code will execute once an HTML page in the hybrid app has begun loading. The overriding code does the following:

  1. Retrieve the URL of the page that just started loading, relative to the assets folder (file:///android_asset/) where all HTML/JS/CSS files are assumed to be located. The URL is one of onPageStarted() 's parameters, so you can retrieve it by simply accessing the value of this parameter. Example : If the HTML file is located in assets/www/foo.html, the relative URL is www/foo.html
  2. Retrieve the HTML code by setting up an input stream via activity.getAssets().open(relativeUrl) , where activity is the current DroidGap activity and relativeUrl is the relative URL found in the previous step.
  3. Parse the HTML code into a Document object (I used Jsoup to do this).
  4. Prepare the JS code you want to add as instrumentation (eg, by storing the new code in a String object) and insert that new JS code as part of a new script tag in the Document object. (In my case, I inserted the new script tag at the beginning of the HTML's head element)
  5. Re-convert the modified Document object into a String (say we call this string newHTML ), and call the loadDataWithBaseURL() method as follows: webView.loadDataWithBaseURL("file:///android_asset/www/", newHTML, "text/html", "UTF-8", null) . This essentially reloads the page, albeit with the instrumented code included.

I then set this extended CordovaWebViewClient as the webView object's new webView client (using the webView.setWebViewClient() method). Finally, I called webView.reload() so that the web view client change above takes into effect. As a result, each time a new HTML page loads in the web view, the onPageStarted code I wrote would execute.

Note that since item #5 above uses "file:///android_asset/www/ as the base URL and the HTML is loaded by string, you should also include a check at the beginning of your overriding onPageStarted() method to make sure that the URL of the page that just started loading is not equal to the base URL (ie, check that the URL is not exactly equal to file:///android_asset/www/). Otherwise, you'll encounter an infinite loop. (I also directed the onPageStarted not to execute whenever the url does not begin with file:///android_asset, which is the expected base folder).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM