I'm trying to load a script so I can use scripts on the page that is spawned by the bookmarklet. (view src: XHR followed by beautify.js followed by prettify.js)
I know what I am basically supposed to do (like this) but what's happening is I can't find a good way to detect when the functions I need are actually loaded.
var doWhenLoaded = function (name) {
if (typeof(eval(name)) === 'function') {
eval(name+'()');
} else {
setTimeout(
function () {
console.log("from timeout: "+new Date().getTime());
doWhenLoaded(name,call);
} , 50
);
}
}
I tried that but eval(name+'()'); throws an error.
I can't answer your question, but to test if a function is available use:
var doWhenLoaded = function (name) {
if (typeof window[name] == 'function') {
window[name]();
} else {
// set the timeout. Should have a limit, else it wil go on forever.
}
...
};
Edit
Updated to use window[name], but really should use a reference to the global object. But I guess it's ok to use window for a browser specific script.
The code above should not throw any errors. Since name is in the formal parameters, it's essentially a declared local variable. If name is undefined, then typeof name will return the string "undefined", which fails the test so name() is not evaluated.
I think I can force the scripts to get loaded synchronously before I end up calling them by simply writing the document rather than setting them into the dom.
Related
I want to know whether a piece of JavaScript is executed in an ES module or a simple script.
This is what I tried so far:
function isEsm1() {
try {
// Script gives a syntax error during parsing when script is not an esm
return Boolean(import.meta.url);
} catch(err) {
return false;
}
}
function isEsm2() {
// will always return false, because `eval` always seems to be executed in regular script context
try {
return eval('Boolean(import.meta.url)');
} catch(err) {
return false;
}
}
function isEsm3() {
// Of course doesn't work, but had to try 😉
return 'meta' in import;
}
Is the regular script executed in the browser or in another context?
In a browser, how about:
var anyvar = {};
var inModule = anyvar === window.anyvar;
If you're in a module, you are not declaring anything on the window...
In NodeJS you could do something similar with global or this:
let inModule = this === module.exports..
Did not try it yet.. but should work I guess...
After testing, just the check for this === undefined is enough to test if you're executing in or out of a module..
Inside a module, this is undefined (as per spec). In global scope this points to global this, which is the window object in the case of a browser context...
Thanks to the discussion in John Gorter's answer, I think we've found a way.
console.log('In module: ' + (this === undefined));
It's as simple as that. Inside a module (and only inside a module (I hope)), this will be undefined. I found it in the v8 documentation here: https://v8.dev/features/modules#intro
Would you please tell me if there is anything unsafe, in the JS of a standard web page, about invoking a function by it's name as a string using window['function_name']()? I remember reading something about it quite some time ago but cannot locate it now.
I thought there was a reason to not use the window object to do so, because to invoke functions between a background script and content script in a web extension, I always declare a different object in each and declare the functions that can be invoked in this manner as properties of the object and pass the function names as strings in the communication port to invoke them; but I cannot remember why.
Thank you.
It depends on which context your running the code,
1. JS Execution Context
Its fine to use string as function name and call the corresponding function residing in an object.
const functionName = "someFunction";
window[functionName]()
But If string is part of a untrusted data or user controllable string then it not safe to use. i.e Reading a string from a url parameter.
Example:
const unTrustedUserInput = window.location.hash
window[unTrustedUserInput]();
2. web Extension BG & CS Context
As per chrome recommendation, you should not trust the message received from content-script. You should always sanitise the input and place necessary validation before executing it.
So, I would recommend not to pass function name as string, instead use a dictionary map with corresponding guid to validate to which function the call is made.
Example:
// cs context
chrome.extension.sendMessage({ id: "<GUID-1>", data: "data" }, (response) => {
// handle response if any
});
// BG context
var CSBGCommunicationMap = {
"<GUID>-1": window.someFunction
}
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
if (sender.id !== "<varlidate id-ur-Extension>" && sender. origin !== "<validate origin>") {
// Early return if it comes from malicious content or unknown sender
return;
}
if (message.id && message.id in CSBGCommunicationMap) {
const functionToBeCalled = CSBGCommunicationMap[message.id];
// functionToBeCalled(message, sendResponse);
}
});
I hope this clarifies your concern.
According to MDN, The Window object is a global object which contains the entire DOM document. So if you call a function foo() (without specifying any object), Javascript will search it in window object.
In other hand,
foo() , window.foo() and window['foo']() are same. But when talk about security, Let's say if user injects some malicious code into the function foo,
doesn't matter you invoke the function foo() or window['foo'](), The injection will effect both.
Avoid using Window object
You don't need to specify the window object to call a global scoped functions or variable unless, it shadowed by your current scope.
function x() {
console.log('hey i am global');
}
function y() {
function x() {
console.log('I have power only inside y()');
}
x(); // I have power only inside y()
window.x() // hey i am global
}
And If you don't handle window object properly, There are lot of chances to get run-time errors and the entire object will be collapsed.
Note that doesNotExist intentionally does not exist, the alert is never shown, and instead we're left with an error in the browser's console. What's special about running this code in a jQuery callback that makes it fail so hard like this?
HTML:
<div>Shown! But we failed before the alert. Check your console...</div>
CSS:
div { display: none; }
JS:
$('div').fadeIn(100, function() {
if (doesNotExist) {
alert('Nope');
}
else {
alert('Still nope');
}
});
https://jsfiddle.net/mrpowell3j/gnhoheze/2/
There's nothing special about running that code in a callback. It'll cause that exception no matter how it runs. Using an undeclared variable name in an expression is always an error (except with typeof).
To check whether a variable is defined, you can use
if (typeof doesNotExist === "undefined") {
// it is undefined
}
Now that just tells you that it's either not defined at all, or that it has no value; either might be appropriate depending on the application. If it's supposed to be a global, then:
if (!("doesNotExist" in window)) {
// not defined
}
That checks for a property in the window object, which is where globals go (well, global var variables).
The problem is that your are executing invalid javascript code. The callback do not matter in this case.
You must initialize your variable before using it or check if it exist using a different approach:
var variableName;
// execute your code.
-
//or checking if variable is defined.
if(typeof variableName !== 'undefined') {
alert('nope');
} else {
alert('still nope');
}
My web app includes an ActiveX control. However, when I run the app, I got error "object expected" error intermittently. It seems sometimes the control is not ready when I call its properties/methods. Is there a way that I can detect whether an object is ready using JS?
Thanks a lot.
If its not you own app, see if you can identify some harmless property or method and then design a wrapper method around the call that tests with try catch if it can access the object, and if yes, call next method in chain (maybe using a delegate to include arguments, and if not ready, use setTimeout to call the wrapper again in say 100 ms.
You might want to include a retry counter to bailout after a few tries so that it's not an infinite loop if the object is broken.
Example:
function TryCallObject(delegate, maxtries, timebetweencalls, failCallback, retrycount)
{
if(typeof retrycount == "undefined")
retrycount = 0;
if(typeof failCallback == "undefined")
failCallback null;
try {
//code to do something harmless to detect if objects is ready
delegate(); //If we get here, the object is alive
} catch(ex) {
if(retrycount >= maxtries)
{
if(failCallback != null)
failCallback();
return;
}
setTimeout(function () {
TryCallObject(delegate, maxtries, timebetweencalls, failCallback, retryCount + 1);
}, timebetweencalls);
}
}
And its called like this
TryCallObject(function() { /* your code here */ }, 5, 100);
or
TryCallObject(function() { /* your code here */ }, 5, 100, function() {alert("Failed to access ActiveX");});
If it is your own app, include a readystate event
http://msdn.microsoft.com/en-us/library/aa751970%28VS.85%29.aspx
The way we do this in FireBreath (http://firebreath.org) is to fire an event to javascript; it does this by providing a function name in a <param> tag, get a reference to the browser window IDispatch pointer, and do a PROPERTYGET for the function named in the param tag.
We then call that method when the plugin is ready to go. This has the advantage of working pretty much the same way in all browsers, since FireBreath plugins work both as ActiveX controls and NPAPI Plugins.
I have some Firebug console function calls that I wanted to disable when Firebug wasn't enabled, e.g. console isn't defined. This works fine in IE6 and FF3, but not in Chrome:
var log;
if(console){
log = console.log;
}else{
log = function(){ return; }
}
I get an "Uncaught TypeError: Illegal Invocation" in Chrome =/
I read about the issue here, where you have to apply a context, which is kind of new to me... and I can't seem to figure how to accomplish the above in all browsers...
Yes, you should persist the context :
var log;
if (window.console && typeof console.log === "function"){
// use apply to preserve context and invocations with multiple arguments
log = function () { console.log.apply(console, arguments); };
} else {
log = function(){ return; }
}
What is happening is that the context (the this value), is implicitly set when you call a function, for example:
var obj = {
method: function () { return this; }
};
obj.method() === obj; // true
In this case, you are calling a function that is defined as a property of an object, when the function is invoked, the this value is set to that object.
Now as in your example, if you copy a reference of that method to a variable:
var method = obj.method;
method() === window; // global object
As you can see, the this value refers to the global object.
So, to avoid this implicit behavior you can set the context explicitly, with the call or apply functions.
The problem with wrapping a function (like console.log) in a function is that it loses its context, i.e. it will not show the correct line number of the file that we've put our "log" shortcut in.
Instead I suggest something like this:
window.log = ((window.console && window.console.log) ?
console.log.bind(console) :
function(){});
This works with firebug & chrome dev tools and does not throw errors when no console is available. And - most importantly - shows the correct file & line number.
This doesn't work:
log("hi");
While this does:
log.call(console, "hi");
It is obvious that you need to call the aliased function with the correct context -- as you yourself have mentioned.
I think you'll have to use a function wrapper (a closure that has a reference to the original context) rather than an alias...
Update
Also note that if you check for console directly, you may get a run-time error when the variable doesn't exist. You're better off checking it explicitly as window.console. Here's one way to implement a conditional log wrapper:
var log = (function (console) {
return console
? function () { console.log.apply(console, arguments); }
: function () {}
})(window.console);
This solution modifies the earlier and excellent answer from CMS to work with IE8. You’ll need to open the IE8 console (press F12) before executing this. (If you forget, you’ll need to exit IE8 entirely and start again because even if the console exists, IE8 won’t subsequently create the console object.)
Note that we don’t set the context, which was the original problem but, as it turns out, IE8 doesn’t require that context. (Good thing, because IE8 also doesn’t provide the apply method on the console.log object!).
This code works with the latest versions of Chrome, FireFox, and MSIE. (It is compatible with MSIE6 and doesn’t throw an error.)
if((typeof console !== "undefined") && ((typeof console.log) !== "undefined"))
{
if ((typeof console.log.apply !== "undefined"))
{
log = function() { console.log.apply(console,arguments) };
}
else
{
log = console.log;
}
}
else
{
log = function() {};
// alert("No debug console");
}
I did this
var log;
log = function() {
if ((window.console != null) && (window.console.log.apply != null)) {
return console.log.apply(console, arguments);
} else {
return function() {};
}
};