I've been experimenting with metaprogramming in webpages, and need to delay a script tag from running until just after another script tag has been run. However, the script tag needs to be loaded first or both of them will fail.
Shortened and more readable version of what I'm trying to do:
<script defer>
w=function(){
<stuff that gives a parser error until modified by the next script tag>
}
</script>
<script>
<stuff that changes the previous script tag and any other script tags that ever will be added via the DOM
so it doesn't give a parser error>
</script>
<button onclick='w()'></button>
This would work perfectly well, except that the button's onclick attribute fails because the button was loaded before the first script tag was run.
Thanks in advance!
(EDIT: I linked a pastebin to show the full version of my code, it might clear things up a bit since it seems my summed-up version wasn't very good.
As suggested by #meagar in the comments, if you don't mind changing the type property of your "not actually javascript" script blocks you can do something like this:
<script type='derpscript'>
var derp;
var w=function(){alert('hello')};
derp||=5;
console.log(derp);
</script>
<script>
function compileDerps() {
// find all derpscript script tags
var x = document.querySelectorAll('script[type=derpscript]');
for(var i=0;i<x.length;i++){
meta=x[i].text
while(true){
pastmeta=meta;
console.log(exc=regex.exec(meta))
if(exc){
meta=meta.replace(regex,exc[1]+'='+exc[1]+'||');
}
if(pastmeta==meta){break;}
}
// make a new javascript script tag to hold the compiled derp
var s = document.createElement('script');
s.text = meta;
document.body.appendChild(s);
// delete the derpscript tag
x[i].parentNode.removeChild(x[i]);
}
}
//stuff that changes the previous script tag and any other script tags that ever will be added via the DOM
var regex=/([a-zA-Z$_][a-zA-Z$_1-9]*)(\|\|\=)/;
var meta;
var pastmeta='';
var exc='';
compileDerps();
</script>
<button onclick='w()'>THIS IS W</button>
from an issue I am experiencing I understand how it works, but I can't find any formal reference that helps me to clarify the behaviour.
<head>
<title>Chapter 7: Example 7</title>
<script type="text/javascript">
var formWeek = document.form1;
var weekDays = new Array();
weekDays = formWeek.theDay.options;
function btnRemoveWed_onclick()
{
console.log("In btnRemoveWed_onclick");
}
</script>
</head>
<body>
<form action="" name="form1">
<select name="theDay" size="5">
<option value="0" selected="selected"></option>
With this code I receive an error on line "weekDays = formWeek.theDay.options;" because "theDay" is not defined. So I believe that while the JS code is executed the browser has not parsed and loaded the DOM (hence it doesn't know about form1).
If I move the variable definition inside the function, everything works fine.
function btnRemoveWed_onclick()
{
console.log("In btnRemoveWed_onclick");
var formWeek = document.form1;
var weekDays = new Array();
weekDays = formWeek.theDay.options;
}
At function execution the browser knows about form1 (load all the HTML code).
So... from the code the behaviour is clear but still it has not 'clicked' on my mind how it works.
I thought that the link below was a good reference to understand the behaviour.
Where should I put <script> tags in HTML markup?
Can you point me to some good reading that explains HTML-JS loading?
For what i know, javascript is loaded in line with HTML. So if you have an element <foo> and then a script that uses <foo> after that, it works. Turn them around, and the script is loaded first, after that the foo element. This way your script cannot find the element.
Change your javascript to:
function init()
{
var formWeek = document.form1;
var weekDays = new Array();
weekDays = formWeek.theDay.options;
function btnRemoveWed_onclick()
{
console.log("In btnRemoveWed_onclick");
}
}
document.addEventListener('DOMContentLoaded', init, false);
this way you make sure the javascript is loaded when the DOM is ready.
When you have an inline script tag in HTML, it blocks the parsing of HTML and it is executed immediately. Anything written after it has not been parsed yet.
It's common practice to put script tags at the end of the body tag, because at that point the DOM has been parsed and JS can safely execute.
As far as the error you pointed out is concerned, you can wait for the browser to finish loading the page by using something like window.onload. Notice lower in the documentation, in the Notes section
The load event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images, scripts, links and sub-frames have finished loading.
This means by the time the code is run, your HTML has been parsed and put into the DOM. Your script tag, then, will be:
<script type="text/javascript">
window.onload = function() {
var formWeek = document.form1;
var weekDays = new Array();
weekDays = formWeek.theDay.options;
}
function btnRemoveWed_onclick()
{
console.log("In btnRemoveWed_onclick");
}
</script>
I am on an e-commerce platform where I can edit the <head>, however some things that are injected into the head are out of reach for users. So even though we can edit the <head>, there are injections which are out of reach and therefore unremovable via the traditional method.
PS: I can put script before or after these injected JS script tags, which are generated and populated along with my scripts. And so my script would run before the injected tags if I place my script before their "tag injection line."
The Problem
The problem is, this platform started injecting analytics and spam into the head, basically jacking our customers info and selling it to third parties. So I want to disable their crappy scripts.
<script type="text/javascript" async="" src="/some.JS.file.min.js"></script>
<script type="text/javascript" async="" src="/another.JS.file.min.js"></script>
The Question
Is it possible with javascript or jquery to write a script that will edit tags before they run? I can insert this custom script before the tags are in injected. I was wrong -- the unwanted <script> tags are always PREpended to the first non-commented <script> tag, and so no javascript will work to hack up the tags before they run.
What I Have Tried So Far
I found this incomplete and not working answer from this SO question.
When I run the full script with the right details entered for my own site, I get so many errors it's difficult to know where to begin as I have no idea what all the XHR stuff is for or what it does, and some of the errors are ones I've never even seen before.
When I run just this part, which I somewhat understand:
doc = document.implementation.createHTMLDocument(""+(document.title || ""));
scripts = doc.getElementsByTagName("script");
//Modify scripts as you please
[].forEach.call( scripts, function( script ) {
if(script.getAttribute("src") == "/some.JS.file.min.js"
|| script.getAttribute("src") == "/another.JS.file.min.js") {
script.removeAttribute("src");
}
});
EDIT UPDATE:
Their script is inserted AFTER my scripts. That is, I can insert the script into the <head> before their script tags or after. We are looking into new platforms now but I still need to solve this in the meantime as it will be months before we switch. I was hoping g there is some JavaScript I am not aware of that can edit HTML script tags before they run, if this script runs before they do.
EDIT 2:
Nit's answer window.bcanalytics = function () {}; works great and breaks most of it by breaking window.bcanalytics.push but somehow some of it still survives.
In this block:
<script type="text/javascript">
(function() {
window.bcanalytics || (window.bcanalytics = []), window.bcanalytics.methods = ["debug", "identify", "track",
"trackLink", "trackForm", "trackClick", "trackSubmit", "page", "pageview", "ab", "alias", "ready", "group",
"on", "once", "off", "initialize"], window.bcanalytics.factory = function(a) {
return function()
{
var b = Array.prototype.slice.call(arguments);
return b.unshift(a), window.bcanalytics.push(b),
window.bcanalytics
}
};
for (var i = 0; i < window.bcanalytics.methods.length; i++)
{
var method = window.bcanalytics.methods[i];
window.bcanalytics[method] = window.bcanalytics.factory(method)
}
window.bcanalytics.load = function() {
var a = document.createElement("script");
a.type = "text/javascript",
a.async = !0, a.src = "http://cdn5.bigcommerce.com/r-2b2d3f12176a8a1ca3cbd41bddc9621d2657d707/app/assets/js/vendor/bigcommerce/analytics.min.js";
var b = document.getElementsByTagName("script")[0];
// This line still runs and loads analytics.min.js
// This line still runs and loads analytics.min.js
// This line still runs and loads analytics.min.js
b.parentNode.insertBefore(a, b)
// ^^^ This line still runs and loads analytics.min.js
// This line still runs and loads analytics.min.js
// This line still runs and loads analytics.min.js
}, window.bcanalytics.SNIPPET_VERSION = "2.0.8", window.bcanalytics.load();
bcanalytics.initialize({"Fornax": {"host": "https:\/\/analytics.bigcommerce.com","cdn": "http:\/\/cdn5.bigcommerce.com\/r-2b2d3f12176a8a1ca3cbd41bddc9621d2657d707\/app\/assets\/js\/vendor\/bigcommerce\/fornax.min.js","defaultEventProperties": {"storeId": 729188,"experiments": {"shipping.eldorado.ng-shipment.recharge-postage": "on","shipping.eldorado.label_method": "on","cp2.lightsaber": "on","PMO-272.cp1_new_product_options": "on","cart.limit_number_of_unique_items": "control","cart.auto_remove_items_over_limit": "control","BIG-15465.limit_flash_messages": "control","BIG-15230.sunset_design_mode": "control","bigpay.checkout_authorizenet.live": "on","bigpay.checkout_authorizenet.live.employee.store": "control","bigpay.checkout_authorizenet.test": "on","bigpay.checkout_authorizenet.test.employee.store": "control","bigpay.checkout_stripe.live": "on","bigpay.checkout_stripe.live.employee.store": "control","bigpay.checkout_stripe.test": "on","bigpay.checkout_stripe.test.employee.store": "control","sessions.flexible_storage": "on","PMO-439.ng_payments.phase1": "control","PMO-515.ng_payments.phase2": "control","PROJECT-331.pos_manager": "control","PROJECT-453.enterprise_apps": "control","shopping.checkout.cart_to_paid": "legacy_ui","onboarding.initial_user_flow.autoprovision": "on","faceted_search.enabled": "off","faceted_search.displayed": "off","themes.previewer": "enabled"}},"defaultContext": {"source": "Bigcommerce Storefront"},"anonymousId": "24a35a36-7153-447e-b784-c3203670f644"}});
})();
</script>
window.bcanalytics.load manages to survive and loads analytics.min.js (according to the Network tab), though I can't tell if the script then runs or doesn't.
Also, I've figured out that these pesky HTML lines:
<script type="text/javascript" defer="" async="" src="http://tracker.boostable.com/boost.bigcommerce.js"></script>
<script type="text/javascript" async="" defer="" src="http://cdn5.bigcommerce.com/r-2b2d3f12176a8a1ca3cbd41bddc9621d2657d707/javascript/jirafe/beacon_api.js"></script>
<script type="text/javascript" async="" src="http://cdn5.bigcommerce.com/r-2b2d3f12176a8a1ca3cbd41bddc9621d2657d707/app/assets/js/vendor/bigcommerce/analytics.min.js"></script>
<script type="text/javascript" async="" src="http://www.google-analytics.com/plugins/ua/ecommerce.js"></script>
are Always PREpended to the first non-commented <script> opening tag, so unfortunately, none of the creatively destructive methods below will work, as any script I try to insert ahead of these tags will automatically find the pesky unwanted lines appended before it.
Assuming the offending code is similar to that of the question you linked to, I would simply try to break the offending code so it fails to execute.
From hereon the answer relies on code from the other question since you didn't provide any.
The offending code relies on analytics, which is ensured on the page at the beginning of the script:
(function(){
window.analytics||(window.analytics=[]),window.analytics.methods=["debug","identify","track","trackLink","trackForm","trackClick","trackSubmit","page","pageview","ab","alias","ready","group","on","once","off","initialize"],window.analytics.factory=function(a){return function(){var b=Array.prototype.slice.call(arguments);return b.unshift(a),window.analytics.push(b),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var method=window.analytics.methods[i];window.analytics[method]=window.analytics.factory(method)}window.analytics.load=function(){var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src="http://cdn2.bigcommerce.com/r6cb05f0157ab6c6a38c325c12cfb4eb064cc3d6f/app/assets/js/analytics.min.js";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b)},window.analytics.SNIPPET_VERSION="2.0.8",window.analytics.load();
//The rest of the script
})();
To break the whole script and prevent it from running you should simply assign window.analytics a value that will conflict with the methods that are used.
So, for example, you could run a script before the offending script that simply assigns the following:
window.analytics = function () {};
Which will result in the offending script failing due to a type error.
If you know you can at least get your scripts to run first, one (albeit hacky) solution is to just absolutely "trash" the JS environment for the next script, so it has some problems. For example:
//trash it
document.getElementById=null;
document.querySelector=null;
document.querySelectorAll=null;
window.console=null;
window.alert=null;
document.getElementsByTagName=null;
document.getElementsByClassName=null;
As soon as the enemy script tries using one of those functions, it will just crap out. Those are just some common methods off the top of my head... find out which ones its using, and nuke those. Of course, nuking anything you need for events on your own page could be an issue.
How are the scripts being injected? If it's through something like document.createElement, you could attempt to hijack that function and disable it if the element name is script:
var origCreate = document.createElement;
document.createElement = function (name) {
if (name.toLowerCase() !== 'script') {
origCreate.call(document, name);
}
};
Since the scripts are being inserted server-side, you won't be able to disable the running of the scripts in your JavaScript. However, if you're able to inject any arbitrary text before and after the scripts being inserted, you could try commenting out the script tags by inserting this first:
<!--
...then this after:
-->
If the scripts get injected between these, it will hopefully cause the HTML parser to ignore the scripts.
Update:
Sounds like you need to disable just some of this content, so commenting everything out won't work. However, if before/after hijacking works, you could potentially wrap the injected scripts in a DOM element, parse that content, strip out the scripts you don't want, and inject the scripts so they run:
Inject something like this before:
<style id="hijack" type="text/html">
...and this after:
</style>
<script>
var hijackedWrapper = document.getElementById('hijack');
var scripts = hijackedWrapper.textContent;
scripts = scripts.replace('<script src="http://some.domain.com/foo.js"></s' + 'cript>', '');
document.write(scripts); // There's better ways to do this, but is just an illustration
</script>
Like the others, I would suggest sabotaging the js environment for the hostile script, and then recovering it back once you need it.
For example, if the script relies on document.getElementById, you can do this
var restore = {
getElementById: document.getElementById
};
document.getElementById = null;
and then if you have a need to use document.getElementById later, you can restore it back:
document.getElementById = restore.getElementById;
I also wanted to note that removing the actual script tags, as far as I can tell, is not possible:
If you put in a script before the hostile scripts, then they will not be loaded in the DOM yet, so it can't see anything to remove.
If you put in a script after the hostile scripts, the hostile scripts will already be loaded.
I am trying to get html of newly open window after activating a link that uses javascript by zombie.js.
Here is the html code
<html>
<head>
<script type="text/javascript">
function newin(id)
{
var url="page.php?id="+id;
window.open(url,id,"toolbar=no,location=top,directories=no,status=no,scrollbars=yes,hscroll=no,resizable=yes,copyhistory=no,width=1025,height=1250");
}
</script>
</head>
<body>
<div>
123<br/>
234<br/>
345<br/>
</div>
</body>
The Script I am using is:
var Browser = require("zombie");
var browser = new Browser();
browser.visit("http://localhost:8000/testpage.html", function () {
browser.wait(function(){
var selector = "a[href*='newin']";
var elements = browser.queryAll(selector);
for (var e=0;e<elements.length;e++){
browser.clickLink(elements[e],function(){
browser.wait(function(){
console.log(browser.html());
});
});
}
});
});
I am not able to get HTML of any window.Any ideas what is wrong in this code ? Or is this possible with phantomjs??
Finally I come to know that if a link contains JavaScript directly in the href or action, Zombie seems to understand that as opening a new page like a normal hyperlink would. While the JavaScript is still executed correctly, the DOM is lost as a result of Zombie trying to load the invalid target as a new page.
A problematic link would be e.g.
test
There’s no support for javascript:links, it is still an open issue:
https://github.com/assaf/zombie/issues/700
I was trying to check some of javascript code and I found one thing which I am not able to understand the exact reason. In my html file, I have a div with id called test which dont have any value. Now, I want to update the a text/ sentence inside this div through innerHTML. as it is just for testing purpose I am not using any function/ event. Just adding a to update the value.
<body>
<script type="text/javascript">
var test_content = "This is new text in my test div";
document.getElementById("test").innerHTML = test_content;
</script>
<div id="test"></div>
</body>
Now, when I load the page, it showing empty nothing inside the test div but if put the javascript code below the div as in below, then it is showing the value in the variable. (note: I am not using any function nor event, just want to update on page load).
<body>
<div id="test"></div>
<script type="text/javascript">
var test_content = "This is new text in my test div";
document.getElementById("test").innerHTML = test_content;
</script>
</body>
can any one explain me the reason for this? Thanks in advance.
Thanks!
Robin
That's because the first is executed before the div#test is created, so it currently doesn't exist. That's why is a good practice to either put your script tags at the bottom of the page or wrap them with an window.onload event listener.
<body>
<script type="text/javascript">
window.onload = function () {
var test_content = "This is new text in my test div";
document.getElementById("test").innerHTML = test_content;
}
</script>
<div id="test"></div>
</body>
If you are using jQuery, you can also do this:
$(function () {
var test_content = "This is new text in my test div";
document.getElementById("test").innerHTML = test_content;
});
And since you seem to be a beginner in JavaScript coding, I recommend you read some articles on MDN, like this one and this one.
Pretty standard issue. Needs an 'onload' of some sort!
<body>
<div id="test"></div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
var test_content = "This is new text in my test div";
document.getElementById("test").innerHTML = test_content;
});
</script>
</body>
The reason this is not happening in the the first instance, is because the DOM element, 'test' has not been created yet.
When you place the script after the div, the element has already been created and hence, the script can execute.
You will need to execute your code once the DOM is ready, by listening for load event dispatched from the body tag. This can be done quite simply using an in-line listener such as <body onload='myFunction'> or by an onload handler in javascript:
body.onload = function() {...}
Javascript is executed at runtime, as soon at it is being called. In your first example, the parser reads the script tag, executes it and then loads the rest of the page (top-to-bottom). As the script is executed before the div is laoded and created, the div will stay empty. That's the reason the onload event was introduced. http://www.w3schools.com/jsref/event_onload.asp
take one example of jquery you either have to write $(document).ready() or you have to write your jquery code at the last of html code and, both have same meaning i.e when all the html is loaded then do some function. this is same in this case, do some function after all the document content is loaded. take two cases:
case #1:
in this case we have the javascript code written above the html as in your first case which is without any event handler, the html engine will start reading the html code from top to bottom and at the moment it will hit to script tag it will call javascript engine. so according to this javascript code will be executed first.
if you write this line document.getElementById("test").innerHTML = test_content;
as :
var x = document.getElementById("test");
x.innerHTML = test_content;
then the console will return null i.e the value of x would benull.because div is still not loaded, therefore the value of div will not change
case #2:
script tag is placed at the last. so now, all the html is loaded by html engines, so now the value of x will be <div id="test"></div> and now all the javascript code will be executed without any error.
as i mentioned earlier about jquery $(document).ready()... well this is a jquery method but this can be written as in javascript as:
<script type="text/javascrip">
var start_script = function(){
// function to be performend
}
</script>
<body onload="start_script();">
......
</body>
because all the event are triggered when all the html is loaded and compiled.