// ==UserScript==
// @name        Convert 2 PHF (for Opera 9.10 or later)
// @version     3.22
// @author      Mike Samokhvalov <mikivanch at gmail dot com>
// @author      Azamadt Smaguloff <azekeprofit at gmail dot com>
// @date        2007-08-02
// @download    http://www.puzzleclub.ru/files/convert2phf.js
// ==/UserScript==

// javascript:(function(){convert2PHF.showPanel();})()
// Paste this to your address bar and press 'Enter'.
// If you have set this UserJS right,
// you will the panel which has savings' options.
//
// This panel also has icon (uper-left corner), which
// you can drag to your panel to show this panel next
// time.

// RUSSIAN:
// Скопируйте в адресную строку:
// javascript:(function(){convert2PHF.showPanel();})()
// У появившейся панели будет иконка, она же -- перетаскиваемая кнопка.
// Её можно сбросить себе на панель чтобы вызывать панель сохранения в PHF
// в дальнейшем или просто щёлкнуть мышкой.

var convert2PHF = {

  panel : null,
  panelStyle : null,
  
  domain : '',
  imgUrl : new Array(),
  styleSheets : null,
  extCSS : '',
  emptyCSS : ' ',
  
  imgExtFrame : new Array(),    
  ssExtFrame : null,
  urlImgToken : '#convert2PHF_frame_img_',
  urlSSToken : '#convert2PHF_frame_ss_',
  
  // Format
  // 0 - PHF (data: encoded images and embedded script and styles)
  // 1 - MHT (MIME-compatible mail message with HTML, and all other files as attachments)
  format : 0,
  
  // Image Saving
  bOnlyCurrentDomain : true,  
  bAlreadyLoaded : false,
  bBackgroundImages : true,
  
  //Script Saving
  // 0 - All
  // 1 - Inline only
  // 2 - None
  scriptSaving : 0,
  
  bRemoveFrames : false,
  bRemoveObjectsAndEmbeds : false,
  
  bCleanPage : false,
  
  imgDataUrlRegExp : /^data\s*:\s*image\//i,  
  backgroundImageContainer : 'ujs_convert2PHF_bgimage_container',
  panelId : 'ujs_PHF_panel_id',
  panelContentElementId : 'ujs_PHF_panel_cnt',
    
  storageDomain : 'bce20b24-afdf-4482-b379-737f29497fa0.com',
  preferencesCookie : 'convert2PHF_preferences',
  storage : new ujs_Storage('Convert2PHF'),
  storageFrameId : 'Convert2PHF_storage_frame',
  preferenceParams : new Array(
    'format', 'bOnlyCurrentDomain', 'bAlreadyLoaded', 'bBackgroundImages',
    'scriptSaving', 'bRemoveFrames', 'bRemoveObjectsAndEmbeds', 'bCleanPage'
  ),
  
  imgNum : 0,
  bImages : false,
  bContinue : false,

  closePanel : function()
  {
    if(this.panel)
      this.panel.parentNode.removeChild(this.panel);
      
    if(this.panelStyle)
      this.panelStyle.parentNode.removeChild(this.panelStyle);
  },
  

  showPanel : function()
  {
    // storage
    this.storage.domain = this.storageDomain;
    this.storage.getDataFunction = this.getDataFromStorage;
    var onload = function(){
      convert2PHF.storage.loadData(convert2PHF.preferencesCookie,
        convert2PHF.preferencesCookie);
    };
    this.storage.createFrame(this.storageFrameId, onload);
  },
  
  
  createPanel : function()
  {
    styleId = 'ujs_PHF_panel_style_id';
    if(!document.getElementById(styleId))
    {
      var css = (
        '#' + this.panelId + '{color: #162d59; background: #bfcfef; border: 1px solid #4070cf; margin: 0 auto; padding: 0; font-size: 9pt; font-family: verdana, arial, helvetica, sans-serif; font-weight: normal; display: block; text-align: left; width: 252px; height: auto; min-width: 0; min-height: 0; max-width: none; max-height: none; opacity: 0; z-index: 9995; float: none; clear: both;}'
        +' #' + this.panelId + ' * {color: inherit; border: none; font-size: inherit; font-family: inherit; font-style: normal; font-weight: inherit; float: none; margin: 0; padding: 0; line-height: normal; text-align: left; text-decoration: none; width: auto; height: auto; min-width: 0; min-height: 0; max-width: none; max-height: none;}'
        +' #ujs_PHF_panel_hdr{color: #fff; background: #7396dc; padding: 2px 2px 3px 2px; display: block; font-weight: bold; font-size: 12pt; vertical-align: middle;}'
        +' #ujs_PHF_panel_hdr a{background: inherit;}'
        +' #ujs_PHF_panel_hdr img{display: inline; vertical-align: middle;}'
        +' #' + this.panelContentElementId + '{color: #162d59; background: inherit; display: block; padding: 5px 5px; line-height: 1.6;}'
        +' #' + this.panelContentElementId + ' fieldset{color: #162d59; background: inherit; border: 1px solid #7396dc; padding: 5px;}'
        +' #' + this.panelContentElementId + ' legend{color: #1d3973; background: inherit; font-weight: bold; word-spacing: normal;}'
        +' #' + this.panelContentElementId + ' label{display: inline-block; color: #162d59; background: inherit; word-spacing: normal; min-width: 50px;}'
        +' #' + this.panelContentElementId + ' .ujs_PHF_panel_foot {color: #162d59; background: inherit; text-align: right;}'
        +' #' + this.panelContentElementId + ' input[type="radio"], #ujs_PHF_panel_cnt input[type="checkbox"] {}'
        +' #' + this.panelContentElementId + ' input[type="button"]{color: #fff; background: #7396dc; border: 1px solid #4070cf; padding: 2px 5px; min-width: 50px; text-align: center;}'
        +' #' + this.panelContentElementId + ' .ujs_PHF_panel_info {color: #3060bf; border: 1px solid #7396dc; font-weight: bold; padding: 0 5px;}'
      );  
      
      var s = document.createElement('style');
      s.id = styleId;
      s.setAttribute('type', 'text/css');
  		s.setAttribute('style', 'display:none !important;');			
  		s.appendChild(document.createTextNode(css));
      this.panelStyle = document.documentElement.appendChild(s);
    }
  
    if(!document.getElementById(this.panelId))
    {
      var btnSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAACPElEQVR42oySTYtcRRSGn9tdPT0z7c0kQUYcEEQigpAwRFf+DfFvuM3GP6WIuBey8gcEFUGCX0x3IJq+tz7OeY+Le2OcRewpqFWdeuo5dd7u8fcWz//a/v3hBxv6TQcLDq4QRMDV1vny2yv6s3f61EwcH530t/pT3tgsoLsBKKadcyZapVUn1SqsCc2vEHPxAZACTImmU9YSqVSx3zfMAgm6bgYeAglaC4Yx2PQi5ezs943WAveps/gfpfgPyD0wq0hGGouxHxrNArPrxa8jvTx3B0lITmrNadUxA/Prn3mwPZ/IkpMmonADn410Q5BbEPIJFHIUjkmYv7KJODA5QbPA5ciNJDmS2D0bkU+TMwfp9ZgOUATb7XjdKOfGd4+fIg9yCYbBKNXn9l4Fq+ug6zoWHQRBALkEd2UkhXN+3iMF7sFJE8enojVx0yUZqVZDEVztCp88POHBg/dZLuFoBes1HK8hLWHRzWk2+PmXxlffPOHHn3a89fabAKRhaLQmfvv9GbvdMat0j0dffE1/doeLi1s8+vz+BHCoDUqGfrOi3ywYxpHQZJ5eDHVuy3B3lkuIEOujxNEqTQnWZFIKDNnJ2XF3wqeJAySfUygvWE2sUnD39pqIKbHuk00pYhidYTTyaHgEEfrXaBEhIkRzUauTlvDZp5e89+4Zd26vcA9qdXIxcm7ksTHkNuWjU7y8n2KO8HJRqW3kyQ9bPr4856PLc6SgNp9BTilGLo39UPnjz19zLjsiLk4A/hkA4jTviQXLypUAAAAASUVORK5CYII=';
      var p = document.createElement('div');
      p.id = this.panelId;    
      var html = '<div id="ujs_PHF_panel_hdr">';
      html += '<a href="opera:/button/Go%20to%20page,%22javascript:(function(){convert2PHF.showPanel();})()%22,%22PHF%22,%22Save%20to%20PHF%22,%22Save%20document%22" title="Convert to PHF">';
      html += '<img src="' + btnSrc + '"';
      html += ' width="18" height="18"/></a>&nbsp;'
      html += 'Convert to PHF</div><div id="' + this.panelContentElementId + '">';

      // Format
       html += '<fieldset><legend>Format</legend>';
       html += '<label title="Portable Hypertext Format"><input type="radio" name="convert2PHF_format" value="PHF" onclick="convert2PHF.format = 0;"';
      if(this.format == 0)
      {
        html += ' checked';
      }
      html += '/>&nbsp;PHF</label>&nbsp;&nbsp; <span class="ujs_PHF_panel_info" title="Opened by Opera, Firefox, Safari, Konqueror">?</span><br/>';
      html += '<label title="Microsoft Hypertext Archive Format"><input type="radio" name="convert2PHF_format" value="MHT" onclick="convert2PHF.format = 1;"';
      if(this.format == 1)
      {
        html += ' checked';
      }
      html += '/>&nbsp;MHT</label>&nbsp;&nbsp; <span class="ujs_PHF_panel_info" title="Opened by Opera, Internet Explorer">?</span>';
      html += '</fieldset><br/>';
      
      // Image Saving
      html += '<fieldset><legend>Image Saving</legend>';
      html += '<label><input type="radio" name="convert2PHF_domain" value="From current domain" onclick="convert2PHF.bOnlyCurrentDomain = true;"';
      if(this.bOnlyCurrentDomain)
      {
        html += ' checked';
      }
      html += '/>&nbsp;From current domain</label><br/>';
      html += '<label><input type="radio" name="convert2PHF_domain" value="From all domains" onclick="convert2PHF.bOnlyCurrentDomain = false;"';
      if(!this.bOnlyCurrentDomain)
      {
        html += ' checked';
      }    
      html += '/>&nbsp;From all domains</label><br/>';
      html += '<br/><label><input type="checkbox" value="Already loaded" onclick="convert2PHF.bAlreadyLoaded = this.checked;"';
      if(this.bAlreadyLoaded)
      {
        html += ' checked';
      } 
      html += '/>&nbsp;Already loaded</label><br/>';
      html += '<label><input type="checkbox" value="Background images" onclick="convert2PHF.bBackgroundImages = this.checked;"';
      if(this.bBackgroundImages)
      {
        html += ' checked';
      } 
      html += '/>&nbsp;Background images</label>';
      html += '</fieldset><br/>';

      // Script Saving
      html += '<fieldset><legend>Script Saving</legend>';
      html += '<label><input type="radio" name="convert2PHF_script" value="All" onclick="convert2PHF.scriptSaving = 0;"';
      if(this.scriptSaving == 0)
      {
        html += ' checked';
      }
      html += '/>&nbsp;All</label> <span class="ujs_PHF_panel_small">(inline and external)</span><br/>';
      html += '<label><input type="radio" name="convert2PHF_script" value="Inline only" onclick="convert2PHF.scriptSaving = 1;"';
      if(this.scriptSaving == 1)
      {
        html += ' checked';
      }
      html += '/>&nbsp;Inline only</label><br/>';
      html += '<label><input type="radio" name="convert2PHF_script" value="None" onclick="convert2PHF.scriptSaving = 2;"';
      if(this.scriptSaving == 2)
      {
        html += ' checked';
      }
      html += '/>&nbsp;None</label>';
      html += '</fieldset><br/>';
      
      // Remove inline frames
      html += '<label><input type="checkbox" value="Remove inline frames" onclick="convert2PHF.bRemoveFrames = this.checked;"';
      if(this.bRemoveFrames)
      {
        html += ' checked';
      } 
      html += '/>&nbsp;Remove inline frames</label><br>';
      
      // Remove embedded content
      html += '<label><input type="checkbox" value="Remove embedded content" onclick="convert2PHF.bRemoveObjectsAndEmbeds = this.checked;"';
      if(this.bRemoveObjectsAndEmbeds)
      {
        html += ' checked';
      } 
      html += '/>&nbsp;Remove embedded content</label><br>';
      
      if(this.isPageCleaningEnable())
      {
        html += '<label><input type="checkbox" value="Clean web page" onclick="convert2PHF.bCleanPage = this.checked;"';
        if(this.bCleanPage)
        {
          html += ' checked';
        } 
        html += '/>&nbsp;Clean web page</label><br>'
      }
      
      html += '<br>';
      html += '<div class="ujs_PHF_panel_foot">';
      html += '<input type="button" value="OK" onclick="convert2PHF.putPreferencesToStorage(); convert2PHF.restorePage();">&nbsp;';
      html += '<input type="button" value="Cancel" onclick="convert2PHF.closePanel();">';
      html += '</div></div>';
      p.innerHTML = html;      
      this.panel = document.body.appendChild(p);
      this.centerPanel();
      
      convert2PHF.reloadImage(btnSrc, null);
      
      document.addEventListener('keyup', function(e){
        if(e.keyCode == 27)
        {
          e.target.removeEventListener(e.type, arguments.callee, false);
          convert2PHF.closePanel();          
        }
        else if(e.keyCode == 13)
        {
          e.target.removeEventListener(e.type, arguments.callee, false);
          convert2PHF.restorePage();
        }
      }, false);
    }
  },  


  centerPanel : function()
  {
    var l = (window.innerWidth - this.panel.offsetWidth) / 2;
    var t = (window.innerHeight - this.panel.offsetHeight) / 2;
    this.panel.style.position = 'fixed';
    this.panel.style.left = l;
    this.panel.style.top = t;
    this.panel.style.opacity = '0.9';
  },
  
  
  setPanelHtml : function(html)
  {
    var cnt = document.getElementById(this.panelContentElementId);
    if(cnt)
    {
      cnt.innerHTML = html;
      this.centerPanel();
    }
  },
  
  
  isPageCleaningEnable : function()
  {
    if(document.location.protocol == 'file:')
      return false;
    
    return true;  
  },
  
  
  restorePage : function()
  {
    this.domain = window.location.protocol + "//" + window.location.host;
    
    if(this.bCleanPage && this.isPageCleaningEnable())
    {
      this.setPanelHtml('Please wait...');
        
      var xmlhttp = new XMLHttpRequest();      
      xmlhttp.open("GET", window.location.href, true);
      xmlhttp.setRequestHeader("Content-Type", "text/html");
      xmlhttp.onreadystatechange = function() {
        if(this.readyState == 4)
        {
          if(this.responseText)
          {
            convert2PHF.closePanel();
            
            var prevent = function(e) {
              e.preventDefault();
            };
            window.opera.addEventListener('BeforeExternalScript', prevent ,false);
            window.opera.addEventListener('BeforeScript', prevent, false);

            document.open();
            document.write(this.responseText);
            
            convert2PHF.prepareStyleSheets();
          } 
          else
          {
            convert2PHF.closePanel();
            convert2PHF.prepareStyleSheets();
          }
        }
      };
      xmlhttp.send();
    }
    else    
    {
      this.closePanel();
      this.prepareStyleSheets();
    }
  },
 

  checkImage : function(src)
  {
    if(src.search(this.imgDataUrlRegExp) != -1)
      return false;
      
    if(this.bOnlyCurrentDomain)
      return (src.indexOf(this.domain) == 0);
    
    return true;
  },
  
  
  initConvertation : function()
  {
    this.getImagesUrl();
    this.getInputImagesUrl();
    this.getBgImagesUrlFromBgAttributes();
    this.getBgImagesUrl();
    this.getBgImagesUrlFromExplicitStyles();
    
    this.imgNum = 0;
    this.bImages = false;
    this.bContinue = false;
    
    var onLoad = function()
    {
      convert2PHF.imgNum--;      
      convert2PHF.bImages = false;
      if(convert2PHF.imgNum <= 0 && convert2PHF.bContinue)
        convert2PHF.convertImages();
    };

    if(!this.bAlreadyLoaded)
    {
      for(var i in this.imgUrl)
      {
        var img = new Image();
        img.src = this.imgUrl[i][0];
        if(!img.complete)
        {
          this.bImages = true;
          this.imgNum++;
          this.reloadImage(this.imgUrl[i][0], onLoad);
        }
      }
    }
    
    if(!this.bImages)
      this.convertImages();
    else
      this.bContinue = true;
  }, 


  prepareStyleSheets : function()
  {
    styleSheets = document.styleSheets;
    if(styleSheets && styleSheets.length > 0)
      this.prepareStyleSheetOwner(0);
  },
  
  
  prepareStyleSheetOwner : function(index)
  {
    if(index >= styleSheets.length)
    {
      this.initConvertation();
      return;
    }

    var owner = styleSheets[index].ownerNode;    
    if(this.checkStyleSheetOwner(owner))
    {
      if(owner.hasAttribute('href') && owner.href.indexOf(this.domain) != 0)
      {
        var url = this.encodeExtUrl(owner.href, 0, this.urlSSToken);
        var frame = this.getSSFrame(url);
        this.ssExtFrame = frame;
        frame.createFrame();
        
        var count = 0;
        var onGetExtStyleSheet = function()
        {
          if(convert2PHF.extCSS)
          {
            if(convert2PHF.extCSS == convert2PHF.emptyCSS)
            {
              owner.parentNode.removeChild(owner);
              convert2PHF.prepareStyleSheetOwner(index + 1);
            }
            else
            { 
              var style = document.createElement('style');
              style.type = 'text/css';
              if(styleSheets[index].media.mediaText)
                style.media = styleSheets[index].media.mediaText;
            
              style.text = convert2PHF.extCSS;
              style = owner.parentNode.insertBefore(style, owner);
              owner.parentNode.removeChild(owner);
              
              convert2PHF.prepareStyleSheetRules(style.sheet, index);
            }            
          }
          else if(count <= 1200)
            setTimeout(onGetExtStyleSheet, 50);
          else
            convert2PHF.prepareStyleSheetOwner(index + 1);
        };   

        setTimeout(onGetExtStyleSheet, 50);
      }
      else
      {
        convert2PHF.prepareStyleSheetRules(styleSheets[index], index);
      }
    }
    else
      this.prepareStyleSheetOwner(index + 1);
  },
  
  
  prepareStyleSheetRules : function(sheet, index)
  {
    try
    {
      if(sheet.cssRules.length > 0)      
        void(sheet.cssRules[0].cssText);
    }  
    catch(e)
    {
      this.prepareStyleSheetOwner(index + 1);
      return;
    }
    
    var rIndex = -1;
    var count = 0;
    var onGetExtStyleSheet = function()
    {
      if(convert2PHF.extCSS)
      {
        if(convert2PHF.extCSS == convert2PHF.emptyCSS)
        {
          convert2PHF.prepareStyleSheetRules(sheet, index);
          return;
        }
        else
        {
          var css = '';
          var bInserted = false;          
          for(var i = 0; i < sheet.cssRules.length; i++)
          {
            if(i == rIndex)
            {
              bInserted = true;
              css += '\n' + convert2PHF.extCSS;              
            }            
            css += '\n' + sheet.cssRules[i].cssText;
          }         
          
          if(!bInserted)
            css += '\n' + convert2PHF.extCSS;

          var owner = sheet.ownerNode;
          owner.text = css;

          convert2PHF.prepareStyleSheetRules(owner.sheet, index);
          return;
        }
      }
      else if(count <= 1200)
        setTimeout(onGetExtStyleSheet, 50);
      else
        convert2PHF.prepareStyleSheetOwner(index + 1);
    };   
    
    var bExtSS = false;    
    for(var i = sheet.cssRules.length - 1; i >= 0; i--)
    {
      if(sheet.cssRules[i].type == 3)
      {
        var url = /@import (?:url)\x28?\x22?([^\x22\x28\x29]+)\x22?\x29?/i.exec(sheet.cssRules[i].cssText);
        if(url && url.length > 1)
        {
          if(url[1].indexOf(this.domain) != 0)
          {          
            bExtSS = true;
            rIndex = i;
            sheet.deleteRule(i); 
          
            url = url[1];
            url = this.encodeExtUrl(url, 0, this.urlSSToken);
            var frame = this.getSSFrame(url);
            this.ssExtFrame = frame;
            frame.createFrame();

            setTimeout(onGetExtStyleSheet, 50);
            break;
          }
        }
      }
    }
    
    if(!bExtSS)
      this.prepareStyleSheetOwner(index + 1);
  },
  
  
  convertImages : function()
  {
    this.imgNum = 0;
    this.bImages = false;
    this.bContinue = false;
    
    for(var i in this.imgUrl)
    {
      var img = new Image();
      img.src = this.imgUrl[i][0];
      
      if(img.src.indexOf(this.domain) == 0)
      {
        var data = this.imgSrcToData(img.src);
        if(data)
          this.imgUrl[i][2] = data;
      }
      else
      {
        this.bImages = true;
        this.imgNum++;
        
        var url = this.encodeExtUrl(img.src, this.imgNum, this.urlImgToken);
        var frame = this.getImgFrame(url, this.imgNum);
        this.imgExtFrame[img.src] = frame;
        frame.createFrame();
      }
    }
    
    if(!this.bImages)
    {
      delete convert2PHF.imgExtFrame;
      this.convert();
    }
    else
      this.bContinue = true;
  },
  
  
  onExternalImageConvert : function(data, src)
  {
    if(data && src && convert2PHF.imgUrl[src])
    {
      convert2PHF.imgUrl[src][2] = data;
    }
    
    convert2PHF.imgNum--;
    convert2PHF.bImages = false;
    if(convert2PHF.imgNum <= 0 && convert2PHF.bContinue)
    {
      delete convert2PHF.imgExtFrame;
      convert2PHF.convert();
    }
  },
  
  
  onExternalSSConvert : function(data, src)
  {
    delete convert2PHF.ssExtFrame;
    convert2PHF.ssExtFrame = null;
    
    if(data)
      convert2PHF.extCSS = data;
  },


  reloadImage : function(src, onLoadFunction)
  {
    var f = document.createElement('iframe');
    f.src = src;
    f.width = 0;
    f.height = 0;
    f.frameBorder = 'no';
    f.scrolling = 'no';
    f.onload = function(){
      this.parentNode.removeChild(this);
      if(onLoadFunction)
        onLoadFunction();
    };
    document.documentElement.appendChild(f);
  },  
  
  
  addImageUrl : function(src, bBackground)
  {
    if(!src || this.imgUrl[src])
      return;
      
    if(this.bAlreadyLoaded)
    {
      var i = new Image();
      i.src = src;
      if(!i.complete)
        return;
    }
    
    if(!this.checkImage(src))
      return;

    this.imgUrl[src] = new Array(src, bBackground, '');
  },
  
    
  getImagesUrl : function()
  {
    var obj = document.getElementsByTagName('img');
    for(var i = 0; i < obj.length; i++)
      this.addImageUrl(obj[i].src, false);
  },
  
  
  getInputImageOjects : function()
  {
    return document.evaluate('//input[@type="image"][@src]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  },
  
  getInputImagesUrl : function()
  {
    var obj = this.getInputImageOjects();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
      this.addImageUrl(el.src, false);
  },
  
  
  getBgImagesUrl : function()
  {
    if(!this.bBackgroundImages)
      return;
    
    var s = document.styleSheets;
    for(var i = 0; i < s.length; i++)
    { 
      var owner = s[i].ownerNode;
      if(this.checkStyleSheetOwner(owner))
      {        
        this.getBgImagesUrlFromStyleSheets(s[i]);
      }
    }
  },
  
  
  getObjectsWithExplicitStyles : function()
  {
    return document.evaluate('//*[@style]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  },
  
  getBgImagesUrlFromExplicitStyles: function()
  {
    var obj = this.getObjectsWithExplicitStyles();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
      this.getBgImagesUrlFromCssText(el.getAttribute('style'));
  },
  
  
  getObjectsWithBgAttribute : function()
  {
    return document.evaluate('//*[@background]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  },
  
  
  getBgImagesUrlFromBgAttributes: function()
  {
    var obj = this.getObjectsWithBgAttribute();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
      this.addImageUrl(el.getAttribute('background'), false);
  },
  
  
  getBgImagesUrlFromCssText : function(cssText)
  {
    if(!cssText)
      return;
      
    cssText.replace(/(background-image|list-style-image|content)(: url\(")(\S+)("\))/g, function(text, a, b, c, d){
      convert2PHF.addImageUrl(c, true);      
      return text;
    });
  },
  
    
  getBgImagesUrlFromStyleSheets : function(sheet)
  {
    try
    {
      if(sheet.cssRules.length > 0)      
        void(sheet.cssRules[0].cssText);
    }  
    catch(e)
    {
      return;
    }    
    
    for(var i = 0; i < sheet.cssRules.length; i++)
    {        
      if(sheet.cssRules[i].type == 3)
        this.getBgImagesUrlFromStyleSheets(sheet.cssRules[i].styleSheet);
      else
        this.getBgImagesUrlFromCssText(sheet.cssRules[i].cssText);
    }
  },


  checkStyleSheetOwner : function(owner)
  {
    if(owner)
    {
      if(owner.hasAttribute('href') && owner.href.search(/^data:text\/css/) != -1)
        return false;
        
      if(((!owner.hasAttribute('type') || owner.type == 'text/css') && 
        (!owner.hasAttribute('rel') || owner.rel == 'stylesheet')) ||
        (owner.text && owner.text.match(/@import|url\(/gi)))
        return true;        
    }
    return false;
  },


  imgSrcToData : function(src)
  {
    var type = 'image/png';
    var a = document.createElement('a');
    a.href = src;
    if(a.pathname.search(/\.jpg$|\.jpeg$|\.jfi$|\.jfif$/i) != -1)
      type = "image/jpeg";    
    
    var i = new Image();
    i.src = src;
    if(!i.complete)
      return '';
    
    var data = '';
    try
    {    
      var canvas = document.createElement('canvas');    
      var c = canvas.getContext('2d');
      canvas.width = i.width;
      canvas.height = i.height;
    
      c.drawImage(i, 0, 0, i.width, i.height);
      data = canvas.toDataURL(type);
    }
    catch(e)
    {
      data = '';
    }

    return data;
  },  
  
  
  getImgFrame : function(url, i)
  {
    var a = document.createElement('a');
    a.href = url;
    
    var f = new ujs_phfFrame();
    f.msgPrefix = 'PHF_ext_image_' + i;
    f.frameId = 'ujs_PHF_ext_image_frame_' + i;
    f.url = url;
    f.domain = a.hostname;
    f.deleteFrame = true;
    f.getDataFunction = convert2PHF.onExternalImageConvert;
    
    return f;
  },
  
  
  getSSFrame : function(url)
  {
    var a = document.createElement('a');
    a.href = url;
    
    var f = new ujs_phfFrame();
    f.msgPrefix = 'PHF_ext_ss';
    f.frameId = 'ujs_PHF_ext_ss_frame';
    f.url = url;
    f.domain = a.hostname;
    f.deleteFrame = true;
    f.getDataFunction = convert2PHF.onExternalSSConvert;
    
    return f;
  },


  encodeExtUrl : function(src, i, token)
  {    
    return (src + token + i.toString() + '&' + encodeURIComponent(src + '\n'));
  },
  
  
  decodeExtUrl : function(url, token)
  {
    var d = new Array();
    d[0] = ''; // image source
    d[1] = ''; // image index
    
    var a = document.createElement('a');
    a.href = url;
    
    var i = a.hash.indexOf(token);
    if(i == -1)
      return d;

    var src = a.hash.substr(i + token.length);
    i = src.indexOf('&');
    if(i >= 0)
    {
      d[1] = src.substring(0, i);
      src = src.substr(i + 1);
    }    
    src = decodeURIComponent(src);
    d[0] = src.replace(/\n.*/, '');    
    
    return d;
  },


  convert : function()
  {
    if(this.format == 0)
    {
      // PHF
      this.convertImagesToPHF();
      this.convertInputImagesToPHF();
      this.convertBgAttributesToPHF();  
    }
    else if(this.format == 1)
    {
      // MHT
    } 

    this.convertScripts();
    this.convertStyleSheetsToPHF();
    this.convertExplicitStylesToPHF();
    this.postConvert();  
  },
  
  
  convertImagesToPHF : function()
  {
    var obj = document.getElementsByTagName('img');
    for(var i = 0; i < obj.length; i++)
    {
      if(obj[i].src && this.imgUrl[obj[i].src] && this.imgUrl[obj[i].src][2])
        obj[i].src = this.imgUrl[obj[i].src][2];
    }
  },
  
  
  convertInputImagesToPHF : function()
  {
    var obj = this.getInputImageOjects();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
    {
      if(el.src && this.imgUrl[el.src] && this.imgUrl[el.src][2])
        el.src = this.imgUrl[el.src][2];
    }
  },
  
  
  convertBgAttributesToPHF : function()
  {
    var obj = this.getObjectsWithBgAttribute();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
    {
      var s = el.getAttribute('background', false);
      if(s && this.imgUrl[s] && this.imgUrl[s][2])
        el.setAttribute('background', this.imgUrl[s][2], false);
    }
  },
  
  
  convertStyleSheetsToPHF : function()
  {
    var s = document.styleSheets;
    for(var i = 0; i < s.length; i++)
    { 
      var owner = s[i].ownerNode;
      if(this.checkStyleSheetOwner(owner))
      {
        var css = this.convertStyleSheetToPHF(s[i]);
        if(css && css != -1)
        {
          var style = document.createElement('style');
          style.type = 'text/css';
          if(s[i].media.mediaText)
            style.media = s[i].media.mediaText;
            
          style.text = css + '\n';        
          owner.parentNode.replaceChild(style, owner);
        }
      }
    }
  },
  
  
  convertExplicitStylesToPHF : function()
  {
    var obj = this.getObjectsWithExplicitStyles();
    var el = null;
    for (var i = 0; el = obj.snapshotItem(i); i++)
    {
      var s = this.convertCssTextToPHF(el.getAttribute('style'));
      if(s)
        el.setAttribute('style', s, false);
    }
  },
 
  
  convertCssTextToPHF : function(cssText)
  {
    if(!cssText)
      return cssText;

    if(this.bBackgroundImages && this.format == 0)
    {    
      cssText = cssText.replace(/(background-image|list-style-image|content)(: url\(")(\S+)("\))/g, function(text, a, b, c, d){
        if(c && convert2PHF.imgUrl[c] && convert2PHF.imgUrl[c][2])
        {
          return a + b + convert2PHF.imgUrl[c][2] + d;
        }        
        return text;
      });
    }    
    cssText = cssText.replace(/(font-family: )(, )([^;\}]*)/g, '$1$3');
    
    return cssText;
  },
  
    
  convertStyleSheetToPHF : function(sheet)
  {
    var css = '';
    try
    {
      if(sheet.cssRules.length > 0)      
        void(sheet.cssRules[0].cssText);
    }  
    catch(e)
    {
      return -1;
    }
    
    for(var i = 0; i < sheet.cssRules.length; i++)
    {        
      if(sheet.cssRules[i].type == 3)
      {
        var importCss = this.convertStyleSheetToPHF(sheet.cssRules[i].styleSheet);
        if(importCss && importCss != -1)        
          css += '\n' + importCss;
        else
          css += '\n' + sheet.cssRules[i].cssText;
      }
      else
        css += '\n' + this.convertCssTextToPHF(sheet.cssRules[i].cssText);
    }  
    
    return css;
  },
  
  
  convertScripts : function()
  { 
    var s = document.scripts;
    if(this.scriptSaving == 0) // All
    {
      for(var i = 0; i < s.length; i++)
      {
        if(s[i].src)
        {
          var t = s[i].text;
          s[i].removeAttribute('src');
          s[i].appendChild(document.createTextNode(t));        
        }
      }
    }
    else if(this.scriptSaving == 1) // Inline only
    {
      for(var i = 0; i < s.length; i++)
      {
        if(s[i].src)
          s[i].removeAttribute('src');
      }
    }
    else if(this.scriptSaving == 2) // None
    {
      for(var i = 0; i < s.length; i++)
      {
        if(s[i].src)
          s[i].removeAttribute('src');
        else
          s[i].innerText = '';
      }
    }
  },


  removeElements : function(tag)
  {
    var e = document.getElementsByTagName(tag);
    try
    {    
      for(var i = e.length - 1; i >= 0; i--)
        e[i].parentNode.removeChild(e[i]);
    }
    catch(e)
    {
    }
  },
  
  
  MimePart: function(delimiter, filename, type, location, encoding, text)
  {
    var s = '--' + delimiter + '\r\n';    
    s += 'Content-Disposition: inline; filename=' + filename + '\r\n';
    s += 'Content-Type: ' + type + '; name=' + filename + '\r\n';    
    s += 'Content-Location: ' + location + '\r\n';
    //s += 'Content-Id: <' + location + '>\r\n';    
    s += 'Content-Transfer-Encoding: ' + encoding + '\r\n\r\n';
    var i = 0, len = 76;
    while(i < text.length)
    {
      s += text.substr(i, len);
      s += '\n';
      i += len;
    }
    //s += text + '\r\n\r\n';
    s += '\r\n';
    return s;
  },
  

  saveAsMHT : function(d)
  {
    var delimiter = "----------HpNgCKDHubtbeDqD9oa6LE";    
    var s = 'From: <Saved by Convert2PHF for Opera 9>\r\n';
    s += 'Content-Type: multipart/related; type="text/html"; start=<index>;';
    //s += 'charset=' + document.charset + ';';
    s += ' boundary=' + delimiter + '\r\n';
    
    var title = document.title;
    if(title)
    {
      title = this.encodeToUTF8(title);
      title = this.encodeStringToQP(title, 0, false);    
      s += 'Subject: =?utf-8?Q?' + title + '?=\r\n';
    }
    else
      s += 'Subject: \r\n';    
    
    var now = new Date();
    //s += 'Date: ' + document.lastModified + '\r\n';
    s += 'Date: ' + now + '\r\n';
    s += 'MIME-Version: 1.0\r\n\r\n';
    //s += 'This is a multi-part message in MIME format.\r\n\r\n';
    
    
    // Save document
    s += '--' + delimiter + '\r\n';
    s += 'Content-Disposition: inline; filename=default.htm\r\n';
    s += 'Content-Type: text/html; name=default.htm\r\n';
    s += 'Content-Id: <index>\r\n';
    s += 'Content-Location: ' + window.location.href + '\r\n';
    s += 'Content-Transfer-Encoding: Quoted-Printable\r\n\r\n';
    
    var lines = d.split(/(?:\r\n|\r|\n)/g);
    for(var i = 0; i < lines.length; i++)
    {
      if(lines[i].length > 0)
      {
        var text = this.encodeToUTF8(lines[i]);
        s += this.encodeStringToQP(text, 76, false) + '\r\n';
      }
      else
        s += '\r\n';
    }    
    s += '\r\n';

    // Save images
    for(var i in this.imgUrl)
    {
      if(this.imgUrl[i][2])
      {
        var parts = /^data:(\w+\/[\w-]+);base64,([\w+\/]+={0,2}$)/i.exec(this.imgUrl[i][2]);
        if(parts && parts.length > 2)
        {
          var img = new Image();
          img.src = this.imgUrl[i][0];
          var a = document.createElement('a');          
          a.href = img.src;
          var filename = a.pathname;
          filename = filename.replace(/(?:[^\/\\]*\/)+([^\/\\]+)/, '$1');
          s += this.MimePart(delimiter, filename, parts[1], img.src, 'base64', parts[2]);
        }
      }
    }

    s += delimiter + '--\r\n';
    return s;
  },


  postConvert : function()
  {
    if(this.bRemoveFrames)
    {
      this.removeElements('iframe');
    }    
    
    if(this.bRemoveObjectsAndEmbeds)
    {
      this.removeElements('object');
      this.removeElements('embed');
    }
  
    if(document.charset != 'utf-8')
    {
      var m = document.getElementsByTagName('meta');
      for(var i in m)
      {
        if(m[i].httpEquiv && m[i].httpEquiv.toLowerCase() == 'content-type')
        {
          m[i].content = 'text/html; charset=utf-8';
          break;
        }
      }
    }
    
    if(document.location.protocol.indexOf('http') == 0)
    {
      if(document.getElementsByTagName('base').length == 0)
      {
        var b = document.createElement('base');
        b.href = document.location.href;
        
        var head = document.getElementsByTagName('head');
        if(head && head.length > 0)        
          head[0].appendChild(b);
        else if(document.body)  
        {
          head = document.createElement('head');
          head.appendChild(b);
          document.documentElement.insertBefore(head, document.body);
        }
      }
    }

    if(this.imgUrl.length > 0)
    {
      this.imgNum = 0;
      this.bImages = false;
      this.bContinue = false;     
      
      for(var i in this.imgUrl)
      {
        if(this.imgUrl[i][2])
        {
          var img = new Image();
          img.src = this.imgUrl[i][2];
          if(!img.complete)
          {
            this.bImages = true;
            this.imgNum++;
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.open("GET", this.imgUrl[i][2], true);
            xmlhttp.onreadystatechange = function() {
              if(this.readyState == 4)
              { 
                convert2PHF.imgNum--;
                convert2PHF.bImages = false;
                if(convert2PHF.imgNum <= 0 && convert2PHF.bContinue)
                  convert2PHF.saveDocument();
              }
            };
            xmlhttp.send();
          }
        }
      }
    }

    if(!this.bImages)
      this.saveDocument();
    else
      this.bContinue = true;
  },
  
  
  saveDocument : function()
  {
    var dt = '';    
    if(document.doctype)
    {
      if(document.doctype.name);
        dt += ' ' + document.doctype.name;
      if(document.doctype.publicId)
        dt += ' PUBLIC "' + document.doctype.publicId + '"';
      if(document.doctype.systemId)
        dt += ' "' + document.doctype.systemId + '"';
    }
    
    if(dt)
      dt = '<!DOCTYPE' + dt + '>';
    
    var loc = dt + document.documentElement.outerHTML;
    loc += '\n\n<!-- This document saved from ' + document.location.href + ' -->';
    if(this.format == 0)
    {
      // PHF
      loc = encodeURIComponent(loc);
      loc = 'data:text/phf;charset=utf-8,' + loc;
    }
    else if(this.format == 1)
    {
      // MHT
      loc = this.saveAsMHT(loc);
      loc = encodeURIComponent(loc);
      loc = 'data:text/mhtphf;charset=utf-8,' + loc;
    }
    document.location.href = loc;    
  },
  
  
  encodeStringToQP : function(line, maxLine, convertSpace)
  {
    // Encode to quoted-printable
    var hex = new Array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');        
    var eol = '\r\n';
    var escape = "=";
    var output = "";    
    var linlen = line.length;
    var newline = "";
    
    for(var i = 0; i < linlen; i++)
    {
      var c = line.substr(i, 1);        
      var dec = line.charCodeAt(i);
      
      if((i == 0) && (dec == 46))
        c = "=2E";
        
      if(dec == 32)
      {
        if (i == (linlen - 1))
          c = "=20";
        else if(convertSpace)
          c = "=20";
      }
      else if((dec == 61) || (dec < 32) || (dec > 126))
      {
        h2 = Math.floor(dec / 16);
        h1 = Math.floor(dec % 16);
        c = escape + hex[h2] + hex[h1];
      }
      if((maxLine > 0) && ((newline.length + c.length) >= maxLine))
      {
        output += newline + escape + eol;        
        newline = "";        
        if(dec == 46)
          c = "=2E";
      }
      
      newline += c;
    }
    
    output += newline;    
    return output.replace(/^\s+|\s+$/, '');
  },
  
  
  // http://www.worldtimzone.com/res/encode/
  // Copyright (c) 2003 Timothy Powell, Wilmore, KY
  /* ***************************
  ** Most of this code was kindly 
  ** provided to me by
  ** Andrew Clover (and at doxdesk dot com)
  ** http://and.doxdesk.com/ 
  ** in response to my plea in my blog at 
  ** http://worldtimzone.com/blog/date/2002/09/24
  ** It was unclear whether he created it.
  */
  encodeToUTF8 : function(wide)
  {
    var c, s;
    var enc = "";
    var i = 0;
    while(i < wide.length)
    {
      c = wide.charCodeAt(i++);
      // handle UTF-16 surrogates
      if(c >= 0xDC00 && c < 0xE000)
        continue;
      if(c >= 0xD800 && c < 0xDC00)
      {
        if(i >= wide.length)
          continue;
        s = wide.charCodeAt(i++);
        if(s < 0xDC00 || c >= 0xDE00)
          continue;
        c = ((c - 0xD800) << 10) + (s - 0xDC00) + 0x10000;
      }
      // output value
      if(c < 0x80)
        enc += String.fromCharCode(c);
      else if(c < 0x800)
        enc += String.fromCharCode(0xC0 + (c >> 6), 0x80 + (c & 0x3F));
      else if(c < 0x10000)
        enc += String.fromCharCode(0xE0 + (c >> 12), 0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
      else
        enc += String.fromCharCode(0xF0 + (c >> 18), 0x80 + (c >> 12 & 0x3F), 0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
    }
    return enc;
  },
  
  
  getDataFromStorage : function(data, id)
  {
    if(id == convert2PHF.preferencesCookie)
    {
      if(!data)
      {
        convert2PHF.createPanel();
        return;
      }

      var v = data.split('**');
      if(v.length >= 2)
      {
        var count = parseInt(v[0]);
        if(!isNaN(count))
        {
          if(count == convert2PHF.preferenceParams.length)
          {
            v = v[1].split('-');
            if(v.length == convert2PHF.preferenceParams.length)
            {
              for(var i = 0; i < convert2PHF.preferenceParams.length; i++)
              {
                if(v[i] == 'false')                
                  eval('convert2PHF.' + convert2PHF.preferenceParams[i] + '= false');
                else if(v[i] == 'true')
                  eval('convert2PHF.' + convert2PHF.preferenceParams[i] + '= true');
                else
                  eval('convert2PHF.' + convert2PHF.preferenceParams[i] + '= v[i]');
              }
            }
          }
        }
      }

      convert2PHF.createPanel();
    }
  },

    
  putPreferencesToStorage : function()
  {
    var v = this.preferenceParams.length.toString() + '**';        
    var sep = '';
    for(var i = 0; i < this.preferenceParams.length; i++)
    {
      v += sep + eval('convert2PHF.' + this.preferenceParams[i]);
      sep = '-';
    }
    
    this.storage.saveData(v, this.preferencesCookie);
  }
};



(function(){

  if(!window.opera)
    return;
      
  var bFrame = false;
  try
  {
    if(window.parent != window)
    {
      bFrame = true;
    }
  }
  catch(e)
  {
    bFrame = true;
  }
    
  var prevent = function(e) {
    e.preventDefault();
  };    
    
  if(!bFrame)
  {    
    //convert2PHF.showPanel();        
    document.addEventListener('message', function(e) {  
      if(convert2PHF.storage.processMessage(e))
        return;
      else if(convert2PHF.ssExtFrame && convert2PHF.ssExtFrame.processMessage(e))
        return;    
      else
      {
        var i = e.uri.indexOf(convert2PHF.urlImgToken);
        if(i > 0)
        { 
          // imgParams[0] - image source, imgParams[1] - image index
          var imgParams = convert2PHF.decodeExtUrl(e.uri, convert2PHF.urlImgToken);
          
          if(convert2PHF.imgExtFrame && convert2PHF.imgExtFrame[imgParams[0]])          
          {
            if(convert2PHF.imgExtFrame[imgParams[0]].processMessage(e))
              return;
          }
          
          convert2PHF.onExternalImageConvert('', imgParams[0]);        
          return;
        }
      }
    }, false);
  }
  else if(window.location.host == convert2PHF.storageDomain)
  {
    // Storage frame
    window.opera.addEventListener('BeforeExternalScript', prevent ,false);
    window.opera.addEventListener('BeforeScript', prevent, false);      
    window.opera.addEventListener('BeforeEventListener.load', prevent, false); 
    window.opera.addEventListener('BeforeEventListener.DOMContentLoaded', prevent, false);    
    window.opera.addEventListener('BeforeEventListener.message', function(e) {
      if(!e.event.data || (e.event.data.indexOf(convert2PHF.storage.msgPrefix) == -1))
        e.preventDefault();
    }, false);
    
    document.addEventListener('message', function(e){
      if(convert2PHF.storage.processMessage(e))
        return;
    }, false);
  }
  else
  {
    var bSS = false;
    var index = window.location.hash.indexOf(convert2PHF.urlImgToken);
    if(index == -1)
    {
      index = window.location.hash.indexOf(convert2PHF.urlSSToken);
      if(index == -1)
        return;
      else
        bSS = true;
    }
    
    window.opera.addEventListener('BeforeExternalScript', prevent ,false);
    window.opera.addEventListener('BeforeScript', prevent, false);
    window.opera.addEventListener('BeforeEventListener.message', prevent, false);
    window.opera.addEventListener('BeforeEventListener.DOMContentLoaded', prevent, false);
    window.opera.addEventListener('BeforeEventListener.load', prevent, false);
    
    window.stop();
    
    if(bSS)
    {
      // External style sheet
      
      // ssParams[0] - style sheet source
      var ssParams = convert2PHF.decodeExtUrl(window.location.href, convert2PHF.urlSSToken);
      
      var frameObj = convert2PHF.getSSFrame(window.location.href);
     
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("GET", ssParams[0], true);
      xmlhttp.onreadystatechange = function() {
        if(this.readyState == 4)
        {
          
          if(this.responseText)
          {
            var bProcessed = false;
            if(document && document.documentElement)
            {
              var s = document.createElement('style');      
              s.setAttribute('type', 'text/css');
              s.setAttribute('style', 'display:none !important;');
              s.text = this.responseText;
              document.documentElement.appendChild(s);
              
              if(s.sheet)
              {
                var css = '', sep = '';
                for(var i = 0; i < s.sheet.cssRules.length; i++)
                {
                  css += sep + s.sheet.cssRules[i].cssText;
                  sep = '\n';
                }
                
                if(css)
                {
                  bProcessed = true;
                  frameObj.getData(ssParams[0], css);                  
                }
              }              
            }            
            
            if(!bProcessed)            
              frameObj.getData(ssParams[0], this.responseText);
          }
          else
            frameObj.getData(ssParams[0], convert2PHF.emptyCSS);
        }
      };
      xmlhttp.send();
    }
    else
    {
      // External image
    
      // imgParams[0] - image source, imgParams[1] - image index
      var imgParams = convert2PHF.decodeExtUrl(window.location.href, convert2PHF.urlImgToken);
      
      var frameObj = convert2PHF.getImgFrame(window.location.href, imgParams[1]);
      var img = new Image();
      img.src = imgParams[0];
      if(!img.complete)
      {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", imgParams[0], true);
        xmlhttp.onreadystatechange = function() {
          if(this.readyState == 4)
          {
            var data = convert2PHF.imgSrcToData(imgParams[0]);
            frameObj.getData(imgParams[0], data);
          }
        };
        xmlhttp.send();
      }
      else
      {
        var data = convert2PHF.imgSrcToData(imgParams[0]);
        frameObj.getData(imgParams[0], data);
      } 
    }
  }
})();

  


/////////////////////////////////////////////////
// ujs_phfFrame
// (from SEObar srcipt: http://www.puzzleclub.ru/files/seobar/seobar.js)

function ujs_phfFrame(prefix, frameId, url, domain)
{
  this.msgPrefix = prefix;
  this.frameId = frameId;
  this.url = url;
  this.domain = domain;  
  // Function to which the received data will be transferred.
  // 1-st parameter - data string,
  // 2-nd parameter - identifier allowing to determine type of data.
  this.getDataFunction = null;  
  
  this.deleteFrame = true;
  this.style = '';
  this.msg = '_frame_data';
  
  this.dataSeparator = '\n';
  this.msgSeparator = '\n';
  
  this.getUrl = function()
  {
    if(this.checkUrl)
      return this.checkUrl;
      
    return this.url;
  };
  
  this.createFrame = function() 
  {
    if(!this.url)
    {
      alert('ujs_phfFrame: [url] property must be initialized before calling [createFrame] function.');
      return;
    }    
    
    if(!this.frameId)
    {
      alert('ujs_phfFrame: [frameId] property must be initialized before calling [createFrame] function.');
      return;
    }
    
    var f = document.createElement('IFRAME');
    f.src = this.url;
    f.id = this.frameId;
    f.width = 0;
    f.height = 0;
    f.frameBorder = 'no';
    f.scrolling = 'no';
    document.documentElement.appendChild(f);
  };
  
  this.getData = function(msgId, data)
  {
    if(!msgId)
    {
      alert('ujs_phfFrame: Incorrect call to the [getData] function. [msgId] parameter is not specified.');
      return;
    }
  
    if(!this.msgPrefix)
    {
      alert('ujs_phfFrame: [msgPrefix] property must be initialized before calling [getData] function.');
      return;
    }
    
    if(!this.getDataFunction)
    {
      alert('ujs_phfFrame: [getDataFunction] property must be initialized before calling [getData] function.');
      return;
    }
    
    var msg = this.msgPrefix + this.msg + this.msgSeparator + msgId;
    if(data)      
    {
      data = encodeURIComponent(data);
    }
    msg += this.msgSeparator + data;
    window.parent.document.postMessage(msg);
  };
  
  this.processMessage = function(e)
  {
    if(e.data && (e.data.indexOf(this.msgPrefix) == 0))
    {
      if(this.url.indexOf(this.domain) < 0)
      {
        alert('ujs_phfFrame: The [domain]: "' + this.domain + '" is not a part of the [url]: "' + this.url + '"');
        return false;
      }
    
      var d = e.data.split(this.msgSeparator);
      if(d.length == 0)
      {
        return false;
      }      

      if((e.domain == this.domain) && (d[0] == this.msgPrefix + this.msg))
      {
        // process message from frame
        var id = '', data = '';
        if(d[1])
          id = d[1];
        if(d[2])
          data = decodeURIComponent(d[2]);

        if(this.deleteFrame)
        {
          var frame = document.getElementById(this.frameId);
          if(frame)      
          {
            frame.parentNode.removeChild(frame);
          }
        }
        
        this.getDataFunction(data, id);
        return true;
      }
    }
    
    return false;
  };
}


/////////////////////////////////////////////////
// UserJS Storage (http://www.puzzleclub.ru/files/ujs_storage.zip)

function ujs_Storage(prefix, domain, getDataFunction)
{
  // Prefix used for recognition of the messages.
  this.msgPrefix = prefix;
  // Domain used for storing data in the cookies.
  this.domain = domain;
  // Function for obtaining stored data.
  // 1-st parameter - data string,
  // 2-nd parameter - identifier allowing to determine type of data.  
  this.getDataFunction = getDataFunction; 
  
  this.frameId = '';
  this.msgSave = '_ujs_storage_save';
  this.msgLoad = '_ujs_storage_load';
  this.msgDelete = '_ujs_storage_delete';
  this.msgSeparator = '\n';
  
  this.createFrame = function(id, onload)
  {
    if(!id)
    {
      alert('UserJS Storage: Incorrect call to the [createFrame] function. [id] parameter is not specified.');
      return;
    }    
    
    if(!this.domain)
    {
      alert('UserJS Storage: [domain] property must be initialized before calling [createFrame] function.');
      return;
    }
    
    this.frameId = id;
            
    var f = document.createElement('IFRAME');
    f.src = 'http://' + this.domain;
    f.id = id;
    f.width = 0;
    f.height = 0;
    f.frameBorder = 'no'; 
    f.scrolling = 'no';
    if(onload)
      f.onload = onload;
    document.documentElement.appendChild(f);
  };
  
  this.sendMessageToFrame = function(msg)
  { 
    var f = document.getElementById(this.frameId);
    if(f)
    {
      var d = f.contentDocument;
      if(d)      
      {
        d.postMessage(msg);
      }
    }
  };
  
  this.saveData = function(data, cookie)
  {
    if(!cookie)
    {
      alert('UserJS Storage: Incorrect call to the [saveData] function. [cookie] parameter is not specified.');
      return;
    }
    
    if(!this.msgPrefix)
    {
      alert('UserJS Storage: [msgPrefix] property must be initialized before calling [saveData] function.');
      return;
    }
    
    var msg = this.msgPrefix + this.msgSave + this.msgSeparator;    
    msg += cookie + this.msgSeparator + encodeURIComponent(data);
    this.sendMessageToFrame(msg);
  };
  
  this.loadData = function(cookie, msgId)
  { 
    if(!cookie)
    {
      alert('UserJS Storage: Incorrect call to the [loadData] function. [cookie] parameter is not specified.');
      return;
    }

    if(!msgId)
    {
      alert('UserJS Storage: Incorrect call to the [loadData] function. [msgId] parameter is not specified.');
      return;
    }
  
    if(!this.msgPrefix)
    {
      alert('UserJS Storage: [msgPrefix] property must be initialized before calling [loadData] function.');
      return;
    }
    
    if(!this.getDataFunction)
    {
      alert('UserJS Storage: [getDataFunction] property must be initialized before calling [loadData] function.');
      return;
    }
    
    var msg = this.msgPrefix + this.msgLoad + this.msgSeparator;    
    msg += cookie + this.msgSeparator + msgId;
    this.sendMessageToFrame(msg);
  };
  
  this.deleteData = function(cookie)
  {
    if(!cookie)
    {
      alert('UserJS Storage: Incorrect call to the [deleteData] function. [cookie] parameter is not specified.');
      return;
    }
    
    if(!this.msgPrefix)
    {
      alert('UserJS Storage: [msgPrefix] property must be initialized before calling [deleteData] function.');
      return;
    }
    
    var msg = this.msgPrefix + this.msgDelete + this.msgSeparator;    
    msg += cookie;    
    this.sendMessageToFrame(msg);
  };
  
  this.processMessage = function(e)
  {
    if(e.data && (e.data.indexOf(this.msgPrefix) == 0))
    {    
      var d = e.data.split(this.msgSeparator);
      
      if(e.domain == this.domain)
      {
        // process message from frame
        if(d[0] && d[0] == this.msgPrefix + this.msgLoad)
        {
          // load
          var id = '', data = '';
          if(d[1])
            id = d[1];
          if(d[2])
            data = decodeURIComponent(d[2]);
          
          this.getDataFunction(data, id);
        }
        
        return true;
      }    
      else if(d[0])
      {
        // process message to frame
        if(d[0] == this.msgPrefix + this.msgSave)
        {
          // save
          var cookie = '', data = '';
          if(d[1])
            cookie = d[1];
          else
            return true;
            
          if(d[2])
            data = d[2];
            
          expdate = new Date("November 22, 2025 00:00:00");
          this.setCookie(cookie, data, expdate);
          
          return true;
        }
        else if(d[0] == this.msgPrefix + this.msgLoad)
        {
          // load
          var cookie = '', id = '';
          if(d[1])
            cookie = d[1];
          else
            return true;
            
          if(d[2])
            id = d[2];
            
          var msg = this.msgPrefix + this.msgLoad + this.msgSeparator + id;            
          var data = this.getCookie(cookie);
          if(data)        
            msg += this.msgSeparator + data;
            
          e.source.postMessage(msg);
          
          return true;
        }
        else if(d[0] == this.msgPrefix + this.msgDelete)
        {
          if(d[1])        
            this.deleteCookie(d[1]);
            
          return true;  
        }
      }    
    }
    
    return false;
  };
  
  this.setCookie = function(name, value, expires)
  {
    var curCookie = name + "=" + escape(value) +
    ((expires) ? "; expires=" + expires.toGMTString() : "");

    if ((name + "=" + escape(value)).length <= 4000)
      document.cookie = curCookie;
  }
  
  this.getCookie = function(name)
  {
    var prefix = name + "=";
    var cookieStartIndex = document.cookie.indexOf(prefix);
    if (cookieStartIndex == -1)
      return null;
  
    var cookieEndIndex = document.cookie.indexOf(";", cookieStartIndex + prefix.length);
    if (cookieEndIndex == -1)
      cookieEndIndex = document.cookie.length;
      
    var len = prefix.length;	
    
    return unescape(document.cookie.substring(cookieStartIndex + len, cookieEndIndex));
  };
  
  this.deleteCookie = function (name)
  {
    if (this.getCookie(name))
    {
      document.cookie = name + "=" +
      "; expires=Thu, 01-Jan-70 00:00:01 GMT";
    }
  };
}