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