/*
* emulator has methods:
* 
* loadDevice(string: media, int: width, int: height, int: connection, int:storage)
* - this function will prepare the device and show the screen
* - at the end it will call this.launchWidget()
*
* launchWidget()
* - prepares config.xml for reading and waits for the onload event to call this.analyseWidget()
*
* analyseWidget()
* - reads the config.xml and alerts the user if it is invalid
* - sets the initial coordinates of the widget based on the width/height defined
* - calls this.runWidget()
*
* runWidget()
* - sets some more properties
* - creates the iframe for the widget to run in
*
* begin()
* - gets called by the index.html inside the iframe (while it is loading)
* - creates all the wrappers and environment stuff for the index.html
* 
*/

var emulator = new function()
{
    // e_ is an element reference
  var e_device;
  var e_screen;
  var e_screenContent;
  var e_frame;
  var e_desktop;
  var e_dockBlocker;
  
  var self = this;
  
  // initialise element references
  this.init = function()
  {
    e_device = g('device');
    e_screen = g('screen');
    e_screenContent = g('screen-content');
    e_frame = g('frame');
    e_desktop = g('desktop');
    e_dockBlocker = g('dockBlocker');
    
    g('info-program').addEventListener('click', function()
    {
      e_screen.removeClass('widgetMinimised');
    }, false);
    
  };
  
  this.interval = 0;
  
  this.clearPreferences = function()
  {
    ui.device.widget.preferences = {};
    ui.device.widget.storage = 0;
    widget.setPreferenceForKey('','preferences');
  }
  
  this.loadDevice = function(mediaType, width, height, framerate, connection, storage, dockX, dockY, autoload)
  {
    e_dockBlocker.style.display = 'none';
    e_device.style.display = 'block';
  
    if (mediaType == 'tv')
    {
      g('fuzz').style.display = 'block';
      var startDate = (new Date()).getTime();
      var interval = setInterval(function()
      {
        var elapsed = (new Date()).getTime() - startDate;
        if (elapsed < 0 || elapsed > 600)
        {
          g('fuzz').style.display = '';
          clearInterval(interval);
        }
        else
        {
          g('fuzz').style.backgroundPosition = Math.floor(Math.random()*400)+'px '+Math.floor(Math.random()*400)+'px';
        }
      }, 25);
    }
  
    var d = ui.device; // shorthand
  
    d.settings = {
      loaded: true,
      framerate: framerate,
      connection: connection,
      storage: storage,
      barsize: 0,
      dockX: dockX,
      dockY: dockY
    };
    
    // media object
    d.media = new Media();
    d.media.type = mediaType;
    
    d.media.features['device-width'] = width;
    d.media.features['device-height'] = height;
    d.media.features['device-aspect-ratio'] = width/height;
    d.media.features['color'] = 8;
    d.media.features['monochrome'] = 0;
    d.media.features['resolution'] = 96.4;
    d.media.features['scan'] = "progressive";
    d.media.features['grid'] = 0;
    /* width, height and -o-widget-mode are set after analysing confix.xml */
    
    // directly exposed to the iframe's window:
    d.screen = {
      width: width,
      height: height,
      availWidth: width,
      availHeight: height-d.settings.barsize
    };
    
    d.widget.opened = false;
    d.widget.loaded = false;
    d.widget.began = false;
    
    e_screen.style.width = width + 'px';
    e_screen.style.height = height + 'px';
    
    g('info').style.top = height;
    g('info').style.display = 'block'; // make sure we can read offsetWidth
    var infoWidth = Math.min(450, Math.max(240, width*0.8));
    g('info').style.left = Math.floor((width-infoWidth)/2)+'px';
    g('info').style.width = Math.floor(infoWidth)+'px';
    
    e_device.className = mediaType;
    
    window.scrollbars.fix(true); // true causes full scrollbar reset
    
    ui.updateStatus();
    
    ui.resizeWindow();
  
    if (autoload) this.launchWidget(); // autoload
  };
  
  
  
  var divElement = null;
  
  // loads the config.xml
  this.launchWidget = function()
  {
    g('desktop').removeClass('error');
    e_dockBlocker.style.display = 'none';
    e_screen.removeClass('widgetMinimised');
    g('frameWrapper').innerHTML = '';
    divElement = document.createElement('div');
    divElement.setAttribute('style','position:absolute;visibility:hidden;width:1px;height:1px;top:-100px;left:-100px;overflow:hidden;');
    document.body.appendChild(divElement);
    divElement.innerHTML = '<iframe src="widget/config.xml" onload="emulator.analyseWidget(this.contentDocument)"></iframe>';
  }
  
  this.analyseWidget = function(xml)
  {

    divElement.parentNode.removeChild(divElement);

    var widget = null;
    var width = null;
    var height = null;
    
    try {
      widget = xml.getElementsByTagName('widget')[0];
    }
    catch(e){
      ui.failWidget('config');
      return;
    }
    
    if (!widget)
    {
      ui.failWidget('config');
      return;
    }
    
    var dockable = widget.getAttribute('dockable');
    
    // fixme: dockable="yes" is not allowed per spec. Added as workaround until bug is fixed (runeh)
    ui.device.widget.dockable = ui.device.settings.dockX && (dockable=='true' || dockable=='dockable' || dockable=='yes');
    
    if (ui.device.widget.dockable)
    {
      g('info-program').addClass('dockable');
    }
    else
    {
      g('info-program').removeClass('dockable');
    }
    
    function stripInt(str)
    {
      return parseInt(str.replace(/^\s+0*|\s+$/g,''));
    }
    
    try { width  = stripInt(widget.getElementsByTagName('width' )[0].firstChild.nodeValue); } catch ( e ) { }
    try { height = stripInt(widget.getElementsByTagName('height')[0].firstChild.nodeValue); } catch ( e ) { }
    
    // default values
    if ( !width  || width  <= 0) width  = 200;
    if ( !height || height <= 0) height = 150;
    
    try {
      ui.setIcon(widget.getElementsByTagName('icon')[0].firstChild.nodeValue);
    }
    catch (e)
    {
      ui.setIcon(null)
    }
    
    var d = ui.device;
    
    d.widget.width  = width;
    d.widget.height = height;
    
    d.widget.top  = Math.max( 0, Math.round( ( d.screen.availHeight - d.widget.height ) / 2) );
    d.widget.left = Math.max( 0, Math.round( ( d.screen.availWidth  - d.widget.width  ) / 2) );
    
    d.media.features['width'] = width;
    d.media.features['height'] = width;
    d.media.features['-o-widget-mode'] = 'widget';
    
    d.widget.opened = true;
    d.widget.loaded = false;
    
    this.runWidget();
    
    
  };
    
  this.runWidget = function()
  {
    
    e_desktop.style.display = 'none';
    e_screenContent.style.display = 'block';
    g('info-program').getElementsByTagName('span')[0].innerHTML = '';
    g('info-program').style.display = 'block';
    
    var d = ui.device;
    
    d.widget.scrollTop = 0;
    d.widget.scrollLeft = 0;

    d.widget.preferences = widget.preferenceForKey('preferences') || {} ;
    
    d.widget.storage = 0;
    for (var i in d.widget.preferences)
    {
      if (typeof d.widget.preferences[i] == 'string')
      {
        d.widget.storage += (i+'').length + (d.widget.preferences[i]+'').length;
      }
    }
    
    d.widget.mode = 'widget';
    
    g('frameWrapper').innerHTML = '\
      <iframe id="frameObject" scroll="no" style="top:'+d.widget.top+'px;left:'+d.widget.left+'px;" src="widget/index.html" width="'+ui.device.widget.width+'" height="'+ui.device.widget.height+'">\
      <\/iframe>\
    ';
    
    if (!g('frameObject').document)
    {
      ui.failWidget('index');
      return;
    }
    
    g('frameObject').onload = function()
    {
      if (!d.widget.began)
      {
        ui.failWidget('script');
      }
    }
    
    window.scrollbars.fix(true); // true causes full scrollbar reset
    
    ui.updateStatus();
  }
  
  var interval = 0;
  
  this.closeWidget = function()
  {
    if (ui.device.widget.opened)
    {
      if (emulator.interval)
      {
        clearInterval(emulator.interval);
        emulator.interval = 0;
      }
      e_dockBlocker.style.display = 'none';
      
      ui.device.widget.opened = false;
      ui.device.widget.began = false;
      
      emulator.changeWidgetMode = null;
    }
  }
  
  this.begin = function(frameWindow) // creates the wrappers and such
  {
    ui.device.widget.began = true;
    
    if (!frameWindow)
    {
      opera.postError('Send a reference to the window object as the first argument to parent.emulator.begin.');
      return;
    }
    emulator.window = frameWindow;
    
    emulator.window.addEventListener('load',function()
    {
       ui.device.widget.loaded = true;
    }, false);
    
    // add css to make it undraggable, and remove scrollbars
    frameWindow.document.write('<style type="text/css">\html{-apple-dashboard-region:dashboard-region(control rectangle);overflow:hidden;}</style>');
    
    var frame = document.getElementById('frameObject');
    
    var e_infoProgramTitle = g('info-program').getElementsByTagName('span')[0];
    var docTitle = '';

    function looper()
    {
      if (!frameWindow || !frameWindow.document)
      {
        clearInterval(interval);
        interval = 0;
        return;
      }
      
      var str = frameWindow.document.title || '';
      if (str != docTitle)
      {
        docTitle = str;
        e_infoProgramTitle.innerText = str;
      }
      
      ui.device.media.matchDocument(frameWindow.document);
    }
    
    if (interval)
    {
      clearInterval(interval);
    }
    interval = setInterval(looper, 500);
    setTimeout(looper,1); // fast response
    
    /**
     * Change the widget mode
     *
     * <p>This function will change the mode
     * the widget is running in, currently
     * supports 'widget' and 'docked' modes.</p>
     *
     * @param {string} mode The new mode to switch to
     */
    
    self.toggleDocked = function()
    {
      changeWidgetMode( ui.device.widget.mode == 'docked' ? 'widget' : 'docked');
    };
    
    /**
     * Changes widgetMode
     *
     * <p>This will change the mode of the widget
     * (exmaple from 'widget' to 'docked'). It will
     * make the visible changes (by adjusting the iFrame),
     * set the appropriate values (screen.availWidth etc.)
     * then trigger the events on the widget object.</p>
     *
     * @param {string} mode The new mode to change to
     */
    
    function changeWidgetMode( mode )
    {
      var d = ui.device; // alias
      
      if (mode != 'widget' && mode != 'docked')
      {
        opera.postError('Unsupported mode attempted: ' + mode)
        return;
      }
      if (d.widget.mode == mode)
      {
        return; // nothing to change
      }
      if ( mode == 'docked' && !d.widget.dockable)
      {
        return; // widget does not implement 'docked'
      }
      
      d.widget.mode = mode;
      
      var width;
      var height;
      var top;
      var left;
      
      if (mode == 'docked')
      {
        d.widget.old = { // store old values for when we go back
          top: d.widget.top,
          left: d.widget.left,
          width: d.widget.width,
          height: d.widget.height
        };
        d.widget.width = d.settings.dockX;
        d.widget.height = d.settings.dockY;
        d.widget.top = 0;
        d.widget.left = d.screen.width-d.widget.width;
        
        e_dockBlocker.style.width = d.widget.width+'px';
        e_dockBlocker.style.height = d.widget.height+'px';
        e_dockBlocker.style.display = 'block';
        
        frame.addClass('docked');
        /*
          uncommented the avail stuff since I'm not sure
          if it should be touched or not
        */
        //d.screen.availWidth = width;
        //d.screen.availHeight = height;
      }
      else
      {
        e_dockBlocker.style.display = 'none';
      }
      if (mode == 'widget')
      {
        d.widget.width = d.widget.old.width;
        d.widget.height = d.widget.old.height;
        d.widget.top = d.widget.old.top;
        d.widget.left = d.widget.old.left;
        d.widget.old = null; // not needed any more
        
        frame.removeClass('docked');
        /*
          uncommented the avail stuff since I'm not sure
          if it should be touched or not
        */
        //d.screen.availWidth = d.screen.width;
        //d.screen.availHeight = d.screen.height - d.settings.barsize;
      }
      
      /* set actual dom values */
      frame.width = d.widget.width;
      frame.height = d.widget.height;
      frame.style.top = d.widget.top+'px';
      frame.style.left = d.widget.left+'px';
      
      /* broadcast values to the widget */
      frameWindow.screenLeft = d.widget.left;
      frameWindow.screenTop = d.widget.top;
      frameWindow.innerWidth = d.widget.width;
      frameWindow.innerHeight = d.widget.height;
      
      d.media.features['-o-widget-mode'] = mode;
      d.media.features['width'] = d.widget.width;
      d.media.features['height'] = d.widget.height;
      
      ui.device.media.matchDocument(frameWindow.document)
      
      frameWindow.widget.dispatchEvent({
        type: 'widgetModeChange',
        widgetMode: mode
      });
      frameWindow.widget.dispatchEvent({
        type: 'resolution',
        width: width,
        height: height
      });
      
      scrollbars.fix();
      ui.updateStatus();
      
    };
    
    // change the screen size and properties
    frameWindow.screen = ui.device.screen;
    frameWindow.screenLeft = ui.device.widget.left;
    frameWindow.screenTop = ui.device.widget.top;
    frameWindow.innerWidth = ui.device.widget.width;
    frameWindow.innerHeight = ui.device.widget.height;
    
    
    // remove reference to the parent window
    frameWindow.parent = frameWindow;
    frameWindow.top = frameWindow;
    
    // over-ride the close function
    frameWindow.close = function()
    {
      ui.closeWidget();
    };
    
    frameWindow.document.addEventListener('click',function(e)
    {
      if (ui.device.widget.mode == 'docked')
      {
        self.toggleDocked(); // clicking docked widget should 'undock'
      }
      else
      {
        if (document.onclick) document.onclick(); /* not sure why this line is here, pending deletion... */
      }
    }, false);
    
    e_dockBlocker.onclick = function()
    {
      if (ui.device.widget.mode == 'docked')
      {
        self.toggleDocked();
      }
    }
    
    frameWindow.document.documentElement.addEventListener('DOMNodeInserted',function(e)
    {
        ui.device.media.matchDocument(frameWindow.document);
    }, false);
    
    // will be called by window.resizeTo and resizeBy
    function resizeTo(x, y)
    {
      if (ui.device.widget.mode == 'docked')
      {
        return; // docked widgets cannot resize
      }
      
      x = parseInt(x);
      y = parseInt(y);
      if ( typeof x != 'number' || isNaN(x) || !isFinite(x) ) return;
      if ( typeof y != 'number' || isNaN(y) || !isFinite(y) ) return;
      
      x = x<1 ? 1 : x>10000 ? 10000 : x;
      y = y<1 ? 1 : y>10000 ? 10000 : y;
      
      var d = ui.device; // shortcut
      // store the values
      d.widget.width = x;
      d.widget.height = y;
      // apply the values
      frame.width = x;
      frame.height = y;
      // broadcast new values to the widget
      frameWindow.innerWidth = x;
      frameWindow.innerHeight = y;
      // store values on media object
      ui.device.media.features['width'] = x;
      ui.device.media.features['height'] = y;
    
      if (
        d.widget.left > 0 && x + d.widget.left > d.screen.availWidth
        ||
        d.widget.top > 0 && y + d.widget.top > d.screen.availHeight
      )
      {
        moveTo( Math.min(d.widget.left, d.screen.width-x), Math.min(d.widget.top, d.screen.height-y) );
      }
      // output to the developer
      ui.updateStatus();
      scrollbars.fix();
      
      ui.device.media.matchDocument(frameWindow.document);
    }
    
    // will be called by window.moveTo and window.moveBy
    function moveTo(x, y)
    {
      if (ui.device.widget.mode == 'docked')
      {
        return; // docked widgets cannot move
      }
      
      x = parseInt(x);
      y = parseInt(y);
      if ( typeof x != 'number' || isNaN(x) || !isFinite(x) ) return;
      if ( typeof y != 'number' || isNaN(y) || !isFinite(y) ) return;
      
      x = Math.max(0, Math.min( x, ui.device.screen.availWidth - ui.device.widget.width) );
      y = Math.max(0, Math.min( y, ui.device.screen.availHeight - ui.device.widget.height) );
      
      // store the values
      ui.device.widget.left = x;
      ui.device.widget.top = y;
      // apply the values
      frame.style.left = x + 'px';
      frame.style.top = y + 'px';
      // broadcast new values to the widget
      frameWindow.screenLeft = x;
      frameWindow.screenTop = y;
      // output to the developer
      ui.updateStatus();
    }
    
    var hideNotification = null; // function
    var showNotification = null; // function
    
    // function below sets both variables above
    (function()
    {
      var parent = document.getElementById('notifier');
      var text = document.getElementById('notifier-text');
      var close = document.getElementById('notifier-close');
      
      var HEIGHT = 28;
      
      var frame = 0;
      var interval = 0;
      var reservedCallback = null;
      
      function nextFrame()
      {
        frame++;
        
        var off = frame/20;
        off = off < 1 ? off : off > 7 ? 8-off : 1;
        
        parent.style.marginBottom = Math.round((off-1)*HEIGHT) + 'px';
        
        if (frame == 160)
        {
          hideNotification();
        }
      }      
      
      hideNotification = function()
      {
        reservedCallback = null;
        
        clearInterval(interval);
        interval = 0;
        
        text.disabled = true;
        text.value = '';
        close.disabled = true;
        parent.style.display = '';
      };
      
      showNotification = function(str, callback)
      {
        reservedCallback = callback;
        
        parent.style.display = 'block';
        
        text.value = str+'';
        text.disabled = false;
        close.disabled = false;
        
        frame = 0;
        if (!interval)
        {
          interval = setInterval(nextFrame, 30);
          nextFrame();
        }
      };
      
      close.onclick = hideNotification;
      text.onclick = function()
      {
        /* we want to call hide() before
        we do the callback in case an error
        occurs in the callback (We don't want
        the notifier to still be there, but
        then we need to store a reference to
        the callback because hide will
        destroy it */
        var callback = reservedCallback;
        hideNotification();
        if (callback)
        {
          callback();
        }
      }
      
    })();
    
    frameWindow.resizeTo = function(valueWidth, valueHeight)
    {
      resizeTo(valueWidth, valueHeight);
    };
    
    frameWindow.resizeBy = function(deltaWidth, deltaHeight)
    {
      frameWindow.resizeTo(ui.device.widget.width + deltaWidth, ui.device.widget.height + deltaHeight);
    };
    
    frameWindow.moveTo = function(valueLeft, valueTop)
    {
      moveTo(valueLeft, valueTop);
    };
    
    frameWindow.moveBy = function(deltaLeft, deltaTop)
    {    
      frameWindow.moveTo(ui.device.widget.top + deltaTop, ui.device.widget.left + deltaLeft);
    };
  
    /*
      For the preferences, we add an underscore before each key so that
      they do not conflict with our own preferences
    */
    
    var eventListeners = {};
    
    frameWindow.widget = {
      
      /*
        Three functions that follow allow events
        to be added to the widget object.
      */
      mode: 'widget',
      addEventListener: function(evt, funct)
      {
	evt = evt.toLowerCase();
        if (!eventListeners.hasOwnProperty(evt))
        {
          eventListeners[evt] = [];
        }
        eventListeners[evt].push(funct);
      },
      removeEventListener: function(evt, funct)
      {
	evt = evt.toLowerCase();
        if (!eventListeners.hasOwnProperty(evt)) { return; }
        var arr = eventListeners[evt];
        for (var i=0; i<arr.length; i++)
        {
          if (arr[i] == funct)
          {
            arr.splice(i,1);
            i--;
          }
        }
      },
      dispatchEvent: function(event)
      {
	var type = event.type.toLowerCase();
        if (!eventListeners.hasOwnProperty(type)){return;}
        var arr = eventListeners[type];
        
        for (var i=0; i<arr.length; i++)
        {
          arr[i].call(frameWindow.widget, event);
        }
      },
      
      preferenceForKey: function(key)
      {
        return ui.device.widget.preferences[key] || '';
      },
      setPreferenceForKey: function(pref, key)
      {
        pref = '' + pref, key = '' + key; // make them strings
        var oldPref = ui.device.widget.preferences[key];
        var storage;
        if (pref)
        {
          storage = ui.device.widget.storage + (oldPref ? pref.length - oldPref.length : pref.length + key.length);
          if (storage > ui.device.settings.storage)
          {
            throw new Error('Widget preferences have exceeded the maximum volume.');
          }
          ui.device.widget.storage += oldPref ? pref.length - oldPref.length : pref.length + key.length;
          ui.device.widget.preferences[key] = pref.toString();
        }
        else
        {
          delete ui.device.widget.preferences[key];
        }
        ui.updateStatus();
        widget.setPreferenceForKey(ui.device.widget.preferences, 'preferences');
      },
      openURL: function()
      {
        widget.openURL.apply(widget, arguments);
      },
      show : function()
      {
        e_screen.removeClass('widgetMinimised');
      },
      hide : function()
      {
        e_screen.addClass('widgetMinimised');
      },
      showNotification : showNotification,
      
      getAttention : (function()
      {
        var count = 0;
        var MAX = 12;
        var interval = 0;

        function flash()
        {
          count++;
          if (count == MAX)
          {
            clearInterval(interval);
            interval = 0;
            g('info-program').removeClass('flashing');
          }
          else
          {
            g('info-program').toggleClass('flashing')
          }
        }
        
        return function()
        {
          count = 0;
          if (!interval)
          {
            interval = setInterval(flash, 300);
          }
        };
      })()
      
      
    };
    
    
    if ( ui.device.settings.framerate > 0 )
    {
      var timeoutHandicap = ui.device.settings.framerate; // the shortest timeout to allow
      var frameSetTimeout = frameWindow.setTimeout;
      var frameSetInterval = frameWindow.setInterval;
      frameWindow.setTimeout = function()
      {
        if (window.config.emulateFramerate && arguments[1] < 2*timeoutHandicap)
        {
          arguments[1] = Math.floor( arguments[1] / 2 + timeoutHandicap );
        }
        return frameSetTimeout.apply(null,arguments);
      };
      frameWindow.setInterval = function()
      {
        if (window.config.emulateFramerate && arguments[1] < 2*timeoutHandicap)
        {
          arguments[1] = Math.floor( arguments[1] / 2 + timeoutHandicap );
        }
        return frameSetInterval.apply(null,arguments);
      };
    }
    
    /* wrap the XHR object */
    
    // we make a copy of the frame's xhr instead of our xhr to ensure the xhr heppens inside the context of the frame
    // this is important for relative uri's
    var frameXHR = frameWindow.XMLHttpRequest;
    
    var xhrSpeed = ui.device.settings.connection/1000; // bits per millisecond
    
    // avoid a closure
    function applyReadystatechange(self, xhr)
    {
      // first update properties such as readyState and responseText
      for (var i in xhr)
      {
        try
        {
          {
            if (typeof xhr[i] != 'function')
            {
              self[i] = xhr[i];
            }
          }
        }
        catch (e) { }
      }
    
      // dispatch readystatechange event
      if (typeof self.onreadystatechange == 'function') {
        self.onreadystatechange();
      }
    }
    
    frameWindow.XMLHttpRequest = function()
    {
      
      var self = this;
      
      var xhr = new frameXHR();
      
      // copy all methods and variables (except special ones we wish to customise)
      for (var i in xhr)
      {
        if (i == 'send' || i == 'abort') continue;
        try {
          if (typeof xhr[i] == 'function')
          {
            this[i] = (function(i){return function()
            {
              return xhr[i].apply(xhr, arguments);
            }})(i);
          }
          else
          {
            this[i] = xhr[i];
          }
        }
        catch (e) {}
      }
      
      var sendTime;
      var loadTimeout = 0;
      this.send = function()
      {
        sendTime = (new Date()).getTime();
        xhr.send.apply(xhr, arguments);
      };
      this.abort = function()
      {
        if (loadTimeout)
        {
          clearTimeout(loadTimeout);
          loadTimeout = 0;
        }
        xhr.abort.apply(xhr, arguments);
      };
      
      xhr.onreadystatechange = function()
      {

        if (this.readyState == 4 && xhrSpeed > 0) // (xhrSpeed == -1) is a sentinel value which triggers "default connection speed".
        {
          var totalTime = ((new Date()).getTime() - sendTime);
          var expectTime = (1000*32/xhrSpeed) + (this.responseText.length*8) / xhrSpeed; // *8 to go from bytes to bits
          if (window.config.emulateConnection && totalTime >=0 && totalTime < expectTime) // the >=0 check is in case the user changed the system time during transaction
          {
            loadTimeout = setTimeout(function()
            {
              loadTimeout = 0;
              applyReadystatechange(self, xhr);
            }, Math.ceil(expectTime - totalTime) ); // ceil to avoid 0
            return;
          }
        }
        // do it immediately for readystates 1,2,3 (and 4 if the xhr took a long time OR if xhrSpeed is sentinel value (-1) )
        applyReadystatechange(self, xhr);
      }
    };
  
    scrollbars.fix(false, frameWindow); // this allows us to add scroll events to the window
  }
  
  
};