I have some javascript that is not required for my initial page load. I need to load it based on some condition that will be evaluated client-side.
$(document).ready(function() {
let someCondition = true; // someCondition is dynamic
if (someCondition) {
var element = document.createElement('script');
element.src = 'https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js';
element.defer = true; // does this make a difference?
element.onload = function() {
// do some library dependent stuff here
document.getElementById("loading").textContent = "Loaded";
};
document.body.appendChild(element);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="loading">Loading...</h1>
Does it make a difference (in terms of how browser will treat the script tag), if a new tag created using javascript, after document is ready, has 'defer' attribute or not? I think there is no difference, but how can I say for sure?
I believe I understand how deferred scripts behave when script tag is part of the initial html (as described here). Also, this question is not about whether element.defer=true can be used or not (subject of this question).
No that doesn't make any difference, the defer attribute is ignored in case of "non-parser-inserted" scripts:
<script defer src="data:text/javascript,console.log('inline defer')"></script>
<script>
const script = document.createElement("script");
script.src = "data:text/javascript,console.log('dynamic defer')";
script.defer = true;
document.body.append(script);
</script>
<!-- force delaying of parsing -->
<script src="https://deelay.me/5000/https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Look at your browser's console or pay attention to the logs timestamps to see that the dynamically inserted script actually did execute while we were waiting for the delayed script to be fetched.
There's a difference between adding them to the function and adding directly the CDN ( especially in your case ).
Let's look at the code execution of the above-mentioned code first,
You have added the jquery CDN first ( without defer ) so that loads first.
$(document).ready will be fired once after the complete load of jquery.
There'll be the creation and insertion of a new script tag to the dom.
Download the https://raw.githubusercontent.com/Useless-Garbage-Institute/useless-garbage/master/index.js asynchronously.
Let's look at another approach: adding CDN to the code:
Your DOM will have 2 script tags.
Both will start loading based on the type of load parallelly ( defer async etc ).
Notice you are not waiting for the dom ready event to load the second script.
I suggest adding only the main JS part in a js file and adding it to the CDN. Others can wait load with the delay.
In case you are really needed with a js src, then don't load it the first way since it waits for the complete page load.
I suggest you read and look at web-vitals and SEO for this.
and for your other question, yes you can add defer attribute with element.defer=true to the elements while creating and loading to DOM.
Hope this answer helps you!
Feel free to comment if you get any errors or doubts.
I think the JQuery Arrive lib will solve your case.
I have a <script> that generates a both <style> and inline style attributes with !important tags. I'd like to remove all this styling.
My plan was to use a javascript onload callback (and some jQuery) to remove the <style> block and all inline style attributes — but I can't seem to select any of these elements. Here's what I've been toying with:
var script = document.createElement("script");
script.src = "//script.path.js";
script.onload = function(){
$(this).parent().find("style").remove();
$(this).parent().find("[style]").removeAttr("style");
};
$(target).append(script);
UPDATE
It seems that the elements generated by the <script> just aren't available in the DOM right away. If I use setInterval to check if the elements exist first, I can get this to work. I imagine there's a better way to do this though...
According to this other question, you must append the script tag to the DOM before setting onload.
var script = document.createElement("script");
$(target).append(script);
script.src = "//script.path.js";
script.onload = function(){
$(this).parent().find("style").remove();
$(this).parent().find("[style]").removeAttr("style");
};
https://jsbin.com/minoyeyicu/edit?html,js,output
UPDATE: Having clarified that the issue is that the style tag/attributes haven't yet been applied to the DOM until after the downloaded script has executed, one alternative (depending on whether the loaded script is under your control), is to pass a callback parameter to the loaded script and have the loaded script execute the callback when it finishes executing (which is how the Google Maps API works). E.g.
script.src = '//script.path.js?callback=removeStyles'
In order to use the callback parameter from within script.path.js, something like this could be done.
I'm trying to load a script from an external source after the user executes a click function. Like so:
$("#test-button").click(function() {
$.getScript("http://someurl.com/widget/javascript?key=4&t=uid&q=94777&show=all", function (data) {
$('#myDiv').append(data);
});
});
The script I'm loading (which I have no control over) includes a document.write, so I'm getting this error when I execute the click:
It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
I'm not sure how to get around this. I thought getScript would manage this.
It seems like you have append the script like text
please see this example
var script = document.createElement('script');
script.src = '../web3.0/lib/charts/google-charts.js';
document.body.appendChild(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'm trying to dynamically load a Javascript based on the return value of an API call. I dynamically insert the script tag but it does not get executed. Can someone help understand why? The relevant code snippet is pasted below
onError: function(code) {
if(code == "false") {
var headID = document.getElementsByTagName("head")[0];
var scriptTag = document.createElement("script");
scriptTag.type="text/javascript";
scriptTag.src= 'scriptURL';
headID.appendChild(scriptTag);
}
}
Using firebug/chrome inspector, I can see that the script tag is added to the dom but the script is not executed (at least not that I can determine). It is a 3rd Party script hence I do not have direct control over it and hence cannot modify it either.
After reading the comments below the question it seems that the third party script is doing its job on window.onload event. Many programmers use this style.
window.onload = function() {
// Whatever task
};
If the onload event of your page has already been fired before you add the script tag dynamically, the 'Whatever task' code would never execute.
Check the source of the third party script. If it uses window.onload, you can try calling window.onload(); after you add the script tag dynamically.