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).
Related
Is it possible to use ES6 Proxies to track changes of location.pathname?
As far as I know, Proxies can be used for objects, and location.pathname is an object, right?
If the answer is yes, can someone help me understand how to achieve this?
The only working thing I managed to create is the following code:
const handler = {
get(target, key) {
console.log(`Reading value from ${key}`)
return target[key];
},
};
const p = new Proxy(location, handler);
console.log(p.pathname);
But the above code will only show once the current URL is in the browser's tab.
How can I make it work to keep tracking the changes?
window.location is a nonconfigurable getter/setter, which means that it can't be replaced:
console.log(Object.getOwnPropertyDescriptor(window, 'location'));
The same is true for the .pathname property of the object returned by the getter:
console.log(Object.getOwnPropertyDescriptor(window.location, 'pathname'));
And a location variable can't be declared on the top level:
const location = { newObject: 'foo' };
Which means that there's no way to intercept changes to it by replacing it with a Proxy - any references or assignments in the existing code to window.location.pathname will necessarily invoke the browser's built-in implementation.
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 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.
My application is using Protractor and the "Page Model" for testing. There's an external module that looks like this:
'use strict';
var AdminTestPage = function () {
browser.get('/');
};
AdminTestPage.prototype = Object.create({}, {
detailTabClear: {
get: function () {
element(by.id('name')).clear();
}
}
});
module.exports = AdminTestPage;
Can someone explain why the developer has used Object.create? Is this different from just combining the first and second parts of the code?
The properties in the property descriptor become non-enumerable and non-configurable by default, as explained in the documentation Object.defineProperty.
So the call is the same as
AdminTestPage.prototype = Object.create({}, {
detailTabClear: {
get: function () {
element(by.id('name')).clear();
},
enumerable: false,
configurable: false
}
});
What does that mean? It means that this property won't show up when you iterate over the object with for...in and you won't be able to delete it from the object via delete.
The same could have been written as
Object.defineProperty(AdminTestPage.prototype, 'detailTabClear', {
get: function () {
element(by.id('name')).clear();
}
});
Note: This is very different from assigning the property directly. Properties created via the assignment statement (i.e. obj.prop = foo)` are enumerable and configurable by default.
Whether this was the intention of the author or not I cannot say though.
Same question, I've got no idea why the developer decided to use Object.create.
Object.create does exactly what it says. It creates an object, in this case there is no difference from using the short hand notation...
AdminTestPage.prototype = {...};
it is exactly the same. Object.create is most useful when you already have a prototype of an object and want to create another "instance" of the same object because it "inherits" the prototype so you will not have to define all of its members. For example, consider this prototype...
var car = { make: "", model: ""};
Then you can create an instance of a car...
var subaru = Object.create(car);
the subaru variable is already a prototype of car...in OOP terms it'd be something like an instance of a car...
subaru.make = "Subaru";
subaru.model = "WRX";
Be aware that Object.create is not supported by all browsers
Is it possible to observe private (underscored ) properties from
within the object itself?
I need to know when _view_layer is getting set, so that I can apply
some jQuery even handlers. Unfortunately init() and render() are
really early, so _view_layer is undefined.
Unfortunately, observing _view_layer doesn't seem to work as well.
Please, tell me what I can do. Basically, if there is another possible
solution, I am open to see that as well
In Sproutcore the underscore is only a convention that the property/method is private. Its not actually private.
In Sproutcore, the views have life-cycle methods. This one might be of interest (taken from SC 1.4.5 code in view):
didCreateLayer: the render() method is used to generate new HTML.
Override this method to perform any additional setup on the DOM you
might
need to do after creating the view. For example, if you need to
listen
for events.
Views have changed drastically in SC 1.6 and later, but I believe that didCreateLayer is still recognized.
(function() {
var value = obj._view_layer;
delete obj._view_layer;
var callback = function() {
/* observation logic */
}
Object.defineProperty(obj, "_view_layer", {
get: function() {
return value;
},
set: function(val) {
value = val;
callback(val);
},
writable: true,
enumerable: true
});
})();
Requires an ES5 browser.
Only recommended to use for debugging. You can also use .watch when debugging in firefox.