home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / Vyzkuste / Reget / regetjr_093.exe / nsContextMenu092.js < prev    next >
Encoding:
JavaScript  |  2001-07-22  |  33.1 KB  |  807 lines

  1. /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /*
  3.  * The contents of this file are subject to the Netscape Public
  4.  * License Version 1.1 (the "License"); you may not use this file
  5.  * except in compliance with the License. You may obtain a copy of
  6.  * the License at http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the License is distributed on an "AS
  9.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10.  * implied. See the License for the specific language governing
  11.  * rights and limitations under the License.
  12.  *
  13.  * The Original Code is Mozilla Communicator client code,
  14.  * released March 31, 1998.
  15.  *
  16.  * The Initial Developer of the Original Code is Netscape Communications
  17.  * Corporation.  Portions created by Netscape are
  18.  * Copyright (C) 1998 Netscape Communications Corporation. All
  19.  * Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *     William A. ("PowerGUI") Law <law@netscape.com>
  23.  *     Blake Ross <blakeross@telocity.com>
  24.  *     Gervase Markham <gerv@gerv.net>
  25.  */
  26.  
  27. /*------------------------------ nsContextMenu ---------------------------------
  28. |   This JavaScript "class" is used to implement the browser's content-area    |
  29. |   context menu.                                                              |
  30. |                                                                              |
  31. |   For usage, see references to this class in navigator.xul.                  |
  32. |                                                                              |
  33. |   Currently, this code is relatively useless for any other purpose.  In the  |
  34. |   longer term, this code will be restructured to make it more reusable.      |
  35. ------------------------------------------------------------------------------*/
  36. function nsContextMenu( xulMenu ) {
  37.     this.target         = null;
  38.     this.menu           = null;
  39.     this.onTextInput    = false;
  40.     this.onImage        = false;
  41.     this.onLink         = false;
  42.     this.onMailtoLink   = false;
  43.     this.onSaveableLink = false;
  44.     this.link           = false;
  45.     this.inFrame        = false;
  46.     this.hasBGImage     = false;
  47.     this.inDirList      = false;
  48.     this.shouldDisplay  = true;
  49.  
  50.     // Initialize new menu.
  51.     this.initMenu( xulMenu );
  52. }
  53.  
  54. function initCatcherMenu( menu ) {
  55.     var olditem=document.getElementById( "download-by-catcher" ); 
  56.     var common = Components.classes["RgCommon"].createInstance();
  57.     common = common.QueryInterface(Components.interfaces.nsIRgCommon);
  58.     if (!olditem)
  59.     {
  60.         var newitem = document.createElement( "menuitem" );
  61.         newitem.setAttribute( "id", "download-by-catcher" );
  62.         newitem.setAttribute( "label",  "Download by" );
  63.         newitem.setAttribute( "name", "download_by_catcher" );
  64.         newitem.setAttribute( "oncommand", "DownloadBy(contextMenu);" );
  65.         var saveLink = document.getElementById( "context-savepage" );
  66.         menu.menu.insertBefore(newitem, saveLink);
  67.         var newitem2 = document.createElement( "menuitem" );
  68.         newitem2.setAttribute( "id", "download-all-by-catcher" );
  69.         newitem2.setAttribute( "label", "Download All by");
  70.         newitem2.setAttribute( "name", "download_all_by_catcher" );
  71.         newitem2.setAttribute( "oncommand", "DownloadAll(contextMenu);" );
  72.         menu.menu.insertBefore(newitem2, saveLink);
  73.      }   
  74.      var old1=document.getElementById( "download-by-catcher" ); 
  75.      var old2=document.getElementById( "download-all-by-catcher" ); 
  76.     if (common.menu1 != null) 
  77.     {
  78.          old1.setAttribute( "label", common.menu1);
  79.          menu.showItem("download-by-catcher",menu.onLink);
  80.     }
  81.     else
  82.         menu.showItem("download-by-catcher",false);
  83.     if (common.menu2 != null) 
  84.     {
  85.          old2.setAttribute( "label", common.menu2);
  86.          menu.showItem("download-all-by-catcher",true);
  87.     }
  88.     else
  89.         menu.showItem("download-all-by-catcher",false);
  90.  
  91. }
  92.  
  93. function DownloadBy( menu ) {
  94.     var url = Components.classes["RgUrl"].createInstance();
  95.     url = url.QueryInterface(Components.interfaces.nsIRgUrl);
  96.     url.url = menu.linkURL();
  97.     url.info = menu.linkText();
  98.     url.referer = window._content.location.href;
  99.     url.add();
  100. }
  101.  
  102. function DownloadAll( menu ) {
  103.     var list = Components.classes["RgUrlList"].createInstance();
  104.     list = list.QueryInterface(Components.interfaces.nsIRgUrlList);
  105.     var doc = menu.target.ownerDocument;
  106.     var links = doc.links
  107.     for (i = 0;i<links.length;i++)
  108.     {
  109.         var url = Components.classes["RgUrl"].createInstance();
  110.         url = url.QueryInterface(Components.interfaces.nsIRgUrl);
  111.         url.url = links[i].href;
  112.         url.info = links[i].text; 
  113.         url.referer = window._content.location.href;
  114.         list.add(url);
  115.     }
  116.     list.list();
  117.  
  118. }
  119.  
  120. // Prototype for nsContextMenu "class."
  121. nsContextMenu.prototype = {
  122.     // onDestroy is a no-op at this point.
  123.     onDestroy : function () {
  124.     },
  125.     // Initialize context menu.
  126.     initMenu : function ( popup ) {
  127.         // Save menu.
  128.         this.menu = popup;
  129.  
  130.         // Get contextual info.
  131.         this.setTarget( document.popupNode );
  132.  
  133.         // Initialize (disable/remove) menu items.
  134.         this.initItems();
  135.     },
  136.     initItems : function () {
  137.         initCatcherMenu(this);
  138.         this.initOpenItems();
  139.         this.initNavigationItems();
  140.         this.initViewItems();
  141.         this.initMiscItems();
  142.         this.initSaveItems();
  143.         this.initClipboardItems();
  144.         this.initMetadataItems();
  145.     },
  146.     initOpenItems : function () {
  147.         // Remove open/edit link if not applicable.
  148.         this.showItem( "context-openlink", this.onSaveableLink || ( this.inDirList && this.onLink ) );
  149.         this.showItem( "context-editlink", this.onSaveableLink && !this.inDirList );
  150.  
  151.         // Remove open frame if not applicable.
  152.         this.showItem( "context-openframe", this.inFrame );
  153.         this.showItem( "context-showonlythisframe", this.inFrame );
  154.  
  155.         // Remove separator after open items if neither link nor frame.
  156.         this.showItem( "context-sep-open", this.onSaveableLink || ( this.inDirList && this.onLink ) || this.inFrame );
  157.     },
  158.     initNavigationItems : function () {
  159.         // Back determined by canGoBack broadcaster.
  160.         this.setItemAttrFromNode( "context-back", "disabled", "canGoBack" );
  161.  
  162.         // Forward determined by canGoForward broadcaster.
  163.         this.setItemAttrFromNode( "context-forward", "disabled", "canGoForward" );
  164.  
  165.         // Reload is OK if not on a frame; vice-versa for reload-frame.
  166.         this.showItem( "context-reload", !this.inFrame );
  167.         this.showItem( "context-reload-frame", this.inFrame );
  168.  
  169.         // XXX: Stop is determined in navigator.js; the canStop broadcaster is broken
  170.         //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
  171.     },
  172.     initSaveItems : function () {
  173.         // Save page is always OK, unless in directory listing.
  174.         this.showItem( "context-savepage", !this.inDirList );
  175.  
  176.         // Save frame as depends on whether we're in a frame.
  177.         this.showItem( "context-saveframe", this.inFrame );
  178.  
  179.         // Save link depends on whether we're in a link.
  180.         this.showItem( "context-savelink", this.onSaveableLink );
  181.  
  182.         // Save image depends on whether there is one.
  183.         this.showItem( "context-saveimage", this.onImage );
  184.         if (this.onImage){ //if onImage, let's get the imagename into the context menu
  185.            var saveImageMenuItem = document.getElementById( 'context-saveimage' );
  186.            var imageName = extractFileNameFromUrl(this.imageURL);
  187.            var bundle = srGetStrBundle("chrome://communicator/locale/contentAreaCommands.properties");
  188.            var caption = bundle.formatStringFromName("saveImageAs",[imageName],1);
  189.  
  190.            saveImageMenuItem.setAttribute( "label", caption );
  191.         }
  192.  
  193.         // Remove separator if none of these were shown.
  194.         var showSep = !this.inDirList || this.inFrame || this.onSaveableLink || this.hasBGImage || this.onImage;
  195.         this.showItem( "context-sep-save", showSep );
  196.     },
  197.     initViewItems : function () {
  198.         // View source is always OK, unless in directory listing.
  199.         this.showItem( "context-viewsource", !( this.inDirList || this.onImage ) );
  200.  
  201.         // View frame source depends on whether we're in a frame.
  202.         this.showItem( "context-viewframesource", this.inFrame );
  203.  
  204.         // View Info is available, unless in directory listing
  205.         this.showItem( "context-viewinfo", !this.inDirList );
  206.  
  207.         // View Frame Info depends on whether we're in a frame
  208.         this.showItem( "context-viewframeinfo", this.inFrame );
  209.  
  210.         // View Image depends on whether an image was clicked on.
  211.         this.showItem( "context-viewimage", this.onImage );
  212.  
  213.         // View background image depends on whether there is one.
  214.         this.showItem( "context-viewbgimage", this.hasBGImage && !this.onImage );
  215.  
  216.         // Remove separator if all items are removed.
  217.         this.showItem( "context-sep-view", !this.inDirList || this.inFrame || this.onImage );
  218.     },
  219.     initMiscItems : function () {
  220.         // Use "Bookmark This Link" if on a link.
  221.         this.showItem( "context-bookmarkpage", !this.onLink );
  222.         this.showItem( "context-bookmarklink", this.onLink );
  223.  
  224.         // Send Page not working yet.
  225.         this.showItem( "context-sendpage", false );
  226.     },
  227.     initClipboardItems : function () {
  228.         // Select All is always OK, unless in directory listing.
  229.         this.showItem( "context-selectall", !this.inDirList );
  230.  
  231.         // Copy depends on whether there is selected text.
  232.         // Enabling this context menu item is now done through the global
  233.         // command updating system
  234.         // this.setItemAttr( "context-copy", "disabled", this.isNoTextSelected() );
  235.  
  236.         goUpdateGlobalEditMenuItems();
  237.  
  238.         // Items for text areas
  239.         this.showItem( "context-cut", this.onTextInput );
  240.         this.showItem( "context-paste", this.onTextInput );
  241.  
  242.         // XXX dr
  243.         // ------
  244.         // nsDocumentViewer.cpp has code to determine whether we're
  245.         // on a link or an image. we really ought to be using that...
  246.  
  247.         // Copy email link depends on whether we're on an email link.
  248.         this.showItem( "context-copyemail", this.onMailtoLink );
  249.  
  250.         // Copy link location depends on whether we're on a link.
  251.         this.showItem( "context-copylink", this.onLink );
  252.  
  253.         // Copy image location depends on whether we're on an image.
  254.         this.showItem( "context-copyimage", this.onImage );
  255.     },
  256.     initMetadataItems : function () {
  257.         // Show if user clicked on something which has metadata.
  258.         this.showItem( "context-metadata", this.onMetaDataItem );
  259.         this.showItem( "context-sep-clip", this.onMetaDataItem );
  260.     },
  261.     // Set various context menu attributes based on the state of the world.
  262.     setTarget : function ( node ) {
  263.         // Initialize contextual info.
  264.         this.onImage    = false;
  265.         this.onMetaDataItem = false;
  266.         this.onTextInput = false;
  267.         this.imageURL   = "";
  268.         this.onLink     = false;
  269.         this.inFrame    = false;
  270.         this.hasBGImage = false;
  271.         this.bgImageURL = "";
  272.  
  273.         // Remember the node that was clicked.
  274.         this.target = node;
  275.  
  276.         // See if the user clicked on an image.
  277.         if ( this.target.nodeType == 1 ) {
  278.              if ( this.target.tagName.toUpperCase() == "IMG" ) {
  279.                 this.onImage = true;
  280.                 this.imageURL = this.target.src;
  281.                 // Look for image map.
  282.                 var mapName = this.target.getAttribute( "usemap" );
  283.                 if ( mapName ) {
  284.                     // Find map.
  285.                     var map = this.target.ownerDocument.getElementById( mapName.substr(1) );
  286.                     if ( map ) {
  287.                         // Search child <area>s for a match.
  288.                         var areas = map.childNodes;
  289.                         //XXX Client side image maps are too hard for now!
  290.                         areas.length = 0;
  291.                         for ( var i = 0; i < areas.length && !this.onLink; i++ ) {
  292.                             var area = areas[i];
  293.                             if ( area.nodeType == 1
  294.                                  &&
  295.                                  area.tagName.toUpperCase() == "AREA" ) {
  296.                                 // Get type (rect/circle/polygon/default).
  297.                                 var type = area.getAttribute( "type" );
  298.                                 var coords = this.parseCoords( area );
  299.                                 switch ( type.toUpperCase() ) {
  300.                                     case "RECT":
  301.                                     case "RECTANGLE":
  302.                                         break;
  303.                                     case "CIRC":
  304.                                     case "CIRCLE":
  305.                                         break;
  306.                                     case "POLY":
  307.                                     case "POLYGON":
  308.                                         break;
  309.                                     case "DEFAULT":
  310.                                         // Default matches entire image.
  311.                                         this.onLink = true;
  312.                                         this.link = area;
  313.                                         this.onSaveableLink = this.isLinkSaveable( this.link );
  314.                                         break;
  315.                                 }
  316.                             }
  317.                         }
  318.                     }
  319.                 }
  320.              } else if ( this.target.tagName.toUpperCase() == "OBJECT"
  321.                          &&
  322.                          // See if object tag is for an image.
  323.                          this.objectIsImage( this.target ) ) {
  324.                 // This is an image.
  325.                 this.onImage = true;
  326.                 // URL must be constructed.
  327.                 this.imageURL = this.objectImageURL( this.target );
  328.              } else if ( this.target.tagName.toUpperCase() == "INPUT") {
  329.                if(this.target.getAttribute( "type" ).toUpperCase() == "IMAGE") {
  330.                  this.onImage = true;
  331.                  // Convert src attribute to absolute URL.
  332.                  this.imageURL = this.makeURLAbsolute( this.target.baseURI,
  333.                                                        this.target.src );
  334.                } else /* if (this.target.getAttribute( "type" ).toUpperCase() == "TEXT") */ {
  335.                  this.onTextInput = this.isTargetATextBox(this.target);
  336.                }
  337.             } else if ( this.target.tagName.toUpperCase() == "TEXTAREA" ) {
  338.                  this.onTextInput = true;
  339.             } else if ( this.target.getAttribute( "background" ) ) {
  340.                  this.hasBGImage = true;
  341.                  // Convert background attribute to absolute URL.
  342.                  this.bgImageURL = this.makeURLAbsolute( this.target.baseURI,
  343.                                                          this.target.getAttribute( "background" ) );
  344.             } else if ( "HTTPIndex" in _content &&
  345.                         _content.HTTPIndex instanceof Components.interfaces.nsIHTTPIndex ) {
  346.                 this.inDirList = true;
  347.                 // Bubble outward till we get to an element with URL attribute
  348.                 // (which should be the href).
  349.                 var root = this.target;
  350.                 while ( root && !this.link ) {
  351.                     if ( root.tagName == "tree" ) {
  352.                         // Hit root of tree; must have clicked in empty space;
  353.                         // thus, no link.
  354.                         break;
  355.                     }
  356.                     if ( root.getAttribute( "URL" ) ) {
  357.                         // Build pseudo link object so link-related functions work.
  358.                         this.onLink = true;
  359.                         this.link = { href : root.getAttribute("URL") };
  360.                         // If element is a directory, then you can't save it.
  361.                         if ( root.getAttribute( "container" ) == "true" ) {
  362.                             this.onSaveableLink = false;
  363.                         } else {
  364.                             this.onSaveableLink = true;
  365.                         }
  366.                     } else {
  367.                         root = root.parentNode;
  368.                     }
  369.                 }
  370.             } else if ( this.target.parentNode.tagName == "scrollbar"
  371.                         ||
  372.                         this.target.parentNode.tagName == "thumb"
  373.                         ||
  374.                         this.target.parentNode.tagName == "xul:slider") {
  375.                 this.shouldDisplay = false;
  376.             } else {
  377.                 try {
  378.                     var cssAttr = this.target.style.getPropertyValue( "list-style-image" ) ||
  379.                                   this.target.style.getPropertyValue( "list-style" ) ||
  380.                                   this.target.style.getPropertyValue( "background-image" ) ||
  381.                                   this.target.style.getPropertyValue( "background" );
  382.                     if ( cssAttr ) {
  383.                         this.onImage = true;
  384.                         var url = cssAttr.toLowerCase().replace(/url\("*(.+)"*\)/, "$1");
  385.                         // Convert attribute to absolute URL.
  386.                         this.imageURL = this.makeURLAbsolute( this.target.baseURI, url );
  387.                     }
  388.                 } catch ( exception ) {
  389.                 }
  390.             }
  391.         }
  392.  
  393.         // We have meta data on images.
  394.         this.onMetaDataItem = this.onImage;
  395.         
  396.         // See if the user clicked in a frame.
  397.         if ( this.target.ownerDocument != window._content.document ) {
  398.             this.inFrame = true;
  399.         }
  400.         
  401.         // Bubble out, looking for items of interest
  402.         elem = this.target;
  403.         while ( elem ) {
  404.             if ( elem.nodeType == 1 ) {
  405.                 var tagname = elem.tagName.toUpperCase();
  406.                 
  407.                 // Link?
  408.                 if ( !this.onLink && 
  409.                     ( tagname === "A"    ||
  410.                       tagname === "AREA" ||
  411.                       elem.getAttributeNS( "http://www.w3.org/1999/xlink", "type") == "simple" ) ) {
  412.                     // Clicked on a link.
  413.                     this.onLink = true;
  414.                     this.onMetaDataItem = true;
  415.                     // Remember corresponding element.
  416.                     this.link = elem;
  417.                     this.onMailtoLink = this.isLinkType( "mailto:", this.link );
  418.                     // Remember if it is saveable.
  419.                     this.onSaveableLink = this.isLinkSaveable( this.link );
  420.                 }
  421.                 
  422.                 // Text input?
  423.                 if ( !this.onTextInput ) {
  424.                     // Clicked on a link.
  425.                     this.onTextInput = this.isTargetATextBox(elem);
  426.                 }
  427.                 
  428.                 // Metadata item?
  429.                 if ( !this.onMetaDataItem ) {
  430.                     // We currently display metadata on anything which fits
  431.                     // the below test.
  432.                     if ( ( tagname === "BLOCKQUOTE" && elem.cite )        ||
  433.                          ( tagname === "Q"          && elem.cite )        ||
  434.                          ( tagname === "TABLE"      && elem.summary )     ||
  435.                          ( ( tagname === "INS" || tagname === "DEL" ) &&
  436.                            ( elem.cite || elem.dateTime ) )               ||
  437.                          elem.title                                       ||
  438.                          elem.lang ) {
  439.                         dump("On metadata item.\n");
  440.                         this.onMetaDataItem = true;
  441.                     }
  442.                 }
  443.  
  444.                 // Background image?
  445.                 if ( !this.hasBGImage && elem.background ) {
  446.                     this.hasBGImage = true;
  447.                     // Convert background attribute to absolute URL.
  448.                     this.bgImageURL = this.makeURLAbsolute( elem.baseURI,
  449.                                                             elem.background );
  450.                 }
  451.             }
  452.             elem = elem.parentNode;    
  453.         }
  454.     },
  455.     // Returns true iff clicked on link is saveable.
  456.     isLinkSaveable : function ( link ) {
  457.         // We don't do the Right Thing for news/snews yet, so turn them off
  458.         // until we do.
  459.         return !(this.isLinkType( "mailto:" , link )     ||
  460.                  this.isLinkType( "javascript:" , link ) ||
  461.                  this.isLinkType( "news:", link )        || 
  462.                  this.isLinkType( "snews:", link ) ); 
  463.     },
  464.     // Returns true iff clicked on link is of type given.
  465.     isLinkType : function ( linktype, link ) {        
  466.         try {
  467.             // Test for missing protocol property.
  468.             if ( !link.protocol ) {
  469.                 // We must resort to testing the URL string :-(.
  470.                 var protocol;
  471.                 if ( link.href ) {
  472.                     protocol = link.href.substr( 0, linktype.length );
  473.                 } else {
  474.                     protocol = link.getAttributeNS("http://www.w3.org/1999/xlink","href");
  475.                     if ( protocol ) {
  476.                         protocol = protocol.substr( 0, linktype.length );
  477.                     }
  478.                 }
  479.                 return protocol.toLowerCase() === linktype;        
  480.             } else {
  481.                 // Presume all but javascript: urls are saveable.
  482.                 return link.protocol.toLowerCase() === linktype;
  483.             }
  484.         } catch (e) {
  485.             // something was wrong with the link,
  486.             // so we won't be able to save it anyway
  487.             return false;
  488.         }
  489.     },
  490.     // Open linked-to URL in a new window.
  491.     openLink : function () {
  492.         // Determine linked-to URL.
  493.         openNewWindowWith( this.linkURL() );
  494.     },
  495.     // Edit linked-to URL in a new window.
  496.     editLink : function () {
  497.         urlSecurityCheck( this.linkURL(), window.document );
  498.         editPage( this.linkURL(), window, false );
  499.     },
  500.     // Reload clicked-in frame.
  501.     reloadFrame : function () {
  502.         this.target.ownerDocument.location.reload();
  503.     },
  504.     // Open clicked-in frame in its own window.
  505.     openFrame : function () {
  506.         openNewWindowWith( this.target.ownerDocument.location.href );
  507.     },
  508.     // Open clicked-in frame in the same window
  509.     showOnlyThisFrame : function () {
  510.         window._content.location.href = this.target.ownerDocument.location.href;
  511.     },
  512.     // Open new "view source" window with the frame's URL.
  513.     viewFrameSource : function () {
  514.       window.openDialog(  "chrome://navigator/content/viewSource.xul",
  515.                           "_blank",
  516.                           "scrollbars,resizable,chrome,dialog=no",
  517.                           this.target.ownerDocument.location.href,
  518.                           "charset=" + this.target.ownerDocument.characterSet);
  519.     },
  520.     viewInfo : function () {
  521.       BrowserPageInfo();
  522.     },
  523.     viewFrameInfo : function () {
  524.       BrowserPageInfo(this.target.ownerDocument);
  525.     },
  526.     // Change current window to the URL of the image.
  527.     viewImage : function () {
  528.         openTopWin( this.imageURL );
  529.     },
  530.     // Change current window to the URL of the background image.
  531.     viewBGImage : function () {
  532.         openTopWin( this.bgImageURL );
  533.     },
  534.     // Save URL of clicked-on frame.
  535.     saveFrame : function () {
  536.         this.savePage( this.target.ownerDocument.location.href, true );
  537.     },
  538.     // Save URL of clicked-on link.
  539.     saveLink : function () {
  540.         this.savePage( this.linkURL(), false );
  541.     },
  542.     // Save URL of clicked-on image.
  543.     saveImage : function () {
  544.         this.savePage( this.imageURL, true );
  545.     },
  546.     // Generate email address and put it on clipboard.
  547.     copyEmail : function () {
  548.         // Copy the comma-separated list of email addresses only.
  549.         // There are other ways of embedding email addresses in a mailto:
  550.         // link, but such complex parsing is beyond us.
  551.         var url = this.linkURL();
  552.         var qmark = url.indexOf( "?" );
  553.         var addresses;
  554.         
  555.         if ( qmark > 7 ) {                   // 7 == length of "mailto:"
  556.             addresses = url.substring( 7, qmark );
  557.         } else {
  558.             addresses = url.substr( 7 );
  559.         }
  560.  
  561.         var clipboard = this.getService( "@mozilla.org/widget/clipboardhelper;1",
  562.                                          Components.interfaces.nsIClipboardHelper );
  563.         clipboard.copyString(addresses);
  564.     },    
  565.     // Open Metadata window for node
  566.     showMetadata : function () {
  567.         window.openDialog(  "chrome://navigator/content/metadata.xul",
  568.                             "_blank",
  569.                             "scrollbars,resizable,chrome,dialog=no",
  570.                             this.target);
  571.     },
  572.  
  573.     ///////////////
  574.     // Utilities //
  575.     ///////////////
  576.  
  577.     // Create instance of component given contractId and iid (as string).
  578.     createInstance : function ( contractId, iidName ) {
  579.         var iid = Components.interfaces[ iidName ];
  580.         return Components.classes[ contractId ].createInstance( iid );
  581.     },
  582.     // Get service given contractId and iid (as string).
  583.     getService : function ( contractId, iidName ) {
  584.         var iid = Components.interfaces[ iidName ];
  585.         return Components.classes[ contractId ].getService( iid );
  586.     },
  587.     // Show/hide one item (specified via name or the item element itself).
  588.     showItem : function ( itemOrId, show ) {
  589.         var item = null;
  590.         if ( itemOrId.constructor == String ) {
  591.             // Argument specifies item id.
  592.             item = document.getElementById( itemOrId );
  593.         } else {
  594.             // Argument is the item itself.
  595.             item = itemOrId;
  596.         }
  597.         if ( item ) {
  598.             var styleIn = item.getAttribute( "style" );
  599.             var styleOut = styleIn;
  600.             if ( show ) {
  601.                 // Remove style="display:none;".
  602.                 styleOut = styleOut.replace( "display:none;", "" );
  603.  
  604.             } else {
  605.                 // Set style="display:none;".
  606.                 if ( styleOut.indexOf( "display:none;" ) == -1 ) {
  607.                     // Add style the first time we need to.
  608.                     styleOut += "display:none;";
  609.                 }
  610.             }
  611.             // Only set style if it's different.
  612.             if ( styleIn != styleOut ) {
  613.                 item.setAttribute( "style", styleOut );
  614.             }
  615.         }
  616.     },
  617.     // Set given attribute of specified context-menu item.  If the
  618.     // value is null, then it removes the attribute (which works
  619.     // nicely for the disabled attribute).
  620.     setItemAttr : function ( id, attr, val ) {
  621.         var elem = document.getElementById( id );
  622.         if ( elem ) {
  623.             if ( val == null ) {
  624.                 // null indicates attr should be removed.
  625.                 elem.removeAttribute( attr );
  626.             } else {
  627.                 // Set attr=val.
  628.                 elem.setAttribute( attr, val );
  629.             }
  630.         }
  631.     },
  632.     // Set context menu attribute according to like attribute of another node
  633.     // (such as a broadcaster).
  634.     setItemAttrFromNode : function ( item_id, attr, other_id ) {
  635.         var elem = document.getElementById( other_id );
  636.         if ( elem && elem.getAttribute( attr ) == "true" ) {
  637.             this.setItemAttr( item_id, attr, "true" );
  638.         } else {
  639.             this.setItemAttr( item_id, attr, null );
  640.         }
  641.     },
  642.     // Temporary workaround for DOM api not yet implemented by XUL nodes.
  643.     cloneNode : function ( item ) {
  644.         // Create another element like the one we're cloning.
  645.         var node = document.createElement( item.tagName );
  646.  
  647.         // Copy attributes from argument item to the new one.
  648.         var attrs = item.attributes;
  649.         for ( var i = 0; i < attrs.length; i++ ) {
  650.             var attr = attrs.item( i );
  651.             node.setAttribute( attr.nodeName, attr.nodeValue );
  652.         }
  653.  
  654.         // Voila!
  655.         return node;
  656.     },
  657.     // Generate fully-qualified URL for clicked-on link.
  658.     linkURL : function () {
  659.         if (this.link.href) {
  660.           return this.link.href;
  661.         }
  662.         var href = this.link.getAttributeNS("http://www.w3.org/1999/xlink","href");
  663.         if (href == "") {
  664.           throw "Empty href"; // Without this we try to save as the current doc, for example, HTML case also throws if empty
  665.         }
  666.         href = this.makeURLAbsolute(this.link.baseURI,href);
  667.         return href;
  668.     },
  669.     // Get text of link (if possible).
  670.     linkText : function () {
  671.         var text = this.gatherTextUnder( this.link );
  672.         return text;
  673.     },
  674.     // Gather all descendent text under given document node.
  675.     gatherTextUnder : function ( root ) {
  676.          var text = "";
  677.          var node = root.firstChild;
  678.          var depth = 1;
  679.          while ( node && depth > 0 ) {
  680.              // See if this node is text.
  681.              if ( node.nodeName == "#text" ) {
  682.                  // Add this text to our collection.
  683.                  text += " " + node.data;
  684.              } else if ( node.tagName == "IMG" ) {
  685.                  // If it has an alt= attribute, use that.
  686.                  altText = node.getAttribute( "alt" );
  687.                  if ( altText && altText != "" ) {
  688.                      text = altText;
  689.                      break;
  690.                  }
  691.              }
  692.              // Find next node to test.
  693.              // First, see if this node has children.
  694.              if ( node.hasChildNodes() ) {
  695.                  // Go to first child.
  696.                  node = node.firstChild;
  697.                  depth++;
  698.              } else {
  699.                  // No children, try next sibling.
  700.                  if ( node.nextSibling ) {
  701.                      node = node.nextSibling;
  702.                  } else {
  703.                      // Last resort is our next oldest uncle/aunt.
  704.                      node = node.parentNode.nextSibling;
  705.                      depth--;
  706.                  }
  707.              }
  708.          }
  709.          // Strip leading whitespace.
  710.          text = text.replace( /^\s+/, "" );
  711.          // Strip trailing whitespace.
  712.          text = text.replace( /\s+$/, "" );
  713.          // Compress remaining whitespace.
  714.          text = text.replace( /\s+/g, " " );
  715.          return text;
  716.     },
  717.     // Returns "true" if there's no text selected, null otherwise.
  718.     isNoTextSelected : function ( event ) {
  719.         // Not implemented so all text-selected-based options are disabled.
  720.         return "true";
  721.     },
  722.     // Determine if target <object> is an image.
  723.     objectIsImage : function ( objElem ) {
  724.         var result = false;
  725.         // Get type and data attributes.
  726.         var type = objElem.getAttribute( "type" );
  727.         var data = objElem.getAttribute( "data" );
  728.         // Presume any mime type of the form "image/..." is an image.
  729.         // There must be a data= attribute with an URL, also.
  730.         if ( type.substring( 0, 6 ) == "image/" && data && data != "" ) {
  731.             result = true;
  732.         }
  733.         return result;
  734.     },
  735.     // Extract image URL from <object> tag.
  736.     objectImageURL : function ( objElem ) {
  737.         // Extract url from data= attribute.
  738.         var data = objElem.getAttribute( "data" );
  739.         // Make it absolute.
  740.         return this.makeURLAbsolute( objElem.baseURI, data );
  741.     },
  742.     // Convert relative URL to absolute, using document's <base>.
  743.     makeURLAbsolute : function ( base, url ) {
  744.         // Construct nsIURL.
  745.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  746.                       .getService(Components.interfaces.nsIIOService);
  747.         var baseURI  = ioService.newURI(base, null);
  748.         
  749.         return ioService.newURI(baseURI.resolve(url), null);
  750.     },
  751.     // Save specified URL in user-selected file.
  752.     savePage : function ( url, doNotValidate ) {
  753.         var postData = null; // No post data, usually.
  754.         // Default is to save current page.
  755.         if ( !url ) {
  756.             url = window._content.location.href;
  757.  
  758.             try {
  759.                 var sessionHistory = getWebNavigation().sessionHistory;
  760.                 var entry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
  761.                 postData = entry.postData;
  762.             } catch(e) {
  763.             }
  764.         }
  765.  
  766.         // Use stream xfer component to prompt for destination and save.
  767.         var xfer = this.getService( "@mozilla.org/appshell/component/xfer;1",
  768.                                     "nsIStreamTransfer" );
  769.         try {
  770.             xfer.SelectFileAndTransferLocationSpec( url, window, "", "", doNotValidate, postData );
  771.         } catch( exception ) {
  772.             // Failed (or cancelled), give them another chance.
  773.         }
  774.         return;
  775.     },
  776.     // Parse coords= attribute and return array.
  777.     parseCoords : function ( area ) {
  778.         return [];
  779.     },
  780.     toString : function () {
  781.         return "contextMenu.target     = " + this.target + "\n" +
  782.                "contextMenu.onImage    = " + this.onImage + "\n" +
  783.                "contextMenu.onLink     = " + this.onLink + "\n" +
  784.                "contextMenu.link       = " + this.link + "\n" +
  785.                "contextMenu.inFrame    = " + this.inFrame + "\n" +
  786.                "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
  787.     },
  788.     isTargetATextBox : function ( node )
  789.     {
  790.       if (node.tagName.toUpperCase() == "INPUT") {
  791.         var attrib = node.getAttribute("type").toUpperCase();
  792.         return( (attrib != "IMAGE") &&
  793.                 (attrib != "PASSWORD") &&
  794.                 (attrib != "CHECKBOX") &&
  795.                 (attrib != "RADIO") &&
  796.                 (attrib != "SUBMIT") &&
  797.                 (attrib != "RESET") &&
  798.                 (attrib != "FILE") &&
  799.                 (attrib != "HIDDEN") &&
  800.                 (attrib != "RESET") &&
  801.                 (attrib != "BUTTON") );
  802.       } else  {
  803.         return(node.tagName.toUpperCase() == "TEXTAREA");
  804.       }
  805.     }
  806. };
  807.