Problem adding plus one button on XUL window - javascript

I am trying to add plus one button on a XUL window, but I get this error:
Error: Permission denied for <https://plusone.google.com> to call method ChromeWindow.postMessage
Source file: https://ssl.gstatic.com/webclient/js/gc/22431124-0a127465/googleapis.client__plusone.js
Line: 14
I am adding an iframe to https://plusone.google.com/u/0/_/+1/fastbutton?url= ...
Is there a way to add the +1 button on a XUL window and make it accept the postMessage?
The addon I am trying to develop is in the image bellow. The only problem is that it does not register the click due the permission.
bootstrap.js (bootstrap-vsdoc.js)
/// <reference path="bootstrap-vsdoc.js" />
/// <summary>
/// Made by Bruno Leonardo Michels
/// </summary>
var watcher = Components.classes["#mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
var listenOpen = {
observe : function(cWindow, cEvent) {
if (cEvent != "domwindowopened") return;
cWindow.addEventListener("load", start, false);
}
};
function startup(data, reason) {
watcher.registerNotification(listenOpen);
var mWindows = watcher.getWindowEnumerator();
while (mWindows.hasMoreElements()) {
start(mWindows.getNext());
}
}
function shutdown(data, reason) {
watcher.unregisterNotification(listenOpen);
var mWindows = watcher.getWindowEnumerator();
while (mWindows.hasMoreElements()) {
end(mWindows.getNext());
}
}
function install(data, reason) {
}
function uninstall(data, reason) {
}
/// #region Methods
function getWindow(cWindow)
{
try
{
if (cWindow instanceof Components.interfaces.nsIDOMEvent)
{
cWindow = cWindow.currentTarget;
}
if (cWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser")
return;
}
catch(ex) { }
return cWindow;
}
function ajaxGet(cWindow, url, cb)
{
var xmlhttp;
xmlhttp = new cWindow.XMLHttpRequest();
xmlhttp.open("GET", url, true);
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
{
cb(xmlhttp);
}
};
xmlhttp.send();
}
var eventList = [];
function bind(gBrowser, cWindow, target, eventName, fn)
{
var ev = function(e) { fn(gBrowser, cWindow, e); };
eventList.push(ev);
target.addEventListener(eventName, eventList[eventList.length-1], false);
}
function unbind(target, eventName, fn)
{
var b = target.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
function( elem, type, handle ) {
if ( elem.detachEvent ) {
elem.detachEvent( "on" + type, handle );
}
};
b(target, eventName, fn);
}
function unbindAll(target, eventName)
{
for (var i in eventList)
{
unbind(target, eventName, eventList[i]);
}
}
/// #endregion
/// #region Events
function start(cWindow) {
cWindow = getWindow(cWindow);
if (!cWindow) return;
with (cWindow)
{
bind(gBrowser, cWindow, gBrowser.tabContainer, "TabAttrModified", tabChange);
var window = cWindow;
var document = cWindow.document;
var url = window.location.href;
if (!/^http/i.test(url))url="http://www.orkutmanager.net/";
var urlE= window.encodeURIComponent(url);
var iconsBar = document.getElementById("urlbar-icons");
function insScript(w)
{
var sc = document.createElement("script");
sc.src = "https://apis.google.com/js/plusone.js";
sc.type= "text/javascript";
sc.setAttribute("extension", "plusoneany");
(document.lastChild).appendChild(sc);
}
insScript(this);
insScript(this.parent);
insScript(this.top);
var button = document.createElement("iframe");
button.id = "extensions.plusoneany.button";
button.setAttribute("src", "https://plusone.google.com/u/0/_/+1/fastbutton?url=" + urlE +
"&size=small&count=true&hl=en-US&_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe");
button.setAttribute("class", "urlbar-icon extensions-plusoneany");
button.setAttribute("style", "border:0;padding:0;margin:0;width:70px;height:16px;");
iconsBar.insertBefore(button, iconsBar.lastChild);
}
}
function end(cWindow) {
try
{
unbindAll(gBrowser.tabContainer, "TabAttrModified");
}
catch(ex){}
try
{
var elements = cWindow.document.getElementsByClassName("extensions-plusoneany");
for (var i in elements)
{
elements[i].parentNode.removeChild(elements[i]);
}
}
catch(ex){}
}
function tabChange(gBrowser, cWindow, e) {
var win = gBrowser.selectedBrowser.contentWindow;
var uns = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
uns.clearTimeout(uns.PlusOneAnyTimeout);
uns.PlusOneAnyTimeout = uns.setTimeout(function() {
var url = win.location.href;
if (!/^http/i.test(url))url="http://www.orkutmanager.net/";
var urlE= uns.encodeURIComponent(url);
try {
var ifr = cWindow.document.getElementById("extensions.plusoneany.button");
ifr.setAttribute("src", "https://plusone.google.com/u/0/_/+1/fastbutton?url=" + urlE +
"&size=small&count=true&hl=en-US&_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe");
}catch(ex){}
}, 500);
}
/// #endregion

#systempuntoout is right that, theoretically, setting the iframe type attribute to "content" should fix this. I've had problems with this in the past, however. It might work, but I think XUL is a bit buggy in this respect. If it were me I'd embed a XUL <browser> element instead of a XUL <iframe> and load a static HTML page into the browser (i.e. by calling browser.loadURI) which contains the code I want to run (in this case the iframe with src set to "https://plusone.google.com..."). That way your code will run as real content, just like it would in the main browser content window.
You can write the HTML page to disk (since part of the iframe source is generated dynamically) and then reference it with a file:// URL. Since the code will be pretty short in this case, you can even try using a data URI, which would save you from having to write the temporary file to disk.
In other words you'd create an HTML file (in memory or on disk) with:
<html>
<body>
<iframe src="https://plusone.google.com..." />
</body>
</html>
Then you'd create a <browser> element just like you do now for the iframe, insert it into your XUL document and call loadURI() on it, referencing the HTML file (via file: or data: URI).

I would try to specify the type adding this line:
ifr.setAttribute("type","content");

I think this might be helpful, it could be the issue you are having.
How do you use window.postMessage across domains?

Extremely evil answer, but cant you just get the content of https://apis.google.com/js/plusone.js and then eval it? It's Google, what could go wrong ;]

Related

Don't show page until content has fully loaded

I am creating a landing page which should exist in two languages. The texts that should be shown are in two JSON files, called accordingly "ru.json" and "en.json". When a user clicks on the "Change language" button, the following function is executed:
function changeLang(){
if (userLang == 'ru') {
userLang = 'en';
document.cookie = 'language=en';
}
else {
userLang = 'ru';
document.cookie = 'language=ru';
}
var translate = new Translate();
var attributeName = 'data-tag';
translate.init(attributeName, userLang);
translate.process();
}
Where Translate() is the following:
function Translate() {
//initialization
this.init = function(attribute, lng){
this.attribute = attribute;
if (lng !== 'en' && lng !== 'ru') {
this.lng = 'en'
}
else {
this.lng = lng;
}
};
//translate
this.process = function(){
_self = this;
var xrhFile = new XMLHttpRequest();
//load content data
xrhFile.open("GET", "./resources/js/"+this.lng+".json", false);
xrhFile.onreadystatechange = function ()
{
if(xrhFile.readyState === 4)
{
if(xrhFile.status === 200 || xrhFile.status == 0)
{
var LngObject = JSON.parse(xrhFile.responseText);
var allDom = document.getElementsByTagName("*");
for(var i =0; i < allDom.length; i++){
var elem = allDom[i];
var key = elem.getAttribute(_self.attribute);
if(key != null) {
elem.innerHTML = LngObject[key] ;
}
}
}
}
};
xrhFile.send();
}
Everything works fine, however, when a user opens the page for the first time, if his Internet connection is bad, he just sees the elements of the page without text. It is just 1-2 seconds, but still annoying.
The question is, is there any way to check the text has loaded and display the page elements only on this condition?
You can use $(document).ready() in this way
$(document).ready(function(){
//your code here;
})
You can use the JavaScript pure load event in this way
window.addEventListener('load', function () {
//your code right here;
}, false);
Source: Here
translate.process() is asynchronous code which needs to make a call to a server and wait for its response. What it means is that, when you call this function, it goes in the background to go do its own thing while the rest of the page continues loading. That is why the user sees the page while this function is still running.
One minimal way I can think around this is by adding this to your css files in the head tag.
body { display: none }
And then, under this.process function, after the for loop ends, add
document.body.style.display = 'block'
If you want to suppori IE8:
document.onreadystatechange = function () {
if (document.readyState == "interactive") {
// run some code.
}
}
Put the code you want to execute when the user initially loads the page in a DOMContentLoaded event handler like below:
document.addEventListener('DOMContentLoaded', function() {
console.log('Whereas code execution in here will be deffered until the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.');
});
console.log('This will log immediatley');
It's important to note that DOMContentLoaded is different than the load event

How to properly implement a custom ajax

In order to keep the logotext <div class="small-7 medium-4 columns logo"> and the menu <nav class="pagedMenu" role="navigation">,without clipping on page refresh or while the content is loading from a page to another, I am trying to implement this solution made by #Buzinas (special thanks). In a few more words:
In header.php we have this script:
<head>
...
<script>
function ajax(url, callback, method, params) {
if (!method) method = 'GET';
var xhr = new XMLHttpRequest();
xhr.open(method, url);
if (callback) xhr.addEventListener('load', function() {
callback.call(this, xhr);
});
if (params) {
params = Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
xhr.send(params);
} else {
xhr.send();
}
}
// CUSTOM AJAX CONTENT LOADING FUNCTION
function ajaxRevslider(obj) {
// obj.type : Post Type
// obj.id : ID of Content to Load
// obj.aspectratio : The Aspect Ratio of the Container / Media
// obj.selector : The Container Selector where the Content of Ajax will be injected. It is done via the Essential Grid on Return of Content
var content = "";
data = {};
data.action = 'revslider_ajax_call_front';
data.client_action = 'get_slider_html';
data.token = '<?php echo wp_create_nonce("RevSlider_Front"); ?>';
data.type = obj.type;
data.id = obj.id;
data.aspectratio = obj.aspectratio;
// SYNC AJAX REQUEST
jQuery.ajax({
type:"post",
url:"<?php echo admin_url('admin-ajax.php'); ?>",
dataType: 'json',
data:data,
async:false,
success: function(ret, textStatus, XMLHttpRequest) {
if(ret.success == true)
content = ret.data;
},
error: function(e) {
console.log(e);
}
});
// FIRST RETURN THE CONTENT WHEN IT IS LOADED !!
return content;
};
// CUSTOM AJAX FUNCTION TO REMOVE THE SLIDER
function ajaxRemoveRevslider(obj) {
return jQuery(obj.selector+" .rev_slider").revkill();
}
document.addEventListener('DOMContentLoaded', function() {
var main = document.querySelector('div[role=main]'),
spinner = document.querySelector('div.sk-spinner'),
pages = {};
window.addEventListener('load', function() {
toggleSpinner(false);
});
function toggleSpinner(b) {
spinner.classList[b ? 'remove' : 'add']('hidden');
document.getElementById('wrapper').style.opacity = b ? 0 : 1;
}
function changePage(url, title) {
setTimeout(function() {
window.SITE.init();
window.vc_js();
}, 0);
history.pushState({
html: main.innerHTML,
title: title
}, '', url);
toggleSpinner(false);
}
document.getElementById('menu-menu-2').addEventListener('click', function(e) {
var el = e.target;
if (el.tagName === 'A') {
e.preventDefault();
toggleSpinner(true);
if (pages[el.href]) {
main.innerHTML = '';
main.appendChild(pages[el.href]);
changePage(el.href);
}
else {
ajax(el.href, function(xhr) {
var frag = document.createRange().createContextualFragment(xhr.responseText);
main.innerHTML = '<div>' + frag.querySelector('div[role=main]').innerHTML + '</div>';
//pages[el.href] = main.firstElementChild;
var _currentScripts = [].slice.call(document.querySelectorAll('script'));
[].forEach.call(frag.querySelectorAll('script'), function(el, i) {
if ((el.src === '' && el.parentNode)
|| el.src.indexOf('slider') >= 0
|| el.src.indexOf('Scroll') >= 0
|| el.src.indexOf('vendor') >= 0
|| el.src.indexOf('composer') >= 0
) {
var s = _currentScripts.filter(function(x) {
return x.src === el.src;
});
while (s.length) {
if (s[0].parentNode)
s[0].parentNode.removeChild(s[0]);
s.shift();
}
document.body.appendChild(el);
}
});
[].forEach.call(frag.querySelectorAll('style'), function(el, i) {
document.querySelector('head').appendChild(el);
});
changePage(el.href, frag.querySelector('title').textContent);
});
}
}
});
window.addEventListener('popstate', function(e) {
if (e.state) {
main.innerHTML = e.state.html;
document.title = e.state.title;
}
});
});
</script>
...
</head>
The following jquery-ready.js is registered/enqueued in script-calls.php:
(function($){
var readyList = [];
// Store a reference to the original ready method.
var originalReadyMethod = jQuery.fn.ready;
// Override jQuery.fn.ready
jQuery.fn.ready = function(){
var args = [].slice.call(arguments);
if(args.length && args.length > 0 && typeof args[0] === 'function') {
readyList.push(args[0]);
}
// Execute the original method.
originalReadyMethod.apply( this, args );
};
// Used to trigger all ready events
$.triggerReady = function() {
$(readyList).each(function(i, el) {
try {
el.apply(el);
} catch(e) {
console.log(e);
}
});
};
})(jQuery);
Also, in page.php I replaced get_header() and get_footer() functions as follows:
<?php
if(!isset($_REQUEST['ajax'])){
get_header();
}
?>
<?php
if (is_page()) {
$id = $wp_query->get_queried_object_id();
$sidebar = get_post_meta($id, 'sidebar_set', true);
$sidebar_pos = get_post_meta($id, 'sidebar_position', true);
}
?>
...
<?php
if(!isset($_REQUEST['ajax'])){
get_footer();
}
?>
There are still some issues trying to load a page with Revolution slider or Visual Composer Parallax content, like we have on Parallax or About us pages for example.
You can use this link and navigate to the above mentioned pages; Tests are made only in Chrome 45.0.2454.101 m 64-bit/ Win7, not yet prepared for IE, Firefox, mobile etc .
About the behaviour:
Rev slider parallax content, will become scrambled from the second link visit (Home or Parallax pages); The Visual Composer parallax content (the guy at the desk picture, About us page for example) is fixed on the first link visit - after F5 will be fine;
The menu mynewmenu.js will remember the state on session, so u'll have to close the browser in oder to visit multiple direct links properly.
I've received an answer from Rev slider support team telling me:
The best option for Ajax is to just add the slider's shortcode to a regular page/post, and then the slider's "init" script is will automatically be included with the slider's HTML. Then when the slider's HTML is removed from the DOM, all jQuery events are also removed. So all you really need to do is pull in the slider as page/post content, and then you won't need any custom script for the slider specifically.
Unfortunately I have no idea how can I approach this, implementing the above sugestion into my already received solution.
Could be something related to API(?) I've found these infos on Revolution slider / Visual Composer pages. Any thoughts?
You should probably read:
https://codex.wordpress.org/AJAX_in_Plugins#Ajax_on_the_Viewer-Facing_Side
http://wptheming.com/2013/07/simple-ajax-example/
You should pass PHP variables to your javascript files using wp_localize_script.
Even if you don't do it that way, you shouldn't need to hack the main page templates just to serve specific content -- create a one-off page, then make a specific template for it. Then you can use that page's URL as your ajax endpoint.
But if what you really need to do is run Rev Slider's shortcode (and the Parallax thing if it has one too) somewhere other than a page:
In code (page template, functions.php, etc) - https://developer.wordpress.org/reference/functions/do_shortcode/
In a widget (which links to the next) -- https://digwp.com/2010/03/shortcodes-in-widgets/
Basically everywhere -- http://stephanieleary.com/2010/02/using-shortcodes-everywhere/
Do you need help with this still? I think the revolution slider's support team nailed it with the statements
just add the slider's shortcode to a regular page/post
and
all you really need to do is pull in the slider as page/post content
So, use the slider on your WordPress page/post through the shortcode, [shortcode]. Then reference the $_GET[] and/or $_POST[] array elements in php (or javascript, however you're doing it) as needed.

Prevent other scripts from setting window.title?

I'm working on a chrome extension, and I set window.title in the onload handler. It seems, though, that the page I'm modifying sets the document title dynamically as well. There's a huge collection of scripts being linked. Is there any way for me to prevent anyone else from modifying document.title or any of its variants, without knowing where the modification is coming from? Alternatively, is there a quick way for me to see where the change is coming from?
I had same problem, some external scripts are changed my page title by document.title = "..."
I've made own solution for it:
try {
window.originalTitle = document.title; // save for future
Object.defineProperty(document, 'title', {
get: function() {return originalTitle},
set: function() {}
});
} catch (e) {}
See the answer to how to listen for changes to the title element?. Notably:
function titleModified() {
window.alert("Title modifed");
}
window.onload = function() {
var titleEl = document.getElementsByTagName("title")[0];
var docEl = document.documentElement;
if (docEl && docEl.addEventListener) {
docEl.addEventListener("DOMSubtreeModified", function(evt) {
var t = evt.target;
if (t === titleEl || (t.parentNode && t.parentNode === titleEl)) {
titleModified();
}
}, false);
} else {
document.onpropertychange = function() {
if (window.event.propertyName == "title") {
titleModified();
}
};
}
};
This SO answer suggest a technique for how to listen for changes to the document title.
Perhaps you could use that technique to create a callback which changes the title back to whatever you want it to be, as soon as some other script tries to change it.

Chaining dynamically loaded javascript

I have a static page that I'm trying to add jQuery and the BlockUI plugin to. jQuery needs to be loaded first before I can use BlockUI, and while I can load jQuery just fine, I cant seem to get BlockUI to load after and call its loaded handler so I can do the work. I do see the BlockUI script tag in my html page, so it is at least being injected in okay as far as I can see. Here's my code:
var jqueryScript = document.createElement( "script" );
jqueryScript.src = "/glptools/scripts/jquery-1.9.1.min.js";
if ( jqueryScript.addEventListener ) {
jqueryScript.addEventListener( "load", jqueryReady, false );
}
else if ( jqueryScript.readyState ) {
jqueryScript.onreadystatechange = jqueryReady;
}
document.getElementsByTagName( 'head' )[0].appendChild( jqueryScript );
function jqueryReady() {
if ( typeof jQuery != 'undefined' ) {
$( document ).ready( function () {
//Initialize Tabber
tabberAutomatic( "" );
// Add BlockUI plugin
var blockUIScript = document.createElement( "script" );
blockUIScript.src = "/glptools/scripts/blockui/jquery.blockUI.js";
if ( blockUIScript.addEventListener ) {
blockUIScript.addEventListener( "load", blockUIReady, false );
}
else if ( blockUIScript.readyState ) {
blockUIScript.onreadystatechange = blockUIReady;
}
document.getElementsByTagName( 'head' )[0].appendChild( blockUIScript );
} );
}
}
function blockUIReady() {
$( "#tabbertabhide" ).each( function ( index, elem ) {
$( elem ).block();
} );
}
My goal is to use BlockUI to block the tabs located on my page. I tried putting the block ui load code outside the ready() call, but then the loaded handler gets called before jQuery has been loaded.
You should consider use of script loader such as http://requirejs.org/ or http://headjs.com/ which can resolve dependecies tree for you and make code cleaner.
If BlockUI depends on jQuery, you will need to load it sequentially. You can do something like this:
//This function creates a script element using "resource" and
//adds it to the head. callback is used as the onload callback
//for the script
function loadScript(resource, callback) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = resource + "?t=" + new Date().getTime(); //prevent caching
if (callback) {
script.onload = callback;
}
head.appendChild(script);
}
//Array of scripts to load
var resources = [
"/glptools/scripts/jquery-1.9.1.min.js",
"/glptools/scripts/blockui/jquery.blockUI.js"
];
//This function will load the scripts one after the other, using a callback
//that calls this function itself.
function load(i) {
if(i < resources.length) {
loadResource(resources[i], function() {
load(++i);
});
} else {
//Everything has finished loading so you can start
//using jQuery and BlockUI
}
}
load(0);
As far as both jQuery and BlockUI are located in the same origin as your page you can get jQuery and BlockUI scripts as text, concat them and add to document as merged script. Just like this:
function createXMLHttp() {
//Initializing our object
var xmlHttp = null;
//if XMLHttpRequest is available then creating and returning it
if (typeof(XMLHttpRequest) != undefined) {
xmlHttp = new XMLHttpRequest;
return xmlHttp;
//if window.ActiveXObject is available than the user is using IE...so we have to create the newest version XMLHttp object
} else if (window.ActiveXObject) {
var ieXMLHttpVersions = ['MSXML2.XMLHttp.5.0', 'MSXML2.XMLHttp.4.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp', 'Microsoft.XMLHttp'];
//In this array we are starting from the first element (newest version) and trying to create it. If there is an
//exception thrown we are handling it (and doing nothing)
for (var i = 0; i < ieXMLHttpVersions.length; i++) {
try {
xmlHttp = new ActiveXObject(ieXMLHttpVersions[i]);
return xmlHttp;
} catch (e) {
}
}
}
}
function getData(url, callback) {
var xmlHttp = createXMLHttp();
xmlHttp.open('get', url, true);
xmlHttp.send(null);
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status === 200) {
callback(xmlHttp.responseText);
}
}
};
}
getData('/glptools/scripts/jquery-1.9.1.min.js', function(jQuery) {
getData('/glptools/scripts/blockui/jquery.blockUI.js', function(blockUi) {
var head = document.getElementsByTagName("head")[0],
script = document.createElement('script');
script.innerHTML = jQuery + ';' + blockUi;
head.appendChild(script);
});
});

