Proxy object cannot be added to DOM (traps doesn't trigger either) - javascript

I am trying to make a Proxy object of Image to trap properties but even with an empty handler I get an error message.
TypeError: Argument 1 of Node.appendChild does not implement interface Node.
The proxy object is suppose to act as the target object so this baffles me a little. As far as I understand you should be able to do this with DOM nodes as well (?).
Also: I cannot start loading the image and have the onload handler triggered when setting the src property.
How should I use the Proxy so I can "take over" for example the "src" property and otherwise have it act like a regular image object?
My code
'use strict';
//--- normal image use ---
var imgNormal = new Image();
imgNormal.onload = function(){
console.log('Normal loaded OK');
document.body.appendChild(imgNormal);
};
imgNormal.src = 'https://i.imgur.com/zn7O7QWb.jpg';
//--- proxy image ---
var imgProxy = new Proxy(Image, { // I also tried with 'new Image()' and HTMLImageElement
set: function(a,b,c,d){
console.log('set '+b);
return Reflect.set(a,b,c,d);
}
});
imgProxy.onload = function(){
console.log('Proxy loaded OK');
document.body.appendChild(imgProxy);
};
imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';
document.body.appendChild(imgProxy); // double-up to demo error
Update: Thanks to #Harangue! using "new" (bah..) certainly made the proxy object come to life but now I am unable to trap the setting of properties. It seem to ignore the trap completely - example:
var proxy = new Proxy(Image, {
set: function(a,b,c,d){
console.log('set '+b); // doesn't show
return Reflect.set(a,b,c,d);
}
});
var imgProxy = new proxy();
imgProxy.onload = function(){
console.log('Proxy loaded OK');
document.body.appendChild(imgProxy);
};
imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';
How can I trap the property setting using a valid proxy?
Update 2 On the other hand - using new with the new proxy only seem to use the original constructor. All examples I can find does not use new:
var myProxy = new Proxy(.., ..); // should suffer
Using then on top of that new myProxy() only seem to use the original constructor which is not what I want as it ignores the traps.
var proxy = new Proxy(Image, {}); //should be sufficent??
var proxy2 = new proxy();
console.log(proxy2); //-> says Image (not proxy..)
The traps seem to work in my first attempts but the proxy doesn't behave as expected. This is so confusing, and so new. Happy for any input how both of these can be solved (traps and behavior).

Never underestimate the importance of the new keyword. ;)
//--- proxy image ---
var imgProxy = new Proxy(Image, { // I also tried with 'new Image()'
set: function(a,b,c,d){
console.log('set '+b);
return Reflect.set(a,b,c,d);
}
});
imgProxy.src = 'https://i.imgur.com/zn7O7QWb.jpg';
document.body.appendChild(new imgProxy); // double-up to demo error
With the proxy you effectively extend the Image object. But sending the Image constructor itself, rather than the DOM Node returned by it, would indeed be missing the needed appendChild.

As an alternative to a proxy you can also overwrite the property on the object itself and therefore control it's behaviour:
function findDescriptor(obj, prop){
if(obj != null){
return Object.hasOwnProperty.call(obj, prop)?
Object.getOwnPropertyDescriptor(obj, prop):
findDescriptor(Object.getPrototypeOf(obj), prop);
}
}
var img = new Image();
var {get, set} = findDescriptor(img, "src");
Object.defineProperty(img, "src", {
configurable: true,
enumerable: true,
//get: get, //keep behaviour
get(){ //overwrite getter
var v = get.call(this); //call the original getter
console.log("get src:", v, this);
return v;
},
//same for setter
set(v){
console.log("set src:", v, this);
//modify value before applying it to the default setter
v = v.toLowerCase();
set.call(this, v);
}
});
img.src = "FileWithUppercaseLetters.jpg"; //setter
img.src; //trigger getter
And since this property is defined on the Image.prototype*, you can simply extend this class and modify the behaviour on the prototype of the inherited Class
*at least in FF, have to check the other browsers

