Injecting Javascript to Iframe - javascript

I am making a live editor for my website. I have the CSS and HTML parts down, only issue is the JS part now. Here is a snippet of the code
var frame = $('#preview_content'),
contents = frame.contents(),
body = contents.find('body');
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
$('.area_content_box').focus(function() {
var $this = $(this);
var check = $this.attr('id');
$this.keyup(function() {
if (check === "html_process"){
body.html($this.val());
} else if(check === "css_process") {
csstag.text($this.val());
} else if (check === "java_process"){
java.text( $this.val() );
}
});
});
Problem is it is not injecting script tags in the iframes body nor the head when ever I try this. I've read up on injecting and some issues containing the ending script tag. I need to figure out how to inject script tags, really want them in the head but if it is easier in the body that is fine.
jfriend00 - I will be focusing on making this vanilla, if you think I should honestly.
So any words of advice on how to go about making my editor work correctly with the injecting JS?

These two lines of code look like they could have problems:
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
It seems like it would be much better to create the style tag and remember that DOM element.
var iframe = document.getElementById("preview_content");
var iframewindow = iframe.contentWindow || iframe.contentDocument.defaultView;
var doc = iframewindow.document;
var csstag = doc.createElement("style");
var scripttag = doc.createElement("script");
var head = doc.getElementsByTagName("head")[0];
head.appendChild.cssTag;
head.appendChild.scriptTag;
$('.area_content_box').focus(function() {
var $this = $(this);
var check = this.id;
$this.keyup(function() {
if (check === "html_process") {\
// I would expect this to work
doc.body.html($this.val());
} else if(check === "css_process") {
// I don't know if you can just replace CSS text like this
// or if you have to physically remove the previous style tag
// and then insert a new one
csstag.text($this.val());
} else if (check === "java_process"){
// this is unlikely to work
// you probably have to create and insert a new script tag each time
java.text( $this.val() );
}
});
});
This should work for the body HTML and it may work for the CSS text into the style tag.
I do not believe it will work for the javascript as you can't change a script by assigning text to the tag the way you are. Once a script has been parsed, it's in the javascript namespace.
There is no public API I'm aware of for removing previously defined and interpreted scripts. You can redefine global symbols with subsequent scripts, but not remove previous scripts or their effects.
If this were my code, I'd remove the previous style tag, create a new one, set the text on it before it was inserted in the DOM and then insert it in the DOM.
If you're not going to do it that way, then you'll have to test to see if this concept of setting .text() on an already inserted (and parsed) style tag works or not in all relevant browsers.
For the script tag, I'm pretty sure you'll have to create a new script tag and reinsert it, but there's no way to get rid of older code that has already been parsed other than redefining global symbols. If you really wanted to start fresh on the code, you'd have to create a new iframe object from scratch.
There are other issues with this too. You're installing a .keyup() event handler every time the .area_content_box gets focus which could easily end up with many of the event handlers installed, all getting called and all trying to do the same work.

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).

Avoid wrong interpretation of source with document.createElement for dynamic sources

I have script that I would like visitors on my website to run when they load a web page. It looks like this:
window.onload = function(){
var pxl=document.createElement('img');
pxl.setAttribute('src', 'http://localhost:8080/getTrackingPixel')
document.body.appendChild(pxl);
}
Most of the times the source returns an image and it works fine. However, sometimes it returns this:
<html><body style="background-color:transparent"></body></html>
And I can't really change the fact that it might sometimes not return an image. How do I change the javascript so that it can handle the html response without any errors? It might be possible for me to predict when it happens though - but I haven't managed to find a good way to request the source and return the html either.
You can achieve it by using the javascript Image object which, unlike the createElement approach, allows you to fetch the src url before inserting the img in the DOM.
The onload event of the Image object won't fire if the loaded content isn't an img.
Here it is :
window.onload = function(){
var pxl = new Image();
pxl.onload = function(){
// is IMG
document.body.appendChild(pxl);
}
pxl.onerror = function(){
// is not IMG
// Meaning in your case : <html><body style="background-color:transparent"></body></html>
}
pxl.src = 'http://localhost:8080/getTrackingPixel';
}
(Note that your code also missed the semicolon ";" line 4)

Best way to execute js only on specific page

