Find the iframe that called a function in its parent - javascript

I have several iframes on a page that display ads unicorns/bacon to users. Because its not possible to detect an iframe's domready event via the parent (please let me know if this isn't true) I have some initialization-code in each iframe like this:
<body data-controller="unicorn">
<!-- content -->
<script>
var $ = parent.jQuery;
if($ && $.frameReady){
$(document).ready(function(){
$.frameReady(document);
});
}
</script>
</body>
The parent document has code very similar to the following (about this technique via #Paul Irish):
var frames = {
// the following is irrelevant to my question but awesome.
"unicorn": function (document) {
var script = document.createElement("script"),
element = document.getElementsByTagName("script")[0];
script.src = "http://www.cornify.com/js/cornify.js";
script.onload = function () {
// defaultView is the DOMWindow.
document.defaultView.cornify_add();
$(document).click(document.defaultView.cornify_add);
script.parentNode.removeChild(script);
};
element.parentNode.appendChild(script, element);
},
"bacon" : function(document) { /** mmm, bacon **/ }
};
// relevant but boring...
$.frameReady = function(document){
var controller = $(document.body).data("controller");
controller && frames[controller] && frames[controller](document);
};
Here is an example in jsfiddle (you can edit it here). It works great (at least it does in Chrome dev).
Now what I would LIKE to do is get rid of the data-controller bit in the iframe and instead use the id (or data-* or whatever) of the actual iframe element that is in the parent document to initialize the code.
If I could query the DOM via DOMWindow it would look like this:
$.frameReady = function(document){
var iframe = $("body").find(document.defaultView),
controller = iframe.data("controller");
controller && frames[controller] && frames[controller](document);
};
Luckily, I only need this to run on webkit based browsers, Adobe Air 2.5 actually (but I'm testing in Chrome ATM).
Because S.O. answerers like it when a Question has a question here it is:
Is there any (efficient) ways to query the DOM via document or window in webkit-based browsers - including Adobe Air 2.5?

I have now found that one iframe containing the unicorn for you
console.log(
$("iframe").contents().filter( function(){
return this == document
}).length
);

Related

How to find word wysiwyg in html document using Javascript?

I need to find out if html document inside iframe contains some occurance of word wysiwyg (purpose: to check if it is wysiwyg editor).
What I have tried:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
var success = this.innerHTML.match(/wysiwyg/i);
if ( success && success.length )
return this;
}
return result;
});
using JQuery.
The problem here is that innerHTML is empty. I thought, contentDocument could contain innerhtml, but this is not the case. I try to do case insensitive search, where the word wysiwyg can be in the middle of any element. Originally I tried to find a tag with href value or img tag with src value but I found that is too much complicated and the word could be used in other parts of the html document and I would miss it. I don't know where the word could be, it can be anywhere in the iframe -> html document.
Your suggestion?
Note:
Permissions here are not problem, they are granted by Firefox webextentions API - which is not subject of the question.
If permissions are not the problem, you should be able to access the iframe HTML by doing the following:
$('#specificIframe').contents().find('#thingToFind');
jQuery .contents()
You may use .contents() and jQuery( ":contains(text)" ) plus the load event to check if the iframe contains the string.
In order to test if the id contains the string you may refer to attributeContains selector.
$(function () {
$('iframe[id*="wysiwyg"]').on('load', function (e) {
var iframes = $(this).contents().find(':contains("wysiwyg")');
});
});
As guest271314 has mentioned, you are not currently using .contentDocument in your code.
You could change your code as follows:
iframes = $('iframe').filter(function() {
var result = this.id.match(/wysiwyg/i);
if (!result){
result = this.className.match(/wysiwyg/i);
}
if (!result) {
var success = this.contentDocument.querySelector('html').innerHTML.match(/wysiwyg/i);
if ( success && success.length ){
return this;
}
}
return result;
});
From MDN's <iframe> page:
From the DOM iframe element, scripts can get access to the window object of the included HTML page via the contentWindow property. The contentDocument property refers to the document element inside the iframe (this is equivalent to contentWindow.document), but is not supported by Internet Explorer versions before IE8.
However, if this is the same issue you were asking about in a prior, now deleted, question, this will, not solve your actual problem. The actual problem appeared to be that your (nearly identical) code was executing prior to the <iframe> you are looking for being inserted into the DOM by other JavaScript on the page. Your code, of course, can not find it when it is not, yet, in the DOM. Your code in that question was verified to find the <iframe> desired if it existed in the DOM in the state that the DOM was once the page scripts finished setting up the DOM. Prior to that code, ckeditor_new/ckeditor.js, executing, what exists on the page is:
<script src="ckeditor_new/ckeditor.js"></script>
<textarea id="FCKeditor1" name="FCKeditor1" rows="8" cols="60"></textarea>
<script>CKEDITOR.replace( 'FCKeditor1', {customConfig: '/ckeditor_new/config.js'});</script>
The page script hides that <textarea> and inserts a <div> containing the <iframe> in which you are interested (about 15kB of inserted HTML).
You will need to delay looking for the existence of that <iframe> until after that other JavaScript inserts it into the DOM.
While there may be better ways to perform this delay (e.g. watching for the insert, etc.), it could be something as simple as:
setTimeout(findIframeAndDoAllTasksNeedingIt,150);
If still not found, you could retry looking for the <iframe> a limited number of times after additional delays.
I thought this could be solution:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
}
return result;
});
edit: bugfix
I tried improvement which enables to use this code for almost all WYSIWYG editors, except TINYMCE which is kind of strange behaviour. There are found some frames with different id than that one containing "mce". Maybe we will find solution later.
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/editable|wysiwyg|editor/i);
if (!result)
result = this.id.match(/mce/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
}
return result;
});
I love this code (I use it in my program which adds css styles to WYSIWYG editors - quite usable).

