In the mobile version of my web site I have a JavaScript confirm dialog that appears under specific circumstances. Using setTimeout I trigger the confirm dialog.
No matter which tab the user is in, he should see the confirm dialog but in iOS 10 loses focus.
In iOS version 8 & 9 works fine when I have two tabs and I am in the 2nd tab, the confirm dialog shows up in front like it should.
Is there any solution or workaround for that?
var cf = confirm("Close?");
if (cf){ do that....} else { do this... }
SafariDriver is implemented in JS so in order to intercept calls to alert, confirm, and prompt, one must override the functions in the context of the web page.
Change the injected script to be injected as a Start script instead of End script - which means the script is injected once the DOM has been loaded, but before it has been parsed (as opposed to being injected after the onload event):
http://developer.apple.com/library/safari/#documentation/Tools/Conceptual/SafariExtensionGuide/InjectingScripts/InjectingScripts.html#//apple_ref/doc/uid/TP40009977-CH6-SW5
Override the global alert functions in the context of the page under test, not the injected script. This is similar to the requirements of the executeScript command. Therefore, the first thing our injected script should do is add a script tag to DOM that sets up the alert overrides. This script tag should be added as the first child of the documentElement to ensure it is executed before any others in the page. This will ensure we set-up our alert handlers before anything in the page has a chance to fire an alert.
Once an alert fires, we must notify the extension that there was an alert, while simultaneously blocking the current JS thread in the page. Normally, our page scripts communicate with the injected script using window.postMessage. postMessage fires a MessageEvent asynchronously. To maintain synchronicity, we can manually fire a MessageEvent:
Use a MessageEvent instead of some other DOM event so we can include a JSON object describing the alert.
var event = document.createEvent('MessageEvent');
event.initMessageEvent('message', false, false, {
type: "alert", // confirm, or prompt
text: "hello"
}, window.location.origin, '0', window, null);
window.dispatchEvent(event);
The injected script must listen for an respond to the page's alert message. To synchronously send the alert to the extension for processing, we can (ab)use the Safari extension's mechanism for blocking content from loading:
http://developer.apple.com/library/safari/#documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW9
window.addEventListener('message', function(e) {
// Create a beforeload event, which is required by the canLoad method
var e = document.createEvent('Events');
e.initEvent('beforeload', false, false);
// canLoad sends and waits for a response synchronously. It is the only
// synchronous function in the Safari extension messaging API.
var response = safari.self.tab.canLoad(e, e.data);
// Send the response back to the page using another MessageEvent.
var responseEvent = document.createEvent('MessageEvent');
responseEvent.initMessageEvent('message', false, false, {
accepted: response.accepted,
response: response.value
}, window.location.origin, '0', window, null);
window.dispatchEvent(responseEvent);
}, true);
Note, the extension's alert response must be communicated back to the page using another message since we are crossing context boundaries. The only other option is to store the response on the DOM to be read on the other side.
The final step, and this is the open-ended question, is how the extension should handle the alert. Since we're maintaining the blocking behavior of alerts, it's not possible to let anymore commands execute (even if they result in unhandled alert errors).
One possibility is to have the WebDriver client participate in the alert handling. In addition to providing a WebSocket server, the WebDriver client will be expected to also provide an XHR end-point. When an alert is detected, the server will send a synchronous POST XHR to this end-point. The client should respond only once the user has accepted or dismissed the alert (or an unhandled alert error was thrown from another command). When the XHR response is received, the extension completes the chain and sends the response back to the injected script.
You can find more here.
Related
I've set up a fiddle to try to verify if the beforeunload event is triggered when the page is used in an iframe.
Since the fiddle show its result in an iframe, I figured it could be easy to verify by just closing the page. I've set up a request bin at pipedream just to see if any requests gets sent, but it doesn't seem to trigger in Chrome.
window.onbeforeunload = function() {
fetch('https://eoaczcjrpegb7wv.m.pipedream.net')
}
Is is possible to use this event from an iframe or do I need to look in to a different approach?
After a closer look it seem to capture some of the requests. Is this prone to race conditions? If so, are there any more robust alternatives?
Yes, there is a race condition. Since your event handler does nothing to stop the page unload in any way (it does not even trigger a confirmation prompt to delay it), immediately after your event handler is processed, the browser will proceed to unload the page. This aborts most pending requests; if a request did not manage to be submitted at that point, it will not be sent to the server at all.
Sending a request from a beforeunload event handler is a poor idea anyway. For starters, you are not even guaranteed that the event will fire at all; the browser may be unable or unwilling to trigger the event. MDN warns that it will only fire after the user had interacted with the page and it may fail when the session is terminated out of browser’s control. The only legitimate purpose of beforeunload is to check whether the page contains any unsaved state that the user may lose, and to trigger a confirmation prompt; even that should be understood to work on a best-effort basis. Anything else is prone to abuse and suspect; I would not be surprised if a future browser plug-in or vendor ‘intervention’ were to block all web requests when a page is about to unload.
However, if you insist, there are ways to make the request survive unload. You can use navigator.sendBeacon:
window.onbeforeunload = function () {
navigator.sendBeacon('https://example.net', '');
};
or the keepalive fetching option:
window.onbeforeunload = function () {
fetch('https://example.net', { keepalive: true });
};
Chrome provides both, but Firefox, as of version 106, only implements the former. Using those APIs comes with some restrictions: at any given moment, the total amount of data sent by active keep-alive requests must fit within 64 KiB, as per the Fetch specification.
You may notice using those APIs in a beforeunload handler is still not recommended usage, as it worsens performance of navigating back to the page with the back button. MDN suggests listening for the visibilitychange event, but that is of course not the same thing.
Last but not least, nothing stops the user from having the browser lie to you that the request has been sent, like with this uBlock filter:
##+js(no-fetch-if, keepalive:true)
##+js(set, navigator.sendBeacon, trueFunc)
So try not to be too obnoxious with your spyware ‘analytics’.
I want to intercept some user requests, cancel it and then present a hand made HTML.
First I tried to call window.stop() in the beginning of content.js, but after checking the Network tab of the Developer Tool I noticed that some elements are still being requested (although not loaded, it seems).
Then I tried to add a chrome.webRequest.onBeforeRequest listener inside background.js. So inside the callback I set my hand made HTML (document.getElementsByTagName("html")[0].innerHTML = myHtml) and in the end I canceled the original request (return {cancel: true}), but, although the original request is being canceled, my HTML doesn't appear.
So basically I want to be able to tell the difference between when an ajax call failed naturally (eg 404, server down, etc etc) and when it has been canceled by the browser, eg when a user closes a tab or hits f5.
I listen for failed ajax attempts and display an error dialogue, describing the error when ajax fails. However when someone presses f5 (especially noticeable in firefox), the error callback of not yet complete ajax calls occur, then displays errors and while the page loads the user is stuck looking at the error dialogue until the whole page is torn down by the browser (which is not nice to look at, when in reality it doesn't matter that the ajax call failed because the whole web app is being reloaded).
I have examined the xmlHttp request object, and nothing stands out to me as a way to tell the two situations apart. I have also tried adding a delay, but this hasn't really worked so well either. So I am just wondering if it is possible, and if so how?
I found a work-around. If you set a window.onbeforeunload handler, that will get called before the ajax error. So, if you set a flag that the document is unloading, you can check that flag before showing your ajax error and skip the error if the document is in the process of unloading.
Works for me with jQuery 1.11 in Firefox, Chrome and IE.
You can see it work here: http://jsfiddle.net/jfriend00/G3wat/
var unloading = false;
$(window).on("beforeunload", function() {
unloading = true;
});
Then, in your ajax calls:
error: function(jqXHR, status, err) {
if (!unloading) {
alert("Error status " + status);
}
And, as you found, it appears that jQuery 2.x does this for you.
AngularJS 1.2.13
var httpdelete = $http.delete("/api/categories/" + id);
httpdelete.success(function(data){
alert("Success");
});
httpdelete.error(function(data, status, header, config){
alert("Error!");
});
I do an asynchronous $http.delete request
The success callback function is executed and the alert box "success" appears.
I hit the browser refresh button
The $http.delete line is not executed (debugged with break points). Instead the error callback function is immedialy executed. The alert box "error" appears. No request made it to the server after clicking on the browser's refresh button
I was expecting the entire page to reload when I hit the browser's refresh button.
Instead, AngularJS seems to attempt to resend my last delete query without having to execute $http.delete and goes straight to the error callback.
How can I restore the natural behaviour of the browser's refresh button? I want it to reload the entire page and not attempt to resend the last asynchronous http request.
Open the network tab of the chrome dev tools. Load your page and hit F5. If you don't see a get to your index.html (or whatever your base url is), it's because angular handled it. If you do see the get, the you have rebooted the app for real.
Once you know which one it is, you can investigate further. Setting a breakpoint in the httpdelete callback and inspecting the callstack might also help.
Okay so here is what happened, my backend Nodejs+Express+MongoDB delete action was not returning anything to the client (browser). I didn't think it was necessary to return any information after deleting the document from mongodb.
The side effect of that is as I described in the original post. After deleting the document on the server, if a user refreshes the page using the browser refresh button then the page is not refreshed. Instead the $http.delete request is resent to the server and on top of it the error callback is executed.
After modifying my server side action and make it return a json document such as { success: true, message: "" } after a delete request, the browser's refresh button behaves as it should have which is to reload the entire single application page index.html.
I have a page that enable user to perform image manipulation via HTML5 canvas, on the page, there's a facebook share button for sharing a generated image of the canvas on facebook.
When the link is clicked, an ajax request is sent to the server (ASP.NET MVC) to perform the image generation, save the image on the server, then generate a url(that links to the image) that is returned as the ajax response. The returned url is what I want to pass as the parameter for facebook to share. The issue is that popup blocker is blocking facebook share dialog when I call "window.open".
Is there any other way to open a new tab without popup blocker. I believe that since the user initiated the action, there should be a way for me to bypass popup blocker. Thanks.
Update Oct 2014:
It was noted correctly in the comments, that Firefox has deprecated the synchronous setting in June 2014, but it is still working in this browser.
Furthermore, Chrome received updates which will only allow this to work as wanted if the ajax call returns in less than a second. Which is rather hard to gurantee. I've created another question devoted to the Chrome timeout:
Synchronous Ajax - does Chrome have a timeout on trusted events?
The linked post contains a JSFiddle demonstrating this concept and the problem.
Original Answer
Short answer: Make the ajax request synchronous.
Full answer:
A browser will only open a tab/popup without the popup blocker warning, if the command to open the tab/popup comes from a trusted event. That means: The user has to actively click somewhere to open a popup.
In your case, the user performs a click so you have the trusted event. You do loose that trusted context however, by performing the Ajax request. Your success handler does not have that event any more.
The only way to circumvent this is to perform a synchronous Ajax request which will block your browser while it runs, but will preserve the event context.
In jQuery this should do the trick:
$.ajax({
url: 'http://yourserver/',
data: 'your image',
success: function(){window.open(someUrl);},
async: false
});
Here's how I got round the issue of my async ajax request losing the trusted context:
I opened the popup directly on the users click, directed the url to about:blank and got a handle on that window. You could probably direct the popup to a 'loading' url while your ajax request is made
var myWindow = window.open("about:blank",'name','height=500,width=550');
Then, when my request is successful, I open my callback url in the window
function showWindow(win, url) {
win.open(url,'name','height=500,width=550');
}
The answer from wsgeorge is the one that got me on the right track. Here is a function that hopefully illustrates the technique more clearly.
function openNewAjaxTab(url) {
var tabOpen = window.open("about:blank", 'newtab'),
xhr = new XMLHttpRequest();
xhr.open("GET", '/get_url?url=' + encodeURIComponent(url), true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
tabOpen.location = xhr.responseText;
}
}
xhr.send(null);
}