I need to make a fake window.location = "testCall" call in order to generate an event to bypass parameters on a mobile device. Works as native, however, I need then to dissmiss a NotFound exception or mainly dissmiss a fake window.location call. Possible? Thank you
Object.getOwnPropertyDescriptor(window, 'location').configurable === false
in Chrome and Safari (and I presume in other browsers). So seems like you can't change the native behavior.
If it behaved as a normal EcmaScript 5 property and configurable was set to true than you could have done something like that:
var descriptor = Object.getOwnPropertyDescriptor(window, 'location');
var setter = descriptor.set; // Doesn't exist although it should in spirit of ES5
descriptor.set = function (newLocation) {
try {
setter(newLocation);
} catch (e) {
console.log('Location error: ', newLocation, e);
}
};
// The line below will throw exception in real browser :(
// TypeError: Cannot redefine property: location
Object.defineProperty(window, 'location', descriptor);
I hope browser vendors migrate all their magical properties and objects to standard EcmaScript mechanics but at the moment we are out of luck.
Related
I am logging my errors on a web page that's running javascript, and I see that the following code:
JSON.stringify(
Object.getOwnPropertyDescriptors(Node.prototype)
);
fails with:
undefined is not a function
What could be the explanation for that? I am under the assumption this happens when the page was loaded not on a normal browser but by using WebView.
I opened an Android Emulator and verified this page on a normal browser and using a WebView Test app - it worked fine.
What else can I do to debug it? This error is raised quite often.
Edit
I added further logging and I checked the following:
Object.getOwnPropertyDescriptor(Node.prototype, "childNodes");
Object.getOwnPropertyDescriptor(Node.prototype, "parentNode");
Object.getOwnPropertyDescriptor(Node.prototype, "hasChildNodes");
And only for hasChildNodes a value is returned.
It would appear that on some of the browser/webview brands or versions your code runs on, either JSON.stringify or (more likely) Object.getOwnPropertyDescriptors is not defined (the former was added in ES5, the latter much more recently in ES2017).
What else can I do to debug it?
Add tests and log the user agent string:
if (!JSON.stringify) {
/* ...log that `JSON.stringify` isn't supported, w/`navigator.userAgent`...*/
} else if (!Object.getOwnPropertyDescriptors) {
/* ...log that `Object.getOwnPropertyDescriptors` isn't supported, w/`navigator.userAgent`...*/
} else {
/* ...log `JSON.stringify(Object.getOwnPropertyDescriptors(Node.prototype))` ... */
}
In a comment you've asked:
So do you know of a way in which I could get the getter function childNodes of the Node.prototype in whichever browser? In all my tests (mobile emulators and browsers) it is always an "own" property of the Node.prototype.
If you're seeing undefined for Object.getOwnPropertyDescriptor(Node.prototype, "childNodes") in some unusual implementation, you may have to loop using Object.getPrototypeOf to find where it is in the inheritance chain:
function getDescriptor(obj, name) {
let descr;
while (obj && !(descr = Object.getOwnPropertyDescriptor(obj, name))) {
obj = Object.getPrototypeOf(obj);
}
return descr;
}
Then you'd use const childNodesDescriptor = getDescriptor(Node.prototype, "childNodes"); or similar.
But beware that host-provided objects like Node.prototype are also allowed to break some (but not all) rules. You may have to allow for the possibility there are implementations where you just can't get that getter.
I need to log setting of document.cookie. I can not redefine cookie property just with document.cookie = {...} So I need to get setter for document.cookie. But Object.getOwnPropertyDescriptor(document, "cookie") returns undefined.
UPD. While I was writing the question I found a working solution, but it uses deprecated __lookupGetter__ and __lookupSetter__ methods. Is there any solution which doesn't use obsolete API?
The standardized way of accessing getters and setters is with Object.getOwnPropertyDescriptor, but as the name suggests, it only looks on the objects own properties (it does not look up the prototype chain). document is an instance of HTMLDocument, which inherits from Document. In modern browsers the cookie property is defined on Document.prototype, whereas in older versions of Firefox it is defined on HTMLDocument.prototype.
var cookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') ||
Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie');
if (cookieDesc && cookieDesc.configurable) {
Object.defineProperty(document, 'cookie', {
get: function () {
return cookieDesc.get.call(document);
},
set: function (val) {
console.log(val);
cookieDesc.set.call(document, val);
}
});
}
Ironically, in the most privacy-concerned browser Safari, the descriptor has set configurable to false and does not contain the getter nor setter, and neither does __lookupGetter__ or __lookupSetter__. So I haven't found a way to override document.cookie in Safari yet (8.0.8 on OS X and iOS 9.0.2). WebKit nightly acts the same way as Safari, so it doesn't seem to get fixed anytime soon.
Update October 2019: Tested the above code in Safari 12.1.2 on MacOS Mojave, and cookieDesk is now configurable! This means my proof of concept document.cookie protection from 2015 might actually work now :)
While I was writing the question I found next code solves my problem:
var cookie_setter_orig = document.__lookupSetter__("cookie").bind(document);
var cookie_getter_orig = document.__lookupGetter__("cookie").bind(document);
Object.defineProperty(document, "cookie", {
get: function () {
return cookie_getter_orig();
},
set: function (val) {
console.log(val);
cookie_setter_orig(val);
}
});
But I don't like using deprecated methods, so I hope there is a better solution.
I have chosen to avoid identifying the browser as unreliable and messy. Instead I am just testing for actual support of each of the elements or APIs my page uses. For testing audio support I have come up with this.
function testAudio()
{
if(window.AudioContext = window.AudioContext)
{
myAudio = new AudioContext();
alert("Browser supports AudioContext");
}
else
{
if(window.Audio = window.Audio)
{
myAudio = new Audio();
alert("Browser supports Audio Element");
}
else {alert("This browser does not support AudioContext or Audio Element");}
}
}
Works like a charm. Current browsers return true for AudioContext, IE11 returns true only for Audio() and IE 8 returns false for both. But WHY?
In the case of true I am assigning an object to an object property of the window object. Is there an AudioContext and Audio element property of the window object? Or is the browser returning true for an assignment to an object that could exist but doesn't?
In the case of false I am assigning an object that the browser doesn't recognize to a property of the window object that doesn't exist. Why does it return false instead of throwing an error, null or undefined? And why would this return false while
if(myAudio = new AudioContext()) or if(myAudio = new Audio()) throw errors?
IE8 does not fully support HTML5, and that includes the Audio API. If you want IE8 support you'll need to include a polyfill.
When you try to instantiate a new object using a non-existent object prototype (i.e. new AudioContext()) you should expect an error. But if (window.AudioContext) is merely checking for the existence of the property--it is not calling a function, so there is no error.
The issue you're facing here is that this is an odd way to check if the browser supports the API in question.
For example,
if (window.Audio = window.Audio)
in IE8 is going to be the same as this:
if (window.Audio = undefined)
which, if you remove the assignment, can be reduced to this:
if (undefined)
which is falsy, meaning that it's going to have the same result as this:
if (false)
so you'll never get into that block.
I need to log setting of document.cookie. I can not redefine cookie property just with document.cookie = {...} So I need to get setter for document.cookie. But Object.getOwnPropertyDescriptor(document, "cookie") returns undefined.
UPD. While I was writing the question I found a working solution, but it uses deprecated __lookupGetter__ and __lookupSetter__ methods. Is there any solution which doesn't use obsolete API?
The standardized way of accessing getters and setters is with Object.getOwnPropertyDescriptor, but as the name suggests, it only looks on the objects own properties (it does not look up the prototype chain). document is an instance of HTMLDocument, which inherits from Document. In modern browsers the cookie property is defined on Document.prototype, whereas in older versions of Firefox it is defined on HTMLDocument.prototype.
var cookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') ||
Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie');
if (cookieDesc && cookieDesc.configurable) {
Object.defineProperty(document, 'cookie', {
get: function () {
return cookieDesc.get.call(document);
},
set: function (val) {
console.log(val);
cookieDesc.set.call(document, val);
}
});
}
Ironically, in the most privacy-concerned browser Safari, the descriptor has set configurable to false and does not contain the getter nor setter, and neither does __lookupGetter__ or __lookupSetter__. So I haven't found a way to override document.cookie in Safari yet (8.0.8 on OS X and iOS 9.0.2). WebKit nightly acts the same way as Safari, so it doesn't seem to get fixed anytime soon.
Update October 2019: Tested the above code in Safari 12.1.2 on MacOS Mojave, and cookieDesk is now configurable! This means my proof of concept document.cookie protection from 2015 might actually work now :)
While I was writing the question I found next code solves my problem:
var cookie_setter_orig = document.__lookupSetter__("cookie").bind(document);
var cookie_getter_orig = document.__lookupGetter__("cookie").bind(document);
Object.defineProperty(document, "cookie", {
get: function () {
return cookie_getter_orig();
},
set: function (val) {
console.log(val);
cookie_setter_orig(val);
}
});
But I don't like using deprecated methods, so I hope there is a better solution.
I'm trying to override the document.cookie since i need to control cookie
creation, but seems that getOwnPropertyDescriptor casted on document.cookie doesn't retrieve its getter and setter ( tried on chrome and firefox ). Could someone explain me this behaviour?
https://jsfiddle.net/1363ktwp/7/
var obj={};
// creating a property using document.cookie descriptors
Object.defineProperty(
obj,
"oldCookie",
Object.getOwnPropertyDescriptor(document, "cookie")
);
// setting cookies succesfully
document.cookie="test1=ok;path=/;expires=365;";
document.cookie="test2=ok;path=/;expires=365;";
alert(document.cookie);
Object.defineProperty(document, "cookie", {
get: function () {
return obj.oldCookie;
},
set: function (cookie) {
/*
...preliminar operations
*/
obj.oldCookie = cookie;
}
});
// obj.oldCookie is just a string without getter/setter
// so assignments below doesn't works correctly
document.cookie="test3=ok;path=/;expires=365;";
document.cookie="test4=ok;path=/;expires=365;";
alert(document.cookie);
The reason for
Object.getOwnPropertyDescriptor(document, 'cookie');
returning undefined is the way that getOwnPropertyDescriptor works: it does not traverse the prototype chain.
A global variable document contains object actually inheriting from Document.prototype:
Document.prototype.isPrototypeOf(document) // true
and does not own a property called "cookie", the Document.prototype does:
document.hasOwnProperty('cookie'); // false
Document.prototype.hasOwnProperty('cookie'); // true
A way to retrieve the descriptor of document.cookie is to retrieve the descriptor of Document.prototype.cookie itself:
Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
Deprecated functions called __lookupGetter__ and __lookupSetter__ do actually traverse the prototype chain and therefore, you can retrieve these methods calling it on document, and not the Document.prototype:
const cookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
cookieDescriptor.get === document.__lookupGetter__('cookie') // true
You can use __lookupSetter__ and __lookupGetter__ methods, but be warn that they are deprecated and not supported everywhere. They work properly in Chrome, Firefox, IE11. Don't work in IE<10. Opera provides such methods, but they seam to always return undefined. Didn't check anything else.
Here is an example:
var cookieSetterOrig = document.__lookupSetter__("cookie");
var cookieGetterOrig = document.__lookupGetter__("cookie");
Object.defineProperty(document, "cookie", {
get: function () {
return cookieGetterOrig.apply(document);
},
set: function () {
return cookieSetterOrig.apply(document, arguments);
},
configurable: true
});
Could someone explain me this behaviour?
document.cookie is a property of a host object. Host objects are frequently not true JavaScript objects (called native objects) and are neither required nor guaranteed to have the features of JavaScript objects.
I'd be truly shocked, in fact, if many or even more than one or two browsers implemented document.cookie using ES5 property getters/setters. Perhaps for some newer APIs (or perhaps not), but for one that old, there's going to be a lot of cruft about. (I'd also have to think a long time about the security ramifications...)
If they did implement it via ES5 getters/setters, it wouldn't surprise me if they made it a non-configurable property (e.g., such that you couldn't change it).