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.
Using JavaScript, is there a way to detect whether or not an external script (from a third-party vendor) has completely loaded?
The script in question is used to pull in and embed the markup for a list of jobs and, unfortunately, doesn't make use of any variables or functions. It uses document.write to output all of the content that gets embedded in my page.
Ideally, I'd like to display some kind of loading message while I'm waiting for the external script to load, and if it fails to load, display a "We're sorry, check back later..." message.
I'm using jQuery on the site, but this external script is called before I make the jQuery call.
Here's what the document.write stuff from the external script looks like:
document.write('<div class="jt_job_list">');
document.write("
<div class=\"jt_job jt_row2\">
<div class=\"jt_job_position\">
Position Title
</div>
<div class=\"jt_job_location\">City, State</div>
<div class=\"jt_job_company\">Job Company Name</div>
</div>
");
Attach an function to the load event:
<script type="text/javascript" src="whatever.js" onload ="SomeFunction()" />
As far as your loading... problem goes, try displaying a div for loading and then just display:none-ing it in your onload function. Make sure to handle cases where your script fails to load too, though.
Script tags block downloads, so as long as the content dependent on your script is below where your script it loaded, you should be fine. This is true even if the script is in-line in the body of your page.
This website has a great example of how this works.
This obviously does not work if you're loading the scripts asynchronously.
Scripts without async or defer attributes are fetched and executed immediately, before the browser continues to parse the page.
Source: MDN
You could put a script block after it on the page:
<script src="external_script.js"></script>
<script type="text/javascript">
ExternalScriptHasLoaded();
</script>
Thanks for the assistance above, especially ngmiceli for the Steve Souders link!
I decided to take what's probably a "lazy" approach, and also forego the "loading" message:
$(document).ready(function(){
if ($('.jt_job_list').length === 0){
$('#job-board').html("<p>We're sorry, but the Job Board isn't currently available. Please try again in a few minutes.</p>");
};
});
Pretty simple, but I'm looking to see if an element with the .jt_job_list class is in the dom. If it isn't, I display an error message.
This worked for me: it does however, rely on the newer querySelector interface which most modern browsers support. But if you're using really old browsers, you can use getElement... and run a for loop.
function loadJS(file, callback, error, type) {
var _file = file ;
var loaded = document.querySelector('script[src="'+file+'"]') ;
if (loaded) {
loaded.onload = callback ;
loaded.onreadystatechange = callback;
return
}
var script = document.createElement("script");
script.type = (typeof type ==="string" ? type : "application/javascript") ;
script.src = file;
script.async = false ;
script.defer = false ;
script.onload = callback ;
if (error) {
script.onerror = error ;
}
else {
script.onerror = function(e) {
console.error("Script File '" + _file + "' not found :-(");
};
}
script.onreadystatechange = callback;
document.body.appendChild(script);
}
You could give what ever your looking for an ID
and check whether not the ID has been loaded using document.getElementById("ID");
Is that what your looking for not sure I fully understand?
ive installed typekit on my site, the usual two lines of js just after the opening head tag but its extremely slow / unresponsive to load in the fonts, this is completly remedied by refreshing the page, after that the typekit font load in perfectly and really quickly. But from a users point of view they will never know to do that, so they will be served the default fonts.
I have the typekit js as the very first thing under the opening head tag before the metatags and before loading in jquery, jquery-ui and other scripts.
Has any one else had trouble with this ?
what seemed to work for me was to load the script in an asynchronous pattern - as specified on the typekit blog, ive copied it in bellow
Standard asynchronous pattern
This first pattern is the most basic. It’s based on patterns written about by web performance experts like Steve Souders and used in other JavaScript embed codes like Google Analytics.
<script type="text/javascript">
TypekitConfig = {
kitId: 'abc1def'
};
(function() {
var tk = document.createElement('script');
tk.src = '//use.typekit.com/' + TypekitConfig.kitId + '.js';
tk.type = 'text/javascript';
tk.async = 'true';
tk.onload = tk.onreadystatechange = function() {
var rs = this.readyState;
if (rs && rs != 'complete' && rs != 'loaded') return;
try { Typekit.load(TypekitConfig); } catch (e) {}
};
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(tk, s);
})();
</script>
This pattern uses a single inline tag to dynamically add a new script element to the page, which loads the kit without blocking further rendering. An event listener is attached that calls Typekit.load() once the script has finished loading.
How to use it:
Place this snippet at the top of the so the download starts as soon as possible.
Edit the highlighted TypekitConfig object and replace the default with your own Kit ID.
You can add JavaScript font event callbacks to the TypekitConfig object.
Advantages:
Loads the kit asynchronously (doesn’t block further page rendering while it loads).
Disadvantages:
Adds more bytes to your html page than the standard Typekit embed code.
Causes an initial FOUT in all browsers that can’t be controlled or hidden with font events.
I use this code to load my JS async in the head
<script type='text/javascript'>
// Add a script element as a child of the body
function downloadJSAtOnload() {
var element4= document.createElement("script");
var element5= document.createElement("script");
element4.src="http:///ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
element5.src="http://yourjavascript.com/301810712121/slidemenu_horiz.js"
element4.async=true;
element5.async=true;
document.body.appendChild(element4);
document.body.appendChild(element5);
}
// Check for browser support of event handling capability
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
In IE and Firefox works fine, but in Chrome I have this error:
"Uncaught ReferenceError: jQuery is not defined "
When I refresh the page for second time (or third) the script works fine in Chrome, please I need to know how to resolve this.
Given your needs, and because I've used it successfully in the past I'd suggest using LABjs - http://labjs.com/
As mentioned, there are loads of script loaders to choose from - LABjs is focused on performance more than anything and doesn't include many of the extra features that others such as requirejs (AMD loader), YepNope (feature detection-based conditional loader) have. If all you need is to load your scripts asynchronously and have control over the execution order, LABjs is a very small script that handles this well.
Using LABjs, you'd do the following to replicate your code above:
<script src="js/libs/LAB.js"></script>
<script>
$LAB
.script('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js').wait()
.script('http://yourjavascript.com/301810712121/slidemenu_horiz.js')
.wait(function () {
// Check jQuery has loaded (could do this for the slider as well)
if (window.jQuery) {
// Do something with your slider
}
});
</script>
In the example above, the .wait() function ensures that jQuery has executed before slidemenu_horiz.js - the last .wait() is passed an anonymous function as a callback - within this you can test that everything has loaded and then do your initialisations.
It's worth checking out all the options as far a script loaders go. There really are loads out there and each has a different feature set that you may find addresses your problem better.
EDIT: Added script reference to LABjs in code example for clarity
I'm having problems with debugging DOM changes introduced by some JavaScript code I'm running. Somewhere in the code an element's class is changed, and I'm trying to pinpoint where exactly. Unfortunately, the new class name is so generic that searching through all the JS code gives too many results to be a viable option.
I've tried debugging a bit with Firebug, but despite the nice "Break on Attribute Change" feature, I can't get it to work in a way I would want. The Firebug demo works correctly, but it's a post load situation.
The problem seems to be that I want to watch for mutations before the page is fully loaded. I assume that the changes occur somewhere in $(document).ready(), so it's in the DOM, but I can't select elements for UI breakpoints as would be the case with the demo (after page load).
Is there some way to debug this kind of situation other than grepping/going through the code by hand?
I propose that you remove the target element where its classname is changed. With some luck, you may be able to generate an error in the JavaScript code, so you will find where is your problem.
Otherwise, if it's done by JQuery (addClass), you may want to modify the JQuery code itself just to figure out the callstack.
The code would look like this (make sure this code is the first code called after JQuery inclusion):
(function () {
var addClass = jQuery.fn.addClass;
jQuery.fn.addClass = function (value) {
for (var i = 0, l = this.length; i < l; i++) {
// Here you put your method to start the debugger if you get your right element
if (this[i].id === "abc") {
debugger;
}
}
addClass(value);
}
})();
Hope that helps.
This answer may sound pretty lame, but I honestly think the best solution for bugs like this is "deconstructing" your program. Just make a copy of the entire project, then rip it apart. Take out chunks of code one by one. Turn function calls into stub functions or whatever to keep things running. Find the minimal amount of code that triggers the bug. Then the solution should be obvious.
Have you considered adding a mutation event? The event I think you want, DOMAttrModified, is not supported in webkit, so you might have to test with Firefox or Opera. In fact it is deprecated in DOM level 3.
There are two jQuery plugins for mutation events here (documentation) and here but since you want to do this before page load they might not be the answer.
You are probably best writing your own JavaScript bind for this event - there is an example in the answer to is there an alternative to DOMAttrModified that will work in webkit
I hope this helps.
If you want to use the "Break on Attribute Change" feature to debug, you can do the following:
Comment out all JS in the page (which is hopefully all in the head) except for the base jQuery load.
Load the page and set your watch points.
Add a button, which fires the JS, to the HTML. Or, optionally fire it from the console.
Trigger the JS load/fire. Hopefully your watch and break points will fire as desired.
For example, suppose your page loads/has:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js" type="text/javascript"></script>
<script src="/Library_X.js" type="text/javascript"></script>
<script src="/MyJS.js" type="text/javascript"></script>
Then you could run this code in the console after setting the watch points:
function addJS_Node (text, s_URL)
{
var scriptNode = document.createElement ('script');
scriptNode.type = "text/javascript";
if (text) scriptNode.textContent = text;
if (s_URL) scriptNode.src = s_URL;
document.head.appendChild (scriptNode);
}
addJS_Node (null, '/Library_X.js');
//-- Possible pause here.
addJS_Node (null, '/MyJS.js');
// etc.
Or temporarily code a button that fires the same JS, into the page's HTML.