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