Related

Javascript Proxy gives Illegal invocation error [duplicate]

What I was trying to accomplish. I wanted to share a single canvas (because what I'm doing is very heavy) and so I thought I'd make a limited resource manager. You'd ask it for the resource via promise, in this case a Canvas2DRenderingContext. It would wrap the context in a revokable proxy. When you're finished you are required to call release which both returns the canvas to the limited resource manager so it can give it to someone else AND it revokes the proxy so the user can't accidentally use the resource again.
Except when I make a proxy of a Canvas2DRenderingContext it fails.
const ctx = document.createElement('canvas').getContext('2d');
const proxy = new Proxy(ctx, {});
// try to change the width of the canvas via the proxy
test(() => { proxy.canvas.width = 100; }); // ERROR
// try to translate the origin of via the proxy
test(() => { proxy.translate(1, 2); }); // ERROR
function test(fn) {
try {
fn();
} catch (e) {
console.log("FAILED:", e, fn);
}
}
The code above generates Uncaught TypeError: Illegal invocation in Chrome and TypeError: 'get canvas' called on an object that does not implement interface CanvasRenderingContext2D. in Firefox
Is that an expected limitation of Proxy or is it a bug?
note: of course there are other solutions. I can remove the proxy and just not worry about it. I can also wrap the canvas in some JavaScript object that just exposes the functions I need and proxy that. I'm just more curious if this is supposed to work or not. This Mozilla blog post kind of indirectly suggests it's supposed to be possbile since it actually mentions using a proxy with an HTMLElement if only to point out it would certainly fail if you called someElement.appendChild(proxiedElement) but given the simple code above I'd expect it's actually not possible to meanfully wrap any DOM elements or other native objects.
Below is proof that Proxies work with plain JS objects. They work with class based (as in the functions are on the prototype chain). And they don't work with native objects.
const img = document.createElement('img')
const proxy = new Proxy(img, {});
console.log(proxy.src);
Also fails with the same error. where as they don't with JavaScript objects
function testNoOpProxy(obj, msg) {
log(msg, '------');
const proxy = new Proxy(obj, {});
check("get property:", () => proxy.width);
check("set property:", () => proxy.width = 456);
check("get property:", () => proxy.width);
check("call fn on object:", () => proxy.getContext('2d'));
}
function check(msg, fn) {
let success = true;
let r;
try {
r = fn();
} catch (e) {
success = false;
}
log(' ', success ? "pass" : "FAIL", msg, r, fn);
}
const test = {
width: 123,
getContext: function() {
return "test";
},
};
class Test {
constructor() {
this.width = 123;
}
getContext() {
return `Test width = ${this.width}`;
}
}
const testInst = new Test();
const canvas = document.createElement('canvas');
testNoOpProxy(test, 'plain object');
testNoOpProxy(testInst, 'class object');
testNoOpProxy(canvas, 'native object');
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
Well FWIW the solution I choose was to wrap the canvas in a small class that does the thing I was using it for. Advantage is it's easier to test (since I can pass in a mock) and I can proxy that object no problem. Still, I'd like to know
Why doesn't Proxy work for native object?
Do any of the reasons Proxy doesn't work with native objects apply to situations with JavaScript objects?
Is it possible to get Proxy to work with native objects.
const handlers = {
get: (target, key) => key in target ? target[key] : undefined,
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
}
};
const { revoke, proxy } = Proxy.revocable(ctx, handlers);
// elsewhere
try {
proxy.canvas.width = 500;
} catch (e) { console.log("Access has been revoked", e); }
Something like that should do what you're expecting.
A revocable proxy, with handlers for get and set traps, for the context.
Just keep in mind that when an instance of Proxy.revocable() is revoked, any subsequent access of that proxy will throw, and thus everything now needs to use try/catch, in the case that it has, indeed, been revoked.
Just for fun, here's how you can do the exact same thing without fear of throwing (in terms of simply using the accessor; no guarantee for doing something wrong while you still have access):
const RevocableAccess = (item, revoked = false) => ({
access: f => revoked ? undefined : f(item),
revoke: () => { revoked = true; }
});
const { revoke, access: useContext } = RevocableAccess(ctx);
useContext(ctx => ctx.canvas.width = 500);
revoke();
useContext(ctx => ctx.canvas.width = 200); // never fires
Edit
As pointed out in the comments below, I completely neglected to test for the method calls on the host object, which, it turns out, are all protected. This comes down to weirdness in the host objects, which get to play by their own rules.
With a proxy as above, proxy.drawImage.apply(ctx, args) would work just fine.
This, however, is counter-intuitive.
Cases that I'm assuming fail here, are Canvas, Image, Audio, Video, Promise (for instance based methods) and the like. I haven't conferred with the spec on this part of Proxies, and whether this is a property-descriptor thing, or a host-bindings thing, but I'm going to assume that it's the latter, if not both.
That said, you should be able to override it with the following change:
const { proxy, revoke } = Proxy.revocable(ctx, {
get(object, key) {
if (!(key in object)) {
return undefined;
}
const value = object[key];
return typeof value === "function"
? (...args) => value.apply(object, args)
: value;
}
});
Here, I am still "getting" the method off of the original object, to call it.
It just so happens that in the case of the value being a function, I call bind to return a function that maintains the this relationship to the original context. Proxies usually handle this common JS issue.
...this causes its own security concern; someone could cache the value out, now, and have permanent access to, say, drawImage, by saying
const draw = proxy.drawImage;...
Then again, they already had the ability to save the real render context, just by saying
const ctx = proxy.canvas.getContext("2d");
...so I'm assuming some level of good-faith, here.
For a more secure solution, there are other fixes, though with canvas, unless it's in-memory only, the context is ultimately going to be available to anyone who can read the DOM.

