home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / firefox-3.0.14 / chrome / browser.jar / content / browser / nsContextMenu.js < prev    next >
Encoding:
JavaScript  |  2008-04-10  |  46.4 KB  |  1,265 lines

  1. //@line 59 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  2.  
  3. function nsContextMenu(aXulMenu, aBrowser) {
  4.   this.target            = null;
  5.   this.browser           = null;
  6.   this.menu              = null;
  7.   this.isFrameImage      = false;
  8.   this.onTextInput       = false;
  9.   this.onKeywordField    = false;
  10.   this.onImage           = false;
  11.   this.onLoadedImage     = false;
  12.   this.onCompletedImage  = false;
  13.   this.onCanvas          = false;
  14.   this.onLink            = false;
  15.   this.onMailtoLink      = false;
  16.   this.onSaveableLink    = false;
  17.   this.onMetaDataItem    = false;
  18.   this.onMathML          = false;
  19.   this.link              = false;
  20.   this.linkURL           = "";
  21.   this.linkURI           = null;
  22.   this.linkProtocol      = null;
  23.   this.inFrame           = false;
  24.   this.hasBGImage        = false;
  25.   this.isTextSelected    = false;
  26.   this.isContentSelected = false;
  27.   this.inDirList         = false;
  28.   this.shouldDisplay     = true;
  29.   this.isDesignMode      = false;
  30.   this.possibleSpellChecking = false;
  31.   this.ellipsis = "\u2026";
  32.   try {
  33.     this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
  34.                                                  Ci.nsIPrefLocalizedString).data;
  35.   } catch (e) { }
  36.  
  37.   // Initialize new menu.
  38.   this.initMenu(aXulMenu, aBrowser);
  39. }
  40.  
  41. // Prototype for nsContextMenu "class."
  42. nsContextMenu.prototype = {
  43.   // onDestroy is a no-op at this point.
  44.   onDestroy: function () {
  45.   },
  46.  
  47.   // Initialize context menu.
  48.   initMenu: function CM_initMenu(aPopup, aBrowser) {
  49.     this.menu = aPopup;
  50.     this.browser = aBrowser;
  51.  
  52.     this.isFrameImage = document.getElementById("isFrameImage");
  53.  
  54.     // Get contextual info.
  55.     this.setTarget(document.popupNode, document.popupRangeParent,
  56.                    document.popupRangeOffset);
  57.  
  58.     this.isTextSelected = this.isTextSelection();
  59.     this.isContentSelected = this.isContentSelection();
  60.  
  61.     // Initialize (disable/remove) menu items.
  62.     this.initItems();
  63.   },
  64.  
  65.   initItems: function CM_initItems() {
  66.     this.initOpenItems();
  67.     this.initNavigationItems();
  68.     this.initViewItems();
  69.     this.initMiscItems();
  70.     this.initSpellingItems();
  71.     this.initSaveItems();
  72.     this.initClipboardItems();
  73.     this.initMetadataItems();
  74.   },
  75.  
  76.   initOpenItems: function CM_initOpenItems() {
  77.     var shouldShow = this.onSaveableLink ||
  78.                      (this.inDirList && this.onLink);
  79.     this.showItem("context-openlink", shouldShow);
  80.     this.showItem("context-openlinkintab", shouldShow);
  81.     this.showItem("context-sep-open", shouldShow);
  82.   },
  83.  
  84.   initNavigationItems: function CM_initNavigationItems() {
  85.     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
  86.                        this.onCanvas || this.onTextInput);
  87.     this.showItem("context-back", shouldShow);
  88.     this.showItem("context-forward", shouldShow);
  89.     this.showItem("context-reload", shouldShow);
  90.     this.showItem("context-stop", shouldShow);
  91.     this.showItem("context-sep-stop", shouldShow);
  92.  
  93.     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
  94.     //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
  95.   },
  96.  
  97.   initSaveItems: function CM_initSaveItems() {
  98.     var shouldShow = !(this.inDirList || this.onTextInput || this.onLink ||
  99.                        this.isContentSelected || this.onImage || this.onCanvas);
  100.     this.showItem("context-savepage", shouldShow);
  101.     this.showItem("context-sendpage", shouldShow);
  102.  
  103.     // Save+Send link depends on whether we're in a link.
  104.     this.showItem("context-savelink", this.onSaveableLink);
  105.     this.showItem("context-sendlink", this.onSaveableLink);
  106.  
  107.     // Save image depends on whether we're on a loaded image, or a canvas.
  108.     this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
  109.     // We can send an image (even unloaded), but not a canvas:
  110.     this.showItem("context-sendimage", this.onImage);
  111.   },
  112.  
  113.   initViewItems: function CM_initViewItems() {
  114.     // View source is always OK, unless in directory listing.
  115.     this.showItem("context-viewpartialsource-selection",
  116.                   this.isContentSelected);
  117.     this.showItem("context-viewpartialsource-mathml",
  118.                   this.onMathML && !this.isContentSelected);
  119.  
  120.     var shouldShow = !(this.inDirList || this.isContentSelected ||
  121.                        this.onImage || this.onLink || this.onTextInput);
  122.     this.showItem("context-viewsource", shouldShow);
  123.     this.showItem("context-viewinfo", shouldShow);
  124.  
  125.     this.showItem("context-sep-properties",
  126.                   !(this.inDirList || this.isContentSelected ||
  127.                     this.onTextInput));
  128.  
  129.     // Set as Desktop background depends on whether an image was clicked on,
  130.     // and only works if we have a shell service.
  131.     var haveSetDesktopBackground = false;
  132. //@line 190 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  133.     // Only enable Set as Desktop Background if we can get the shell service.
  134.     var shell = getShellService();
  135.     if (shell)
  136.       haveSetDesktopBackground = true;
  137. //@line 195 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  138.     this.showItem("context-setDesktopBackground",
  139.                   haveSetDesktopBackground && this.onLoadedImage);
  140.  
  141.     if (haveSetDesktopBackground && this.onLoadedImage) {
  142.       document.getElementById("context-setDesktopBackground")
  143.               .disabled = this.disableSetDesktopBackground();
  144.     }
  145.  
  146.     // Show image depends on an image that's not fully loaded
  147.     this.showItem("context-showimage", (this.onImage && !this.onCompletedImage));
  148.  
  149.     // View image depends on having an image that's not standalone
  150.     // (or is in a frame), or a canvas.
  151.     this.showItem("context-viewimage", (this.onImage &&
  152.                   (!this.onStandaloneImage || this.inFrame)) || this.onCanvas);
  153.  
  154.     // View background image depends on whether there is one.
  155.     this.showItem("context-viewbgimage", shouldShow);
  156.     this.showItem("context-sep-viewbgimage", shouldShow);
  157.     document.getElementById("context-viewbgimage")
  158.             .disabled = !this.hasBGImage;
  159.   },
  160.  
  161.   initMiscItems: function CM_initMiscItems() {
  162.     // Use "Bookmark This Link" if on a link.
  163.     this.showItem("context-bookmarkpage",
  164.                   !(this.isContentSelected || this.onTextInput ||
  165.                     this.onLink || this.onImage));
  166.     this.showItem("context-bookmarklink", this.onLink && !this.onMailtoLink);
  167.     this.showItem("context-searchselect", this.isTextSelected);
  168.     this.showItem("context-keywordfield",
  169.                   this.onTextInput && this.onKeywordField);
  170.     this.showItem("frame", this.inFrame);
  171.     this.showItem("frame-sep", this.inFrame);
  172.  
  173.     // Hide menu entries for images, show otherwise
  174.     if (this.inFrame) {
  175.       if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
  176.         this.isFrameImage.removeAttribute('hidden');
  177.       else
  178.         this.isFrameImage.setAttribute('hidden', 'true');
  179.     }
  180.  
  181.     // BiDi UI
  182.     this.showItem("context-sep-bidi", top.gBidiUI);
  183.     this.showItem("context-bidi-text-direction-toggle",
  184.                   this.onTextInput && top.gBidiUI);
  185.     this.showItem("context-bidi-page-direction-toggle",
  186.                   !this.onTextInput && top.gBidiUI);
  187.  
  188.     if (this.onImage) {
  189.       var blockImage = document.getElementById("context-blockimage");
  190.  
  191.       var uri = this.target
  192.                     .QueryInterface(Ci.nsIImageLoadingContent)
  193.                     .currentURI;
  194.  
  195.       // this throws if the image URI doesn't have a host (eg, data: image URIs)
  196.       // see bug 293758 for details
  197.       var hostLabel = "";
  198.       try {
  199.         hostLabel = uri.host;
  200.       } catch (ex) { }
  201.  
  202.       if (hostLabel) {
  203.         var shortenedUriHost = hostLabel.replace(/^www\./i,"");
  204.         if (shortenedUriHost.length > 15)
  205.           shortenedUriHost = shortenedUriHost.substr(0,15) + this.ellipsis;
  206.         blockImage.label = gNavigatorBundle.getFormattedString("blockImages", [shortenedUriHost]);
  207.  
  208.         if (this.isImageBlocked())
  209.           blockImage.setAttribute("checked", "true");
  210.         else
  211.           blockImage.removeAttribute("checked");
  212.       }
  213.     }
  214.  
  215.     // Only show the block image item if the image can be blocked
  216.     this.showItem("context-blockimage", this.onImage && hostLabel);
  217.   },
  218.  
  219.   initSpellingItems: function() {
  220.     var canSpell = InlineSpellCheckerUI.canSpellCheck;
  221.     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
  222.     this.showItem("spell-check-enabled", canSpell);
  223.     this.showItem("spell-separator", canSpell || this.possibleSpellChecking);
  224.     if (canSpell) {
  225.       document.getElementById("spell-check-enabled")
  226.               .setAttribute("checked", InlineSpellCheckerUI.enabled);
  227.     }
  228.  
  229.     this.showItem("spell-add-to-dictionary", onMisspelling);
  230.  
  231.     // suggestion list
  232.     this.showItem("spell-suggestions-separator", onMisspelling);
  233.     if (onMisspelling) {
  234.       var menu = document.getElementById("contentAreaContextMenu");
  235.       var suggestionsSeparator =
  236.         document.getElementById("spell-add-to-dictionary");
  237.       var numsug = InlineSpellCheckerUI.addSuggestionsToMenu(menu, suggestionsSeparator, 5);
  238.       this.showItem("spell-no-suggestions", numsug == 0);
  239.     }
  240.     else
  241.       this.showItem("spell-no-suggestions", false);
  242.  
  243.     // dictionary list
  244.     this.showItem("spell-dictionaries", InlineSpellCheckerUI.enabled);
  245.     if (canSpell) {
  246.       var dictMenu = document.getElementById("spell-dictionaries-menu");
  247.       var dictSep = document.getElementById("spell-language-separator");
  248.       InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
  249.       this.showItem("spell-add-dictionaries-main", false);
  250.     }
  251.     else if (this.possibleSpellChecking) {
  252.       // when there is no spellchecker but we might be able to spellcheck
  253.       // add the add to dictionaries item. This will ensure that people
  254.       // with no dictionaries will be able to download them
  255.       this.showItem("spell-add-dictionaries-main", true);
  256.     }
  257.     else
  258.       this.showItem("spell-add-dictionaries-main", false);
  259.   },
  260.  
  261.   initClipboardItems: function() {
  262.     // Copy depends on whether there is selected text.
  263.     // Enabling this context menu item is now done through the global
  264.     // command updating system
  265.     // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
  266.     goUpdateGlobalEditMenuItems();
  267.  
  268.     this.showItem("context-undo", this.onTextInput);
  269.     this.showItem("context-sep-undo", this.onTextInput);
  270.     this.showItem("context-cut", this.onTextInput);
  271.     this.showItem("context-copy",
  272.                   this.isContentSelected || this.onTextInput);
  273.     this.showItem("context-paste", this.onTextInput);
  274.     this.showItem("context-delete", this.onTextInput);
  275.     this.showItem("context-sep-paste", this.onTextInput);
  276.     this.showItem("context-selectall",
  277.                   !(this.onLink || this.onImage) || this.isDesignMode);
  278.     this.showItem("context-sep-selectall", this.isContentSelected );
  279.  
  280.     // XXX dr
  281.     // ------
  282.     // nsDocumentViewer.cpp has code to determine whether we're
  283.     // on a link or an image. we really ought to be using that...
  284.  
  285.     // Copy email link depends on whether we're on an email link.
  286.     this.showItem("context-copyemail", this.onMailtoLink);
  287.  
  288.     // Copy link location depends on whether we're on a non-mailto link.
  289.     this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
  290.     this.showItem("context-sep-copylink", this.onLink && this.onImage);
  291.  
  292. //@line 350 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  293.     // Copy image contents depends on whether we're on an image.
  294.     this.showItem("context-copyimage-contents", this.onImage);
  295. //@line 353 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  296.     // Copy image location depends on whether we're on an image.
  297.     this.showItem("context-copyimage", this.onImage);
  298.     this.showItem("context-sep-copyimage", this.onImage);
  299.   },
  300.  
  301.   initMetadataItems: function() {
  302.     // Show if user clicked on something which has metadata.
  303.     this.showItem("context-metadata", this.onMetaDataItem);
  304.   },
  305.  
  306.   // Set various context menu attributes based on the state of the world.
  307.   setTarget: function (aNode, aRangeParent, aRangeOffset) {
  308.     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  309.     if (aNode.namespaceURI == xulNS ||
  310.         this.isTargetAFormControl(aNode)) {
  311.       this.shouldDisplay = false;
  312.     }
  313.  
  314.     // Initialize contextual info.
  315.     this.onImage           = false;
  316.     this.onLoadedImage     = false;
  317.     this.onCompletedImage  = false;
  318.     this.onStandaloneImage = false;
  319.     this.onCanvas          = false;
  320.     this.onMetaDataItem    = false;
  321.     this.onTextInput       = false;
  322.     this.onKeywordField    = false;
  323.     this.imageURL          = "";
  324.     this.onLink            = false;
  325.     this.linkURL           = "";
  326.     this.linkURI           = null;
  327.     this.linkProtocol      = "";
  328.     this.onMathML          = false;
  329.     this.inFrame           = false;
  330.     this.hasBGImage        = false;
  331.     this.bgImageURL        = "";
  332.     this.possibleSpellChecking = false;
  333.  
  334.     // Clear any old spellchecking items from the menu, this used to
  335.     // be in the menu hiding code but wasn't getting called in all
  336.     // situations. Here, we can ensure it gets cleaned up any time the
  337.     // menu is shown. Note: must be before uninit because that clears the
  338.     // internal vars
  339.     InlineSpellCheckerUI.clearSuggestionsFromMenu();
  340.     InlineSpellCheckerUI.clearDictionaryListFromMenu();
  341.  
  342.     InlineSpellCheckerUI.uninit();
  343.  
  344.     // Remember the node that was clicked.
  345.     this.target = aNode;
  346.  
  347.     // First, do checks for nodes that never have children.
  348.     if (this.target.nodeType == Node.ELEMENT_NODE) {
  349.       // See if the user clicked on an image.
  350.       if (this.target instanceof Ci.nsIImageLoadingContent &&
  351.           this.target.currentURI) {
  352.         this.onImage = true;
  353.         this.onMetaDataItem = true;
  354.                 
  355.         var request =
  356.           this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
  357.         if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
  358.           this.onLoadedImage = true;
  359.         if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
  360.           this.onCompletedImage = true;
  361.  
  362.         this.imageURL = this.target.currentURI.spec;
  363.         if (this.target.ownerDocument instanceof ImageDocument)
  364.           this.onStandaloneImage = true;
  365.       }
  366.       else if (this.target instanceof HTMLCanvasElement) {
  367.         this.onCanvas = true;
  368.       }
  369.       else if (this.target instanceof HTMLInputElement ) {
  370.         this.onTextInput = this.isTargetATextBox(this.target);
  371.         // allow spellchecking UI on all writable text boxes except passwords
  372.         if (this.onTextInput && ! this.target.readOnly &&
  373.             this.target.type != "password") {
  374.           this.possibleSpellChecking = true;
  375.           InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
  376.           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
  377.         }
  378.         this.onKeywordField = this.isTargetAKeywordField(this.target);
  379.       }
  380.       else if (this.target instanceof HTMLTextAreaElement) {
  381.         this.onTextInput = true;
  382.         if (!this.target.readOnly) {
  383.           this.possibleSpellChecking = true;
  384.           InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
  385.           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
  386.         }
  387.       }
  388.       else if (this.target instanceof HTMLHtmlElement) {
  389.         var bodyElt = this.target.ownerDocument.body;
  390.         if (bodyElt) {
  391.           var computedURL = this.getComputedURL(bodyElt, "background-image");
  392.           if (computedURL) {
  393.             this.hasBGImage = true;
  394.             this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
  395.                                               computedURL);
  396.           }
  397.         }
  398.       }
  399.       else if ("HTTPIndex" in content &&
  400.                content.HTTPIndex instanceof Ci.nsIHTTPIndex) {
  401.         this.inDirList = true;
  402.         // Bubble outward till we get to an element with URL attribute
  403.         // (which should be the href).
  404.         var root = this.target;
  405.         while (root && !this.link) {
  406.           if (root.tagName == "tree") {
  407.             // Hit root of tree; must have clicked in empty space;
  408.             // thus, no link.
  409.             break;
  410.           }
  411.  
  412.           if (root.getAttribute("URL")) {
  413.             // Build pseudo link object so link-related functions work.
  414.             this.onLink = true;
  415.             this.link = { href : root.getAttribute("URL"),
  416.                           getAttribute: function (aAttr) {
  417.                             if (aAttr == "title") {
  418.                               return root.firstChild.firstChild
  419.                                          .getAttribute("label");
  420.                             }
  421.                             else
  422.                               return "";
  423.                            }
  424.                          };
  425.  
  426.             // If element is a directory, then you can't save it.
  427.             this.onSaveableLink = root.getAttribute("container") != "true";
  428.           }
  429.           else
  430.             root = root.parentNode;
  431.         }
  432.       }
  433.     }
  434.  
  435.     // Second, bubble out, looking for items of interest that can have childen.
  436.     // Always pick the innermost link, background image, etc.
  437.     const XMLNS = "http://www.w3.org/XML/1998/namespace";
  438.     var elem = this.target;
  439.     while (elem) {
  440.       if (elem.nodeType == Node.ELEMENT_NODE) {
  441.         // Link?
  442.         if (!this.onLink &&
  443.              ((elem instanceof HTMLAnchorElement && elem.href) ||
  444.               (elem instanceof HTMLAreaElement && elem.href) ||
  445.               elem instanceof HTMLLinkElement ||
  446.               elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
  447.             
  448.           // Target is a link or a descendant of a link.
  449.           this.onLink = true;
  450.           this.onMetaDataItem = true;
  451.  
  452.           // xxxmpc: this is kind of a hack to work around a Gecko bug (see bug 266932)
  453.           // we're going to walk up the DOM looking for a parent link node,
  454.           // this shouldn't be necessary, but we're matching the existing behaviour for left click
  455.           var realLink = elem;
  456.           var parent = elem;
  457.           while ((parent = parent.parentNode) &&
  458.                  (parent.nodeType == Node.ELEMENT_NODE)) {
  459.             try {
  460.               if ((parent instanceof HTMLAnchorElement && parent.href) ||
  461.                   (parent instanceof HTMLAreaElement && parent.href) ||
  462.                   parent instanceof HTMLLinkElement ||
  463.                   parent.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")
  464.                 realLink = parent;
  465.             } catch (e) { }
  466.           }
  467.           
  468.           // Remember corresponding element.
  469.           this.link = realLink;
  470.           this.linkURL = this.getLinkURL();
  471.           this.linkURI = this.getLinkURI();
  472.           this.linkProtocol = this.getLinkProtocol();
  473.           this.onMailtoLink = (this.linkProtocol == "mailto");
  474.           this.onSaveableLink = this.isLinkSaveable( this.link );
  475.         }
  476.  
  477.         // Metadata item?
  478.         if (!this.onMetaDataItem) {
  479.           // We display metadata on anything which fits
  480.           // the below test, as well as for links and images
  481.           // (which set this.onMetaDataItem to true elsewhere)
  482.           if ((elem instanceof HTMLQuoteElement && elem.cite) ||
  483.               (elem instanceof HTMLTableElement && elem.summary) ||
  484.               (elem instanceof HTMLModElement &&
  485.                (elem.cite || elem.dateTime)) ||
  486.               (elem instanceof HTMLElement &&
  487.                (elem.title || elem.lang)) ||
  488.               elem.getAttributeNS(XMLNS, "lang")) {
  489.             this.onMetaDataItem = true;
  490.           }
  491.         }
  492.  
  493.         // Background image?  Don't bother if we've already found a
  494.         // background image further down the hierarchy.  Otherwise,
  495.         // we look for the computed background-image style.
  496.         if (!this.hasBGImage) {
  497.           var bgImgUrl = this.getComputedURL( elem, "background-image" );
  498.           if (bgImgUrl) {
  499.             this.hasBGImage = true;
  500.             this.bgImageURL = makeURLAbsolute(elem.baseURI,
  501.                                               bgImgUrl);
  502.           }
  503.         }
  504.       }
  505.  
  506.       elem = elem.parentNode;
  507.     }
  508.     
  509.     // See if the user clicked on MathML
  510.     const NS_MathML = "http://www.w3.org/1998/Math/MathML";
  511.     if ((this.target.nodeType == Node.TEXT_NODE &&
  512.          this.target.parentNode.namespaceURI == NS_MathML)
  513.          || (this.target.namespaceURI == NS_MathML))
  514.       this.onMathML = true;
  515.  
  516.     // See if the user clicked in a frame.
  517.     var docDefaultView = this.target.ownerDocument.defaultView;
  518.     if (docDefaultView != docDefaultView.top)
  519.       this.inFrame = true;
  520.  
  521.     // if the document is editable, show context menu like in text inputs
  522.     var win = this.target.ownerDocument.defaultView;
  523.     if (win) {
  524.       var isEditable = false;
  525.       try {
  526.         var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
  527.                                 .getInterface(Ci.nsIWebNavigation)
  528.                                 .QueryInterface(Ci.nsIInterfaceRequestor)
  529.                                 .getInterface(Ci.nsIEditingSession);
  530.         if (editingSession.windowIsEditable(win) &&
  531.             this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
  532.           isEditable = true;
  533.         }
  534.       }
  535.       catch(ex) {
  536.         // If someone built with composer disabled, we can't get an editing session.
  537.       }
  538.  
  539.       if (isEditable) {
  540.         this.onTextInput       = true;
  541.         this.onKeywordField    = false;
  542.         this.onImage           = false;
  543.         this.onLoadedImage     = false;
  544.         this.onCompletedImage  = false;
  545.         this.onMetaDataItem    = false;
  546.         this.onMathML          = false;
  547.         this.inFrame           = false;
  548.         this.hasBGImage        = false;
  549.         this.isDesignMode      = true;
  550.         this.possibleSpellChecking = true;
  551.         InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
  552.         var canSpell = InlineSpellCheckerUI.canSpellCheck;
  553.         InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
  554.         this.showItem("spell-check-enabled", canSpell);
  555.         this.showItem("spell-separator", canSpell);
  556.       }
  557.     }
  558.   },
  559.  
  560.   // Returns the computed style attribute for the given element.
  561.   getComputedStyle: function(aElem, aProp) {
  562.     return aElem.ownerDocument
  563.                 .defaultView
  564.                 .getComputedStyle(aElem, "").getPropertyValue(aProp);
  565.   },
  566.  
  567.   // Returns a "url"-type computed style attribute value, with the url() stripped.
  568.   getComputedURL: function(aElem, aProp) {
  569.     var url = aElem.ownerDocument
  570.                    .defaultView.getComputedStyle(aElem, "")
  571.                    .getPropertyCSSValue(aProp);
  572.     return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
  573.            url.getStringValue() : null;
  574.   },
  575.  
  576.   // Returns true if clicked-on link targets a resource that can be saved.
  577.   isLinkSaveable: function(aLink) {
  578.     // We don't do the Right Thing for news/snews yet, so turn them off
  579.     // until we do.
  580.     return this.linkProtocol && !(
  581.              this.linkProtocol == "mailto"     ||
  582.              this.linkProtocol == "javascript" ||
  583.              this.linkProtocol == "news"       ||
  584.              this.linkProtocol == "snews"      );
  585.   },
  586.  
  587.   // Open linked-to URL in a new window.
  588.   openLink : function () {
  589.     openNewWindowWith(this.linkURL, this.target.ownerDocument, null, false);
  590.   },
  591.  
  592.   // Open linked-to URL in a new tab.
  593.   openLinkInTab: function() {
  594.     openNewTabWith(this.linkURL, this.target.ownerDocument, null, null, false);
  595.   },
  596.  
  597.   // Open frame in a new tab.
  598.   openFrameInTab: function() {
  599.     var doc = this.target.ownerDocument;
  600.     var frameURL = doc.location.href;
  601.     var referrer = doc.referrer;
  602.  
  603.     return openNewTabWith(frameURL, null, null, null, false,
  604.                           referrer ? makeURI(referrer) : null);
  605.   },
  606.  
  607.   // Reload clicked-in frame.
  608.   reloadFrame: function() {
  609.     this.target.ownerDocument.location.reload();
  610.   },
  611.  
  612.   // Open clicked-in frame in its own window.
  613.   openFrame: function() {
  614.     var doc = this.target.ownerDocument;
  615.     var frameURL = doc.location.href;
  616.     var referrer = doc.referrer;
  617.  
  618.     return openNewWindowWith(frameURL, null, null, false,
  619.                              referrer ? makeURI(referrer) : null);
  620.   },
  621.  
  622.   // Open clicked-in frame in the same window.
  623.   showOnlyThisFrame: function() {
  624.     var doc = this.target.ownerDocument;
  625.     var frameURL = doc.location.href;
  626.  
  627.     urlSecurityCheck(frameURL, this.browser.contentPrincipal,
  628.                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  629.     var referrer = doc.referrer;
  630.     this.browser.loadURI(frameURL, referrer ? makeURI(referrer) : null);
  631.   },
  632.  
  633.   // View Partial Source
  634.   viewPartialSource: function(aContext) {
  635.     var focusedWindow = document.commandDispatcher.focusedWindow;
  636.     if (focusedWindow == window)
  637.       focusedWindow = content;
  638.  
  639.     var docCharset = null;
  640.     if (focusedWindow)
  641.       docCharset = "charset=" + focusedWindow.document.characterSet;
  642.  
  643.     // "View Selection Source" and others such as "View MathML Source"
  644.     // are mutually exclusive, with the precedence given to the selection
  645.     // when there is one
  646.     var reference = null;
  647.     if (aContext == "selection")
  648.       reference = focusedWindow.getSelection();
  649.     else if (aContext == "mathml")
  650.       reference = this.target;
  651.     else
  652.       throw "not reached";
  653.  
  654.     // unused (and play nice for fragments generated via XSLT too)
  655.     var docUrl = null;
  656.     window.openDialog("chrome://global/content/viewPartialSource.xul",
  657.                       "_blank", "scrollbars,resizable,chrome,dialog=no",
  658.                       docUrl, docCharset, reference, aContext);
  659.   },
  660.  
  661.   // Open new "view source" window with the frame's URL.
  662.   viewFrameSource: function() {
  663.     BrowserViewSourceOfDocument(this.target.ownerDocument);
  664.   },
  665.  
  666.   viewInfo: function() {
  667.     BrowserPageInfo(this.target.ownerDocument.defaultView.top.document);
  668.   },
  669.  
  670.   viewFrameInfo: function() {
  671.     BrowserPageInfo(this.target.ownerDocument);
  672.   },
  673.  
  674.   showImage: function(e) {
  675.     urlSecurityCheck(this.imageURL,
  676.                      this.browser.contentPrincipal,
  677.                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  678.  
  679.     if (this.target instanceof Ci.nsIImageLoadingContent)
  680.       this.target.forceReload();
  681.   },
  682.  
  683.   // Change current window to the URL of the image.
  684.   viewImage: function(e) {
  685.     var viewURL;
  686.  
  687.     if (this.onCanvas)
  688.       viewURL = this.target.toDataURL();
  689.     else {
  690.       viewURL = this.imageURL;
  691.       urlSecurityCheck(viewURL,
  692.                        this.browser.contentPrincipal,
  693.                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  694.     }
  695.  
  696.     var doc = this.target.ownerDocument;
  697.     openUILink(viewURL, e, null, null, null, null, doc.documentURIObject );
  698.   },
  699.  
  700.   // Change current window to the URL of the background image.
  701.   viewBGImage: function(e) {
  702.     urlSecurityCheck(this.bgImageURL,
  703.                      this.browser.contentPrincipal,
  704.                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  705.     var doc = this.target.ownerDocument;
  706.     openUILink(this.bgImageURL, e, null, null, null, null, doc.documentURIObject );
  707.   },
  708.  
  709.   disableSetDesktopBackground: function() {
  710.     // Disable the Set as Desktop Background menu item if we're still trying
  711.     // to load the image or the load failed.
  712.     if (!(this.target instanceof Ci.nsIImageLoadingContent))
  713.       return true;
  714.  
  715.     if (("complete" in this.target) && !this.target.complete)
  716.       return true;
  717.  
  718.     if (this.target.currentURI.schemeIs("javascript"))
  719.       return true;
  720.  
  721.     var request = this.target
  722.                       .QueryInterface(Ci.nsIImageLoadingContent)
  723.                       .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
  724.     if (!request)
  725.       return true;
  726.  
  727.     return false;
  728.   },
  729.  
  730.   setDesktopBackground: function() {
  731.     // Paranoia: check disableSetDesktopBackground again, in case the
  732.     // image changed since the context menu was initiated.
  733.     if (this.disableSetDesktopBackground())
  734.       return;
  735.  
  736.     urlSecurityCheck(this.target.currentURI.spec,
  737.                      this.target.ownerDocument.nodePrincipal);
  738.  
  739.     // Confirm since it's annoying if you hit this accidentally.
  740.     const kDesktopBackgroundURL = 
  741.                   "chrome://browser/content/setDesktopBackground.xul";
  742. //@line 815 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  743.     // On non-Mac platforms, the Set Wallpaper dialog is modal.
  744.     openDialog(kDesktopBackgroundURL, "",
  745.                "centerscreen,chrome,dialog,modal,dependent",
  746.                this.target);
  747. //@line 820 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/base/content/nsContextMenu.js"
  748.   },
  749.  
  750.   // Save URL of clicked-on frame.
  751.   saveFrame: function () {
  752.     saveDocument(this.target.ownerDocument);
  753.   },
  754.  
  755.   // Save URL of clicked-on link.
  756.   saveLink: function() {
  757.     // canonical def in nsURILoader.h
  758.     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
  759.     
  760.     var doc =  this.target.ownerDocument;
  761.     urlSecurityCheck(this.linkURL, doc.nodePrincipal);
  762.     var linkText = this.linkText();
  763.     var linkURL = this.linkURL;
  764.  
  765.  
  766.     // an object to proxy the data through to
  767.     // nsIExternalHelperAppService.doContent, which will wait for the
  768.     // appropriate MIME-type headers and then prompt the user with a
  769.     // file picker
  770.     function saveAsListener() {}
  771.     saveAsListener.prototype = {
  772.       extListener: null, 
  773.  
  774.       onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
  775.  
  776.         // if the timer fired, the error status will have been caused by that,
  777.         // and we'll be restarting in onStopRequest, so no reason to notify
  778.         // the user
  779.         if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
  780.           return;
  781.  
  782.         timer.cancel();
  783.  
  784.         // some other error occured; notify the user...
  785.         if (!Components.isSuccessCode(aRequest.status)) {
  786.           try {
  787.             const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  788.                         getService(Ci.nsIStringBundleService);
  789.             const bundle = sbs.createBundle(
  790.                     "chrome://mozapps/locale/downloads/downloads.properties");
  791.             
  792.             const title = bundle.GetStringFromName("downloadErrorAlertTitle");
  793.             const msg = bundle.GetStringFromName("downloadErrorGeneric");
  794.             
  795.             const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  796.                               getService(Ci.nsIPromptService);
  797.             promptSvc.alert(doc.defaultView, title, msg);
  798.           } catch (ex) {}
  799.           return;
  800.         }
  801.  
  802.         var extHelperAppSvc = 
  803.           Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
  804.           getService(Ci.nsIExternalHelperAppService);
  805.         var channel = aRequest.QueryInterface(Ci.nsIChannel);
  806.         this.extListener = 
  807.           extHelperAppSvc.doContent(channel.contentType, aRequest, 
  808.                                     doc.defaultView, true);
  809.         this.extListener.onStartRequest(aRequest, aContext);
  810.       }, 
  811.  
  812.       onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext, 
  813.                                                        aStatusCode) {
  814.         if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
  815.           // do it the old fashioned way, which will pick the best filename
  816.           // it can without waiting.
  817.           saveURL(linkURL, linkText, null, true, false, doc.documentURIObject);
  818.         }
  819.         if (this.extListener)
  820.           this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
  821.       },
  822.        
  823.       onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
  824.                                                            aInputStream,
  825.                                                            aOffset, aCount) {
  826.         this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
  827.                                          aOffset, aCount);
  828.       }
  829.     }
  830.  
  831.     // in case we need to prompt the user for authentication
  832.     function callbacks() {}
  833.     callbacks.prototype = {
  834.       getInterface: function sLA_callbacks_getInterface(aIID) {
  835.         if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
  836.           var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  837.                    getService(Ci.nsIPromptFactory);
  838.           return ww.getPrompt(doc.defaultView, aIID);
  839.         }
  840.         throw Cr.NS_ERROR_NO_INTERFACE;
  841.       } 
  842.     }
  843.  
  844.     // if it we don't have the headers after a short time, the user 
  845.     // won't have received any feedback from their click.  that's bad.  so
  846.     // we give up waiting for the filename. 
  847.     function timerCallback() {}
  848.     timerCallback.prototype = {
  849.       notify: function sLA_timer_notify(aTimer) {
  850.         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
  851.         return;
  852.       }
  853.     }
  854.  
  855.     // set up a channel to do the saving
  856.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  857.                     getService(Ci.nsIIOService);
  858.     var channel = ioService.newChannelFromURI(this.getLinkURI());
  859.     channel.notificationCallbacks = new callbacks();
  860.     channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
  861.                          Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
  862.     if (channel instanceof Ci.nsIHttpChannel)
  863.       channel.referrer = doc.documentURIObject;
  864.  
  865.     // fallback to the old way if we don't see the headers quickly 
  866.     var timeToWait = 
  867.       gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
  868.     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  869.     timer.initWithCallback(new timerCallback(), timeToWait,
  870.                            timer.TYPE_ONE_SHOT);
  871.  
  872.     // kick off the channel with our proxy object as the listener
  873.     channel.asyncOpen(new saveAsListener(), null);
  874.   },
  875.  
  876.   sendLink: function() {
  877.     // we don't know the title of the link so pass in an empty string
  878.     MailIntegration.sendMessage( this.linkURL, "" );
  879.   },
  880.  
  881.   // Save URL of clicked-on image.
  882.   saveImage: function() {
  883.     var doc =  this.target.ownerDocument;
  884.     if (this.onCanvas) {
  885.       // Bypass cache, since it's a data: URL.
  886.       saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle",
  887.                    true, false, doc.documentURIObject);
  888.     }
  889.     else {
  890.       urlSecurityCheck(this.imageURL, doc.nodePrincipal);
  891.       saveImageURL(this.imageURL, null, "SaveImageTitle", false,
  892.                    false, doc.documentURIObject);
  893.     }
  894.   },
  895.  
  896.   sendImage: function() {
  897.     MailIntegration.sendMessage(this.imageURL, "");
  898.   },
  899.  
  900.   toggleImageBlocking: function(aBlock) {
  901.     var permissionmanager = Cc["@mozilla.org/permissionmanager;1"].
  902.                             getService(Ci.nsIPermissionManager);
  903.  
  904.     var uri = this.target.QueryInterface(Ci.nsIImageLoadingContent).currentURI;
  905.  
  906.     if (aBlock)
  907.       permissionmanager.add(uri, "image", Ci.nsIPermissionManager.DENY_ACTION);
  908.     else
  909.       permissionmanager.remove(uri.host, "image");
  910.  
  911.     var brandBundle = document.getElementById("bundle_brand");
  912.     var app = brandBundle.getString("brandShortName");
  913.     var bundle_browser = document.getElementById("bundle_browser");
  914.     var message = bundle_browser.getFormattedString(aBlock ?
  915.      "imageBlockedWarning" : "imageAllowedWarning", [app, uri.host]);
  916.  
  917.     var notificationBox = this.browser.getNotificationBox();
  918.     var notification = notificationBox.getNotificationWithValue("images-blocked");
  919.  
  920.     if (notification)
  921.       notification.label = message;
  922.     else {
  923.       var self = this;
  924.       var buttons = [{
  925.         label: bundle_browser.getString("undo"),
  926.         accessKey: bundle_browser.getString("undo.accessKey"),
  927.         callback: function() { self.toggleImageBlocking(!aBlock); }
  928.       }];
  929.       const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
  930.       notificationBox.appendNotification(message, "images-blocked",
  931.                                          "chrome://browser/skin/Info.png",
  932.                                          priority, buttons);
  933.     }
  934.  
  935.     // Reload the page to show the effect instantly
  936.     BrowserReload();
  937.   },
  938.  
  939.   isImageBlocked: function() {
  940.     var permissionmanager = Cc["@mozilla.org/permissionmanager;1"].
  941.                             getService(Ci.nsIPermissionManager);
  942.  
  943.     var uri = this.target.QueryInterface(Ci.nsIImageLoadingContent).currentURI;
  944.  
  945.     return permissionmanager.testPermission(uri, "image") == Ci.nsIPermissionManager.DENY_ACTION;
  946.   },
  947.  
  948.   // Generate email address and put it on clipboard.
  949.   copyEmail: function() {
  950.     // Copy the comma-separated list of email addresses only.
  951.     // There are other ways of embedding email addresses in a mailto:
  952.     // link, but such complex parsing is beyond us.
  953.     var url = this.linkURL;
  954.     var qmark = url.indexOf("?");
  955.     var addresses;
  956.  
  957.     // 7 == length of "mailto:"
  958.     addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
  959.  
  960.     // Let's try to unescape it using a character set
  961.     // in case the address is not ASCII.
  962.     try {
  963.       var characterSet = this.target.ownerDocument.characterSet;
  964.       const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
  965.                            getService(Ci.nsITextToSubURI);
  966.       addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
  967.     }
  968.     catch(ex) {
  969.       // Do nothing.
  970.     }
  971.  
  972.     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
  973.                     getService(Ci.nsIClipboardHelper);
  974.     clipboard.copyString(addresses);
  975.   },
  976.  
  977.   // Open Metadata window for node
  978.   showMetadata: function () {
  979.     window.openDialog("chrome://browser/content/metaData.xul",
  980.                       "_blank",
  981.                       "scrollbars,resizable,chrome,dialog=no",
  982.                       this.target);
  983.   },
  984.  
  985.   ///////////////
  986.   // Utilities //
  987.   ///////////////
  988.  
  989.   // Show/hide one item (specified via name or the item element itself).
  990.   showItem: function(aItemOrId, aShow) {
  991.     var item = aItemOrId.constructor == String ?
  992.       document.getElementById(aItemOrId) : aItemOrId;
  993.     if (item)
  994.       item.hidden = !aShow;
  995.   },
  996.  
  997.   // Set given attribute of specified context-menu item.  If the
  998.   // value is null, then it removes the attribute (which works
  999.   // nicely for the disabled attribute).
  1000.   setItemAttr: function(aID, aAttr, aVal ) {
  1001.     var elem = document.getElementById(aID);
  1002.     if (elem) {
  1003.       if (aVal == null) {
  1004.         // null indicates attr should be removed.
  1005.         elem.removeAttribute(aAttr);
  1006.       }
  1007.       else {
  1008.         // Set attr=val.
  1009.         elem.setAttribute(aAttr, aVal);
  1010.       }
  1011.     }
  1012.   },
  1013.  
  1014.   // Set context menu attribute according to like attribute of another node
  1015.   // (such as a broadcaster).
  1016.   setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
  1017.     var elem = document.getElementById(aOther_id);
  1018.     if (elem && elem.getAttribute(aAttr) == "true")
  1019.       this.setItemAttr(aItem_id, aAttr, "true");
  1020.     else
  1021.       this.setItemAttr(aItem_id, aAttr, null);
  1022.   },
  1023.  
  1024.   // Temporary workaround for DOM api not yet implemented by XUL nodes.
  1025.   cloneNode: function(aItem) {
  1026.     // Create another element like the one we're cloning.
  1027.     var node = document.createElement(aItem.tagName);
  1028.  
  1029.     // Copy attributes from argument item to the new one.
  1030.     var attrs = aItem.attributes;
  1031.     for (var i = 0; i < attrs.length; i++) {
  1032.       var attr = attrs.item(i);
  1033.       node.setAttribute(attr.nodeName, attr.nodeValue);
  1034.     }
  1035.  
  1036.     // Voila!
  1037.     return node;
  1038.   },
  1039.  
  1040.   // Generate fully qualified URL for clicked-on link.
  1041.   getLinkURL: function() {
  1042.     var href = this.link.href;  
  1043.     if (href)
  1044.       return href;
  1045.  
  1046.     href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
  1047.                                     "href");
  1048.  
  1049.     if (!href || !href.match(/\S/)) {
  1050.       // Without this we try to save as the current doc,
  1051.       // for example, HTML case also throws if empty
  1052.       throw "Empty href";
  1053.     }
  1054.  
  1055.     return makeURLAbsolute(this.link.baseURI, href);
  1056.   },
  1057.   
  1058.   getLinkURI: function() {
  1059.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1060.                     getService(Ci.nsIIOService);
  1061.     try {
  1062.       return ioService.newURI(this.linkURL, null, null);
  1063.     }
  1064.     catch (ex) {
  1065.      // e.g. empty URL string
  1066.     }
  1067.  
  1068.     return null;
  1069.   },
  1070.   
  1071.   getLinkProtocol: function() {
  1072.     if (this.linkURI)
  1073.       return this.linkURI.scheme; // can be |undefined|
  1074.  
  1075.     return null;
  1076.   },
  1077.  
  1078.   // Get text of link.
  1079.   linkText: function() {
  1080.     var text = gatherTextUnder(this.link);
  1081.     if (!text || !text.match(/\S/)) {
  1082.       text = this.link.getAttribute("title");
  1083.       if (!text || !text.match(/\S/)) {
  1084.         text = this.link.getAttribute("alt");
  1085.         if (!text || !text.match(/\S/))
  1086.           text = this.linkURL;
  1087.       }
  1088.     }
  1089.  
  1090.     return text;
  1091.   },
  1092.  
  1093.   // Get selected text. Only display the first 15 chars.
  1094.   isTextSelection: function() {
  1095.     // Get 16 characters, so that we can trim the selection if it's greater
  1096.     // than 15 chars
  1097.     var selectedText = getBrowserSelection(16);
  1098.  
  1099.     if (!selectedText)
  1100.       return false;
  1101.  
  1102.     if (selectedText.length > 15)
  1103.       selectedText = selectedText.substr(0,15) + this.ellipsis;
  1104.  
  1105.     // Use the current engine if the search bar is visible, the default
  1106.     // engine otherwise.
  1107.     var engineName = "";
  1108.     var ss = Cc["@mozilla.org/browser/search-service;1"].
  1109.              getService(Ci.nsIBrowserSearchService);
  1110.     if (isElementVisible(BrowserSearch.searchBar))
  1111.       engineName = ss.currentEngine.name;
  1112.     else
  1113.       engineName = ss.defaultEngine.name;
  1114.  
  1115.     // format "Search <engine> for <selection>" string to show in menu
  1116.     var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearchText",
  1117.                                                         [engineName,
  1118.                                                          selectedText]);
  1119.     document.getElementById("context-searchselect").label = menuLabel;
  1120.  
  1121.     return true;
  1122.   },
  1123.  
  1124.   // Returns true if anything is selected.
  1125.   isContentSelection: function() {
  1126.     return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
  1127.   },
  1128.  
  1129.   toString: function () {
  1130.     return "contextMenu.target     = " + this.target + "\n" +
  1131.            "contextMenu.onImage    = " + this.onImage + "\n" +
  1132.            "contextMenu.onLink     = " + this.onLink + "\n" +
  1133.            "contextMenu.link       = " + this.link + "\n" +
  1134.            "contextMenu.inFrame    = " + this.inFrame + "\n" +
  1135.            "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
  1136.   },
  1137.  
  1138.   // Returns true if aNode is a from control (except text boxes).
  1139.   // This is used to disable the context menu for form controls.
  1140.   isTargetAFormControl: function(aNode) {
  1141.     if (aNode instanceof HTMLInputElement)
  1142.       return (aNode.type != "text" && aNode.type != "password");
  1143.  
  1144.     return (aNode instanceof HTMLButtonElement) ||
  1145.            (aNode instanceof HTMLSelectElement) ||
  1146.            (aNode instanceof HTMLOptionElement) ||
  1147.            (aNode instanceof HTMLOptGroupElement);
  1148.   },
  1149.  
  1150.   isTargetATextBox: function(node) {
  1151.     if (node instanceof HTMLInputElement)
  1152.       return (node.type == "text" || node.type == "password")
  1153.  
  1154.     return (node instanceof HTMLTextAreaElement);
  1155.   },
  1156.  
  1157.   isTargetAKeywordField: function(aNode) {
  1158.     var form = aNode.form;
  1159.     if (!form)
  1160.       return false;
  1161.  
  1162.     var method = form.method.toUpperCase();
  1163.  
  1164.     // These are the following types of forms we can create keywords for:
  1165.     //
  1166.     // method   encoding type       can create keyword
  1167.     // GET      *                                 YES
  1168.     //          *                                 YES
  1169.     // POST                                       YES
  1170.     // POST     application/x-www-form-urlencoded YES
  1171.     // POST     text/plain                        NO (a little tricky to do)
  1172.     // POST     multipart/form-data               NO
  1173.     // POST     everything else                   YES
  1174.     return (method == "GET" || method == "") ||
  1175.            (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
  1176.   },
  1177.  
  1178.   // Determines whether or not the separator with the specified ID should be
  1179.   // shown or not by determining if there are any non-hidden items between it
  1180.   // and the previous separator.
  1181.   shouldShowSeparator: function (aSeparatorID) {
  1182.     var separator = document.getElementById(aSeparatorID);
  1183.     if (separator) {
  1184.       var sibling = separator.previousSibling;
  1185.       while (sibling && sibling.localName != "menuseparator") {
  1186.         if (!sibling.hidden)
  1187.           return true;
  1188.         sibling = sibling.previousSibling;
  1189.       }
  1190.     }
  1191.     return false;
  1192.   },
  1193.  
  1194.   addDictionaries: function() {
  1195.     var uri = formatURL("browser.dictionaries.download.url", true);
  1196.  
  1197.     var locale = "-";
  1198.     try {
  1199.       locale = gPrefService.getComplexValue("intl.accept_languages",
  1200.                                             Ci.nsIPrefLocalizedString).data;
  1201.     }
  1202.     catch (e) { }
  1203.  
  1204.     var version = "-";
  1205.     try {
  1206.       version = Cc["@mozilla.org/xre/app-info;1"].
  1207.                 getService(Ci.nsIXULAppInfo).version;
  1208.     }
  1209.     catch (e) { }
  1210.  
  1211.     uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
  1212.  
  1213.     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
  1214.     var where = newWindowPref == 3 ? "tab" : "window";
  1215.  
  1216.     openUILinkIn(uri, where);
  1217.   },
  1218.  
  1219.   bookmarkThisPage: function CM_bookmarkThisPage() {
  1220.     window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
  1221.   },
  1222.  
  1223.   bookmarkLink: function CM_bookmarkLink() {
  1224.     window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL,
  1225.                                               this.linkText());
  1226.   },
  1227.  
  1228.   addBookmarkForFrame: function CM_addBookmarkForFrame() {
  1229.     var doc = this.target.ownerDocument;
  1230.     var uri = doc.documentURIObject;
  1231.  
  1232.     var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
  1233.     if (itemId == -1) {
  1234.       var title = doc.title;
  1235.       var description = PlacesUIUtils.getDescriptionFromDocument(doc);
  1236.  
  1237.       var descAnno = { name: DESCRIPTION_ANNO, value: description };
  1238.       var txn = PlacesUIUtils.ptm.createItem(uri, 
  1239.                                            PlacesUtils.bookmarksMenuFolderId,
  1240.                                            -1, title, null, [descAnno]);
  1241.       PlacesUIUtils.ptm.doTransaction(txn);
  1242.       itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
  1243.       StarUI.beginBatch();
  1244.     }
  1245.  
  1246.     window.top.StarUI.showEditBookmarkPopup(itemId, this.browser, "overlap");
  1247.   },
  1248.  
  1249.   savePageAs: function CM_savePageAs() {
  1250.     saveDocument(this.browser.contentDocument);
  1251.   },
  1252.  
  1253.   sendPage: function CM_sendPage() {
  1254.     MailIntegration.sendLinkForWindow(this.browser.contentWindow);  
  1255.   },
  1256.  
  1257.   printFrame: function CM_printFrame() {
  1258.     PrintUtils.print(this.target.ownerDocument.defaultView);
  1259.   },
  1260.  
  1261.   switchPageDirection: function CM_switchPageDirection() {
  1262.     SwitchDocumentDirection(this.browser.contentWindow);
  1263.   }
  1264. };
  1265.