home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 May / PCWorld_2003-05_cd.bin / Komunik / phoenix / chrome / comm.jar / content / communicator / nsContextMenu.js < prev    next >
Encoding:
JavaScript  |  2002-11-05  |  39.5 KB  |  936 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.popupURL       = null;
  40.     this.onTextInput    = false;
  41.     this.onImage        = false;
  42.     this.onLink         = false;
  43.     this.onMailtoLink   = false;
  44.     this.onSaveableLink = false;
  45.     this.onMetaDataItem = false;
  46.     this.onMathML       = false;
  47.     this.link           = false;
  48.     this.inFrame        = false;
  49.     this.hasBGImage     = false;
  50.     this.isTextSelected = false;
  51.     this.inDirList      = false;
  52.     this.shouldDisplay  = true;
  53.  
  54.     // Initialize new menu.
  55.     this.initMenu( xulMenu );
  56. }
  57.  
  58. // Prototype for nsContextMenu "class."
  59. nsContextMenu.prototype = {
  60.     // onDestroy is a no-op at this point.
  61.     onDestroy : function () {
  62.     },
  63.     // Initialize context menu.
  64.     initMenu : function ( popup ) {
  65.         // Save menu.
  66.         this.menu = popup;
  67.  
  68.         // Get contextual info.
  69.         this.setTarget( document.popupNode );
  70.         
  71.         this.isTextSelected = this.isTextSelection();
  72.  
  73.         this.initPopupURL();
  74.  
  75.         // Initialize (disable/remove) menu items.
  76.         this.initItems();
  77.     },
  78.     initItems : function () {
  79.         this.initOpenItems();
  80.         this.initNavigationItems();
  81.         this.initViewItems();
  82.         this.initMiscItems();
  83.         this.initSaveItems();
  84.         this.initClipboardItems();
  85.         this.initMetadataItems();
  86.     },
  87.     initOpenItems : function () {
  88.         this.showItem( "context-openlink", this.onSaveableLink || ( this.inDirList && this.onLink ) );
  89.         this.showItem( "context-openlinkintab", this.onSaveableLink || ( this.inDirList && this.onLink ) );
  90.  
  91.         this.showItem( "context-sep-open", this.onSaveableLink || ( this.inDirList && this.onLink ) );
  92.     },
  93.     initNavigationItems : function () {
  94.         // Back determined by canGoBack broadcaster.
  95.         this.setItemAttrFromNode( "context-back", "disabled", "canGoBack" );
  96.  
  97.         // Forward determined by canGoForward broadcaster.
  98.         this.setItemAttrFromNode( "context-forward", "disabled", "canGoForward" );
  99.         
  100.         this.showItem( "context-back", !( this.isTextSelected || this.onLink || this.onImage || this.onTextInput ) );
  101.         this.showItem( "context-forward", !( this.isTextSelected || this.onLink || this.onImage || this.onTextInput ) );
  102.  
  103.         this.showItem( "context-reload", !( this.isTextSelected || this.onLink || this.onImage || this.onTextInput ) );
  104.         
  105.         this.showItem( "context-stop", !( this.isTextSelected || this.onLink || this.onImage || this.onTextInput ) );
  106.         this.showItem( "context-sep-stop", !( this.isTextSelected || this.onLink || this.onImage || this.onTextInput ) );
  107.  
  108.         // XXX: Stop is determined in navigator.js; the canStop broadcaster is broken
  109.         //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
  110.     },
  111.     initSaveItems : function () {
  112.         this.showItem( "context-savepage", !( this.inDirList || this.isTextSelected || this.onTextInput ) && !( this.onLink && this.onImage ) );
  113.  
  114.         // Save link depends on whether we're in a link.
  115.         this.showItem( "context-savelink", this.onSaveableLink );
  116.  
  117.         // Save image depends on whether there is one.
  118.         this.showItem( "context-saveimage", this.onImage );
  119.         
  120.         this.showItem( "context-sendimage", this.onImage );
  121.     },
  122.     initViewItems : function () {
  123.         // View source is always OK, unless in directory listing.
  124.         this.showItem( "context-viewpartialsource-selection", this.isTextSelected && !this.onTextInput );
  125.         this.showItem( "context-viewpartialsource-mathml", this.onMathML && !this.isTextSelected );
  126.         this.showItem( "context-viewsource", !( this.inDirList || this.onImage || this.isTextSelected || this.onLink || this.onTextInput ) );
  127.         this.showItem( "context-viewinfo", !( this.inDirList || this.onImage || this.isTextSelected || this.onLink || this.onTextInput ) );
  128.  
  129.         this.showItem( "context-sep-properties", !( this.inDirList || this.isTextSelected || this.onTextInput ) );
  130.         // Set As Wallpaper depends on whether an image was clicked on, and only works on Windows.
  131.         var isWin = navigator.appVersion.indexOf("Windows") != -1;
  132.         this.showItem( "context-setWallpaper", isWin && this.onImage );
  133.  
  134.         this.showItem( "context-sep-image", this.onImage );
  135.  
  136.         if( isWin && this.onImage )
  137.             // Disable the Set As Wallpaper menu item if we're still trying to load the image
  138.           this.setItemAttr( "context-setWallpaper", "disabled", (("complete" in this.target) && !this.target.complete) ? "true" : null );
  139.  
  140.         // View Image depends on whether an image was clicked on.
  141.         this.showItem( "context-viewimage", this.onImage );
  142.  
  143.         // View background image depends on whether there is one.
  144.         this.showItem( "context-viewbgimage", !( this.inDirList || this.onImage || this.isTextSelected || this.onLink || this.onTextInput ) );
  145.         this.showItem( "context-sep-viewbgimage", !( this.inDirList || this.onImage || this.isTextSelected || this.onLink || this.onTextInput ) );
  146.         this.setItemAttr( "context-viewbgimage", "disabled", this.hasBGImage ? null : "true");
  147.     },
  148.     initMiscItems : function () {
  149.         // Use "Bookmark This Link" if on a link.
  150.         this.showItem( "context-bookmarkpage", !( this.isTextSelected || this.onTextInput ) );
  151.         this.showItem( "context-bookmarklink", this.onLink && !this.onMailtoLink );
  152.         this.showItem( "context-searchselect", this.isTextSelected && !this.onTextInput );
  153.         this.showItem( "frame", this.inFrame );
  154.         this.showItem( "frame-sep", this.inFrame );
  155.         var blocking = true;
  156.         if (this.popupURL)
  157.           try {
  158.             const PM = Components.classes["@mozilla.org/PopupWindowManager;1"]
  159.                        .getService(Components.interfaces.nsIPopupWindowManager);
  160.             blocking = PM.testPermission(this.popupURL) ==
  161.                        Components.interfaces.nsIPopupWindowManager.DENY_POPUP;
  162.           } catch (e) {
  163.           }
  164.  
  165.         this.showItem( "popupwindow-reject", this.popupURL && !blocking);
  166.         this.showItem( "popupwindow-allow", this.popupURL && blocking);
  167.         this.showItem( "context-sep-popup", this.popupURL);
  168.     },
  169.     initClipboardItems : function () {
  170.  
  171.         // Copy depends on whether there is selected text.
  172.         // Enabling this context menu item is now done through the global
  173.         // command updating system
  174.         // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
  175.  
  176.         goUpdateGlobalEditMenuItems();
  177.  
  178.         this.showItem( "context-undo", this.onTextInput );
  179.         this.showItem( "context-redo", this.onTextInput );
  180.         this.showItem( "context-sep-undo", this.onTextInput );
  181.         this.showItem( "context-cut", this.onTextInput );
  182.         this.showItem( "context-copy", true );
  183.         this.showItem( "context-paste", this.onTextInput );
  184.         this.showItem( "context-delete", this.onTextInput );
  185.         this.showItem( "context-sep-paste", this.onTextInput );
  186.         this.showItem( "context-selectall", true );
  187.         this.showItem( "context-sep-selectall", this.isTextSelected && !this.onTextInput );
  188.         // In a text area there will be nothing after select all, so we don't want a sep
  189.         // Otherwise, if there's text selected then there are extra menu items
  190.         // (search for selection and view selection source), so we do want a sep
  191.  
  192.         // XXX dr
  193.         // ------
  194.         // nsDocumentViewer.cpp has code to determine whether we're
  195.         // on a link or an image. we really ought to be using that...
  196.  
  197.         // Copy email link depends on whether we're on an email link.
  198.         this.showItem( "context-copyemail", this.onMailtoLink );
  199.  
  200.         // Copy link location depends on whether we're on a link.
  201.         this.showItem( "context-copylink", this.onLink );
  202.         this.showItem( "context-sep-copylink", this.onLink );
  203.  
  204.         // Copy image location depends on whether we're on an image.
  205.         this.showItem( "context-copyimage", this.onImage );
  206.         this.showItem( "context-sep-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.     },
  212.     // Set various context menu attributes based on the state of the world.
  213.     setTarget : function ( node ) {
  214.         const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  215.         if ( node.namespaceURI == xulNS ) {
  216.           this.shouldDisplay = false;
  217.           return;
  218.         }
  219.         // Initialize contextual info.
  220.         this.onImage    = false;
  221.         this.onMetaDataItem = false;
  222.         this.onTextInput = false;
  223.         this.imageURL   = "";
  224.         this.onLink     = false;
  225.         this.onMathML   = false;
  226.         this.inFrame    = false;
  227.         this.hasBGImage = false;
  228.         this.bgImageURL = "";
  229.  
  230.         // Remember the node that was clicked.
  231.         this.target = node;
  232.  
  233.         // See if the user clicked on an image.
  234.         if ( this.target.nodeType == Node.ELEMENT_NODE ) {
  235.              if ( this.target.localName.toUpperCase() == "IMG" ) {
  236.                 this.onImage = true;
  237.                 this.imageURL = this.target.src;
  238.                 // Look for image map.
  239.                 var mapName = this.target.getAttribute( "usemap" );
  240.                 if ( mapName ) {
  241.                     // Find map.
  242.                     var map = this.target.ownerDocument.getElementById( mapName.substr(1) );
  243.                     if ( map ) {
  244.                         // Search child <area>s for a match.
  245.                         var areas = map.childNodes;
  246.                         //XXX Client side image maps are too hard for now!
  247.                         areas.length = 0;
  248.                         for ( var i = 0; i < areas.length && !this.onLink; i++ ) {
  249.                             var area = areas[i];
  250.                             if ( area.nodeType == Node.ELEMENT_NODE
  251.                                  &&
  252.                                  area.localName.toUpperCase() == "AREA" ) {
  253.                                 // Get type (rect/circle/polygon/default).
  254.                                 var type = area.getAttribute( "type" );
  255.                                 var coords = this.parseCoords( area );
  256.                                 switch ( type.toUpperCase() ) {
  257.                                     case "RECT":
  258.                                     case "RECTANGLE":
  259.                                         break;
  260.                                     case "CIRC":
  261.                                     case "CIRCLE":
  262.                                         break;
  263.                                     case "POLY":
  264.                                     case "POLYGON":
  265.                                         break;
  266.                                     case "DEFAULT":
  267.                                         // Default matches entire image.
  268.                                         this.onLink = true;
  269.                                         this.link = area;
  270.                                         this.onSaveableLink = this.isLinkSaveable( this.link );
  271.                                         break;
  272.                                 }
  273.                             }
  274.                         }
  275.                     }
  276.                 }
  277.              } else if ( this.target.localName.toUpperCase() == "OBJECT"
  278.                          &&
  279.                          // See if object tag is for an image.
  280.                          this.objectIsImage( this.target ) ) {
  281.                 // This is an image.
  282.                 this.onImage = true;
  283.                 // URL must be constructed.
  284.                 this.imageURL = this.objectImageURL( this.target );
  285.              } else if ( this.target.localName.toUpperCase() == "INPUT") {
  286.                type = this.target.getAttribute("type");
  287.                if(type && type.toUpperCase() == "IMAGE") {
  288.                  this.onImage = true;
  289.                  // Convert src attribute to absolute URL.
  290.                  this.imageURL = this.makeURLAbsolute( this.target.baseURI,
  291.                                                        this.target.src );
  292.                } else /* if (this.target.getAttribute( "type" ).toUpperCase() == "TEXT") */ {
  293.                  this.onTextInput = this.isTargetATextBox(this.target);
  294.                }
  295.             } else if ( this.target.localName.toUpperCase() == "TEXTAREA" ) {
  296.                  this.onTextInput = true;
  297.             } else if ( this.target.localName.toUpperCase() == "HTML" ) {
  298.                // pages with multiple <body>s are lame. we'll teach them a lesson.
  299.                var bodyElt = this.target.ownerDocument.getElementsByTagName("body")[0];
  300.                if ( bodyElt ) {
  301.                  var computedURL = this.getComputedURL( bodyElt, "background-image" );
  302.                  if ( computedURL ) {
  303.                    this.hasBGImage = true;
  304.                    this.bgImageURL = this.makeURLAbsolute( bodyElt.baseURI,
  305.                                                            computedURL );
  306.                  }
  307.                }
  308.             } else if ( "HTTPIndex" in _content &&
  309.                         _content.HTTPIndex instanceof Components.interfaces.nsIHTTPIndex ) {
  310.                 this.inDirList = true;
  311.                 // Bubble outward till we get to an element with URL attribute
  312.                 // (which should be the href).
  313.                 var root = this.target;
  314.                 while ( root && !this.link ) {
  315.                     if ( root.tagName == "tree" ) {
  316.                         // Hit root of tree; must have clicked in empty space;
  317.                         // thus, no link.
  318.                         break;
  319.                     }
  320.                     if ( root.getAttribute( "URL" ) ) {
  321.                         // Build pseudo link object so link-related functions work.
  322.                         this.onLink = true;
  323.                         this.link = { href : root.getAttribute("URL"),
  324.                                       getAttribute: function (attr) {
  325.                                           if (attr == "title") {
  326.                                               return root.firstChild.firstChild.getAttribute("label");
  327.                                           } else {
  328.                                               return "";
  329.                                           }
  330.                                       }
  331.                                     };
  332.                         // If element is a directory, then you can't save it.
  333.                         if ( root.getAttribute( "container" ) == "true" ) {
  334.                             this.onSaveableLink = false;
  335.                         } else {
  336.                             this.onSaveableLink = true;
  337.                         }
  338.                     } else {
  339.                         root = root.parentNode;
  340.                     }
  341.                 }
  342.             }
  343.         }
  344.  
  345.         // We have meta data on images.
  346.         this.onMetaDataItem = this.onImage;
  347.         
  348.         // See if the user clicked on MathML
  349.         const NS_MathML = "http://www.w3.org/1998/Math/MathML";
  350.         if ((this.target.nodeType == Node.TEXT_NODE &&
  351.              this.target.parentNode.namespaceURI == NS_MathML)
  352.              || (this.target.namespaceURI == NS_MathML))
  353.           this.onMathML = true;
  354.  
  355.         // See if the user clicked in a frame.
  356.         if ( this.target.ownerDocument != window._content.document ) {
  357.             this.inFrame = true;
  358.         }
  359.         
  360.         // Bubble out, looking for items of interest
  361.         var elem = this.target;
  362.         while ( elem ) {
  363.             if ( elem.nodeType == Node.ELEMENT_NODE ) {
  364.                 var localname = elem.localName.toUpperCase();
  365.                 
  366.                 // Link?
  367.                 if ( !this.onLink && 
  368.                     ( (localname === "A" && elem.href) ||
  369.                       localname === "AREA" ||
  370.                       localname === "LINK" ||
  371.                       elem.getAttributeNS( "http://www.w3.org/1999/xlink", "type") == "simple" ) ) {
  372.                     // Clicked on a link.
  373.                     this.onLink = true;
  374.                     this.onMetaDataItem = true;
  375.                     // Remember corresponding element.
  376.                     this.link = elem;
  377.                     this.onMailtoLink = this.isLinkType( "mailto:", this.link );
  378.                     // Remember if it is saveable.
  379.                     this.onSaveableLink = this.isLinkSaveable( this.link );
  380.                 }
  381.                 
  382.                 // Text input?
  383.                 if ( !this.onTextInput ) {
  384.                     // Clicked on a link.
  385.                     this.onTextInput = this.isTargetATextBox(elem);
  386.                 }
  387.                 
  388.                 // Metadata item?
  389.                 if ( !this.onMetaDataItem ) {
  390.                     // We currently display metadata on anything which fits
  391.                     // the below test.
  392.                     if ( ( localname === "BLOCKQUOTE" && 'cite' in elem && elem.cite)  ||
  393.                          ( localname === "Q" && 'cite' in elem && elem.cite)           ||
  394.                          ( localname === "TABLE" && 'summary' in elem && elem.summary) ||
  395.                          ( ( localname === "INS" || localname === "DEL" ) &&
  396.                            ( ( 'cite' in elem && elem.cite ) ||
  397.                              ( 'dateTime' in elem && elem.dateTime ) ) )               ||
  398.                          ( 'title' in elem && elem.title )                             ||
  399.                          ( 'lang' in elem && elem.lang ) ) {
  400.                         dump("On metadata item.\n");
  401.                         this.onMetaDataItem = true;
  402.                     }
  403.                 }
  404.  
  405.                 // Background image?  Don't bother if we've already found a 
  406.                 // background image further down the hierarchy.  Otherwise,
  407.                 // we look for the computed background-image style.
  408.                 if ( !this.hasBGImage ) {
  409.                     var bgImgUrl = this.getComputedURL( elem, "background-image" );
  410.                     if ( bgImgUrl ) {
  411.                         this.hasBGImage = true;
  412.                         this.bgImageURL = this.makeURLAbsolute( elem.baseURI,
  413.                                                                 bgImgUrl );
  414.                     }
  415.                 }
  416.             }
  417.             elem = elem.parentNode;    
  418.         }
  419.     },
  420.     initPopupURL: function() {
  421.       return; // remove this line to reenable the context menu
  422.       // quick check: if no opener, it can't be a popup
  423.       if (!window.content.opener)
  424.         return;
  425.       try {
  426.         var show = false;
  427.         // is it a popup window?
  428.         const CI = Components.interfaces;
  429.         var xulwin = window
  430.                     .QueryInterface(CI.nsIInterfaceRequestor)
  431.                     .getInterface(CI.nsIWebNavigation)
  432.                     .QueryInterface(CI.nsIDocShellTreeItem)
  433.                     .treeOwner
  434.                     .QueryInterface(CI.nsIInterfaceRequestor)
  435.                     .getInterface(CI.nsIXULWindow);
  436.         if (xulwin.contextFlags &
  437.             CI.nsIWindowCreator2.PARENT_IS_LOADING_OR_RUNNING_TIMEOUT) {
  438.           // do the pref settings allow site-by-site popup management?
  439.           const PB = Components.classes["@mozilla.org/preferences-service;1"]
  440.                      .getService(CI.nsIPrefBranch);
  441.           show = !PB.getBoolPref("dom.disable_open_during_load") &&
  442.                  PB.getIntPref("privacy.popups.policy") ==
  443.                      CI.nsIPopupWindowManager.ALLOW_POPUP &&
  444.                  PB.getBoolPref("privacy.popups.usecustom");
  445.         }
  446.         if (show) {
  447.           // initialize popupURL
  448.           const IOS = Components.classes["@mozilla.org/network/io-service;1"]
  449.                       .getService(CI.nsIIOService);
  450.           var spec = Components.lookupMethod(window.content.opener, "location")
  451.                      .call();
  452.           this.popupURL = IOS.newURI(spec, null, null);
  453.  
  454.           // but cancel if it's an unsuitable URL
  455.           const PM = Components.classes["@mozilla.org/PopupWindowManager;1"]
  456.                      .getService(CI.nsIPopupWindowManager);
  457.           if (!PM.testSuitability(this.popupURL))
  458.             this.popupURL = null;
  459.         }
  460.       } catch(e) {
  461.       }
  462.     },
  463.     // Returns the computed style attribute for the given element.
  464.     getComputedStyle: function( elem, prop ) {
  465.          return elem.ownerDocument.defaultView.getComputedStyle( elem, '' ).getPropertyValue( prop );
  466.     },
  467.     // Returns a "url"-type computed style attribute value, with the url() stripped.
  468.     getComputedURL: function( elem, prop ) {
  469.          var url = elem.ownerDocument.defaultView.getComputedStyle( elem, '' ).getPropertyCSSValue( prop );
  470.          return ( url.primitiveType == CSSPrimitiveValue.CSS_URI ) ? url.getStringValue() : null;
  471.     },
  472.     // Returns true iff clicked on link is saveable.
  473.     isLinkSaveable : function ( link ) {
  474.         // We don't do the Right Thing for news/snews yet, so turn them off
  475.         // until we do.
  476.         return !(this.isLinkType( "mailto:" , link )     ||
  477.                  this.isLinkType( "javascript:" , link ) ||
  478.                  this.isLinkType( "news:", link )        || 
  479.                  this.isLinkType( "snews:", link ) ); 
  480.     },
  481.     // Returns true iff clicked on link is of type given.
  482.     isLinkType : function ( linktype, link ) {        
  483.         try {
  484.             // Test for missing protocol property.
  485.             if ( !link.protocol ) {
  486.                 // We must resort to testing the URL string :-(.
  487.                 var protocol;
  488.                 if ( link.href ) {
  489.                     protocol = link.href.substr( 0, linktype.length );
  490.                 } else {
  491.                     protocol = link.getAttributeNS("http://www.w3.org/1999/xlink","href");
  492.                     if ( protocol ) {
  493.                         protocol = protocol.substr( 0, linktype.length );
  494.                     }
  495.                 }
  496.                 return protocol.toLowerCase() === linktype;        
  497.             } else {
  498.                 // Presume all but javascript: urls are saveable.
  499.                 return link.protocol.toLowerCase() === linktype;
  500.             }
  501.         } catch (e) {
  502.             // something was wrong with the link,
  503.             // so we won't be able to save it anyway
  504.             return false;
  505.         }
  506.     },
  507.     // Block popup windows
  508.     rejectPopupWindows: function(andClose) {
  509.       const PM = Components.classes["@mozilla.org/PopupWindowManager;1"]
  510.                  .getService(Components.interfaces.nsIPopupWindowManager);
  511.       PM.add(this.popupURL, false);
  512.       if (andClose) {
  513.         const OS = Components.classes["@mozilla.org/observer-service;1"]
  514.                    .getService(Components.interfaces.nsIObserverService);
  515.         OS.notifyObservers(window, "popup-perm-close", this.popupURL.spec);
  516.       }
  517.     },
  518.     // Unblock popup windows
  519.     allowPopupWindows: function() {
  520.       const PM = Components.classes["@mozilla.org/PopupWindowManager;1"]
  521.                  .getService(Components.interfaces.nsIPopupWindowManager);
  522.       PM.add(this.popupURL, true);
  523.     },
  524.     // Open linked-to URL in a new window.
  525.     openLink : function () {
  526.         // Determine linked-to URL.
  527.         openNewWindowWith( this.linkURL(), true );
  528.     },
  529.     // Open linked-to URL in a new tab.
  530.     openLinkInTab : function () {
  531.         // Determine linked-to URL.
  532.         openNewTabWith( this.linkURL(), true, false );
  533.     },
  534.     // Open frame in a new tab.
  535.     openFrameInTab : function () {
  536.         // Determine linked-to URL.
  537.         openNewTabWith( this.target.ownerDocument.location.href );
  538.     },
  539.     // Reload clicked-in frame.
  540.     reloadFrame : function () {
  541.         this.target.ownerDocument.location.reload();
  542.     },
  543.     // Open clicked-in frame in its own window.
  544.     openFrame : function () {
  545.         openNewWindowWith( this.target.ownerDocument.location.href );
  546.     },
  547.     // Open clicked-in frame in the same window
  548.     showOnlyThisFrame : function () {
  549.         window.loadURI(this.target.ownerDocument.location.href);
  550.     },
  551.     // View Partial Source
  552.     viewPartialSource : function ( context ) {
  553.         var focusedWindow = document.commandDispatcher.focusedWindow;
  554.         if (focusedWindow == window)
  555.           focusedWindow = _content;
  556.         var docCharset = null;
  557.         if (focusedWindow)
  558.           docCharset = "charset=" + focusedWindow.document.characterSet;
  559.  
  560.         // "View Selection Source" and others such as "View MathML Source"
  561.         // are mutually exclusive, with the precedence given to the selection
  562.         // when there is one
  563.         var reference = null;
  564.         if (context == "selection")
  565.           reference = focusedWindow.__proto__.getSelection.call(focusedWindow);
  566.         else if (context == "mathml")
  567.           reference = this.target;
  568.         else
  569.           throw "not reached";
  570.  
  571.         var docUrl = null; // unused (and play nice for fragments generated via XSLT too)
  572.         window.openDialog("chrome://navigator/content/viewPartialSource.xul",
  573.                           "_blank", "scrollbars,resizable,chrome,dialog=no",
  574.                           docUrl, docCharset, reference, context);
  575.     },
  576.     // Open new "view source" window with the frame's URL.
  577.     viewFrameSource : function () {
  578.         BrowserViewSourceOfDocument(this.target.ownerDocument);
  579.     },
  580.     viewInfo : function () {
  581.       BrowserPageInfo();
  582.     },
  583.     viewFrameInfo : function () {
  584.       BrowserPageInfo(this.target.ownerDocument);
  585.     },
  586.     // Change current window to the URL of the image.
  587.     viewImage : function () {
  588.         openTopWin( this.imageURL );
  589.     },
  590.     // Change current window to the URL of the background image.
  591.     viewBGImage : function () {
  592.         openTopWin( this.bgImageURL );
  593.     },
  594.     setWallpaper: function() {
  595.       var winhooks = Components.classes[ "@mozilla.org/winhooks;1" ].
  596.                        getService(Components.interfaces.nsIWindowsHooks);
  597.       
  598.       winhooks.setImageAsWallpaper(this.target, false);
  599.     },    
  600.     // Save URL of clicked-on frame.
  601.     saveFrame : function () {
  602.         saveDocument( this.target.ownerDocument );
  603.     },
  604.     // Save URL of clicked-on link.
  605.     saveLink : function () {
  606.         saveURL( this.linkURL(), this.linkText(), null, true );
  607.     },
  608.     // Save URL of clicked-on image.
  609.     saveImage : function () {
  610.         saveURL( this.imageURL, null, "SaveImageTitle", false );
  611.     },
  612.     // Generate email address and put it on clipboard.
  613.     copyEmail : function () {
  614.         // Copy the comma-separated list of email addresses only.
  615.         // There are other ways of embedding email addresses in a mailto:
  616.         // link, but such complex parsing is beyond us.
  617.         var url = this.linkURL();
  618.         var qmark = url.indexOf( "?" );
  619.         var addresses;
  620.         
  621.         if ( qmark > 7 ) {                   // 7 == length of "mailto:"
  622.             addresses = url.substring( 7, qmark );
  623.         } else {
  624.             addresses = url.substr( 7 );
  625.         }
  626.  
  627.         var clipboard = this.getService( "@mozilla.org/widget/clipboardhelper;1",
  628.                                          Components.interfaces.nsIClipboardHelper );
  629.         clipboard.copyString(addresses);
  630.     },    
  631.     addBookmark : function() {
  632.       var docshell = document.getElementById( "content" ).webNavigation;
  633.       BookmarksUtils.addBookmark( docshell.currentURI.spec,
  634.                                   docshell.document.title,
  635.                                   docshell.document.charset,
  636.                                   false );
  637.     },
  638.     addBookmarkForFrame : function() {
  639.       var doc = this.target.ownerDocument;
  640.       var uri = doc.location.href;
  641.       var title = doc.title;
  642.       if ( !title )
  643.         title = uri;
  644.       BookmarksUtils.addBookmark( uri,
  645.                                   title,
  646.                                   doc.charset,
  647.                                   false );
  648.     },
  649.     // Open Metadata window for node
  650.     showMetadata : function () {
  651.         window.openDialog(  "chrome://navigator/content/metadata.xul",
  652.                             "_blank",
  653.                             "scrollbars,resizable,chrome,dialog=no",
  654.                             this.target);
  655.     },
  656.  
  657.     ///////////////
  658.     // Utilities //
  659.     ///////////////
  660.  
  661.     // Create instance of component given contractId and iid (as string).
  662.     createInstance : function ( contractId, iidName ) {
  663.         var iid = Components.interfaces[ iidName ];
  664.         return Components.classes[ contractId ].createInstance( iid );
  665.     },
  666.     // Get service given contractId and iid (as string).
  667.     getService : function ( contractId, iidName ) {
  668.         var iid = Components.interfaces[ iidName ];
  669.         return Components.classes[ contractId ].getService( iid );
  670.     },
  671.     // Show/hide one item (specified via name or the item element itself).
  672.     showItem : function ( itemOrId, show ) {
  673.         var item = itemOrId.constructor == String ? document.getElementById(itemOrId) : itemOrId;
  674.         if (item) 
  675.           item.hidden = !show;
  676.     },
  677.     // Set given attribute of specified context-menu item.  If the
  678.     // value is null, then it removes the attribute (which works
  679.     // nicely for the disabled attribute).
  680.     setItemAttr : function ( id, attr, val ) {
  681.         var elem = document.getElementById( id );
  682.         if ( elem ) {
  683.             if ( val == null ) {
  684.                 // null indicates attr should be removed.
  685.                 elem.removeAttribute( attr );
  686.             } else {
  687.                 // Set attr=val.
  688.                 elem.setAttribute( attr, val );
  689.             }
  690.         }
  691.     },
  692.     // Set context menu attribute according to like attribute of another node
  693.     // (such as a broadcaster).
  694.     setItemAttrFromNode : function ( item_id, attr, other_id ) {
  695.         var elem = document.getElementById( other_id );
  696.         if ( elem && elem.getAttribute( attr ) == "true" ) {
  697.             this.setItemAttr( item_id, attr, "true" );
  698.         } else {
  699.             this.setItemAttr( item_id, attr, null );
  700.         }
  701.     },
  702.     // Temporary workaround for DOM api not yet implemented by XUL nodes.
  703.     cloneNode : function ( item ) {
  704.         // Create another element like the one we're cloning.
  705.         var node = document.createElement( item.tagName );
  706.  
  707.         // Copy attributes from argument item to the new one.
  708.         var attrs = item.attributes;
  709.         for ( var i = 0; i < attrs.length; i++ ) {
  710.             var attr = attrs.item( i );
  711.             node.setAttribute( attr.nodeName, attr.nodeValue );
  712.         }
  713.  
  714.         // Voila!
  715.         return node;
  716.     },
  717.     // Generate fully-qualified URL for clicked-on link.
  718.     linkURL : function () {
  719.         if (this.link.href) {
  720.           return this.link.href;
  721.         }
  722.         var href = this.link.getAttributeNS("http://www.w3.org/1999/xlink","href");
  723.         if (!href || !href.match(/\S/)) {
  724.           throw "Empty href"; // Without this we try to save as the current doc, for example, HTML case also throws if empty
  725.         }
  726.         href = this.makeURLAbsolute(this.link.baseURI,href);
  727.         return href;
  728.     },
  729.     // Get text of link.
  730.     linkText : function () {
  731.         var text = gatherTextUnder( this.link );
  732.         if (!text || !text.match(/\S/)) {
  733.           text = this.link.getAttribute("title");
  734.           if (!text || !text.match(/\S/)) {
  735.             text = this.link.getAttribute("alt");
  736.             if (!text || !text.match(/\S/)) {
  737.               if (this.link.href) {                
  738.                 text = this.link.href;
  739.               } else {
  740.                 text = getAttributeNS("http://www.w3.org/1999/xlink", "href");
  741.                 if (text && text.match(/\S/)) {
  742.                   text = this.makeURLAbsolute(this.link.baseURI, text);
  743.                 }
  744.               }
  745.             }
  746.           }
  747.         }
  748.  
  749.         return text;
  750.     },
  751.  
  752.     //Get selected object and convert it to a string to get
  753.     //selected text.   Only use the first 15 chars.
  754.     isTextSelection : function() {
  755.         var result = false;
  756.         var selection = this.searchSelected();
  757.  
  758.         var bundle = srGetStrBundle("chrome://communicator/locale/contentAreaCommands.properties");
  759.  
  760.         var searchSelectText;
  761.         if (selection != "") {
  762.             searchSelectText = selection.toString();
  763.             if (searchSelectText.length > 15)
  764.                 searchSelectText = searchSelectText.substr(0,15) + "...";
  765.             result = true;
  766.  
  767.           // format "Search for <selection>" string to show in menu
  768.           searchSelectText = bundle.formatStringFromName("searchText",
  769.                                                          [searchSelectText], 1);
  770.           this.setItemAttr("context-searchselect", "label", searchSelectText);
  771.         } 
  772.         return result;
  773.     },
  774.     
  775.     searchSelected : function() {
  776.         var focusedWindow = document.commandDispatcher.focusedWindow;
  777.         var searchStr = focusedWindow.__proto__.getSelection.call(focusedWindow);
  778.         searchStr = searchStr.toString();
  779.         searchStr = searchStr.replace( /^\s+/, "" );
  780.         searchStr = searchStr.replace(/(\n|\r|\t)+/g, " ");
  781.         searchStr = searchStr.replace(/\s+$/,"");
  782.         return searchStr;
  783.     },
  784.     
  785.     // Determine if target <object> is an image.
  786.     objectIsImage : function ( objElem ) {
  787.         var result = false;
  788.         // Get type and data attributes.
  789.         var type = objElem.getAttribute( "type" );
  790.         var data = objElem.getAttribute( "data" );
  791.         // Presume any mime type of the form "image/..." is an image.
  792.         // There must be a data= attribute with an URL, also.
  793.         if ( type.substring( 0, 6 ) == "image/" && data && data != "" ) {
  794.             result = true;
  795.         }
  796.         return result;
  797.     },
  798.     // Extract image URL from <object> tag.
  799.     objectImageURL : function ( objElem ) {
  800.         // Extract url from data= attribute.
  801.         var data = objElem.getAttribute( "data" );
  802.         // Make it absolute.
  803.         return this.makeURLAbsolute( objElem.baseURI, data );
  804.     },
  805.     // Convert relative URL to absolute, using document's <base>.
  806.     makeURLAbsolute : function ( base, url ) {
  807.         // Construct nsIURL.
  808.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  809.                       .getService(Components.interfaces.nsIIOService);
  810.         var baseURI  = ioService.newURI(base, null, null);
  811.         
  812.         return ioService.newURI(baseURI.resolve(url), null, null).spec;
  813.     },
  814.     // Parse coords= attribute and return array.
  815.     parseCoords : function ( area ) {
  816.         return [];
  817.     },
  818.     toString : function () {
  819.         return "contextMenu.target     = " + this.target + "\n" +
  820.                "contextMenu.onImage    = " + this.onImage + "\n" +
  821.                "contextMenu.onLink     = " + this.onLink + "\n" +
  822.                "contextMenu.link       = " + this.link + "\n" +
  823.                "contextMenu.inFrame    = " + this.inFrame + "\n" +
  824.                "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
  825.     },
  826.     isTargetATextBox : function ( node )
  827.     {
  828.       if (node.nodeType != Node.ELEMENT_NODE)
  829.         return false;
  830.  
  831.       if (node.localName.toUpperCase() == "INPUT") {
  832.         var attrib = "";
  833.         var type = node.getAttribute("type");
  834.  
  835.         if (type)
  836.           attrib = type.toUpperCase();
  837.  
  838.         return( (attrib != "IMAGE") &&
  839.                 (attrib != "CHECKBOX") &&
  840.                 (attrib != "RADIO") &&
  841.                 (attrib != "SUBMIT") &&
  842.                 (attrib != "RESET") &&
  843.                 (attrib != "HIDDEN") &&
  844.                 (attrib != "RESET") &&
  845.                 (attrib != "BUTTON") );
  846.       } else  {
  847.         return(node.localName.toUpperCase() == "TEXTAREA");
  848.       }
  849.     },
  850.     
  851.     // Determines whether or not the separator with the specified ID should be 
  852.     // shown or not by determining if there are any non-hidden items between it
  853.     // and the previous separator. 
  854.     shouldShowSeparator : function ( aSeparatorID )
  855.     {
  856.       var separator = document.getElementById(aSeparatorID);
  857.       if (separator) {
  858.         var sibling = separator.previousSibling;
  859.         while (sibling && sibling.localName != "menuseparator") {
  860.           if (sibling.getAttribute("hidden") != "true")
  861.             return true;
  862.           sibling = sibling.previousSibling;
  863.         }
  864.       }
  865.       return false;  
  866.     }
  867. };
  868.  
  869. /*************************************************************************
  870.  *
  871.  *   nsDefaultEngine : nsIObserver
  872.  *
  873.  *************************************************************************/
  874. function nsDefaultEngine()
  875. {
  876.     try
  877.     {
  878.         var pb = Components.classes["@mozilla.org/preferences-service;1"].
  879.                    getService(Components.interfaces.nsIPrefBranch);
  880.         var pbi = pb.QueryInterface(
  881.                     Components.interfaces.nsIPrefBranchInternal);
  882.         pbi.addObserver(this.domain, this, false);
  883.  
  884.         // reuse code by explicitly invoking initial |observe| call
  885.         // to initialize the |icon| and |name| member variables
  886.         this.observe(pb, "", this.domain);
  887.     }
  888.     catch (ex)
  889.     {
  890.     }
  891. }
  892.  
  893. nsDefaultEngine.prototype = 
  894. {
  895.     name: "",
  896.     icon: "",
  897.     domain: "browser.search.defaultengine",
  898.  
  899.     // nsIObserver implementation
  900.     observe: function(aPrefBranch, aTopic, aPrefName)
  901.     {
  902.         try
  903.         {
  904.             var rdf = Components.
  905.                         classes["@mozilla.org/rdf/rdf-service;1"].
  906.                         getService(Components.interfaces.nsIRDFService);
  907.             var ds = rdf.GetDataSource("rdf:internetsearch");
  908.             var defaultEngine = aPrefBranch.getCharPref(aPrefName);
  909.             var res = rdf.GetResource(defaultEngine);
  910.  
  911.             // get engine ``pretty'' name
  912.             const kNC_Name = rdf.GetResource(
  913.                                "http://home.netscape.com/NC-rdf#Name");
  914.             var engineName = ds.GetTarget(res, kNC_Name, true);
  915.             if (engineName)
  916.             {
  917.                 this.name = engineName.QueryInterface(
  918.                               Components.interfaces.nsIRDFLiteral).Value;
  919.             }
  920.  
  921.             // get URL to engine vendor icon
  922.             const kNC_Icon = rdf.GetResource(
  923.                                "http://home.netscape.com/NC-rdf#Icon");
  924.             var iconURL = ds.GetTarget(res, kNC_Icon, true);
  925.             if (iconURL)
  926.             {
  927.                 this.icon = iconURL.QueryInterface(
  928.                   Components.interfaces.nsIRDFLiteral).Value;
  929.             }
  930.         }
  931.         catch (ex)
  932.         {
  933.         }
  934.     }
  935. }
  936.