I was wondering what would be the best way to execute a java-script code only on specific pages.
Let's imagine we have a template-based web-site, rewrite rule for the content ist set, jquery available and it basically looks like this:
<!DOCTYPE html>
<html>
<head>
<script src="script.js"></script>
</head>
<body>
...
include $content;
..
</body>
</html>
content 'info' contains a button, we want something to happen on click, content 'alert' should give us a message when you hover a text field.
What is the best way to trigger these actions, without running into an error, because the object is not found?
Option one: using window.location.pathname
$(document).ready(function() {
if (window.location.pathname == '/info.php') {
$("#button1").click(function(){
//do something
})
}else if(window.location.pathname == '/alert.php'){
$("#mytextfield").hover(){
alert('message');
}
}
Option two: checking if elements exist
$(document).ready(function() {
if ($("#button1").length > 0) {
$("#button1").click(function(){
//do something
})
}else if ($("#mytextfield").length > 0){
$("#mytextfield").hover(){
alert('message');
}
}
Option three: include the script in the loaded template
//stands for itself
Is there a better solution? Or do I have to get along with one of these solutions?
Your experience, usage, or any links related to this topic are appreciated.
//EDIT:
I might have choosen a bad example, the actual code would be somethin like:
mCanvas = $("#jsonCanvas");
mMyPicture = new myPicture (mCanvas);
where the myPicture constructor get's the context of the canvas element, and throws an error, if mCanvas is undefined.
Set a class attribute to your body tag.
<body class="PageType">
And then in your script..
$(function(){
if($('body').is('.PageType')){
//add dynamic script tag using createElement()
OR
//call specific functions
}
});
I would use the switch statement and a variable. (I'm using jQuery!)
var windowLoc = $(location).attr('pathname'); //jquery format to get window.location.pathname
switch(windowLoc){
case "/info.php":
//code here
break;
case "/alert.php":
//code here
break;
}
//use windowLoc as necessary elsewhere
This will allow you to change what "button" does based on the page that you're on. If I understood your question correctly; this is what I would do. Also, if I had were serving large amounts of javascript, I would simply add a new JS file completely.
var windowLoc = $(location).attr('pathname'); //jquery format to get window.location.pathname
switch(windowLoc){
case "/info.php":
var infoJS = document.createElement('script');
infoJS.type = 'text/javascript';
infoJS.src = 'location/to/my/info_file.js';
$('body').append(infoJs);
break;
case "/alert.php":
var alertJS = document.createElement('script');
alertJS.type = 'text/javascript';
alertJS.src = 'location/to/my/alert_file.js';
$('body').append(alertJs);
break;
}
Hope this helps -
Cheers.
A little different approach than checking the URL path : You can group page specific event handlers in a single function and then in each include, have a domready which will call these functions.
Eg: in script.js you have two functions (outside domready) viz. onPage1Load() and onPage2Load().
While in your page1.php you have a $(document).ready(onPage1Load)
and so on for other pages. This will make sure that unintended event handlers are not registered.
You can also use vanilla javascript to do the same
console.log(window.location.href);
const host = "http://127.0.0.1:5500/";
// JAVASCRIPT FOR INDEX PAGE
if (window.location.href == host + 'index.html') {
console.log("this is index page");
}
// JAVASCRIPT FOR ORDER PAGE
if (window.location.href == host + 'order.html') {
console.log("this is order page");
}
You can use Require js (RequireJS is a JavaScript file and module loader) and load script if they only needed. Link is http://requirejs.org/, I know using require js not so much easy.

Calling external javascript function through url bar

I want to use javascript in the url bar to manipulate the rendered html of a given page. Please note that I'm not trying to do something illegal here. Long story short, my university generates a weekly schedule based on your courses. I'd like to use javascript to add a button on the generated schedule page that will allow you to push the schedule to a google calendar. Unfortunately, I can't just go and edit the source itself (obviously), so I figured I would use javascript to edit the page once it has been rendered by my browser. I'm having some trouble calling an external javascript file to parse the rendered html.
As it is, this is what I have:
javascript:{{var e=document.createElement('script');
e.src = http://www.url.of/external/js/file.js';
e.type='text/javascript';
document.getElementsByTagName('head')[0].appendChild(e);}
functionToCall(document.body.innerHTML);}
Which, when pasted into the URL bar, SHOULD add my javascript file to the head and then call my function.
Any help would be greatly appreciated, thanks!
EDIT: Here's a working example if you're interested, thanks everyone!
javascript:(function(){var e=document.createElement('script');
e.src = 'http://www.somewebsite.net/file.js';
e.type='text/javascript';e.onload =function(){functiontocall();};
document.getElementsByTagName('head')[0].appendChild(e);})();
If you need the code to execute once it is loaded you can do one of two things:
Execute functionToCall(document.body.innerHTML); at the bottom of your script (http://www.url.of/external/js/file.js) rather than at the end of your bookmarklet.
Use e.onload = function(){ functionToCall(document.body.innerHTML); }; after e.type='text/javascript' near the end of your JavaScript snippet / bookmarklet, rather than calling functionToCall right after appending e to the document head (since e will most likely not have been loaded and parsed right after appendChild(e) is called.
I see that you've accepted an answer, and that's perfectly valid and great, but I would like to provide a useful tool I made for myself.
It's a bookmarklet generator called zbooks.
(Yes it's my website, no I'm not trying to spam you, there are no ads on that page, I gain nothing from you using it)
It's jQuery enabled and I think it's simple to use (but I built it, so who knows). If you need an extensive explanation of how to use it, let me know so I can make it better. You can even browse over the source if you'd like.
The important part is the business logic that gets jQuery on the page:
//s used for the Script element
var s = document.createElement('script');
//r used for the Ready state
var r = false;
//set the script to the latest version of jQuery
s.setAttribute('src', 'http://code.jquery.com/jquery-latest.min.js');
//set the load/readystate events
s.onload = s.onreadystatechange = function()
{
/**
* LOAD/READYSTATE LOGIC
* execute if the script hasn't been ready yet and:
* - the ready state isn't set
* - the ready state is complete
* - note: readyState == 'loaded' executes before the script gets called so
* we skip this event because it wouldn't have loaded the init event yet.
*/
if ( !r && (!this.readyState || this.readyState == 'complete' ) )
{
//set the ready flag to true to keep the event from initializing again
r = true;
//prevent jQuery conflicts by placing jQuery in the zbooks object
window.zbooks = {'jQuery':jQuery.noConflict()};
//make a new zbook
window.zbooks[n] = new zbooks(c);
}
};
//append the jQuery script to the body
b.appendChild(s);
Can't you use a proper tool like Greasemonkey?

How to run a javascript function using the # in the url?

hi this all started when i ran a function (lets call it loadround) that altered the innerHTML of an iframe. now once loadframe was loaded there were links in the iframe that once clicked would change the iframe page. the only problem is when i click the back button the loadround page was gone. i've thought about this numerous times to no avail. so i tried this code.
loadround
then
function loadround(a,b){
window.location.hash = "#loadround('"+a+"','"+b+"')";
var code = "<(h2)>"+a+"</(h2)><(h2)>"+b+"</(h2)>"
var iFrame = document.getElementById('iframe');
var iFrameBody;
iFrameBody = iFrame.contentDocument.getElementsByTagName('body')[0]
iFrameBody.innerHTML = code;
}
(the brackets in the h2 are intentional)
then i would try to reload the function by possibly an onload function but for now i was testing with a simple href as followed.
function check(){
var func = location.hash.replace(/#/, '')
void(func);
}
check
unfortunately the check code doesn't work and im almost certain there is an easier way of doing this. i tried changing the src of the iframe instead of the innerhtml and there was the same problem. thanks in advance
The modern browsers are starting to support the event window.onhashchange
In the meantime you can use the workaround proposed by Lekensteyn or maybe you can find something useful here: JavaScript/jQuery - onhashchange event workaround
You are misunderstanding the function void, which just make sure the return value is undefined. That prevents the browser from navigating away when you put it in a link. You can test that yourself by pasting the next addresses in your browser:
javascript:1 // note: return value 1, browser will print "1" on screen
javascript:void(1) // note: undefined return value, browser won't navigate away
It's strongly discouraged to execute the hash part as Javascript, as it's vulnerable to XSS without proper validating it. You should watch the hash part, and on modification, do something.
An example; watch every 50 milliseconds for modifications in the hash part, and insert in a element with ID targetElement an heading with the hash part. If the hash part is not valid, replace the current entry with home.
var oldHash = '';
function watchHash(){
// strip the first character (#) from location.hash
var newHash = location.hash.substr(1);
if (oldHash != newHash) {
// assume that the parameter are alphanumeric characters or digits
var validated = newHash.match(/^(\w+)$/);
// make sure the hash is valid
if (validated) {
// usually, you would do a HTTP request and use the parameter
var code = "<h1>" + validated[1] + "</h1>";
var element = document.getElementById("targetElement");
element.innerHTML = code;
} else {
// invalid hash, redirect to #home, without creating a new history entry
location.replace("#home");
}
// and set the new state
oldHash = newHash;
}
}
// periodically (every 50 ms) watch for modification in the hash part
setInterval(watchHash, 50);
HTML code:
Home
About Me
Contact
<div id="targetElement">
<!-- HTML will be inserted here -->
</div>

Categories