Differentiate webRequest.onBeforeRequest original page vs. injected - javascript

I'm using webRequest.onBeforeRequest in the background script. Then, in the content(?) script, that is injected to the original page I make an AJAX request to the same URL to which the page makes the request I'm detecting, so naturally it starts to loop which hangs the browser pretty soon!
As a temporary measure, I put another parameter at the end of the URL in my AJAX call (&me=1) and made a urls filter that ends like the original URL ends, but that doesn't seem the best way, since the original URL might change in the future.
What would be better way of doing it? For example, I looked at the requestDetails that my listener returned. But, since the script is injected in the original page, I can't find any difference. Or, maybe I could make a urls filter which would only accept URLs that don't end with "&me=1"...?
(I can't (won't) use a flag variable, because the page changes dynamically and again, it doesn't seem the best way, even if I could make it work somehow (I haven't).)
Of course, alternatively, I could just use the data that the original request provides, but I could not find the event/object that would do it, but maybe I've missed it somehow, since I'm pretty much a beginner in making web extensions.
Edit:
manifest.json
{
"manifest_version": 2,
"name": "testtube",
"version": "1.0",
"permissions": [
"webRequest",
"https://*/*",
"activeTab"
],
"web_accessible_resources": [
"https://www.youtube.com/player_api",
"index.html",
"js/jquery-3.2.1.min.js",
"js/player.js",
"js/jquery-ui.min.js",
"css/jquery-ui.css", "css/style.css"
],
"content_scripts": [
{
"matches": [ "https://www.youtube.com/watch*"],
"css": [ "css/jquery.dataTables.min.css", "css/jquery-ui.css", "css/style.css" ],
"js": [ "js/jquery-3.2.1.min.js","js/jquery-ui.min.js", "js/main.js" ]
}
]
,"background": {
"scripts": ["js/background.js" ]
}
}
background.js
"use strict";
var lastRequestId = 0;
function logURL(requestDetails) {
console.log("requestDetails: ", requestDetails);
//this is actually a solution as per my answer below
if ((lastRequestId != requestDetails.requestId) && (requestDetails.url.indexOf("&me=1") == -1))
{
browser.tabs.sendMessage(requestDetails.tabId, { ccurl: requestDetails.url }).then(response => {
console.log("Message from the content script:");
console.log(response.response);
}).catch(onError);;
}
}
browser.webRequest.onBeforeRequest.removeListener(logURL);
browser.webRequest.onBeforeRequest.addListener(
logURL,
{ urls: ["*://www.youtube.com/api/somepattern*"] }
);
function onError(error) {
console.error(`Error: ${error}`);
}
main.js
"use strict";
var jq;
jq = document.createElement('script');
jq.onload = function () { };
jq.src = chrome.extension.getURL("/js/jquery-3.2.1.min.js");
document.querySelector('head').appendChild(jq);
jq = document.createElement('script');
jq.onload = function () { };
jq.src = chrome.extension.getURL("/js/jquery-ui.min.js");
document.querySelector('head').appendChild(jq);
/*... some other scripts... */
var s = document.createElement('script');
s.src = chrome.extension.getURL('/js/player.js');
s.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
var _url = "";
function UURL(request, sender, sendResponse) {
_url = request.url;
var loadR = " loadRequest('" + _url.toString() + "'); ";
var script = document.createElement('script');
script.textContent = loadR;
document.querySelector('head').appendChild(script);
return Promise.resolve({ response: "Hi from content script" });
}
$(function () {
console.log("jquery loaded");
browser.runtime.onMessage.removeListener(UURL)
browser.runtime.onMessage.addListener(UURL);
/* irrelevant code here... */
});
player.js
var loadRequest = function loadRequest(_url) {
_url = _url + "&me=1";
$.ajax({
type: "get",
url: _url,
dataType: "xml",
success: function (data) {
/* irrelevant code here */

If you are attempting to differentiate between the main page load and your XMLHttpRequest1
There are multiple ways that you could differentiate between different types of requests. Without your actual code to try it out, we have to guess as to what you are actually doing.
The webRequest.onBeforeRequest for a normal page load will look like:
webRequest.onBeforeRequest -> arg[0]= {"frameId":0,"method":"GET","parentFrameId":-1,"requestId":"260870","tabId":411,"timeStamp":1500401223979.044,"type":"main_frame","url":"http://www.example.com/"}
-----------------------------------------------------------------------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^
As you can see, the details Object contains a type property, which is a webRequest.ResourceType. For the load of HTML for the main frame it will contain "main_frame" (the details Object will also have "frameId":0,"parentFrameId":-1).
For your AJAX request, the value of the type property should be xmlhttprequest. However, it's possible that by "ajax request" you meant something other than an XMLHttpRequest. In which case, the type property might have some other value, but it should not be "main_frame".
1. I read the original version of the question differently than okkko intended. As currently written, this does not cover the case which they are interested in. However, it might have some value to someone else reading this question/answer, so I'm leaving it up.

Ok, the improved version of "temporary measure" is that in the listener I add another condition against requested url
(requestDetails.url.indexOf("&me=1") == -1)
And then make my ajax call... and in the urls filter for the request I just leave asterix (*) at the end. In this way, it doesn't matter if the original url changes at the end, however technically additional request is caught.. good enough if nobody else replies.

Related

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.

Chrome extension: XHR request to website, get html content by class name

I am creating a chrome extension that will go to a specified website, and get parts of the site's HTML from the source code.
I want to get the html content contained within a div with class name 'span1 rating-num-span'.
I tried using .getElementsByClassName but it returned undefined, however when I use .getElementsByTagName on ('h2') it worked.
Here is the javascript function to make the request from my main.js
function getFlowSite(){
var request = new XMLHttpRequest();
request.onreadystatechange = function(){
if (request.readyState == 4){
if (request.status == 200){
var temp = document.createElement('div');
temp.innerHTML = request.responseText;
alert(temp.getElementsByTagName('h2')[0].innerText);
alert(temp.getElementsByClassName('span1 rating-num-span')[0].innerText);
}
else{
console.log("Messed up!!!");
}
}
};
request.open("GET", "http://uwflow.com/course/" + courseName, true);
request.send(null);
}
stumped...
Thanks for reading!
--------------------Update------------------------
Turns out the class isn't present initially on the page, and is loaded in dynamically with a script. How can I get the source code of the page after everything is loaded in?
That element is added by the page script dynamically.
It's not present initially on the page. You can check this by examining the first server response from the site when loading it with devtools Network panel open. Or, if you use the great uBlock (origin) extension, simply disable all javascript on the site temporarily and reload the page.
You have two options:
find out how that webpage's code fetches the data from the server/elsewhere and do it yourself, there's usually some kind of JSON API. For example in this case there's a huge config object right in the page:
<script>
window.pageData.courseObj = {"ratings": [{"count": 375, "rating": .............
Simply use XMLHttpRequest with .responseType = "document" mode and get that element, then use JSON.parse on it.
Or, actually, in this case a simple regexp + JSON.parse will do:
var match = request.responseText
.match(/window\.pageData\.courseObj\s*=\s*(\{.+?\});\s*[\r\n]/);
var config = JSON.parse(match[1]);
config.ratings.forEach(function(r) { console.log(r) });
Object {count: 375, rating: 0.6986666666666667, name: "usefulness"}
Object {count: 494, rating: 0.7449392712550608, name: "easiness"}
Object {count: 555, rating: 0.5621621621621622, name: "interest"}
The above code wasn't tested live and doesn't contain any error checks which must be implemented in the real code.
load the page as a normal browser tab without activating it, inject a content script, wait for the element to appear, extract the data, close the tab.
manifest.json:
"permissions": ["http://uwflow.com/*"] - permissions for executeScript on non-active tab
popup.js:
var globalTabId = 0;
function openTab(url) {
chrome.tabs.create({url: url, active: false}, function(tab) {
globalTabId = tab.id;
chrome.tabs.executeScript(tab.id, {file: "getData.js", runAt: "document_end"});
});
}
chrome.runtime.onMessage.addListener(function(msg, sender, response) {
if (msg.action == "data" && sender.tab && sender.tab.id == globalTabId) {
chrome.tabs.remove(globalTabId);
processData(msg.data);
}
});
getData.js, this is a content script but it doesn't need to be declared in manifest.json.
var interval = setInterval(function() {
var ratings = document.querySelector(".span1.rating-num-span");
if (!ratings) {
return;
}
clearInterval(interval);
chrome.runtime.sendMessage({action: "data", data: {ratings: ratings.textContent}});
}, 100);

onBeforeRequest not run every time something is clicked

Update 1:
Now thanks to Xan I no longer get to the start page, adding ["main_frame", "sub_frame", "script", "xmlhttprequest"].
What is still annoying is, if you reached your profile page and now click on start page (or the f-logo) nothing happens. It seams because this is an ajax call and my extension rewrites the url, no "new" page is loaded and we are stuck.
What can I do to get redirected back to facebook.com/messages even if its an ajax call or something else?
My background.js looks now like this:
var patternURL = new RegExp("http(s)?://www\.facebook\.com(/|/\\?ref=[^\/]*)?$");
var targetURL = "https://www.facebook.com/messages";
var patternShowIcon = new RegExp("http(s)?://www\.facebook\.com");
chrome.webRequest.onBeforeRequest.addListener(function(details) {
if (patternURL.test(details.url)) {
return {
redirectUrl : targetURL
};
}
}, {
urls : ["*://*.facebook.com/*"],
types : ["main_frame", "sub_frame", "script", "xmlhttprequest"]
}, ["blocking"]);
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.get(tabId, function(tab) {
if (patternShowIcon.test(tab.url)) {
chrome.pageAction.show(tabId);
} else {
chrome.pageAction.hide(tabId);
}
});
});
The whole source code as a link to the extension is available on github.

Load Javascript into ajax loaded content

I am new to working with AJAX and have some experience with Java/Jquery. I have been looking around for an solution to my problem but i cant seem to find any.
I am trying to build a function in a webshop where the product will appear in a popup window instead of loading a new page.
I got it working by using this code:
$(".product-slot a").live('click', function() {
var myUrl = $(this).attr("href") + " #product-content";
$("#product-overlay-inner").load(myUrl, function() {
});
$("#product-overlay").fadeIn();
return false;
});
product-slot a = Link to the product in the category page.
product-content = the div i want to insert in the popup from the product page.
product-overlay-inner = The popup window.
product-overlay = The popup wrapper.
The problem that i now have is that my Javascript/Jquery isnt working in the productpopup. For example the lightbox for the product image or the button to add product to shoppingcart doesnt work. Is there anyway to make the javascript work inside the loaded content or to load javascript into the popup?
I hope you can understand what my problem is!
Thank you in advance!
EDIT: The platform im using has jquery-ui-1.7.2
I know this is an old thread but I've been working on a similar process with the same script loading problem and thought I'd share my version as another option.
I have a basic route handler for when a user clicks an anchor/button etc that I use to swap out the main content area of the site, in this example it's the ".page" class.
I then use a function to make an ajax call to get the html content as a partial, at the moment they are php files and they do some preliminary rendering server side to build the html but this isn't necessary.
The callback handles placing the new html and as I know what script I need I just append it to the bottom in a script tag created on the fly. If I have an error at the server I pass this back as content which may be just a key word that I can use to trigger a custom js method to print something more meaningful to the page.
here's a basic implementation based on the register route handler:
var register = function(){
$(".page").html("");
// use the getText ajax function to get the page content:
getText('partials/register.php', function(content) {
$(".page").html(content);
var script = document.createElement('script');
script.src = "js/register.js";
$(".page").append(script);
});
};
/******************************************
* Ajax helpers
******************************************/
// Issue a Http GET request for the contents of the specified Url.
// when the response arrives successfully, verify it's plain text
// and if so, pass it to the specified callback function
function getText(url, callback) {
var request = new XMLHttpRequest();
request.open("GET", url);
request.onreadystatechange = function() {
// if the request is complete and was successful -
if (request.readyState === 4 && request.status === 200) {
// check the content type:
var type = request.getResponseHeader("Content-Type");
if (type.match(/^text/)) {
callback(request.responseText);
}
}
};
// send it:
request.send(null); // nothing to send on GET requests.
}
I find this a good way to 'module-ize' my code into partial views and separated JavaScript files that can be swapped in/out of the page easily.
I will be working on a way to make this more dynamic and even cache these 'modules' for repeated use in an SPA scenario.
I'm relatively new to web dev so if you can see any problems with this or a safer/better way to do it I'm all ears :)
Yes you can load Javascript from a dynamic page, but not with load() as load strips any Javascript and inserts the raw HTML.
Solution: pull down raw page with a get and reattach any Javascript blocks.
Apologies that this is in Typescript, but you should get the idea (if anything, strongly-typed TypeScript is easier to read than plain Javascript):
_loadIntoPanel(panel: JQuery, url: string, callback?: { (): void; })
{
// Regular expression to match <script>...</script> block
var re = /<script\b[^>]*>([\s\S]*?)<\/script>/gm;
var scripts: string = "";
var match;
// Do an async AJAX get
$.ajax({
url: url,
type: "get",
success: function (data: string, status: string, xhr)
{
while (match = re.exec(data))
{
if (match[1] != "")
{
// TODO: Any extra work here to eliminate existing scripts from being inserted
scripts += match[0];
}
}
// Replace the contents of the panel
//panel.html(data);
// If you only want part of the loaded view (assuming it is not a partial view)
// using something like
panel.html($(data).find('#product-content'));
// Add the scripts - will evaluate immediately - beware of any onload code
panel.append(scripts);
if (callback) { callback(); }
},
error: function (xhr, status, error)
{
alert(error);
}
});
}
Plain JQuery/Javascript version with hooks:
It will go something like:
var _loadFormIntoPanel = function (panel, url, callback) {
var that = this;
var re = /<script\b[^>]*>([\s\S]*?)<\/script>/gm;
var scripts = "";
var match;
$.ajax({
url: url,
type: "get",
success: function (data, status, xhr) {
while(match = re.exec(data)) {
if(match[1] != "") {
// TODO: Any extra work here to eliminate existing scripts from being inserted
scripts += match[0];
}
}
panel.html(data);
panel.append(scripts);
if(callback) {
callback();
}
},
error: function (xhr, status, error) {
alert(error);
}
});
};
$(".product-slot a").live('click', function() {
var myUrl = $(this).attr("href") + " #product-content";
_loadFormIntoPanel($("#product-overlay-inner"), myUrl, function() {
// Now do extra stuff to loaded panel here
});
$("#product-overlay").fadeIn();
return false;
});

Verify External Script Is Loaded

I'm creating a jquery plugin and I want to verify an external script is loaded. This is for an internal web app and I can keep the script name/location consistent(mysscript.js). This is also an ajaxy plugin that can be called on many times on the page.
If I can verify the script is not loaded I'll load it using:
jQuery.getScript()
How can I verify the script is loaded because I don't want the same script loaded on the page more than once? Is this something that I shouldn't need to worry about due to caching of the script?
Update:
I may not have control over who uses this plugin in our organization and may not be able to enforce that the script is not already on the page with or without a specific ID, but the script name will always be in the same place with the same name. I'm hoping I can use the name of the script to verify it's actually loaded.
If the script creates any variables or functions in the global space you can check for their existance:
External JS (in global scope) --
var myCustomFlag = true;
And to check if this has run:
if (typeof window.myCustomFlag == 'undefined') {
//the flag was not found, so the code has not run
$.getScript('<external JS>');
}
Update
You can check for the existence of the <script> tag in question by selecting all of the <script> elements and checking their src attributes:
//get the number of `<script>` elements that have the correct `src` attribute
var len = $('script').filter(function () {
return ($(this).attr('src') == '<external JS>');
}).length;
//if there are no scripts that match, the load it
if (len === 0) {
$.getScript('<external JS>');
}
Or you can just bake this .filter() functionality right into the selector:
var len = $('script[src="<external JS>"]').length;
Few too many answers on this one, but I feel it's worth adding this solution. It combines a few different answers.
Key points for me were
add an #id tag, so it's easy to find, and not duplicate
Use .onload() to wait until the script has finished loading before using it
mounted() {
// First check if the script already exists on the dom
// by searching for an id
let id = 'googleMaps'
if(document.getElementById(id) === null) {
let script = document.createElement('script')
script.setAttribute('src', 'https://maps.googleapis.com/maps/api/js?key=' + apiKey)
script.setAttribute('id', id)
document.body.appendChild(script)
// now wait for it to load...
script.onload = () => {
// script has loaded, you can now use it safely
alert('thank me later')
// ... do something with the newly loaded script
}
}
}
#jasper's answer is totally correct but with modern browsers, a standard Javascript solution could be:
function isScriptLoaded(src)
{
return Boolean(document.querySelector('script[src="' + src + '"]'));
}
UPDATE July 2021:
The accepted solutions above have changed & improved much over time. The scope of my previous answer above was only to detect if the script was inserted in the document to load (and not whether the script has actually finished loading).
To detect if the script has already loaded, I use the following method (in general):
Create a common library function to dynamically load all scripts.
Before loading, it uses the isScriptLoaded(src) function above to check whether the script has already been added (say, by another module).
I use something like the following loadScript() function to load the script that uses callback functions to inform the calling modules if the script finished loading successfully.
I also use additional logic to retry when script loading fails (in case of temporary network issues).
Retry is done by removing the <script> tag from the body and adding it again.
If it still fails to load after configured number of retries, the <script> tag is removed from the body.
I have removed that logic from the following code for simplicity. It should be easy to add.
/**
* Mark/store the script as fully loaded in a global variable.
* #param src URL of the script
*/
function markScriptFullyLoaded(src) {
window.scriptLoadMap[src] = true;
}
/**
* Returns true if the script has been added to the page
* #param src URL of the script
*/
function isScriptAdded(src) {
return Boolean(document.querySelector('script[src="' + src + '"]'));
}
/**
* Returns true if the script has been fully loaded
* #param src URL of the script
*/
function isScriptFullyLoaded(src) {
return src in window.scriptLoadMap && window.scriptLoadMap[src];
}
/**
* Load a script.
* #param src URL of the script
* #param onLoadCallback Callback function when the script is fully loaded
* #param onLoadErrorCallback Callback function when the script fails to load
* #param retryCount How many times retry laoding the script? (Not implimented here. Logic goes into js.onerror function)
*/
function loadScript(src, onLoadCallback, onLoadErrorCallback, retryCount) {
if (!src) return;
// Check if the script is already loaded
if ( isScriptAdded(src) )
{
// If script already loaded successfully, trigger the callback function
if (isScriptFullyLoaded(src)) onLoadCallback();
console.warn("Script already loaded. Skipping: ", src);
return;
}
// Loading the script...
const js = document.createElement('script');
js.setAttribute("async", "");
js.src = src;
js.onload = () => {
markScriptFullyLoaded(src)
// Optional callback on script load
if (onLoadCallback) onLoadCallback();
};
js.onerror = () => {
// Remove the script node (to be able to try again later)
const js2 = document.querySelector('script[src="' + src +'"]');
js2.parentNode.removeChild(js2);
// Optional callback on script load failure
if (onLoadErrorCallback) onLoadErrorCallback();
};
document.head.appendChild(js);
}
This was very simple now that I realize how to do it, thanks to all the answers for leading me to the solution. I had to abandon $.getScript() in order to specify the source of the script...sometimes doing things manually is best.
Solution
//great suggestion #Jasper
var len = $('script[src*="Javascript/MyScript.js"]').length;
if (len === 0) {
alert('script not loaded');
loadScript('Javascript/MyScript.js');
if ($('script[src*="Javascript/MyScript.js"]').length === 0) {
alert('still not loaded');
}
else {
alert('loaded now');
}
}
else {
alert('script loaded');
}
function loadScript(scriptLocationAndName) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptLocationAndName;
head.appendChild(script);
}
Create the script tag with a specific ID and then check if that ID exists?
Alternatively, loop through script tags checking for the script 'src' and make sure those are not already loaded with the same value as the one you want to avoid ?
Edit: following feedback that a code example would be useful:
(function(){
var desiredSource = 'https://sitename.com/js/script.js';
var scripts = document.getElementsByTagName('script');
var alreadyLoaded = false;
if(scripts.length){
for(var scriptIndex in scripts) {
if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
alreadyLoaded = true;
}
}
}
if(!alreadyLoaded){
// Run your code in this block?
}
})();
As mentioned in the comments (https://stackoverflow.com/users/1358777/alwin-kesler), this may be an alternative (not benchmarked):
(function(){
var desiredSource = 'https://sitename.com/js/script.js';
var scripts = document.getElementsByTagName('script');
var alreadyLoaded = false;
for(var scriptIndex in document.scripts) {
if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
alreadyLoaded = true;
}
}
if(!alreadyLoaded){
// Run your code in this block?
}
})();
Simply check if the global variable is available, if not check again. In order to prevent the maximum callstack being exceeded set a 100ms timeout on the check:
function check_script_loaded(glob_var) {
if(typeof(glob_var) !== 'undefined') {
// do your thing
} else {
setTimeout(function() {
check_script_loaded(glob_var)
}, 100)
}
}
Another way to check an external script is loaded or not, you can use data function of jquery and store a validation flag. Example as :
if(!$("body").data("google-map"))
{
console.log("no js");
$.getScript("https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=initilize",function(){
$("body").data("google-map",true);
},function(){
alert("error while loading script");
});
}
}
else
{
console.log("js already loaded");
}
I think it's better to use window.addEventListener('error') to capture the script load error and try to load it again.
It's useful when we load scripts from a CDN server. If we can't load script from the CDN, we can load it from our server.
window.addEventListener('error', function(e) {
if (e.target.nodeName === 'SCRIPT') {
var scriptTag = document.createElement('script');
scriptTag.src = e.target.src.replace('https://static.cdn.com/', '/our-server/static/');
document.head.appendChild(scriptTag);
}
}, true);
Merging several answers from above into an easy to use function
function GetScriptIfNotLoaded(scriptLocationAndName)
{
var len = $('script[src*="' + scriptLocationAndName +'"]').length;
//script already loaded!
if (len > 0)
return;
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptLocationAndName;
head.appendChild(script);
}
My idead is to listen the error log if there is an error on script loading.
const checkSegmentBlocked = (e) => {
if (e.target.nodeName === 'SCRIPT' && e.target.src.includes('analytics.min.js')) {
window.isSegmentBlocked = true;
e.target.removeEventListener(e.type, checkSegmentBlocked);
}
};
window.addEventListener('error', checkSegmentBlocked, true);
Some answers on this page are wrong. They check for the existence of the <script> tag - but that is not enough. That tells you that the tag was inserted into the DOM, not that the script is finished loading.
I assume from the question that there are two parts: the code that inserts the script, and the code that checks whether the script has loaded.
The code that dynamically inserts the script:
let tag = document.createElement('script');
tag.type = 'text/javascript';
tag.id = 'foo';
tag.src = 'https://cdn.example.com/foo.min.js';
tag.onload = () => tag.setAttribute('data-loaded', true); // magic sauce
document.body.appendChild(tag);
Some other code, that checks whether the script has loaded:
let script = document.getElementById('foo');
let isLoaded = script && script.getAttribute('data-loaded') === 'true';
console.log(isLoaded); // true
If the both of those things (inserting and checking) are in the same code block, then you could simplify the above:
tag.onload = () => console.log('loaded');
I found a quick tip before you start diving into code that might save a bit of time. Check devtools on the webpage and click on the network tab. The js scripts are shown if they are loaded as a 200 response from the server.

Categories