Access element in recently loaded XUL document

I'm developing a Firefox extension and need to do the following:
load a page
get an element from this page
modify the attributes from this element
The code I would like to work looks like this:
gBrowser.loadURI("chrome://myExtension/content/myPage.xul");
let button = content.document.getElementById("myExtension-theButton");
button.setAttribute("oncommand", "myFunction(withParams)");
But when I run this, button is null. (Maybe loadURI returns too early and the document isn't fully loaded, yet.)
add to that gBrowser:
gBrowser.addEventListener('DOMContentLoaded', dofunc, false);
function dofunc(e) {
var win = event.originalTarget.defaultView;
var doc = win.document;
if (doc.location == 'chrome://myExtension/content/myPage.xul') {
let button = content.document.getElementById("myExtension-theButton");
button.setAttribute("oncommand", "myFunction(withParams)");
gBrowser.removeEventListener('DOMCOntentLoaded', dofunc, false);
}
}

Force iframe to (re)load document when only src hash changes

I have an iframe that's supposed to load different modules of a web application.
When the user clicks a navigation menu in the top window, it's passes a new url to the iframe. The trouble is, the new url doesn't actually point to a new page, it only uses a changed hash.
i.e.:
User clicks "dashboard", iframe src set to application.html#/dashboard
User clicks "history", iframe src set to application.html#/history
This means that the iframe does not actually load the src url again because hash changes don't require it to. The application inside the iframe is an angular app which loads the required modules dynamically using requireJS. We need this functionality to remain.
I need to force the frame source to load again even though only the hash changed. It's possible that I instead find a way to rewrite our angular app to dynamically unload/load the modules on push state events but that introduces several layers of issues for the app, plus some IE trouble.
I've tried:
Setting iframe src and calling it's location.reload, but that reloads the originally loaded url
Setting the iframe location.href/hash and calling reload, same issue
Blanking the src attribute and then setting the new url - no effect
The only solution I can find is to set the src to a blank screen, then onload set it to the new url:
var appIFrame = document.getElementById('appIFrame');
appIFrame.src = 'about:blank';
appIFrame.onload = function(){
appIFrame.src = '// set the real source here';
appIFrame.onload = false;
}
This works, yet it seems inefficient because there's an extra step.
Maybe add a dynamic GET parameter – f.e. the current timestamp, which you can get from the JavaScript Date object – to the iframe URL.
Instead of assigning application.html#/dashboard as src value, assign application.html?1234567890#/dashboard from your outside page (with 1234567890 replaced by the current timestamp, obviously).
I don't have a specific answer for you. However, the following script may proved useful (I wrote this about a year or so ago). The following script deals with re-adjusting iframe height when the document changes. This script was tested cross-browser. It does deal with the issues you're experience but indirectly. There is a lot of commenting with the Gist:
https://gist.github.com/say2joe/4694780
Here my solution (based on this stackoverflow answer):
var $ = function(id) { return document.getElementById(id); };
var hashChangeDetector = function(frame, callback) {
var frameWindow = frame.contentWindow || frame.contentDocument;
// 'old' browser
if (! "onhashchange" in window) {
var detecter = function(callback) {
var previousHash = frameWindow.location.hash;
window.setTimeout(function() {
if (frameWindow.location.hash != previousHash) {
previousHash = frameWindow.location.hash;
callback(previousHash);
}
}, 100);
};
}
else // modern browser ?
{
var detecter = function(callback) {
frameWindow.onhashchange = function () {
callback(frameWindow.location.hash);
}
};
}
detecter(callback);
};
hashChangeDetector($('myframe'), function(hash) {
alert ('detecting hash change: ' + hash);
});
You can test this here: http://paulrad.com/stackoverflow/iframe-hash-detection.html

Does Disqus have public events to attach to?

I need to perform some re-calculations after disqus form gets an update. A new comment, error message just to name a few. In essence any event that causes the Disqus iframe to expand vertically. Checked the API, but didn't find any public events. Seems like the events are not publicly accessibly atm. So the first question is – does Disqus have any public events to attach to?
The second would be – if I have no way to attach to events from Disqus I wonder would MutationEvent do the trick for me taking into account that Disqus stuff is within an iFrame?
Best I have come up with so far
function disqus_config() {
this.callbacks.onNewComment = [function() { trackComment(); }];
}
from here:
http://help.disqus.com/customer/portal/articles/466258-how-can-i-capture-disqus-commenting-activity-in-my-own-analytics-tool-
Doing a console.log(DISQUS) in the chrome console shows the disqus object, and there are other callbacks mentioned
_callbacks: Object
switches.changed: Array[2]
window.click: Array[2]
window.hashchange: Array[2]
window.resize: Array[2]
window.scroll: Array[2]
and on and trigger methods
I'm not sure about public events for Disqus in particular, but if you just need to monitor changes to an iframe's height, here's one way:
var iframe = document.getElementById('myIframe');
var iframeHeight = iframe.clientHeight;
setInterval(function() {
if(iframe.clientHeight != iframeHeight) {
// My iframe's height has changed - do some stuff!
iframeHeight = iframe.clientHeight;
}
}, 1000);
Granted, it's basically a hack. But it should work!
Well, they don't have any public events documented (as far I can tell). But, application is triggering a lot of events on its parent window. So it's possible to listen to them and make some actions. You can do that with following snippet:
window.addEventListener('message', function (event) {
// if message is not from discus frame, leap out
if (event.origin != 'https://disqus.com' && event.origin != 'http://disqus.com') return;
// parse data
var data = JSON.parse(event.data);
// do stuff with data. type of action can be detected with data.name
// property ('ready', 'resize', 'fakeScroll', etc)
}, false);
In webkit based browsers it works just fine. With firefox there might be some issues. With IE... well, I don't have any IE on hand to test it.
You can find list of available events in embed payload:
callbacks:{
preData:[],
preInit:[],
onInit:[],
afterRender:[],
onReady:[],
onNewComment:[],
preReset:[],
onPaginate:[],
onIdentify:[],
beforeComment:[]
}
I haven't found any documentation (except example for onNewComment), so you need guess how they works from event name.
You can use them in this way:
var disqus_config = function () {
this.callbacks.onNewComment = [
function() {
// your code
}
];
};
or
var disqus_config = function () {
this.callbacks.onNewComment = this.callbacks.onNewComment || [];
this.callbacks.onNewComment.push(function() {
// your code
});
}
On the side note, I found them completely useless for detecting change in height of comments iframe. I ended with using ResizeSensor from css-element-queries.

How to inject a JavaScript function to all web page using Firefox extension

I am developing a Firefox addon. What I want to do is to inject a custom JavaScript function.
i.e.
function foo() {..}
So all the pages can call the foo without define it first.
I have look from other answer such as: http://groups.google.com/group/greasemonkey-users/browse_thread/thread/3d82a2e7322c3fce
But it requires modification on the web page. What if perhaps I want to inject the function foo into Google.com? Is it possible to do so?
I can do it with a userscript, but I want to use the extension approach if possible.
The first thing I thought when reading your question was "this looks like a scam". What are you trying to achieve?
Anyway, here's a Jetpack (Add-on builder) add-on that injects a script in every page loaded:
main.js:
const self = require("self"),
page_mod = require("page-mod");
exports.main = function() {
page_mod.PageMod({
include: "*",
contentScriptWhen: "ready",
contentScriptFile: self.data.url("inject.js")
});
};
inject.js:
unsafeWindow.foo = function() {
alert('hi');
}
unsafeWindow.foo();
What if you make a simple href with javascript function on the page.
Like bookmarklets work.
Here is a sample code :
function(scriptUrl) {
var newScript = document.createElement('script');
// the Math.random() part is for avoiding the cache
newScript.src = scriptUrl + '?dummy=' + Math.random();
// append the new script to the dom
document.body.appendChild(newScript);
// execute your newly available function
window.foo();
}('[url of your online script]')
To use it, put your script's url.
It must be only one line of code, url formated, but for code readability I've formated it.
I've never developed a Firefox extension, but for javascript injection that's how I would roll.
Hope it helped.
You can use Sandbox
// Define DOMContentLoaded event listener in the overlay.js
document.getElementById("appcontent").addEventListener("DOMContentLoaded", function(evt) {
if (!evt.originalTarget instanceof HTMLDocument) {
return;
}
var view = evt.originalTarget.defaultView;
if (!view) {
return;
}
var sandbox = new Components.utils.Sandbox(view);
sandbox.unsafeWindow = view.window.wrappedJSObject;
sandbox.window = view.window;
sandbox.document = sandbox.window.document;
sandbox.__proto__ = sandbox.window;
// Eval your JS in the sandbox
Components.utils.evalInSandbox("function foo() {..}", sandbox);
}, false);

Categories