Extending an ActiveXObject in javascript

2019-01-15 01:54发布

问题:

I want to add some functionality track certain calls to ActiveX object methods in javascript.

I usually create my activeX object like this: var tconn = new ActiveXObject("Tconnector");

I need to log every time the open method is called on tconn and all other instances of that activeX control.

I cant modify tconn's prototype because it does not have one!

I think that i can create a dummy ActiveXObject function that creates a proxy object to proxy calls to the real one. Can you help me do that?

Note: writing a direct wrapper is out of question, because there are already 1000s of calls to this activeX within the application.

回答1:

You can in fact override ActiveXObject().

This means you can try to build a transparent proxy object around the actual object and hook on method calls. This would mean you'd have to build a proxy around every method and property your ActiveX object has, unless you are absolutely sure there is no code whatsoever calling a particular method or property.

I've built a small wrapper for the "MSXML2.XMLHTTP" object. There are probably all kinds of problems you can run into, so take that with a grain of salt:

var ActualActiveXObject = ActiveXObject;

var ActiveXObject = function(progid) {
  var ax = new ActualActiveXObject(progid);

  if (progid.toLowerCase() == "msxml2.xmlhttp") {
    var o = {
      _ax: ax,
      _status: "fake",
      responseText: "",
      responseXml: null,
      readyState: 0,
      status: 0,
      statusText: 0,
      onReadyStateChange: null
      // add the other properties...
    };
    o._onReadyStateChange = function() {
      var self = o;
      return function() {
        self.readyState   = self._ax.readyState;
        self.responseText = self._ax.responseText;
        self.responseXml  = self._ax.responseXml;
        self.status       = self._ax.status;
        self.statusText   = self._ax.statusText;
        if (self.onReadyStateChange) self.onReadyStateChange();
      }
    }();
    o.open = function(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword) {
      varAsync = (varAsync !== false);
      this._ax.onReadyStateChange = this._onReadyStateChange
      return this._ax.open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword);
    };
    o.send = function(varBody) {
      return this._ax.send(varBody);
    };
    // add the other methods...
  }
  else {
    var o = ax;
  }

  return o;
}

function Test() {
  var r = new ActiveXObject('Msxml2.XMLHTTP');

  alert(r._status);  // "fake"

  r.onReadyStateChange = function() { alert(this.readyState); };
  r.open("GET", "z.xml");
  r.send();

  alert(r.responseText);
}

Disclaimer: Especially the async/onReadyStateChange handling probably isn't right, and the code may have other issues as well. As I said, it's just an idea. Handle with care.

P.S.: A COM object is case-insensitive when it comes to method- and property names. This wrapper is (as all JavaScript) case-sensitive. For example, if your code happens to call both "Send()" and "send()", you will need a skeleton "Send()" method in the wrapper as well:

o.Send = function() { return this.send.apply(this, arguments); };


回答2:

Thank you very much for your wrapper. With your help I was able to create a xmlrequest detector for IE and FF and the rest.

I have added a version (combined from another example) that works for FF , IE and the rest of the gang,

if(window.XMLHttpRequest)
{
var XMLHttpRequest = window.XMLHttpRequest;

// mystery: for some reason, doing "var oldSend = XMLHttpRequest.prototype.send;" and 
//  calling it at the end of "newSend" doesn't work...
var startTracing = function () {
    XMLHttpRequest.prototype.uniqueID = function() {
        // each XMLHttpRequest gets assigned a unique ID and memorizes it 
        //  in the "uniqueIDMemo" property
        if (!this.uniqueIDMemo) {
            this.uniqueIDMemo = Math.floor(Math.random() * 1000);
        }
        return this.uniqueIDMemo;
    }

    // backup original "open" function reference
    XMLHttpRequest.prototype.oldOpen = XMLHttpRequest.prototype.open;

    var newOpen = function(method, url, async, user, password) {
        console.log("[" + this.uniqueID() + "] intercepted open (" + 
                    method + " , " + 
                    url + " , " + 
                    async + " , " + 
                    user + " , " + 
                    password + ")");
        this.oldOpen(method, url, async, user, password);
    }

    XMLHttpRequest.prototype.open = newOpen;

    // backup original "send" function reference
    XMLHttpRequest.prototype.oldSend = XMLHttpRequest.prototype.send;

    var newSend = function(a) {
        console.log("[" + this.uniqueID() + "] intercepted send (" + a + ")");
        var xhr = this;
        var onload = function() { 
            console.log("[" + xhr.uniqueID() + "] intercepted load: " + 
                    xhr.status + 
                    " " + xhr.responseText); 
        };

        var onerror = function() { 
            console.log("[" + xhr.uniqueID() + "] intercepted error: " + 
                    xhr.status); 
        };

        xhr.addEventListener("load", onload, false);
        xhr.addEventListener("error", onerror, false);

        this.oldSend(a);
    }
    XMLHttpRequest.prototype.send = newSend;
}


startTracing();
}
else if (window.ActiveXObject) {
var ActualActiveXObject = ActiveXObject;

var ActiveXObject = function(progid) {
    var ax = new ActualActiveXObject(progid);

    if (progid.toLowerCase() == "msxml2.xmlhttp") {

        var o = {
            _ax: ax,
            _status: "fake",
            responseText: "",
            responseXml: null,
            readyState: 0,
            status: 0,
            statusText: 0,
            onReadyStateChange: null
        };
        o._onReadyStateChange = function() {
            var self = o;
            return function() {
            self.readyState   = self._ax.readyState;
            if (self.readyState == 4) {
                self.responseText = self._ax.responseText;
                self.responseXml  = self._ax.responseXml;
                self.status       = self._ax.status;
                self.statusText   = self._ax.statusText;
            }
                if (self.onReadyStateChange) self.onReadyStateChange();
            }
        }();
        o.open = function(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword) {
            console.log("intercepted open (" + 
                bstrMethod + " , " + 
                bstrUrl + " , " + 
                varAsync + " , " + 
                bstrUser + " , " + 
                bstrPassword + ")");
            varAsync = (varAsync !== false);
            this._ax.onReadyStateChange = this._onReadyStateChange
            return this._ax.open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword);
        };
        o.send = function(varBody) {
            return this._ax.send(varBody);
        };
    }
    else
        var o = ax;
    return o;
}
}


回答3:

A little fix for "The data necessary to complete this operation is not yet available" in IE6 - waiting for completeness before population of reponse properties:

self.readyState   = self._ax.readyState;
 if (self.readyState == 4) {
  self.responseText = self._ax.responseText;
  self.responseXml  = self._ax.responseXml;
  self.status       = self._ax.status;
  self.statusText   = self._ax.statusText;
 }
 if (self.onReadyStateChange) self.onReadyStateChange();


回答4:

The problem here is that it seems that IE wont allow saving of the original activXObject constructor and will give a stack overflow (;-) upon creating the ActualActiveXObject. it seems that this is special for the ActivX because it is working when doing that with other javascript objects.