understanding the code from transit.js

I was just going through the source of transit.js and came across the following fucntion ::
$.cssHooks['transit:transform'] = {
// The getter returns a `Transform` object.
get: function(elem) {
return $(elem).data('transform') || new Transform();
},
// The setter accepts a `Transform` object or a string.
set: function(elem, v) {
var value = v;
if (!(value instanceof Transform)) {
value = new Transform(value);
}
// We've seen the 3D version of Scale() not work in Chrome when the
// element being scaled extends outside of the viewport. Thus, we're
// forcing Chrome to not use the 3d transforms as well. Not sure if
// translate is affectede, but not risking it. Detection code from
// http://davidwalsh.name/detecting-google-chrome-javascript
if (support.transform === 'WebkitTransform' && !isChrome) {
elem.style[support.transform] = value.toString(true);
} else {
elem.style[support.transform] = value.toString();
}
$(elem).data('transform', value);
}
};
I understand the latter part of the function, but its really hard to understand the initial part of the function, the function can be found on git too , HERE .
Initially I see this, $.cssHooks['transit:transform'] what is that line really saying?
After that we have the below line of code I.E. the getter and setter method,
set: function(elem, v) {
But who is passing the elem and v inside the function, I don't see anything being passed?
Read about cssHooks at jQuery cssHooks
Look at the source code (search for hooks.get and hooks.set)
.cssHooks is an array of objects that contains getter and setters tha will be executed by .css(). Thats all.
$.cssHooks['transit:transform'] = {set: function(elem,value){}, get: function(elem){}}
equal:
$.cssHooks['transit:transform'] = {};
$.cssHooks['transit:transform'].set = function(elem, value){};
$.cssHooks['transit:transform'].get = function(elem){};
$(element).css('transit:transform',value)
comes to:
$.cssHooks['transit:transform'].set(element,value)
$(element).css('transit:transform')
comes to:
$.cssHooks['transit:transform'].get(element)
$.cssHooks['transit:transform'] = {set:function(){}, get: function(){} }
{...} is an object creation.get and set not executed at this moment.
They created {set:function(){}, get: function(){} }
So. Simply: .css() will execute set and get functions for hooked property.
If you want to know how real getters and setters works:
Object.defineProperty()
In Javascript, you can add/access to a property with this syntax :
myObject.myProperty
or with this syntax :
myObject['myProperty']
This is the same result
So your line
$.cssHooks['transit:transform']
just mean that we want to store an object (code between {} in your original post) inside the 'transit:transform' property which is inside the cssHooks property which is inside the $ object
This is the same things :
$['cssHooks']['transit:transform']
The reason why they use the [''] syntax is that transit:transform contains the ':' char which is not allowed if you want to access it this way :
$.cssHooks.transit:transform //doesn't work
EDIT:
To answer to your second question, i don't know...the code you are showing is just the 'description' of the "transit:transform' property

How can I modify the XMLHttpRequest responsetext received by another function?

I am trying to modify the responseText received by a function that I cannot modify. This function creates a XMLHttpRequest that I can attach to, but I have been unable to "wrap" the responseText in a way that allows me to modify the content before the original function receives it.
Here's the full original function:
function Mj(a, b, c, d, e) {
function k() {
4 == (m && 'readyState' in m ? m.readyState : 0) && b && ff(b) (m)
}
var m = new XMLHttpRequest;
'onloadend' in m ? m.addEventListener('loadend', k, !1) : m.onreadystatechange = k;
c = ('GET').toUpperCase();
d = d || '';
m.open(c, a, !0);
m.send(d);
return m
}
function ff(a) {
return a && window ? function () {
try {
return a.apply(this, arguments)
} catch(b) {
throw jf(b),
b;
}
} : a
}
I have also tried to manipulate the reiceiving function k(); in an attempt to reach my goal, but since it doesn't depend on any data passing to the function (for example k(a.responseText);) I had no success.
Is there any way that I can achieve this? I do not wish to use js libraries (such as jQuery);
EDIT: I understand that I cannot change .responseText directly since it is read-only, but I am trying to find a way to change the content between the response and receiving function.
EDIT2: Added below one of the methods I have tried to intercept and change .responseText which has been addapted from here: Monkey patch XMLHTTPRequest.onreadystatechange
(function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
if(/results/.test(url)) {
console.log(this.onreadystatechange);
this.addEventListener("readystatechange", function () {
console.log('readystate: ' + this.readyState);
if(this.responseText !== '') {
this.responseText = this.responseText.split('&')[0];
}
}, false);
}
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);
EDIT3: I forgot to include that the functions Mj and ff are not globally available, they are both contained inside an anonymous function (function(){functions are here})();
EDIT4: I have changed the accepted answer because AmmarCSE's does not have any of the problems and complexity linked to jfriend00's answer.
The best answer explained in short is as follows:
Listen to whichever request you want to modify (make sure your listener will intercept it before the original function destination does, otherwise there is no point in modifying it after the response has already been used).
Save the original response (if you want to modify it) in a temporary variable
Change the property you want to modify to "writable: true", it will erase whichever value it had. In my case I use
Object.defineProperty(event, 'responseText', {
writable: true
});
Where event is the object returned by listening to the load or readystatechange event of the xhr request
Now you can set anything you want for your response, if all you wanted was to modify the original response then you can use that data from your temporary variable and then save the modifications in the response.
Edit: See the second code option below (it is tested and works). The first one has some limitations.
Since you can't modify any of those functions, it appears you have to go after the XMLHttpRequest prototype. Here's one idea (untested, but you can see the direction):
(function() {
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
var oldReady;
if (async) {
oldReady = this.onreadystatechange;
// override onReadyStateChange
this.onreadystatechange = function() {
if (this.readyState == 4) {
// this.responseText is the ajax result
// create a dummay ajax object so we can modify responseText
var self = this;
var dummy = {};
["statusText", "status", "readyState", "responseType"].forEach(function(item) {
dummy[item] = self[item];
});
dummy.responseText = '{"msg": "Hello"}';
return oldReady.call(dummy);
} else {
// call original onreadystatechange handler
return oldReady.apply(this, arguments);
}
}
}
// call original open method
return open.apply(this, arguments);
}
})();
This does a monkey patch for the XMLHttpRequest open() method and then when that is called for an async request, it does a monkey patch for the onReadyStateChange handler since that should already be set. That patched function then gets to see the responseText before the original onReadyStateChange handler is called so it can assign a different value to it.
And, finally because .responseText is ready-only, this substitutes a dummy XMLHttpResponse object before calling the onreadystatechange handler. This would not work in all cases, but will work if the onreadystatechange handler uses this.responseText to get the response.
And, here's an attempt that redefines the XMLHttpRequest object to be our own proxy object. Because it's our own proxy object, we can set the responseText property to whatever we want. For all other properties except onreadystatechange, this object just forwards the get, set or function call to the real XMLHttpRequest object.
(function() {
// create XMLHttpRequest proxy object
var oldXMLHttpRequest = XMLHttpRequest;
// define constructor for my proxy object
XMLHttpRequest = function() {
var actual = new oldXMLHttpRequest();
var self = this;
this.onreadystatechange = null;
// this is the actual handler on the real XMLHttpRequest object
actual.onreadystatechange = function() {
if (this.readyState == 4) {
// actual.responseText is the ajax result
// add your own code here to read the real ajax result
// from actual.responseText and then put whatever result you want
// the caller to see in self.responseText
// this next line of code is a dummy line to be replaced
self.responseText = '{"msg": "Hello"}';
}
if (self.onreadystatechange) {
return self.onreadystatechange();
}
};
// add all proxy getters
["status", "statusText", "responseType", "response",
"readyState", "responseXML", "upload"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];}
});
});
// add all proxy getters/setters
["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];},
set: function(val) {actual[item] = val;}
});
});
// add all pure proxy pass-through methods
["addEventListener", "send", "open", "abort", "getAllResponseHeaders",
"getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {
Object.defineProperty(self, item, {
value: function() {return actual[item].apply(actual, arguments);}
});
});
}
})();
Working demo: http://jsfiddle.net/jfriend00/jws6g691/
I tried it in the latest versions of IE, Firefox and Chrome and it worked with a simple ajax request.
Note: I have not looked into all the advanced ways that Ajax (like binary data, uploads, etc...) can be used to see that this proxy is thorough enough to make all those work (I would guess it might not be yet without some further work, but it is working for basic requests so it looks like the concept is capable).
Other attempts that failed:
Tried to derive from the XMLHttpRequest object and then replace the constructor with my own, but that didn't work because the real XMLHttpRequest function won't let you call it as a function to initialize my derived object.
Tried just overriding the onreadystatechange handler and changing .responseText, but that field is read-only so you can't change it.
Tried creating a dummy object that is sent as the this object when calling onreadystatechange, but a lot of code doesn't reference this, but rather has the actual object saved in a local variable in a closure - thus defeating the dummy object.
One very simple workaround is to change the property descriptor for responseText itself
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
So, you can extend XMLHttpRequest like
(function(proxied) {
XMLHttpRequest = function() {
//cannot use apply directly since we want a 'new' version
var wrapped = new(Function.prototype.bind.apply(proxied, arguments));
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
return wrapped;
};
})(XMLHttpRequest);
Demo
I needed to intercept and modify a request response so I came up with a little bit of code. I also found that some websites like to use response as well as the responseText which is why my code modifies both.
The Code
var open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function(urlpattern, callback) {
XMLHttpRequest.prototype.open = function() {
arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function(event) {
if ( this.readyState === 4 ) {
var response = callback(event.target.responseText);
Object.defineProperty(this, 'response', {writable: true});
Object.defineProperty(this, 'responseText', {writable: true});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
};
the first param of the intercept_response function is a regular expression to match the request url and the second param is the function to be used on the response to modify it.
Example Of Usage
intercept_response(/fruit\.json/i, function(response) {
var new_response = response.replace('banana', 'apple');
return new_response;
});
By request I include below an example snippet showing how to modify the response of a XMLHttpRequest before the original function can receive it.
// In this example the sample response should be
// {"data_sample":"data has not been modified"}
// and we will change it into
// {"data_sample":"woops! All data has gone!"}
/*---BEGIN HACK---------------------------------------------------------------*/
// here we will modify the response
function modifyResponse(response) {
var original_response, modified_response;
if (this.readyState === 4) {
// we need to store the original response before any modifications
// because the next step will erase everything it had
original_response = response.target.responseText;
// here we "kill" the response property of this request
// and we set it to writable
Object.defineProperty(this, "responseText", {writable: true});
// now we can make our modifications and save them in our new property
modified_response = JSON.parse(original_response);
modified_response.data_sample = "woops! All data has gone!";
this.responseText = JSON.stringify(modified_response);
}
}
// here we listen to all requests being opened
function openBypass(original_function) {
return function(method, url, async) {
// here we listen to the same request the "original" code made
// before it can listen to it, this guarantees that
// any response it receives will pass through our modifier
// function before reaching the "original" code
this.addEventListener("readystatechange", modifyResponse);
// here we return everything original_function might
// return so nothing breaks
return original_function.apply(this, arguments);
};
}
// here we override the default .open method so that
// we can listen and modify the request before the original function get its
XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open);
// to see the original response just remove/comment the line above
/*---END HACK-----------------------------------------------------------------*/
// here we have the "original" code receiving the responses
// that we want to modify
function logResponse(response) {
if (this.readyState === 4) {
document.write(response.target.responseText);
}
}
// here is a common request
var _request = new XMLHttpRequest();
_request.open("GET", "https://gist.githubusercontent.com/anonymous/c655b533b340791c5d49f67c373f53d2/raw/cb6159a19dca9b55a6c97d3a35a32979ee298085/data.json", true);
_request.addEventListener("readystatechange", logResponse);
_request.send();
You can wrap the getter for responseText in the prototype with a new function and make the changes to the output there.
Here is a simple example that appends the html comment <!-- TEST --> to the response text:
(function(http){
var get = Object.getOwnPropertyDescriptor(
http.prototype,
'responseText'
).get;
Object.defineProperty(
http.prototype,
"responseText",
{
get: function(){ return get.apply( this, arguments ) + "<!-- TEST -->"; }
}
);
})(self.XMLHttpRequest);
The above function will change the response text for all requests.
If you want to make the change to just one request then do not use the function above but just define the getter on the individual request instead:
var req = new XMLHttpRequest();
var get = Object.getOwnPropertyDescriptor(
XMLHttpRequest.prototype,
'responseText'
).get;
Object.defineProperty(
req,
"responseText", {
get: function() {
return get.apply(this, arguments) + "<!-- TEST -->";
}
}
);
var url = '/';
req.open('GET', url);
req.addEventListener(
"load",
function(){
console.log(req.responseText);
}
);
req.send();
I ran into the same problem when I was making a Chrome extension to allow cross origin API calls. This worked in Chrome. (Update: It doesn't work in the newest Chrome version).
delete _this.responseText;
_this.responseText = "Anything you want";
The snippet runs inside a monkeypatched XMLHttpRequest.prototype.send who is redirecting the requests to the extensions background script and replace all the properties on response. Like this:
// Delete removes the read only restriction
delete _this.response;
_this.response = event.data.response.xhr.response;
delete _this.responseText;
_this.responseText = event.data.response.xhr.responseText;
delete _this.status;
_this.status = event.data.response.xhr.status;
delete _this.statusText;
_this.statusText = event.data.response.xhr.statusText;
delete _this.readyState;
_this.readyState = event.data.response.xhr.readyState;
That didn't work in Firefox, but I found a solution that worked:
var test = new XMLHttpRequest();
Object.defineProperty(test, 'responseText', {
configurable: true,
writable: true,
});
test.responseText = "Hey";
That doesn't work in Chrome, but this work in both Chrome and Firefox:
var test = new XMLHttpRequest();
var aValue;
Object.defineProperty(test, 'responseText', {
get: function() { return aValue; },
set: function(newValue) { aValue = newValue; },
enumerable: true,
configurable: true
});
test.responseText = "Hey";
The last was copy past from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
None of the solutions works in Safari. I tried to make a new XMLHttpRequest with writable properties, but it was not allowed to call open or send from it. I also tried this solution: https://stackoverflow.com/a/28513219/3717718. Unfortunately it produced the same error in Safari:
TypeError: Attempting to configurable attribute of unconfigurable property.
First-class function variables are wonderful things! function f() {a; b; c; } is exactly the same thing as var f = function () {a; b; c; } This means you can redefine functions as needed. You want to wrap the function Mj to return a modified object? No problem. The fact that the responseText field is read-only is a pain, but if that's the only field you need...
var Mj_backup = Mj; // Keep a copy of it, unless you want to re-implement it (which you could do)
Mj = function (a, b, c, d, e) { // To wrap the old Mj function, we need its args
var retval = Mj_backup(a,b,c,d,e); // Call the original function, and store its ret value
var retmod; // This is the value you'll actually return. Not a true XHR, just mimics one
retmod.responseText = retval.responseText; // Repeat for any other required properties
return retmod;
}
Now, when your page code calls Mj(), it will invoke your wrapper instead (which will still call the original Mj internally, of course).
I search very a lot and made one solution to solve the problem
const open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function (urlpattern, callback) {
XMLHttpRequest.prototype.open = function () {
arguments['1'].includes(urlpattern) && this.addEventListener('readystatechange', function (event) {
if (this.readyState === 4) {
var response = callback(event.target.responseText);
Object.defineProperty(this, 'response', {writable: true});
Object.defineProperty(this, 'responseText', {writable: true});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
};
and you can use the function like this
intercept_response('SOME_PART_OF_YOUR_API', (response) => {
const new_response = response.replace('apple', 'orange');
return new_response;
})
and now all apples replaced with oranges 😊

TypeError when using Jquery and Object property function

I'm getting an error when using jquery and I would like to know its cause:
here is part of my code
function Wbook(name){
this.name = name;
}
Wbook.prototype.GetHTML = function() {
Object.defineProperty(this, "GetHTML", {enumerable : false,
configurable : true});
var html ='<h1>TEST1</h1>';
return html;
};
var rs = {};
rs.WB = new Wbook('test1');
var foo = rs.WB.GetHTML();
$(foo).appendTo('div#id1'); // This works
$(rs.WB.GetHTML()).appendTo('div#id1');
// This doesn't work >> TypeError: rs.WB.GetHTML is not a function
I can also getting to work if I comment the Object.defineProperty section, so I'm suspecting this might have to do with the enumerability, but I'm not sure of it
//Edit: While creating Jfiddle, I notice rs.WB.GetHTML() is always failing the second time it runs :-/. (It works fine if I comment the Object.defineProperty section)
The first time you call .GetHTML() it's returning some HTML, but in the process the Object.defineProperty call is overwriting the .GetHTML method with a new property that has no value.
It's therefore unsurprising that on the second invocation you get an error because the value of .GetHTML by then is undefined.
If the intent of your code is to ensure that GetHTML is not enumerable, use the following code which directly adds the method to Wbook.prototype and (automatically) sets it non-enumerable:
Object.defineProperty(Wbook.prototype, 'GetHTML', {
value: function() {
...
}
});

Call a function when a property gets set on an object

I don't really know how to explain this but I'll show you code and tell you what I'd like to achieve.
Let's say I make a quick object:
var test = {};
And then I set a property to it: (I insist on the syntax, it mustn't use any function as the setter)
test.hello = 'world';
Pretty simple, eh? Now I'd like to add a function to that object that would get called everytime a new property gets set. Like this:
var test = {
newPropertyHasBeenSet: function(name){
console.log(name + 'has been set.');
}
};
test.hello = 'world';
// Now newPropertyHasBeenSet gets called with 'hello' as an argument.
// hello has been set.
I don't know if it's possible, but that would be quite amazing. Anyone has an idea of how to achieve so?
EDIT: I'd like also to be able to do the same for property get (so test.hello would call get('hello') for example).
EDIT2: This is for server-side javascript using node.js.
Thanks a lot and have a nice day!
try this example in chrome (as mentioned in previous comments it uses ES6 Proxy):
var p = new Proxy(
{},
{
get: function(obj, name) {
console.log('read request to ' + name + ' property');
if (name == 'test_test') return 1234;
else return 'Meh';
},
set: function(obj, name, value) {
console.log('write request to ' + name + ' property with ' + value + ' value');
},
}
);
console.log(p.test_test);
console.log(p.test);
p.qqq = 'test';
result:
read request to test_test property
1234
read request to test property
Meh
write request to qqq property with test value
var test = {};
Object.defineProperty(test, "hello", {
get : function () {
return this._hello;
},
set : function (val) {
alert(val);
this._hello = val;
}
});
test.hello = "world";
Something like that. But it will not work on old browsers.
You can find more options here: http://robertnyman.com/javascript/javascript-getters-setters.html
If you really insist on keeping the test.hello = "world" syntax to detect changes for existing properties, then you'll have to wait a few years for Object.watch to become part of the next EcmaScript standard.
Luckily, you can do the same in EcmaScript 5 using Object.defineProperty. Eli Grey made a nice Object.watch polyfill which you can call like this:
var test = {};
test.watch("hello", function(propertyName, oldValue, newValue) {
console.log(propertyName + " has been set to " + newValue);
});
test.hello = "world"; // triggers the watch handler
You could modify his code to trigger a different handler inside the getter as well, so you can detect property accesses.
Unfortunately, browser support is limited to modern browsers including Internet Explorer 9, Firefox 4, Chrome, Opera 12 and Safari 5.
If you want to trigger a handler when a new property is set, you'll have even more trouble. The best you could do is wrapping your object inside a proxy and placing a set trap. You can then detect whether the property already existed by testing if this.getOwnPropertyDescriptor(name) returns a 'truthy' value. The Proxy API is very experimental though and only a few browsers provide a prototype implementation to play with. You'll probably have to wait quite a while to get a completed API with decent browser support.
you need a library that provides key-value observing and bindings.
ember-metal is one such library.
basically you create objects, and you can register observers on properties of those objects.
var obj = Em.Object.create({
val: null
valDidChange:function(){...}.observes('val')
});
valDidChange will fire whenever val property changes, so
obj.set('val', 'newValue');
will cause the observer to fire.
What about something like this? Here's a jsfiddle.
var objectManager = function(obj, setCallback){
this.obj = obj;
this.setCallback = setCallback;
};
objectManager.prototype.setProperty = function(prop, value){
this.obj[prop] = value;
this.setCallback(prop);
};
objectManager.prototype.getObj = function(){
return this.obj;
};
// USAGE:
var testMgr = new objectManager({}, function(prop){
console.log(name + ' has been set.');
});
testMgr.setProperty("hello", "world"); //should log "hello has been set.";

Categories