Wednesday, August 29, 2012

The pernicious effects of the Comcast Protection Suite....

This mystery had been eluding me for at least 5-6 months since we started introducing JavaScript exception monitoring, but I finally was better able to understand why the Comcast Protection Suite has been causing so many problems for many our users. No, it isn't injecting its own jQuery, and no it isn't defining its own $, but rather it's trying to do anti keystroke logging....

We knew anecdotally that disabling the Comcast plug-in solved the issue, but I could never explain why the exception occurred (such as the one below). Apparently the Comcast Protection Suite installs a Browser Helper Object DLL into Internet Explorer. The DLL has just as much control over the DOM that JavaScript modules do. It apparently is also adding keyup (and perhaps keydown) event handlers to the DOM, presumably to keep somebody else from capturing your keystrokes.

Not only does it slow down overall performance on browsers, but the DLL also causes conflicts with jQuery because jQuery tries to execute the native events after executing jQuery events (i.e. running 'onclick' events after bind('click') or live('click') events). I guess there are some issues running DLL onclick events in JavaScript, since an exception gets generated in not being able to call apply() on these handlers. If you try...catch them, the problem at least gets mitigated...but this requires a change directly within jQuery 1.7.2. jQuery 1.8.0 is out, but it still has this same problem.

Also, apparently the Comcast Protection Suite software that gets passed along for a lot of new Comcast users, which is why we believe this problem is so pervasive. I'm even more astounded by Norton, which had this response after a user complained about the slower keystroke rates once this software was installed: 

This particular issue appears to be isolated to this specific site and is directly related to a JavaScript function the website owners have implemented to test whether the first name or last name text filed is only alpha characters.  The method employed at the website to check for alpha characters is not the standard approach, which is to check for the input as it is added onKeyUp. The standard JS best practice is to use a regular expression that checks and validates the entire field input at form submission.  Therefore we expect this to be an isolated issue.

Besides asking every user to disable the Comcast toolbar plug-in, the workaround/fix is actually quite simple.  Apparently the native onclick handler checks need to verify that apply() can be run.  Normally DLL onclick handlers return 'undefined', which therefore are missing an apply() function.  We can enforce this check within the jQuery code, for which we'll be submitting a patch soon.

32123212            // Note that this is a bare JS function and not a jQuery handler
32133213            handle = ontype && cur[ ontype ];
3214             if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
3214             // Workaround for Comcast Protection Suite bug
3215             // Apparently the handle function is 'undefined' but not really undefined...
3216             if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
32153217                event.preventDefault();
32163218            }
32173219        }

The jQuery bug report is here:

Using JavaScript line breaking in YUI Compressor

For the past 5 months of introducing JavaScript exceptions logging, there have been "Object doesn't support this property or method" or "Object doesn't support property or method 'apply' that has been elusive in trying to diagnose in Internet Explorer browsers.  The error messages never occurred in other browsers but we saw them quite often in different users.  While some of these exceptions were generated from actually calling methods on JavaScript objects didn't exist, many of the stack traces seemed to emanate directly from jQuery.

One of our challenges was to try to understand from where these exceptions within jQuery were occurring.  jQuery usually comes minified with no line breaks, so we ran our JS minifiers with the YUI Compressor with the --line-break option (i.e. --line-break 150).  Before adding this option, Internet Explorer would often report an error on line 2, which pretty much amounted to the entire jQuery code. By breaking the minified code into smaller chunks, the line numbers could then allow further information on pinpointing this exact source of conflicts:

java -jar ../external/yuicompressor-2.4.7.jar jquery-1.7.2.min.js --line-break 150 > jquery-1.7.2_yui.min.js

line: 33
}c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[],0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(;m!=this;m=m.parentNode||this){if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;ke&&j.push({elem:this,matches:d.slice(e)});fo

column: None
line: 34
func: filter

column: None
line: 31
func: trigger

This stack trace helped us pinpoint the issue to the Comcast Protection Suite, since it indicated the  problem was happening directly inside the jQuery Event module.  The jQuery Event module is used to attach and trigger jQuery events, as well as to implement the event propagation path described in the W3 standard.  By issuing try/except clauses within the dispatch() code, we were able to find exactly where the exception occurred:

        for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {

            cur = eventPath[i][0];
            event.type = eventPath[i][1];

            handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
            if ( handle ) {
  handle.apply( cur, data );
     // Note that this is a bare JS function and not a jQuery handler                                                                                            
            handle = ontype && cur[ ontype ];
            if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {

In other words, what jQuery seems to do is execute all the jQuery-related events before attempting to call the native JavaScript events (i.e. jQuery 'click' events will first be executed before the native 'onclick' events get called).  Somehow the Comcast Protection Suite adds an onclick handler that appears as 'undefined' to jQuery.  The if statement passes except fails when attempt to execute the handle.apply() statement.

More on this finding on the Comcast Protection Suite in this next writeup...