I'm trying to cheat a bit with localStorage. The spec defines that when a value in localStorage changes, all other open pages on the same domain receive a storage event callback. I would also like the event to fire on the page where the value was changed.
I added a hidden iFrame to each page which loads an empty document from the same domain and tried using it as the target for the localStorage change (so technically the page that I'm looking at isn't the origin of the localStorage change)
It works fine except for when I do the same thing inside an event callback...
function fnSetupMusicPlayerSection(i, oSection) {
var oAudio, oLocalStorageFrame, oLocalStorageWindow;
oAudio = oSection.querySelector('audio');
oLocalStorageFrame = oSection.querySelector('iframe.local-storage-target');
oLocalStorageWindow = oLocalStorageFrame.contentWindow || oLocalStorageFrame;
oLocalStorageWindow.localStorage.setItem('loadSetter', '1111');
oAudio.addEventListener('play', function(oEvent) {
oLocalStorageWindow.localStorage.setItem('callbackSetter', '2222');
});
}
loadSetter is successfully stored and all windows receive the storage event. When I click to play the audio I get the following error inside the callback - Uncaught DOMException: Failed to execute 'setItem' on 'Storage': access is denied for this document.
Is there anything I can do to solve this? I really don't want to have to write code to update the current page separately
Update: I don't know if I'm doing something wrong in the example I gave above but the code does seem to work inside some callbacks. I have an anchor on the page with a click event where I can set localStorage through the iFrame
You can try postMessage API to enable communication between your page and iFrame. In brief, send a message to instruct iFrame to update its localStorage, and another message to ask iFrame to return its localStorage content whenever you need the data you sent.
Be careful since:
This is a HTML5 API. Check if your app's minimum requirements allows the implementation.
This is a cross-origin communication, which means if other pages in your browser use postMessage, your iFrame will receive it too. You might need to add info into message to notice iFrame which message it should read.
Related
First of all, I know that the topic of accessing iFrame Elements cross-domain is a tricky topic and I might be going with this nowhere.
I have a Google Forms embedded in my app that I need to set up in a way to tell me when a user has submitted his/her response. I already tried lots of things but the most optimistic way that could work would be just to read the HTML of the iFrame when the last page has been loaded saying that the response was submitted.
Therefore, I was looking for solutions on how to simply read any kind of snippet of the iFrame's content and I came across this comment in another thread:
https://stackoverflow.com/a/32265508/3856569
I'm trying reading the content of the iFrame as suggested in the comment and sending it to the parent windows via postMessage like so:
$(document).ready(function() {
document.getElementById("googleForm").addEventListener("load",
function() {
var message = document.getElementById("googleForm");
parent.postMessage(JSON.parse(JSON.stringify(message)), '*')
});
})
and the parent-window reads the message like so:
function receiveMessageGoogleForm (event) {
console.log(event)
}
//Listen for message events
window.addEventListener("message", receiveMessageGoogleForm, false);
However, the data property of the event object upon receiving the message seems to be empty.
Is this another inbuilt mechanism to avoid reading any kind of a cross-origin iFrame or am I missing something here?
Forget about postMessage for a moment.
var message = document.getElementById("googleForm");
var result = JSON.parse(JSON.stringify(message));
console.log(result);
<div id="googleForm">content</div>
If you pass a DOM element into JSON.stringify then you get a (JSON representation of an) empty object out.
DOM elements don't have any properties that will be automatically processed by JSON.stringify.
If you want to get data from a form, then you need to actually read the data from the form (e.g. get the input elements and read their value properties).
If you want to extract data from the DOM of a Google Form or if you want to send a postMessage from a Google Form then you need to write the JavaScript which does that in the HTML document containing the form.
You can't pull it across domains.
The page with the Google Form has access to the data and can push it across domains with post message.
Nothing you can do will let you just help yourself to that data.
It's private between the owner of the browser and the owner of the website and one of them needs to take explicit action if it is to be shared with anyone else (e.g. someone who has wrapped that website in an iframe).
i am embedding a form on a checkout page with an iframe and i am trying to take the price of the cart and have it automatically inputted into the amount field. the value of the cart variable seems to be $("span[data-test='cart-price-value']").innerText;
when i check it in the console, but i am confused on whether i should set the variable in the script that embeds the iframe or in my iframe html itself or whether or not i have to do both and add a jquery listener to the iframe html
and also i need to trim off the '$' from the variable, cause
$("span[data-test='cart-price-value']").innerText; returns a value with a dollar sign in front of it for example "$435.66" and i need it to be just "435.66" any help would be appreciated
There are a few things here -
Browsers have some pretty stringent restrictions about cross-origin iframes. You won't be able to communicate between the BigCommerce Checkout on one URL to an iframe hosted on a different URL - if their origins are different.
If you try to use JavaScript from the BC checkout page to set the value of a form input embedded in the iframe, you'll get a security warning in the console and the browser will dissallow it.
However, there is an API which allows for this communication - you can read more here:
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
You're going to be sending and listening for custom "message" events and running code accordingly.
I'm not 100% on this, but from what it sounds like, you'll want to use Window.postMessage() to the iframe/origin hosting the external form, and send along the price you need to input.
On the page which serves the iframe/form code, you'll need an event listener to wait for this message to come in, and that's when you can have an event handler which will take the value passed along in the cross-origin message and modify the form input value.
Now, in terms of actually implementing this - it will also become more complicated depending on what type of Checkout Page you're hosting. Are you on Optimized One Page Checkout (The new one?) If so, you'll probably need Mutation Listeners, as the OPC is an external React Application, and you can't really hook into the internal state, you need to use mutation listeners to wait until each section (Customer Details, Shipping Details, Billing Details, payment Step) loads - as these are refreshed using internal React State which you can't access from the BC/stencil/cornerstone/blueprint scripts.
More details on that here:
https://medium.com/bigcommerce-developer-blog/the-complete-guide-to-checkout-customization-on-bigcommerce-6b566bc36fa9
If you're using an older version of checkout where you can manually modify the JS and HTML structure of the checkout that BC serves, you might not need this.
I am making an extension that needs to capture POST data directed to a site and once the site response confirms success, change some local data to reflect it.
The issue is, the POST data is located in requestBody from the onBeforeRequest event, while the success confirmation is in the onCompleted event. I understand that the lifetime of a webRequest should be managed using its unique requestId, but I am using an Event Page and therefore trying to avoid the use of global variables.
eventPage.js:
function continueListening(requestDetails){
function finishListening(completeDetails){
if (completeDetails.requestId === requestDetails.requestId){
doStuff(requestDetails, completeDetails);
chrome.webRequest.onErrorOccurred.removeListener(finishListening);
chrome.webRequest.onCompleted.removeListener(finishListening);
}
}
chrome.webRequest.onErrorOccurred.addListener(finishListening,{urls:["*://site*"]});
chrome.webRequest.onCompleted.addListener(finishListening, {urls:["*://site*"]});
}
chrome.webRequest.onBeforeRequest.addListener(continueListening, {urls:["*://site*"]});
I decided to try nesting listener registrations for the finalized request in order to provide them with the scope to compare requestIds with the initial request containing the form data. This appears to work, but I am concerned about a potential race condition between the resolving of the webRequest and the registration of the nested listener intended to listen for it, leading to any number of useless unremoved listeners.
The other option I see is to store the requestDetails in chrome.storage.local and check them against the completeDetails once they arrive. My main hesitation there is that if for whatever reason execution is interrupted the local disk space could be polluted with unresolved requests.
Is there a better way of doing this?
EDIT: Unfortunately although I believed I was making an Event Page, I did not have persistent:false in my manifest. As I learned when I added it, Event Pages do not even support webRequests. The Event Page equivalent, declarativeWebRequest, seems to have died in the beta channel. So making it a Background Page seems to be the necessary solution.
I have a form that uploads a file in an firame to a remote server. As a result at the submission url server returns json data with the result of operation, which my iframe catches.
{'result': 'true' or 'false'}
Now I'd like to retrieve this json as the callback of my iframe. I know that I need jsonp to achieve this since it's a cross-site call. Here's my function with sample code from IBM' site :
function fileUploadFunction(){
var fileUploadForm = $('#file_upload_form');
fileUploadForm.attr('action', uploadURL);
fileUploadForm.submit();
$('#upload_target').load(function () {
alert("IFrame loaded");
$.getJSON(uploadUrl+"&callback=?", function(data) {
alert("Symbol: " + data.symbol + ", Price: " + data.price);
});
});
};
But here few problems arise. First - my uploadUrl is just "http://something/" . Do I need it to support calls with $callback= suffix ?
Secondly - server gives response only as a result to file upload. So I need to get the result that is stored in my iframe and not at the specified url. How to solve this ?
Here's the link. Notice hidden iframe inside the form. Result from server shows there. :
http://ntt.vipserv.org/artifact/
EDIT
I've previously tried :
$('#upload_target').load(function () {
var ret = frames['upload_target'].document.getElementsByTagName("body")[0].innerHTML;
var data = eval("("+ret+")");
});
But it raises 'permissions denied' error.
This is easily done with easyXDM and there is actually a blog post about this exact use case here.
In essence what it does is use cross-domain messaging to relay the response to the invoking document.
Update: Here is a link for this in action, the source can be found at github, the files are prefixed 'upload_'.
Sean's easyXDM recommendation is a great option (& should probably be marked as correct), but I wanted to suggest another light-weight solution that I haven't seen anyone use.
In cases where you're posting to a hidden iframe on another domain & just need a single response back (not two-way communcation), you could pass a message from the iframe to the parent using a busted url. Here's an example:
the parent loads an iframe on different domain
the parent polls myframe.contentWindow.location.href (constantly getting Permission denied errors since the frame is on another domain)
iframe processes, then redirects to
http://parentdomain.com/pagethatdoesnotexist?{'result':'ok'}
iframe gets a 404 but now the location is available to the parent
the parent reads the message from the iframe's URL
one possible solution could be to set the name of the iframe with pure js. This name could be read from the wrapping parent page.
Looks to me that your code will request uploadURL twice: first, .submit() do a POST request to upload the file and the result is shown in the iframe as a webpage; second, .getJSON() do a GET request and the result is executed as javascript in <script>. You will realize this if you open up Firebug while testing your app.
Since two of the requests are independent, I have no idea how .getJSON() will give you any information about the file you just uploaded with .submit().
For these kind of cross-domain communication, I would suggest using postMessage; otherwise you will need to change you application workflow to do everything in the iframe after file have uploaded; e.g. do <script>alert('Submission accepted');</script> in the iframe.
What are you trying to do after a user have successfully upload a file?
dont use .html() at all.
I used
jQuery('.someElement')
and it worked for me. you can save the result in a variable and insert it in new element
e.g
var = jQuery('.someElement');
jQuery('.newElement').html(var);
I want an event handler that fires when the user hits reload. Is onrefresh or onreload the correct handler to add to ? Also, will this even fire before or after onunload? Are there an browser inconsistencies? Thanks.
I don't think there are events called onrefresh or onreload. You can know when the page is unloading, but knowing why (i.e. where the user is going next) is outside JavaScript's security sandbox. The only way to know whether the page has been reloaded is to know where the user was on the last page request, which is also outside the scope of JavaScript. You can sometimes get that via document.referrer, but it relies on the browser's security settings to permit access to that information.
The WindowEventHandlers.onbeforeunload event handler property contains the code executed when the beforeunload is sent. This event fires when a window is about to unload its resources.
window.onbeforeunload = function () {
return 'Are you sure you want to leave?';
}
This will show a confirm dialog to the user with the message you returned in your function. It will give the user a leave this page or cancel option.
There is no way around the confirm as it could be used for malicious reasons.
https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
If you combine setting a cookie with a for the specific page, with a check for the onload event, you can simulate the nonexistent event you seek. You might adjust the cookie expiration so that a reload is counted only if the initial onload was a certain time interval ago.
There are no onreload or onrefresh events that I'm aware of. Certainly from javascript running in a browser this make little sense. The existing window and all its contents are effectively discarded. Hence you either need to use onunload of the existing context or the load event of the new context that is created as result of reload.
I do believe artlung may have indeed found a way, actually... his version, however, relies on cookies, and those can be cut off from use in numerous ways; the solution, then, is to use a server-side language of your choice to save the timestamp of when the page is unloaded via JavaScript (still a vulnerability, yes, but why not throw another idea out there, huh?) and then testing it again upon every page load; if you detect a difference of less than a few seconds, the user probably just reloaded your page. : )
could use a session. easier than a cookie and don't have to worry about expiration or database. That would cover you for any page except the first one. I don't think the session superglobal is available til the second page. If that's a problem, you could start a session and reload the page immediately if there is no active session.
its onunload
because when you hit refresh the browser "unloads" then loads again