Suspend Javascript execution in UIWebView - javascript

I am successfuly using an invisible UIWebView as an execution engine for Javascript snippets. Contrary to what certain pages suggest, it does not require to be visible. If declared dead simply as
UIWebView* myJSExecutor = [UIWebView new];
not only it executes any stringByEvaluatingJavaScriptFromString: thrown at it, it even bubbles alert() to the desktop! (Not that it's something you would like to do often) Disclaimer: tested only on iOS 5+
Now, I have another normally visible UIWebView with normal webpage content, and I want it to suspend JavaScript execution: that is stop acting on timer and DOM events. I thought that removeFromSuperview with stopLoading and setting delegate = nil would do the trick. But no, as soon as I add the UIWebView back to some visible view, I can see that the timers were running all the time.
I understand the schizophreny of my requirement. I appreciate it working in the background on one hand (contrary to some observations), but I want to suspend it on the other hand. But still I would like to ask if there is any, perhaps even private way to suspend it. Safari for iOS is capable of suspending browser tabs in the background. Chrome for iOS can't, which may be a sad negative proof :(

If you're looking for crazy, I have an idea.
The only way I know to pause JavaScript is to show an alert, confirm, prompt, or sending an ajax request with async=false.
Is the alert hidden when the UIWebView is hidden? If so, you could send an alert when you want it to freeze. The user would have to dismiss it when you showed the view again:
[web stringByEvaluatingJavaScriptFromString:#"alert('Press OK to continue...')"];
Or maybe you could dismiss it programmatically. I'm sure you'd agree that this whole suggestion is bound for trouble, but it's a thought.

Do you need a full UIWebView? If not you could just invoke JavaScriptCore directly.
- (NSString *)runJS:(NSString *)aJSString
{
JSContextRef ctx = JSGlobalContextCreate(NULL);
JSStringRef scriptJS = JSStringCreateWithUTF8CString([aJSString UTF8String]);
JSValueRef exception = NULL;
JSValueRef result = JSEvaluateScript([self JSContext], scriptJS, NULL, NULL, 0, &exception);
JSGlobalContextRelease(ctx);
...
This would give you more control over the entire JS runtime, but unfortunately I've not found an API to suspend execution of timers other than releasing the whole context.
For a more complete example of how to use JavaScriptCore see https://github.com/jfahrenkrug/AddressBookSpy/blob/master/AddressBookSpy/AddressBookSpy/ABSEngine.m

This might sound obvious, but if you can edit the JS you're running in the UIWebView, can't you just store a global variable in the JS called 'paused', and check that when your timers are firing?
var paused = false;
var thread = setInterval(function() {
if(!paused) {
// normal timer checking etc
}
}, 300);
Then just fire [webView stringByEvaluatingJavaScriptFromString:#"paused = true;"] when you remove the webview, and set paused = false when you add it back to the view.

Related

Detect user key/mouse in Python Selenium

I'm using Selenium Browser for day to day browsing, and I'd like to fire some code when I press some keys on any page. At first I thought I can just load javascript on every page that registers keys/mouse input, but I'd actually really prefer to have some python list available with past keys/mouse clicks, e.g. my key example in javascript:
var myhistory = []
document.addEventListener("keydown", keyDownTextField, false);
function keyDownTextField(e) {
var keyCode = e.keyCode;
myhistory.push(keyCode)
}
Is there any way to do this in pure Python/Selenium?
What I would try:
Execute a javascript that registers at the document body
<body onkeyup="my_javasctipt_keyup()" and onkeydown="my_javasctipt_keydown()">
using browser.execute_script. (partially solved, see question)
Save the key up and keydown events in a variable in javascript. (solved, see question)
use browser.execute_script to return the variables.
What I am uncertain about:
The return value of browser.execute_script may return json serializable objects or strings only
keyup and keydown in body may not work if they are used in child elements that define their own event listeners
Hopefully this is of help. If any code results form this I would be interested in knowing.
This code is what I feel should work:
from selenium import webdriver
browser = webdriver.Firefox()
browser.execute_script("""var myhistory = []
document.addEventListener("keydown", keyDownTextField, false);
function keyDownTextField(e) {
var keyCode = e.keyCode;
myhistory.push(keyCode)
}""")
def get_history():
return browser.execute_script("myhistory")
# now wait for a while and type on the browser
import time; time.sleep(5000)
print("keys:", get_history())
The point is that the code of selenium can never run at the same time as the browser handles keyboard input. As such, events need to be handled in javascript, the result saved, e.g. in an array and then, when selenium is asked, the array is returned to Python.
boppreh/keyboard would let you do that.
You install it. pip install keyboard
You import it. import keyboard
You use it. keyboard.add_hotkey('left', print, args=['You pressed the left arrow key'])
Then you disable it. keyboard.remove_all_hotkeys()
Well, in that case you had to choose the right tool for the job, i advice puppeteer a web-automation family instrument a pure-made JS, which can easily interact with the browser ( from js to js ) and catch events directly from the other side without any mediation.
Yet with selenium you can still achieve this transitively without messing too much with the pages's code or overcharging it with unnecessary tasks, also reloading the page content resets all its variables, which means it's lossy approach. The best closest way is to set an eventhandler internally and directly catch it from outside using Runtime.evaluate instead because it doesn't affect the page content and specifically it sticks to the function until it yields something using promise calls, it's better away than probing around some global variable over and over which is seen a bad practise see here.
myhistory = []
evt_handler = """
new Promise((rs,rj) => window.onkeydown= e => rs(e.keyCode) )
"""
def waitforclick():
try:
myhistory.append(browser.execute_cdp_cmd('Runtime.evaluate', {'expression': evt_handler, 'awaitPromise': True,'returnByValue': True})['result']['value'])
except:
waitforclick()
To avoid locking out the cpu you need to fork a thread in parallel.
from threading import Timer
t = Timer(0.0, waitforclick)
then t.start() instead of waitforclick().
Also you can use timeout if you want to reject the promise with a zero value after some time.

Why does this simple login script work in Tampermonkey but not Greasemonkey?

I have this tiny little script that I run inside Chrome using Tampermonkey and works great.
However, when I use it in Firefox with Greasemonkey, it shows up on the active list, meaning its matching the page but it doesn't actually execute the code. I know it has to be a simple something I am overlooking but its not hitting me.
var myVar=setInterval(function(){myTimer();},100);
function myStopFunction()
{
clearInterval(myVar);
}
function myTimer()
{
var p1 = "Login";
var p2 = "mode=login";
var x = document.body.innerHTML;
if (x.match(p1) && x.match(p2)){
document.documentURI = "/ucp.php?mode=login";
}
myStopFunction();
}
Script Logic/Function
I am using a timer to prevent the script from triggering over and over in a permanent loop.
It simply detects if I am logged into a phpBB forum or not, if not send me to the login page so I can log in.
I am using document URI so that the location of the original is preserved so upon login, it takes me right back to it.
Often phpBB when you log in, it will take you back to the index page so this preserves my original intent of going to the actual link.
This script works perfectly and as expected on Chrome using TM but on Firefox using GM it doesn't trigger, am I missing something here?
From the Firefox spec:
(document.documentURI)
Returns the document location as string. It is read-only per DOM4 specification.
And, indeed, the latest spec still specifies that this attribute must be read only.
If Chrome lets you write this property, then that is non-standard behavior and maybe a bug.
Use location.assign(), or location.replace(), or just programmatically click the login button -- which often preserves the target page.

Why is javascript saying that addcallback function is not defined?

my first time on here.
My problem is with AS3, Javascript and possibly the browsers Firefox and IE.
I have done so much searching for an answer so i will print my code:
i am using this line to call the flash application and in all browsers its combatible and actually traces in firebug to hold an OBJECT->FLASH_ID so thats not the problem.
var obj = document.getElementById('test');
then i use addcallback:
obj.sendStatus(loggedIn);
now whats weird is that i trace all individual elments in chrome and
-obj = flash object
-sendStatus = flash->function
-loggedIn = either false or true;
everything works great but when i am on firefox or ie
it traces differently
-obj = flash object
-sendStatus = undefined
-loggedIn = either true or false;
now what am i missing??????????
i tried embedding rather than object insertion
i made sure that the id's were all unique
i checked to make sure i had the right flash object selected with getElementById
im so confused.. and it feels like something simple.
I know about some browser - dependent timing problems, making the interface of the flash object available...
A timer could help, try this:
var obj = document.getElementById('test');
setTimeout(function(){obj.sendStatus(loggedIn);}, 500);
500 is a bit to long, but just to be sure. If it works you can try to lower it to 200 - 300.
make sure you declared allowScriptAccess = sameDomain both in embed tag and object tag
in case you don't use swfObject
Maybe the way you get a reference to the swf is wrong, try this
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/external/ExternalInterface.html
The problem is that using ExternalInterface requires both parties (browser and flash) to be ready.
You can have the flash poll a method in the page which just returns true so that you know its ready to receive calls from flash.
On the flip side if the page is cached, it can sometimes happen that the page wants to send to flash before flash is ready, so I use a callback to the page telling it flash is ready, so its like a handshake, once both parties are ready, then we can start sending data back and forth.
This has been my approach since Firefox 3.

Monitor ajax calls from IE BHO

I'm trying to find a way to detect changes on a page from a BHO. For the Firefox version of the plugin I'm using DomNodeInserted, but this isn't available in IE. I've also looked at using onpropertychange, but this only allows you to monitor a single element (not children).
I'm now wondering if it's possible to monitor when AJAX requests are called. If so, I can make the changes to the page after the AJAX request has completed. Only problem is, I can't find a way to do it.
Here's my attempt so far, based on jeffamaphone's suggestions, it doesn't work but maybe it'll jog someone's memory.
public class ChangeMonitor : IHTMLChangeSink {
public void Notify()
{
Trace.WriteLine("notified");
}
}
void registerMonitor()
{
HTMLDocument document = _webBrowser2.Document;
ChangeMonitor monitor = new ChangeMonitor();
IHTMLChangeSink changeSink = monitor;
IHTMLChangeLog changeLog = null;
((IMarkupContainer2)document).CreateChangeLog(changeSink, out changeLog, 1, 1);
}
For IE, onreadystatechange will work as an equivalent to DomNodeInserted. A DHTML behavior must be attached to the element via htc, but the htc file does not have to exist:
document.documentElement.addBehavior("foo.htc");
document.documentElement.attachEvent("onreadystatechange", Notify);
The BHO can inject the script to handle the event.
I've never done it, but my theory is you can use IMarkupContainer2::CreateChangeLog().

Strange problem with stringByEvaluatingJavaScriptFromString not working with NSUSerDefault value...please help!

I am working on a bookmarking feature for a book reader iOS app I have. The way it is setup now, the user scrolls through a view, and when they want to save, or bookmark their spot before they leave, they hit a save button on the bottom toolbar.
When they hit this save button, the saved action is called:
-(IBAction) savePlace:(id)sender{
int pageYOffset = [[webView stringByEvaluatingJavaScriptFromString:#"window.pageYOffset"] intValue];
NSLog(#"%d Set Y Value", pageYOffset);
[[NSUserDefaults standardUserDefaults] setSavedSpot:pageYOffset];
}
Surprisingly enough, I have got this part working. I can hit the save button, the console will read say 200 for where I'm at on the screen, and then when I leave and come back, 200 is again printed out to the console thanks to this method that is called by NSUSerDefaults when the app loads:
- (void) setCurrentSpot:(NSUInteger)ySpot {
NSLog(#"%d Saved Y Value", ySpot);
[webView stringByEvaluatingJavaScriptFromString: [NSString stringWithFormat: #"window.scrollTo(0, %d);", ySpot]];
}
BUT! Nothing happens...I know for a fact that I am saving and correctly retrieving the correct Y-axis value, but when that JavaScript method is called, it won't fire.
To further complicate things, I went ahead and made a custom IBAction that accesses that same ySpot value, and used the exact same JavaScript method to move the view to position 200, and it works perfectly!
What am I missing? I don't see what is going on. Thanks
edit: misread your question,
Perhaps the method you are calling to call that javascript is being called too fast? and not giving the UIWebview time to initialize and react? if it works when you wait on the webview to come upand then call it, that is the only thing i could see.

Categories