I have a situation where I am loading external HTML into a WYSIWYG editor on a web page. The external HTML is from a trusted source, and includes what ever is between two specific <div> tags from a number of different pages.
My problem is that some of the pages contain inline javascript event handlers. So when I am working in the WYSIWYG editor, certain events cause the execution of this js.
Mostly it doesn't do anything other than fill up the console with errors saying ... is not defined, and this may be all it is, and that wouldn't be a problem. But still, it's messy, and I don't know if there isn't a page somewhere that might execute something like alert(...) which could get extremely annoying. The code is all trusted, but detached from its intended context, it could produced undesirable results.
I want to find a way to globally prevent this execution, preferably without modifying the inline script. I could do something like attach =false; to each handler, but then I have to check all the incoming elements, and even with regular expressions, this will degrade performance. Also, I then would have to remove it before submitting the edited HTML back to the server, which seems like a major pain, and difficult to do flawlessly.
Is there a way to prevent this online code from being executed within this particular context?
Depending on what browser you are trying to support, you can checkout 'Content Security Policy' headers. Checkout http://caniuse.com/contentsecuritypolicy for details on browser support.
If you target browser is in the list, CSP can do exactly what you are looking for. It will disable event handlers by default. It will block execution of any code embedded within on the page in addition to blocking event handlers. So you will need to move all your js code, if present on the html page to a separate js, specify that filename in the safe-list and load your js from there.
CSP are set as Http headers but with the new specification it can be set using meta tags as well. Checkout https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#html-meta-element--experimental.
For WebKit supported browsers(Chrome/Safari) something like
would restrict load from any external source. You can add a list of accepted sources to the list and explore what works for you.
For firefox, though its there in the specification, I do not think the meta tag is supported yet.
https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy.
So to summarize, as long as you can set headers for the Web-Server that is hosting the web page (unless you only want Chrome/Safari support), and your target browser supports CSP, you might give it a shot.
I copied this from sitepoint forum, a question similar to yours. This takes an approach of pulling div out of the parent and putting it back on. If this is what you want here is the example code:
var div = document.getElementById('div');
var nextSibling = div.nextSibling;
var parent = div.parentNode;
parent.removeChild(div);
function reCreateElement() {
if (nextSibling) {
parent.insertBefore(div, nextSibling);
} else {
parent.appendChild(div);
}
}
setTimeout(reCreateElement, 100);
// Below is the stuff we don't want to run!
document.getElementById('div').onclick = function() {
alert('CLICKED!');
}
Related
I've written a small HTML5 page that I need to be able to support multiple languages. I've implemented the language control by making the page load a JSON file into memory (in the HEAD) and then running a jQuery command to change the text of any element as required.
Everything works fine except that as the change is being called post render (if the document ready function) there is a slight flash as the language gets changed.
Is there an event that is called before the page is rendered but after the DOM is available? If not, are there any suggestions to change implementation.
Cheers..
UPDATE
I've found a few answers to this on other sites. The general consensus appears to be that this isn't possible as most browsers render as they parse. The workaround that is suggested is to hide (display:'none') the body in script and then show it (display:'') after the updates in the document ready function. It sort of works for me although isn't 100% perfect.
Sounds like you are having an issue with FOUC (Flash Of Unstyled Content)
There are a few ways to get around it. You could add this to your body:
<body class="fouc">
And then have this CSS:
.fouc{display:none;}
And finally this script:
$(function(){
$('.fouc').show();
});
This works by initially hiding the page, and then once you are ready, turning it on with javascript. You may need to ensure your manipulation occurs ahead of the $('.fouc').show(); call.
One effective solution, though not the one you are probably looking for, is to use OUTPUT BUFFERING ... What is output buffering?
I cannot find a way to identify whether the CSS, which was created dynamically via creating a <link> tag and then specifying the href attribute had indeed loaded.
Consider the following example:
var css = document.createElement("link");
css.href = "not-a-directory/not-a-css";
document.body.appendChild(css);
Some browsers (Firefox), will dispatch "onload" event even if the path doesn't designate an existing file, however, this would only happen if sandbox policies would have not been violated, if file existed. Opera, Google Chrome and Safari - all report the problem in some way, but I can't find a way to handle that in JavaScript.
I'm not interested in supporting MS Internet Explorer (the project will be deployed to embedded WebKit / Opera browsers), but if there is a crossplatform way - that's, of course, welcomed.
PS. "onerror" doesn't seem to trigger in any browser.
CLARIFICATION
Sorry, I had to provide more info. Now I'll try to fix that:
I prefer not to use other libraries (like jQuery), but if they solved it somehow, that's a valid solution, if I can take a look at how they did it.
The code will be deployed to either Opera or WebKit browsers on SmartTV (read it as severe size limitations + files may be required to load from file-system rather than from the net). This is why I can't use XMLHttpRequest.
If you can think of a way to test whether the text file exists given the path (think iframe, link - perhaps other elements?) That is a good option too.
Finally, after much struggle, here's what I did (but alternative solutions are welcome!):
Created a class with the name which is unlikely to happen in other CSS files, say:
.x29048723049820394 { color: #514159 }
and then created a div tag in the HTML page, where I wanted to test whether the CSS has indeed loaded. After creating a link tag (don't forget - you also need to specify the rel attribute, otherwise the style won't load), I set timer to wait for the div I just created to change color. After the color changed I'd assume that the CSS had loaded. Or if the time ran out, I'd give up, assuming that the CSS didn't load.
On the way I also discovered, that in case the CSS file is loaded from the file system, even the same directory as the original HTML, which requests it, you will run into security-related errors when either trying to access the innerHTML of the link or when trying to iterate through the style sheets of the document.
If you know a specific class name that should be loaded then you could use javascript to loop through all the style sheets and class names to see if it was loaded.
It is my understanding that there's no elegant and reliable way of detecting it, everything is more or less hacks.
Here's my hack off the top of my head:
$.ajax({
url: "/style.css",
dataType: "text",
success: function (cssString) {
// css file exists, and was loaded correctly.
$("head").append($("<style/>").text(cssString));
},
error: function (e) {
// css file did not exist, we can check status code with e.status (404 etc)
}
});
We basically load the css file as text using ajax and then attach the loaded data to the dom. This way we can reliably detect failures, but other than that it's a total hack. You most likely get a flash of unstyled content. I'm not sure I would use it, but I thought it might be fun to throw it out there along with all other hacks :-)
I have been struggling with choosing unobtrusive javascript over defining it within the html syntax. I want to convince my self to go the unobtrusive route, but I am having trouble getting past the issues listed below. Can you please help convince me :)
1) When you bind events unobtrusively, there is extra overhead on the client's machine to find that html element, where as when you do stuff, you don't have to iterate the DOM.
2) There is a lag between when events are bound using document.ready() (jquery) and when the page loads. This is more apparent on very large sites.
3) If you bind events (onclick etc) unobtrusively, there is no way of looking at the html code and knowing that there is an event bound to a particular class or id. This can become problematic when updating the markup and not realizing that you may be effecting javascript code. Is there a naming convention when defining css elements which are used to bind javascript events (i have seen ppl use js_className)
4) For a site, there are different pieces of javascript for different pages. For example Header.html contains a nav which triggers javascript events on all pages, where as homepage.html and searchPage.html contains elements that trigger javascript on their respective pages
sudo code example:
header.html
<script src="../myJS.js"></script>
<div>Header</div>
<ul>
<li>nav1</li><li>nav2</li>
</ul>
homepage.html
<#include header.html>
<div class="homepageDiv">some stuff</div>
searchpage.html
<#include header.html>
<div class="searchpageDiv">some other stuff</div>
myJS.js
$(document).ready(function(){
$("ul.li").bind("click",doSomething());
$(".homePageDiv").bind("click",doSomethingElse());
$(".searchPageDiv").bind("click",doSomethingSearchy());
});
In this case when you are on the searchPage it will still try to look for the "homepageDiv" which does not exist and fail. This will not effect the functionality but thats an additional unnecessary traversal. I could break this up into seperate javascript files, but then the browser has to download multiple files, and I can't just serve one file and have it cached for all pages.
What is the best way to use unobtrusive javascript so that I could easily maintain a ( pretty script heavy) website, so another developer is aware of scripts being bound to html elements when they are modifying my code. And serve the code so that the client's browser is not looking for elements which do not exist on a particular page (but may exist on others).
Thanks!
You are confused. Unobtrusive JavaScript is not just about defining event handlers in a program. It's a set of rules for writing JavaScript such that the script doesn't affect the functionality of other JavaScript on the same page. JavaScript is a dynamic language. Anyone can make changes to anything. Thus if two separate scripts on the same page both define a global variable add as follows, the last one to define it will win and affect the functionality of the first script.
// script 1
var add = function (a, b) {
return a + b;
};
// script 2
add = 5;
//script 1 again
add(2, 3); // error - add is a number, not a function
Now, to answer your question directly:
The extra overhead to find an element in JavaScript and attach an event listener to it is not a lot. You can use the new DOM method document.querySelector to find an element quickly and attach an event listener to it (it takes less than 1 ms to find the element).
If you want to attach your event listeners quickly, don't do it when your document content loads. Attach your event listeners at the end of the body section or directly after the part of your HTML code to which you wish to attach the event listener.
I don't see how altering the markup could affect the JavaScript in any manner. If you try to attach an event listener to an element that doesn't exist in JavaScript, it will silently fail or throw an exception. Either way, it really won't affect the functionality of the rest of the page. In addition, a HTML designer really doesn't need to know about the events attached any element. HTML is only supposed to be used for semantic markup; CSS is used for styling; and JavaScript is used for behavior. Don't mix up the three.
God has given us free will. Use it. JavaScript supports conditional execution. There are if statements. See if homePageDiv exists and only then attach an event listener to it.
Try:
$(document).ready(function () {
$("ul.li").bind("click",doSomething());
if (document.querySelector(".homePageDiv")) {
$(".homePageDiv").bind("click",doSomethingElse());
} else {
$(".searchPageDiv").bind("click",doSomethingSearchy());
}
});
Your question had very little to do with unobtrusive JavaScript. It showed a lack of research and understanding. Thus, I'm down voting it. Sorry.
Just because jQuery.ready() executes does not mean that the page is visible to the end user. This is a behaviour defined by browsers and these days there are really 2 events to take into consideration here as mootools puts it DomReady vs Load. When jQuery executes the ready method it's talking about the dom loading loaded however this doesn't mean the page is ready to be viewed by the user, external elements which as pictures and even style sheets etc may still be loading.
Any binding you do, even extremely inefficient ones will bind a lot faster than all the external resources being loaded by the browser so IMHO user should experience no difference between the page being displayed and functionality being made available.
As for finding binding on elements in your DOM. You are really just fearing that things will get lost. This has not really been my actual experience, more often than not in your JS you can check what page you are on and only add javascript for that page (as Aadit mentioned above). After that a quick find operation in your editor should help you find anything if stuff gets lost.
Keep in mind that under true MVC the functionality has to be separate from the presentation layer. This is exactly what OO javascript or unobtrusive javascript is about. You should be able to change your DOM without breaking the functionality of the page. Yes, if you change the css class and or element id on which you bind your JS will break, however the user will have no idea of this and the page will at least appear to work. However if this is a big concern you can use OO-Javascript and put div's or span's as placeholders in your dom and use these as markers to insert functionality or tell you that it exists, you can even use html comments. However, in my experience you know the behavior of your site and hence will always know that there is some JS there.
While I understand most of your concerns about useless traversals, I do think you are nickle and dime'ing it at this point if you are worried about 1 additional traversal. Previous to IE8 it used to be the case that traversing with the tag name and id was a lot faster than my selector but this is no longer true infact browsers have evolved to be much faster when using just the selectors:
$("a#myLink") - slowest.
$("a.myLink") - faster.
$("#Link") - fastest.
$(".myLink") - fastest.
In the link below you can see that as many as 34 thousand operations per second are being performed so I doubt speed is an issue.
You can use firebug to test the speed of each in the case of a very large dom.
In Summary:
a) Don't worry about losing js code there is always ctrl+f
b) There is no lag because dom ready does not mean the page is visible to start with.
Update
Fixed order of speed in operations based on the tests results from here
However keep in mind that performances of IE < 8 are really had if you don't specify the container (this used to be the rule, now it seems to be the exception to the rule).
(excuse me if this is not the right forum to post - i couldn't find anything related to non-native programming and related to this topic)
I Am trying to set a dynamic HTML into an iFrame on the webpage. I have tried a couple of things but none of them seem to work. I m able to read the innerHTML but can't seem to update it.
// Able to read using
document.getElementById('iFrameIdentifier').innerHTML;
// On Desktop IE, this code works
document.getElementById('iFrameId').contentWindow.document.open();
document.getElementById('iFrameId').contentWindow.document.write(dynamicHTML);
document.getElementById('iFrameId').contentWindow.document.close();
Ideally the same function should work as how it works for div's but it says 'Object doesn't support this method or property".
I have also tried document.getElementById('iFrameId').document.body.innerHTML.
This apparently replaces the whole HTML of the page and not just the innerHTML.
I have tried out a couple of things and they didn't work
document.getElementById('iFrameId').body.innerHTML
document.frames[0].document.body.innerHTML
My purpose is to have a container element which can contain dynamic HTML that's set to it.
I've been using it well till now when I observed that the setting innerHTML on a div is taking increasing amount of time because of the onClicks or other JS methods that are attached to the anchors and images in the dynamic HTML. Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Also being discussed - http://www.experts-exchange.com/Programming/Languages/Scripting/JavaScript/Q_26185526.html#a32779090
I have tried a couple of things but none of them seem to work.
Welcome to IEMobile! Nothing you know about DOM scripting applies here.
Unfortunately, cross-iframe scripting does not appear to be possible in IEMobile6-7.
frameelement.contentDocument (the standard DOM method) isn't available
frameelement.contentWindow.document (the IE6-7 workaround version) isn't available
the old-school Netscape window.frames array only works for frames, not iframes
having the child document pass up its document object to the window.parent only works for frames, not iframes. In an iframe, window.parent===window.
So the only ways forward I can see are:
use frames instead of iframes. Nasty. Or,
use document.cookie to communicate between parent and child: the child document is just a script, that checks for a particular cookie in document.cookie on a poller, and when it's found that's a message from the parent, and it can write some HTML or whatever. Slow and nasty. Or,
using the server-side to inject content into the frames, passing it in as an argument to a script. Slow, nasty, and potentially insecure. Or,
avoid frames completely (best, if you can). Or,
drop support from IEMobile6-7 (best for preserving your sanity, if you can get away with it!)
Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Yes, probably. IEMobile6-7(*) is close to unusable at dynamic HTML. It gives you a lovely flavour of what scripting used to be like for us poor gits back in the Netscape 4 days.
Try to avoid creating and destroying lots of nodes and event handlers. Keep the page as static as possible, re-using element nodes where possible and setting text node data properties in preference to tearing everything down and making anew with createElement or innerHTML. Use an event stub (onclick="return this._onclick()") in the HTML together with writing to _onclick if you need to set event handlers from JavaScript, in preference to recreating the HTML with a new event handler (or just trying to set the property, which of course doesn't work in IEMobile). Avoid long-running single pages when you can.
It'll still crash, but hopefully it'll take longer.
*: that is, the versions of IE present on WinMo before version 6.1.4, where it became the infinitely better IEMobile8, marketed as “Internet Explorer Mobile 6” (thank you Microsoft).
Okay, I kinda resolved the issues that i was facing earlier and the bigger issue which was setting HTML to an iFrame on IEMobile. But i still have one more PIA which is related to double scollbars - which i am currently looking into. There seems to be more poor souls facing similar problem - if i fix that too. I will post an update here.
How did i finally write to iFrame on IEMobile?
Have 2 divs one to wrap the iFrame and the other to write inside an iFrame.
document.getElementById('OuterDiv').innerHTML = '';
document.getElementById('OuterDiv').innerHTML = '<iframe id="iFrameId" src="somefile.html"></iframe>';
This creates an iFrame each time and in the somefile.html on load there is a InnerDiv.innerHTML which doesn't seem to leak the memory.
In the somefile.html there will be an onLoad method which will fetch the HTML (explained below on how i managed to get it) and do a
document.getElementById('InnerDiv').innerHTML = dynamicHTML;
How did I manage to pass the HTML between parent and child iFrame
As well explained by #bobince earlier, one has to rely on 3rd party service like a cookie or a server to pass around the data between parent and the child iFrame.
I infact used an ActiveXControl to set and get data from the parent and child iFrame's javascript respectively. I won't recommend doing this if you have to introduce an ActiveX Control just for this. I accidentally already have one which I use to get the Dynamic HTML in the first place.
If you need any help you can DM me - Twitter #Swaroop
Thanks #bobince for your help. I am marking this one as an answer because it says what i did to fix the issue.
This is generally how I manage progressive enhancement whilst keep the experience clean, but how safe is it? is there potential for a race condition and this not working?
Imagine the simple abstract scenario, you want to display something differently if you have javascript support.. this is generally what I will end up doing:
<div id="test">original</div>
<script type="text/javascript">
var t = document.getElementById('test');
t.innerHTML = 'changed';
</script>
Many may claim you should use a framework and wait for a domready event, and do changes there.. however there is a significant delay where the 'test' element will have already been rendered before the end of the document and the css are ready and a domready triggers.. thus causing a noticable flicker of 'original'.
Is this code liable to race condition failures? or can I guarentee that an element is discoverable and modifiable if it exists before the script?
Thanks in advance.
You can, but there are issues surrounding doing it.
First off, in IE if you attempt to manipulate a node that has not been closed (e.g. BODY before its close tag which should be below your JS) then you can encounter IE's "OPERATION ABORTED" error which will result in a blank page. Manipulation of a node includes appending nodes, moving nodes, etc.
In other browsers the behavior is undefined, however they do usually behave as you would expect. The main issue is that as your page evolves, the page may load/parse/run differently. This may cause some script to run before a browser defines referenced elements have actually been created and made available for DOM manipulation.
If you are attempting to enhance your user perceived performance (i.e. snappiness). I highly suggest that you avoid this path and look into lightening your pages. You can use Yahoo's YSlow/Google's Page Performance Firebug to help you get started.
Google's Page Speed
Yahoo's YSlow
You can manipulate the DOM before it has fully loaded, but it can be risky. You obviously can't guarantee that the bit of the DOM you are trying to manipulate actually exists yet, so your code may fail intermittently.
As long as you only modify nodes which preceed the script block (ie the node's closing tag preceeds the script's opening tag), you shouldn't encounter any problems.
If you want to make sure the operation succeeds, wrap the code in a try...catch block and call it again via setTimeout() on failure.
In Viajeros.com I have a loading indicator working since 8-9 months and I have no problems so far. It looks like this:
<body>
<script type="text/javascript">
try {
document.write('<div id="cargando"><p>Cargando...<\/p><\/div>');
document.getElementById("cargando").style.display = "block";
} catch(E) {};
</script>
Accessing the DOM prematurely throws exceptions in IE 5 and Navigator 4.