jQuery .ready in a dynamically inserted iframe

We are using jQuery thickbox to dynamically display an iframe when someone clicks on a picture. In this iframe, we are using galleria a javascript library to display multiple pictures.
The problem seems to be that $(document).ready in the iframe seems to be fired too soon and the iframe content isn't even loaded yet, so galleria code is not applied properly on the DOM elements. $(document).ready seems to use the iframe parent ready state to decide if the iframe is ready.
If we extract the function called by document ready in a separate function and call it after a timeout of 100 ms. It works, but we can't take the chance in production with a slow computer.
$(document).ready(function() { setTimeout(ApplyGalleria, 100); });
My question: which jQuery event should we bind to to be able to execute our code when the dynamic iframe is ready and not just it's a parent?
I answered a similar question (see Javascript callback when IFRAME is finished loading?).
You can obtain control over the iframe load event with the following code:
function callIframe(url, callback) {
$(document.body).append('<IFRAME id="myId" ...>');
$('iframe#myId').attr('src', url);
$('iframe#myId').load(function() {
callback(this);
});
}
In dealing with iframes I found good enough to use load event instead of document ready event.
Using jQuery 1.3.2 the following worked for me:
$('iframe').ready(function() {
$('body', $('iframe').contents()).html('Hello World!');
});
REVISION:!
Actually the above code sometimes looks like it works in Firefox, never looks like it works in Opera.
Instead I implemented a polling solution for my purposes. Simplified down it looks like this:
$(function() {
function manipIframe() {
el = $('body', $('iframe').contents());
if (el.length != 1) {
setTimeout(manipIframe, 100);
return;
}
el.html('Hello World!');
}
manipIframe();
});
This doesn't require code in the called iframe pages. All code resides and executes from the parent frame/window.
In IFrames I usually solve this problem by putting a small script to the very end of the block:
<body>
The content of your IFrame
<script type="text/javascript">
//<![CDATA[
fireOnReadyEvent();
parent.IFrameLoaded();
//]]>
</script>
</body>
This work most of the time for me. Sometimes the simplest and most naive solution is the most appropriate.
Following DrJokepu's and David Murdoch idea I implemented a more complete version.
It requires jQuery on both the parent and iframe and the iframe to be in your control.
iframe code:
var iframe = window.frameElement;
if (iframe){
iframe.contentDocument = document;//normalization: some browsers don't set the contentDocument, only the contentWindow
var parent = window.parent;
$(parent.document).ready(function(){//wait for parent to make sure it has jQuery ready
var parent$ = parent.jQuery;
parent$(iframe).trigger("iframeloading");
$(function(){
parent$(iframe).trigger("iframeready");
});
$(window).load(function(){//kind of unnecessary, but here for completion
parent$(iframe).trigger("iframeloaded");
});
$(window).unload(function(e){//not possible to prevent default
parent$(iframe).trigger("iframeunloaded");
});
$(window).on("beforeunload",function(){
parent$(iframe).trigger("iframebeforeunload");
});
});
}
parent test code:
$(function(){
$("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
console.log(e.type);
});
});
Found the solution to the problem.
When you click on a thickbox link that open a iframe, it insert an iframe with an id of TB_iframeContent.
Instead of relying on the $(document).ready event in the iframe code, I just have to bind to the load event of the iframe in the parent document:
$('#TB_iframeContent', top.document).load(ApplyGalleria);
This code is in the iframe but binds to an event of a control in the parent document. It works in FireFox and IE.
This function from this answer is the best way to handle this as $.ready explicitly fails for iframes. Here's the decision not to support this.
The load event also doesn't fire if the iframe has already loaded. Very frustrating that this remains a problem in 2020!
function onIframeReady($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) {
errorFn();
}
}
Basically what others have already posted but IMHO a bit cleaner:
$('<iframe/>', {
src: 'https://example.com/',
load: function() {
alert("loaded")
}
}).appendTo('body');
Try this,
<iframe id="testframe" src="about:blank" onload="if (testframe.location.href != 'about:blank') testframe_loaded()"></iframe>
All you need to do then is create the JavaScript function testframe_loaded().
I'm loading the PDF with jQuery ajax into browser cache. Then I create embedded element with data already in browser cache. I guess it will work with iframe too.
var url = "http://example.com/my.pdf";
// show spinner
$.mobile.showPageLoadingMsg('b', note, false);
$.ajax({
url: url,
cache: true,
mimeType: 'application/pdf',
success: function () {
// display cached data
$(scroller).append('<embed type="application/pdf" src="' + url + '" />');
// hide spinner
$.mobile.hidePageLoadingMsg();
}
});
You have to set your http headers correctly as well.
HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
This was the exact issue I ran into with our client. I created a little jquery plugin that seems to work for iframe readiness. It uses polling to check the iframe document readyState combined with the inner document url combined with the iframe source to make sure the iframe is in fact "ready".
The issue with "onload" is that you need access to the actual iframe being added to the DOM, if you don't then you need to try to catch the iframe loading which if it is cached then you may not. What I needed was a script that could be called anytime, and determine whether or not the iframe was "ready" or not.
Here's the question:
Holy grail for determining whether or not local iframe has loaded
and here's the jsfiddle I eventually came up with.
https://jsfiddle.net/q0smjkh5/10/
In the jsfiddle above, I am waiting for onload to append an iframe to the dom, then checking iframe's inner document's ready state - which should be cross domain because it's pointed to wikipedia - but Chrome seems to report "complete". The plug-in's iready method then gets called when the iframe is in fact ready. The callback tries to check the inner document's ready state again - this time reporting a cross domain request (which is correct) - anyway it seems to work for what I need and hope it helps others.
<script>
(function($, document, undefined) {
$.fn["iready"] = function(callback) {
var ifr = this.filter("iframe"),
arg = arguments,
src = this,
clc = null, // collection
lng = 50, // length of time to wait between intervals
ivl = -1, // interval id
chk = function(ifr) {
try {
var cnt = ifr.contents(),
doc = cnt[0],
src = ifr.attr("src"),
url = doc.URL;
switch (doc.readyState) {
case "complete":
if (!src || src === "about:blank") {
// we don't care about empty iframes
ifr.data("ready", "true");
} else if (!url || url === "about:blank") {
// empty document still needs loaded
ifr.data("ready", undefined);
} else {
// not an empty iframe and not an empty src
// should be loaded
ifr.data("ready", true);
}
break;
case "interactive":
ifr.data("ready", "true");
break;
case "loading":
default:
// still loading
break;
}
} catch (ignore) {
// as far as we're concerned the iframe is ready
// since we won't be able to access it cross domain
ifr.data("ready", "true");
}
return ifr.data("ready") === "true";
};
if (ifr.length) {
ifr.each(function() {
if (!$(this).data("ready")) {
// add to collection
clc = (clc) ? clc.add($(this)) : $(this);
}
});
if (clc) {
ivl = setInterval(function() {
var rd = true;
clc.each(function() {
if (!$(this).data("ready")) {
if (!chk($(this))) {
rd = false;
}
}
});
if (rd) {
clearInterval(ivl);
clc = null;
callback.apply(src, arg);
}
}, lng);
} else {
clc = null;
callback.apply(src, arg);
}
} else {
clc = null;
callback.apply(this, arguments);
}
return this;
};
}(jQuery, document));
</script>

Categories

Resources