browser/base/content/nsContextMenu.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
     7 var gContextMenuContentData = null;
     9 function nsContextMenu(aXulMenu, aIsShift) {
    10   this.shouldDisplay = true;
    11   this.initMenu(aXulMenu, aIsShift);
    12 }
    14 // Prototype for nsContextMenu "class."
    15 nsContextMenu.prototype = {
    16   initMenu: function CM_initMenu(aXulMenu, aIsShift) {
    17     // Get contextual info.
    18     this.setTarget(document.popupNode, document.popupRangeParent,
    19                    document.popupRangeOffset);
    20     if (!this.shouldDisplay)
    21       return;
    23     this.hasPageMenu = false;
    24     if (!aIsShift) {
    25       this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
    26                                                           aXulMenu);
    27     }
    29     this.isFrameImage = document.getElementById("isFrameImage");
    30     this.ellipsis = "\u2026";
    31     try {
    32       this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
    33                                                    Ci.nsIPrefLocalizedString).data;
    34     } catch (e) { }
    36     this.isContentSelected = this.isContentSelection();
    37     this.onPlainTextLink = false;
    39     // Initialize (disable/remove) menu items.
    40     this.initItems();
    41   },
    43   hiding: function CM_hiding() {
    44     gContextMenuContentData = null;
    45     InlineSpellCheckerUI.clearSuggestionsFromMenu();
    46     InlineSpellCheckerUI.clearDictionaryListFromMenu();
    47     InlineSpellCheckerUI.uninit();
    48   },
    50   initItems: function CM_initItems() {
    51     this.initPageMenuSeparator();
    52     this.initOpenItems();
    53     this.initNavigationItems();
    54     this.initViewItems();
    55     this.initMiscItems();
    56     this.initSpellingItems();
    57     this.initSaveItems();
    58     this.initClipboardItems();
    59     this.initMediaPlayerItems();
    60     this.initLeaveDOMFullScreenItems();
    61     this.initClickToPlayItems();
    62   },
    64   initPageMenuSeparator: function CM_initPageMenuSeparator() {
    65     this.showItem("page-menu-separator", this.hasPageMenu);
    66   },
    68   initOpenItems: function CM_initOpenItems() {
    69     var isMailtoInternal = false;
    70     if (this.onMailtoLink) {
    71       var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
    72                           getService(Ci.nsIExternalProtocolService).
    73                           getProtocolHandlerInfo("mailto");
    74       isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
    75                           mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
    76                           (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
    77     }
    79     // Time to do some bad things and see if we've highlighted a URL that
    80     // isn't actually linked.
    81     if (this.isTextSelected && !this.onLink) {
    82       // Ok, we have some text, let's figure out if it looks like a URL.
    83       let selection =  this.focusedWindow.getSelection();
    84       let linkText = selection.toString().trim();
    85       let uri;
    86       if (/^(?:https?|ftp):/i.test(linkText)) {
    87         try {
    88           uri = makeURI(linkText);
    89         } catch (ex) {}
    90       }
    91       // Check if this could be a valid url, just missing the protocol.
    92       else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
    93         // Now let's see if this is an intentional link selection. Our guess is
    94         // based on whether the selection begins/ends with whitespace or is
    95         // preceded/followed by a non-word character.
    97         // selection.toString() trims trailing whitespace, so we look for
    98         // that explicitly in the first and last ranges.
    99         let beginRange = selection.getRangeAt(0);
   100         let delimitedAtStart = /^\s/.test(beginRange);
   101         if (!delimitedAtStart) {
   102           let container = beginRange.startContainer;
   103           let offset = beginRange.startOffset;
   104           if (container.nodeType == Node.TEXT_NODE && offset > 0)
   105             delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
   106           else
   107             delimitedAtStart = true;
   108         }
   110         let delimitedAtEnd = false;
   111         if (delimitedAtStart) {
   112           let endRange = selection.getRangeAt(selection.rangeCount - 1);
   113           delimitedAtEnd = /\s$/.test(endRange);
   114           if (!delimitedAtEnd) {
   115             let container = endRange.endContainer;
   116             let offset = endRange.endOffset;
   117             if (container.nodeType == Node.TEXT_NODE &&
   118                 offset < container.textContent.length)
   119               delimitedAtEnd = /\W/.test(container.textContent[offset]);
   120             else
   121               delimitedAtEnd = true;
   122           }
   123         }
   125         if (delimitedAtStart && delimitedAtEnd) {
   126           let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
   127                            .getService(Ci.nsIURIFixup);
   128           try {
   129             uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
   130           } catch (ex) {}
   131         }
   132       }
   134       if (uri && uri.host) {
   135         this.linkURI = uri;
   136         this.linkURL = this.linkURI.spec;
   137         this.onPlainTextLink = true;
   138       }
   139     }
   141     var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
   142     var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
   143     this.showItem("context-openlink", shouldShow && !isWindowPrivate);
   144     this.showItem("context-openlinkprivate", shouldShow);
   145     this.showItem("context-openlinkintab", shouldShow);
   146     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
   147     this.showItem("context-sep-open", shouldShow);
   148   },
   150   initNavigationItems: function CM_initNavigationItems() {
   151     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
   152                        this.onCanvas || this.onVideo || this.onAudio ||
   153                        this.onTextInput || this.onSocial);
   154     this.showItem("context-back", shouldShow);
   155     this.showItem("context-forward", shouldShow);
   157     let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
   159     let stopReloadItem = "";
   160     if (shouldShow || this.onSocial) {
   161       stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
   162     }
   164     this.showItem("context-reload", stopReloadItem == "reload");
   165     this.showItem("context-stop", stopReloadItem == "stop");
   166     this.showItem("context-sep-stop", !!stopReloadItem);
   168     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
   169     //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
   170   },
   172   initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
   173     // only show the option if the user is in DOM fullscreen
   174     var shouldShow = (this.target.ownerDocument.mozFullScreenElement != null);
   175     this.showItem("context-leave-dom-fullscreen", shouldShow);
   177     // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
   178     if (shouldShow)
   179         this.showItem("context-media-sep-commands", true);
   180   },
   182   initSaveItems: function CM_initSaveItems() {
   183     var shouldShow = !(this.onTextInput || this.onLink ||
   184                        this.isContentSelected || this.onImage ||
   185                        this.onCanvas || this.onVideo || this.onAudio);
   186     this.showItem("context-savepage", shouldShow);
   188     // Save link depends on whether we're in a link, or selected text matches valid URL pattern.
   189     this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
   191     // Save image depends on having loaded its content, video and audio don't.
   192     this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
   193     this.showItem("context-savevideo", this.onVideo);
   194     this.showItem("context-saveaudio", this.onAudio);
   195     this.showItem("context-video-saveimage", this.onVideo);
   196     this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
   197     this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
   198     // Send media URL (but not for canvas, since it's a big data: URL)
   199     this.showItem("context-sendimage", this.onImage);
   200     this.showItem("context-sendvideo", this.onVideo);
   201     this.showItem("context-sendaudio", this.onAudio);
   202     this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
   203     this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
   204   },
   206   initViewItems: function CM_initViewItems() {
   207     // View source is always OK, unless in directory listing.
   208     this.showItem("context-viewpartialsource-selection",
   209                   this.isContentSelected);
   210     this.showItem("context-viewpartialsource-mathml",
   211                   this.onMathML && !this.isContentSelected);
   213     var shouldShow = !(this.isContentSelected ||
   214                        this.onImage || this.onCanvas ||
   215                        this.onVideo || this.onAudio ||
   216                        this.onLink || this.onTextInput);
   217     var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
   218     this.showItem("context-viewsource", shouldShow);
   219     this.showItem("context-viewinfo", shouldShow);
   220     this.showItem("inspect-separator", showInspect);
   221     this.showItem("context-inspect", showInspect);
   223     this.showItem("context-sep-viewsource", shouldShow);
   225     // Set as Desktop background depends on whether an image was clicked on,
   226     // and only works if we have a shell service.
   227     var haveSetDesktopBackground = false;
   228 #ifdef HAVE_SHELL_SERVICE
   229     // Only enable Set as Desktop Background if we can get the shell service.
   230     var shell = getShellService();
   231     if (shell)
   232       haveSetDesktopBackground = shell.canSetDesktopBackground;
   233 #endif
   234     this.showItem("context-setDesktopBackground",
   235                   haveSetDesktopBackground && this.onLoadedImage);
   237     if (haveSetDesktopBackground && this.onLoadedImage) {
   238       document.getElementById("context-setDesktopBackground")
   239               .disabled = this.disableSetDesktopBackground();
   240     }
   242     // Reload image depends on an image that's not fully loaded
   243     this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
   245     // View image depends on having an image that's not standalone
   246     // (or is in a frame), or a canvas.
   247     this.showItem("context-viewimage", (this.onImage &&
   248                   (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
   250     // View video depends on not having a standalone video.
   251     this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
   252     this.setItemAttr("context-viewvideo",  "disabled", !this.mediaURL);
   254     // View background image depends on whether there is one, but don't make
   255     // background images of a stand-alone media document available.
   256     this.showItem("context-viewbgimage", shouldShow &&
   257                                          !this._hasMultipleBGImages &&
   258                                          !this.inSyntheticDoc);
   259     this.showItem("context-sep-viewbgimage", shouldShow &&
   260                                              !this._hasMultipleBGImages &&
   261                                              !this.inSyntheticDoc);
   262     document.getElementById("context-viewbgimage")
   263             .disabled = !this.hasBGImage;
   265     this.showItem("context-viewimageinfo", this.onImage);
   266     this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
   267   },
   269   initMiscItems: function CM_initMiscItems() {
   270     // Use "Bookmark This Link" if on a link.
   271     this.showItem("context-bookmarkpage",
   272                   !(this.isContentSelected || this.onTextInput || this.onLink ||
   273                     this.onImage || this.onVideo || this.onAudio || this.onSocial));
   274     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
   275                                            !this.onSocial) || this.onPlainTextLink);
   276     this.showItem("context-keywordfield",
   277                   this.onTextInput && this.onKeywordField);
   278     this.showItem("frame", this.inFrame);
   280     let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
   281     this.showItem("context-searchselect", showSearchSelect);
   282     if (showSearchSelect) {
   283       this.formatSearchContextItem();
   284     }
   286     // srcdoc cannot be opened separately due to concerns about web
   287     // content with about:srcdoc in location bar masquerading as trusted
   288     // chrome/addon content.
   289     // No need to also test for this.inFrame as this is checked in the parent
   290     // submenu.
   291     this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
   292     this.showItem("context-openframeintab", !this.inSrcdocFrame);
   293     this.showItem("context-openframe", !this.inSrcdocFrame);
   294     this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
   295     this.showItem("open-frame-sep", !this.inSrcdocFrame);
   297     this.showItem("frame-sep", this.inFrame && this.isTextSelected);
   299     // Hide menu entries for images, show otherwise
   300     if (this.inFrame) {
   301       if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
   302         this.isFrameImage.removeAttribute('hidden');
   303       else
   304         this.isFrameImage.setAttribute('hidden', 'true');
   305     }
   307     // BiDi UI
   308     this.showItem("context-sep-bidi", top.gBidiUI);
   309     this.showItem("context-bidi-text-direction-toggle",
   310                   this.onTextInput && top.gBidiUI);
   311     this.showItem("context-bidi-page-direction-toggle",
   312                   !this.onTextInput && top.gBidiUI);
   314     // SocialMarks. Marks does not work with text selections, only links. If
   315     // there is more than MENU_LIMIT providers, we show a submenu for them,
   316     // otherwise we have a menuitem per provider (added in SocialMarks class).
   317     let markProviders = SocialMarks.getProviders();
   318     let enablePageMarks = markProviders.length > 0 && !(this.onLink || this.onImage
   319                             || this.onVideo || this.onAudio);
   320     this.showItem("context-markpageMenu", enablePageMarks && markProviders.length > SocialMarks.MENU_LIMIT);
   321     let enablePageMarkItems = enablePageMarks && markProviders.length <= SocialMarks.MENU_LIMIT;
   322     let linkmenus = document.getElementsByClassName("context-markpage");
   323     [m.hidden = !enablePageMarkItems for (m of linkmenus)];
   325     let enableLinkMarks = markProviders.length > 0 &&
   326                             ((this.onLink && !this.onMailtoLink) || this.onPlainTextLink);
   327     this.showItem("context-marklinkMenu", enableLinkMarks && markProviders.length > SocialMarks.MENU_LIMIT);
   328     let enableLinkMarkItems = enableLinkMarks && markProviders.length <= SocialMarks.MENU_LIMIT;
   329     linkmenus = document.getElementsByClassName("context-marklink");
   330     [m.hidden = !enableLinkMarkItems for (m of linkmenus)];
   332     // SocialShare
   333     let shareButton = SocialShare.shareButton;
   334     let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
   335     let pageShare = shareEnabled && !(this.isContentSelected ||
   336                             this.onTextInput || this.onLink || this.onImage ||
   337                             this.onVideo || this.onAudio);
   338     this.showItem("context-sharepage", pageShare);
   339     this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
   340     this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
   341     this.showItem("context-shareimage", shareEnabled && this.onImage);
   342     this.showItem("context-sharevideo", shareEnabled && this.onVideo);
   343     this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL);
   344   },
   346   initSpellingItems: function() {
   347     var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
   348     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
   349     var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
   350     this.showItem("spell-check-enabled", canSpell);
   351     this.showItem("spell-separator", canSpell || this.onEditableArea);
   352     document.getElementById("spell-check-enabled")
   353             .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
   355     this.showItem("spell-add-to-dictionary", onMisspelling);
   356     this.showItem("spell-undo-add-to-dictionary", showUndo);
   358     // suggestion list
   359     this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
   360     if (onMisspelling) {
   361       var suggestionsSeparator =
   362         document.getElementById("spell-add-to-dictionary");
   363       var numsug =
   364         InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
   365                                                   suggestionsSeparator, 5);
   366       this.showItem("spell-no-suggestions", numsug == 0);
   367     }
   368     else
   369       this.showItem("spell-no-suggestions", false);
   371     // dictionary list
   372     this.showItem("spell-dictionaries", canSpell && InlineSpellCheckerUI.enabled);
   373     if (canSpell) {
   374       var dictMenu = document.getElementById("spell-dictionaries-menu");
   375       var dictSep = document.getElementById("spell-language-separator");
   376       InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
   377       this.showItem("spell-add-dictionaries-main", false);
   378     }
   379     else if (this.onEditableArea) {
   380       // when there is no spellchecker but we might be able to spellcheck
   381       // add the add to dictionaries item. This will ensure that people
   382       // with no dictionaries will be able to download them
   383       this.showItem("spell-add-dictionaries-main", true);
   384     }
   385     else
   386       this.showItem("spell-add-dictionaries-main", false);
   387   },
   389   initClipboardItems: function() {
   390     // Copy depends on whether there is selected text.
   391     // Enabling this context menu item is now done through the global
   392     // command updating system
   393     // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
   394     goUpdateGlobalEditMenuItems();
   396     this.showItem("context-undo", this.onTextInput);
   397     this.showItem("context-sep-undo", this.onTextInput);
   398     this.showItem("context-cut", this.onTextInput);
   399     this.showItem("context-copy",
   400                   this.isContentSelected || this.onTextInput);
   401     this.showItem("context-paste", this.onTextInput);
   402     this.showItem("context-delete", this.onTextInput);
   403     this.showItem("context-sep-paste", this.onTextInput);
   404     this.showItem("context-selectall", !(this.onLink || this.onImage ||
   405                                          this.onVideo || this.onAudio ||
   406                                          this.inSyntheticDoc) ||
   407                                        this.isDesignMode);
   408     this.showItem("context-sep-selectall", this.isContentSelected );
   410     // XXX dr
   411     // ------
   412     // nsDocumentViewer.cpp has code to determine whether we're
   413     // on a link or an image. we really ought to be using that...
   415     // Copy email link depends on whether we're on an email link.
   416     this.showItem("context-copyemail", this.onMailtoLink);
   418     // Copy link location depends on whether we're on a non-mailto link.
   419     this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
   420     this.showItem("context-sep-copylink", this.onLink &&
   421                   (this.onImage || this.onVideo || this.onAudio));
   423 #ifdef CONTEXT_COPY_IMAGE_CONTENTS
   424     // Copy image contents depends on whether we're on an image.
   425     this.showItem("context-copyimage-contents", this.onImage);
   426 #endif
   427     // Copy image location depends on whether we're on an image.
   428     this.showItem("context-copyimage", this.onImage);
   429     this.showItem("context-copyvideourl", this.onVideo);
   430     this.showItem("context-copyaudiourl", this.onAudio);
   431     this.setItemAttr("context-copyvideourl",  "disabled", !this.mediaURL);
   432     this.setItemAttr("context-copyaudiourl",  "disabled", !this.mediaURL);
   433     this.showItem("context-sep-copyimage", this.onImage ||
   434                   this.onVideo || this.onAudio);
   435   },
   437   initMediaPlayerItems: function() {
   438     var onMedia = (this.onVideo || this.onAudio);
   439     // Several mutually exclusive items... play/pause, mute/unmute, show/hide
   440     this.showItem("context-media-play",  onMedia && (this.target.paused || this.target.ended));
   441     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
   442     this.showItem("context-media-mute",   onMedia && !this.target.muted);
   443     this.showItem("context-media-unmute", onMedia && this.target.muted);
   444     this.showItem("context-media-playbackrate", onMedia);
   445     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
   446     this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
   447     this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
   448     var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
   449     this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
   450     this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
   452     // Disable them when there isn't a valid media source loaded.
   453     if (onMedia) {
   454       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
   455       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
   456       this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
   457       this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
   458       var hasError = this.target.error != null ||
   459                      this.target.networkState == this.target.NETWORK_NO_SOURCE;
   460       this.setItemAttr("context-media-play",  "disabled", hasError);
   461       this.setItemAttr("context-media-pause", "disabled", hasError);
   462       this.setItemAttr("context-media-mute",   "disabled", hasError);
   463       this.setItemAttr("context-media-unmute", "disabled", hasError);
   464       this.setItemAttr("context-media-playbackrate", "disabled", hasError);
   465       this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
   466       this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
   467       this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
   468       this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
   469       this.setItemAttr("context-media-showcontrols", "disabled", hasError);
   470       this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
   471       if (this.onVideo) {
   472         let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
   473         this.setItemAttr("context-video-saveimage",  "disabled", !canSaveSnapshot);
   474         this.setItemAttr("context-video-fullscreen", "disabled", hasError);
   475         this.setItemAttr("context-video-showstats", "disabled", hasError);
   476         this.setItemAttr("context-video-hidestats", "disabled", hasError);
   477       }
   478     }
   479     this.showItem("context-media-sep-commands",  onMedia);
   480   },
   482   initClickToPlayItems: function() {
   483     this.showItem("context-ctp-play", this.onCTPPlugin);
   484     this.showItem("context-ctp-hide", this.onCTPPlugin);
   485     this.showItem("context-sep-ctp", this.onCTPPlugin);
   486   },
   488   inspectNode: function CM_inspectNode() {
   489     let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   490     let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
   491     let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab);
   492     return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
   493       let inspector = toolbox.getCurrentPanel();
   494       inspector.selection.setNode(this.target, "browser-context-menu");
   495     }.bind(this));
   496   },
   498   // Set various context menu attributes based on the state of the world.
   499   setTarget: function (aNode, aRangeParent, aRangeOffset) {
   500     // If gContextMenuContentData is not null, this event was forwarded from a
   501     // child process, so use that information instead.
   502     if (gContextMenuContentData) {
   503       this.isRemote = true;
   504       aNode = gContextMenuContentData.event.target;
   505     } else {
   506       this.isRemote = false;
   507     }
   509     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   510     if (aNode.namespaceURI == xulNS ||
   511         aNode.nodeType == Node.DOCUMENT_NODE ||
   512         this.isDisabledForEvents(aNode)) {
   513       this.shouldDisplay = false;
   514       return;
   515     }
   517     // Initialize contextual info.
   518     this.onImage           = false;
   519     this.onLoadedImage     = false;
   520     this.onCompletedImage  = false;
   521     this.imageDescURL      = "";
   522     this.onCanvas          = false;
   523     this.onVideo           = false;
   524     this.onAudio           = false;
   525     this.onTextInput       = false;
   526     this.onKeywordField    = false;
   527     this.mediaURL          = "";
   528     this.onLink            = false;
   529     this.onMailtoLink      = false;
   530     this.onSaveableLink    = false;
   531     this.link              = null;
   532     this.linkURL           = "";
   533     this.linkURI           = null;
   534     this.linkProtocol      = "";
   535     this.onMathML          = false;
   536     this.inFrame           = false;
   537     this.inSrcdocFrame     = false;
   538     this.inSyntheticDoc    = false;
   539     this.hasBGImage        = false;
   540     this.bgImageURL        = "";
   541     this.onEditableArea    = false;
   542     this.isDesignMode      = false;
   543     this.onCTPPlugin       = false;
   544     this.canSpellCheck     = false;
   545     this.textSelected      = getBrowserSelection();
   546     this.isTextSelected    = this.textSelected.length != 0;
   548     // Remember the node that was clicked.
   549     this.target = aNode;
   551     let [elt, win] = BrowserUtils.getFocusSync(document);
   552     this.focusedWindow = win;
   553     this.focusedElement = elt;
   555     // If this is a remote context menu event, use the information from
   556     // gContextMenuContentData instead.
   557     if (this.isRemote) {
   558       this.browser = gContextMenuContentData.browser;
   559     } else {
   560       this.browser = this.target.ownerDocument.defaultView
   561                                   .QueryInterface(Ci.nsIInterfaceRequestor)
   562                                   .getInterface(Ci.nsIWebNavigation)
   563                                   .QueryInterface(Ci.nsIDocShell)
   564                                   .chromeEventHandler;
   565     }
   566     this.onSocial = !!this.browser.getAttribute("origin");
   568     // Check if we are in a synthetic document (stand alone image, video, etc.).
   569     this.inSyntheticDoc =  this.target.ownerDocument.mozSyntheticDocument;
   570     // First, do checks for nodes that never have children.
   571     if (this.target.nodeType == Node.ELEMENT_NODE) {
   572       // See if the user clicked on an image.
   573       if (this.target instanceof Ci.nsIImageLoadingContent &&
   574           this.target.currentURI) {
   575         this.onImage = true;
   577         var request =
   578           this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
   579         if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
   580           this.onLoadedImage = true;
   581         if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
   582           this.onCompletedImage = true;
   584         this.mediaURL = this.target.currentURI.spec;
   586         var descURL = this.target.getAttribute("longdesc");
   587         if (descURL) {
   588           this.imageDescURL = makeURLAbsolute(this.target.ownerDocument.body.baseURI, descURL);
   589         }
   590       }
   591       else if (this.target instanceof HTMLCanvasElement) {
   592         this.onCanvas = true;
   593       }
   594       else if (this.target instanceof HTMLVideoElement) {
   595         this.mediaURL = this.target.currentSrc || this.target.src;
   596         // Firefox always creates a HTMLVideoElement when loading an ogg file
   597         // directly. If the media is actually audio, be smarter and provide a
   598         // context menu with audio operations.
   599         if (this.target.readyState >= this.target.HAVE_METADATA &&
   600             (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
   601           this.onAudio = true;
   602         } else {
   603           this.onVideo = true;
   604         }
   605       }
   606       else if (this.target instanceof HTMLAudioElement) {
   607         this.onAudio = true;
   608         this.mediaURL = this.target.currentSrc || this.target.src;
   609       }
   610       else if (this.target instanceof HTMLInputElement ) {
   611         this.onTextInput = this.isTargetATextBox(this.target);
   612         // Allow spellchecking UI on all text and search inputs.
   613         if (this.onTextInput && ! this.target.readOnly &&
   614             (this.target.type == "text" || this.target.type == "search")) {
   615           this.onEditableArea = true;
   616           InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
   617           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
   618         }
   619         this.onKeywordField = this.isTargetAKeywordField(this.target);
   620       }
   621       else if (this.target instanceof HTMLTextAreaElement) {
   622         this.onTextInput = true;
   623         if (!this.target.readOnly) {
   624           this.onEditableArea = true;
   625           InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
   626           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
   627         }
   628       }
   629       else if (this.target instanceof HTMLHtmlElement) {
   630         var bodyElt = this.target.ownerDocument.body;
   631         if (bodyElt) {
   632           let computedURL;
   633           try {
   634             computedURL = this.getComputedURL(bodyElt, "background-image");
   635             this._hasMultipleBGImages = false;
   636           } catch (e) {
   637             this._hasMultipleBGImages = true;
   638           }
   639           if (computedURL) {
   640             this.hasBGImage = true;
   641             this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
   642                                               computedURL);
   643           }
   644         }
   645       }
   646       else if ((this.target instanceof HTMLEmbedElement ||
   647                 this.target instanceof HTMLObjectElement ||
   648                 this.target instanceof HTMLAppletElement) &&
   649                this.target.mozMatchesSelector(":-moz-handler-clicktoplay")) {
   650         this.onCTPPlugin = true;
   651       }
   653       this.canSpellCheck = this._isSpellCheckEnabled(this.target);
   654     }
   655     else if (this.target.nodeType == Node.TEXT_NODE) {
   656       // For text nodes, look at the parent node to determine the spellcheck attribute.
   657       this.canSpellCheck = this.target.parentNode &&
   658                            this._isSpellCheckEnabled(this.target);
   659     }
   661     // Second, bubble out, looking for items of interest that can have childen.
   662     // Always pick the innermost link, background image, etc.
   663     const XMLNS = "http://www.w3.org/XML/1998/namespace";
   664     var elem = this.target;
   665     while (elem) {
   666       if (elem.nodeType == Node.ELEMENT_NODE) {
   667         // Link?
   668         if (!this.onLink &&
   669             // Be consistent with what hrefAndLinkNodeForClickEvent
   670             // does in browser.js
   671              ((elem instanceof HTMLAnchorElement && elem.href) ||
   672               (elem instanceof HTMLAreaElement && elem.href) ||
   673               elem instanceof HTMLLinkElement ||
   674               elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
   676           // Target is a link or a descendant of a link.
   677           this.onLink = true;
   679           // Remember corresponding element.
   680           this.link = elem;
   681           this.linkURL = this.getLinkURL();
   682           this.linkURI = this.getLinkURI();
   683           this.linkProtocol = this.getLinkProtocol();
   684           this.onMailtoLink = (this.linkProtocol == "mailto");
   685           this.onSaveableLink = this.isLinkSaveable( this.link );
   686         }
   688         // Background image?  Don't bother if we've already found a
   689         // background image further down the hierarchy.  Otherwise,
   690         // we look for the computed background-image style.
   691         if (!this.hasBGImage &&
   692             !this._hasMultipleBGImages) {
   693           let bgImgUrl;
   694           try {
   695             bgImgUrl = this.getComputedURL(elem, "background-image");
   696             this._hasMultipleBGImages = false;
   697           } catch (e) {
   698             this._hasMultipleBGImages = true;
   699           }
   700           if (bgImgUrl) {
   701             this.hasBGImage = true;
   702             this.bgImageURL = makeURLAbsolute(elem.baseURI,
   703                                               bgImgUrl);
   704           }
   705         }
   706       }
   708       elem = elem.parentNode;
   709     }
   711     // See if the user clicked on MathML
   712     const NS_MathML = "http://www.w3.org/1998/Math/MathML";
   713     if ((this.target.nodeType == Node.TEXT_NODE &&
   714          this.target.parentNode.namespaceURI == NS_MathML)
   715          || (this.target.namespaceURI == NS_MathML))
   716       this.onMathML = true;
   718     // See if the user clicked in a frame.
   719     var docDefaultView = this.target.ownerDocument.defaultView;
   720     if (docDefaultView != docDefaultView.top) {
   721       this.inFrame = true;
   723       if (this.target.ownerDocument.isSrcdocDocument) {
   724           this.inSrcdocFrame = true;
   725       }
   726     }
   728     // if the document is editable, show context menu like in text inputs
   729     if (!this.onEditableArea) {
   730       var win = this.target.ownerDocument.defaultView;
   731       if (win) {
   732         var isEditable = false;
   733         try {
   734           var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
   735                                   .getInterface(Ci.nsIWebNavigation)
   736                                   .QueryInterface(Ci.nsIInterfaceRequestor)
   737                                   .getInterface(Ci.nsIEditingSession);
   738           if (editingSession.windowIsEditable(win) &&
   739               this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
   740             isEditable = true;
   741           }
   742         }
   743         catch(ex) {
   744           // If someone built with composer disabled, we can't get an editing session.
   745         }
   747         if (isEditable) {
   748           this.onTextInput       = true;
   749           this.onKeywordField    = false;
   750           this.onImage           = false;
   751           this.onLoadedImage     = false;
   752           this.onCompletedImage  = false;
   753           this.onMathML          = false;
   754           this.inFrame           = false;
   755           this.inSrcdocFrame     = false;
   756           this.hasBGImage        = false;
   757           this.isDesignMode      = true;
   758           this.onEditableArea = true;
   759           InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
   760           var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
   761           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
   762           this.showItem("spell-check-enabled", canSpell);
   763           this.showItem("spell-separator", canSpell);
   764         }
   765       }
   766     }
   767   },
   769   // Returns the computed style attribute for the given element.
   770   getComputedStyle: function(aElem, aProp) {
   771     return aElem.ownerDocument
   772                 .defaultView
   773                 .getComputedStyle(aElem, "").getPropertyValue(aProp);
   774   },
   776   // Returns a "url"-type computed style attribute value, with the url() stripped.
   777   getComputedURL: function(aElem, aProp) {
   778     var url = aElem.ownerDocument
   779                    .defaultView.getComputedStyle(aElem, "")
   780                    .getPropertyCSSValue(aProp);
   781     if (url instanceof CSSValueList) {
   782       if (url.length != 1)
   783         throw "found multiple URLs";
   784       url = url[0];
   785     }
   786     return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
   787            url.getStringValue() : null;
   788   },
   790   // Returns true if clicked-on link targets a resource that can be saved.
   791   isLinkSaveable: function(aLink) {
   792     // We don't do the Right Thing for news/snews yet, so turn them off
   793     // until we do.
   794     return this.linkProtocol && !(
   795              this.linkProtocol == "mailto"     ||
   796              this.linkProtocol == "javascript" ||
   797              this.linkProtocol == "news"       ||
   798              this.linkProtocol == "snews"      );
   799   },
   801   _unremotePrincipal: function(aRemotePrincipal) {
   802     if (this.isRemote) {
   803       return Cc["@mozilla.org/scriptsecuritymanager;1"]
   804                .getService(Ci.nsIScriptSecurityManager)
   805                .getAppCodebasePrincipal(aRemotePrincipal.URI,
   806                                         aRemotePrincipal.appId,
   807                                         aRemotePrincipal.isInBrowserElement);
   808     }
   810     return aRemotePrincipal;
   811   },
   813   _isSpellCheckEnabled: function(aNode) {
   814     // We can always force-enable spellchecking on textboxes
   815     if (this.isTargetATextBox(aNode)) {
   816       return true;
   817     }
   818     // We can never spell check something which is not content editable
   819     var editable = aNode.isContentEditable;
   820     if (!editable && aNode.ownerDocument) {
   821       editable = aNode.ownerDocument.designMode == "on";
   822     }
   823     if (!editable) {
   824       return false;
   825     }
   826     // Otherwise make sure that nothing in the parent chain disables spellchecking
   827     return aNode.spellcheck;
   828   },
   830   // Open linked-to URL in a new window.
   831   openLink : function () {
   832     var doc = this.target.ownerDocument;
   833     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
   834     openLinkIn(this.linkURL, "window",
   835                { charset: doc.characterSet,
   836                  referrerURI: doc.documentURIObject });
   837   },
   839   // Open linked-to URL in a new private window.
   840   openLinkInPrivateWindow : function () {
   841     var doc = this.target.ownerDocument;
   842     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
   843     openLinkIn(this.linkURL, "window",
   844                { charset: doc.characterSet,
   845                  referrerURI: doc.documentURIObject,
   846                  private: true });
   847   },
   849   // Open linked-to URL in a new tab.
   850   openLinkInTab: function() {
   851     var doc = this.target.ownerDocument;
   852     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
   853     var referrerURI = doc.documentURIObject;
   855     // if the mixedContentChannel is present and the referring URI passes
   856     // a same origin check with the target URI, we can preserve the users
   857     // decision of disabling MCB on a page for it's child tabs.
   858     var persistDisableMCBInChildTab = false;
   860     if (this.browser.docShell && this.browser.docShell.mixedContentChannel) {
   861       const sm = Services.scriptSecurityManager;
   862       try {
   863         var targetURI = this.linkURI;
   864         sm.checkSameOriginURI(referrerURI, targetURI, false);
   865         persistDisableMCBInChildTab = true;
   866       }
   867       catch (e) { }
   868     }
   870     openLinkIn(this.linkURL, "tab",
   871                { charset: doc.characterSet,
   872                  referrerURI: referrerURI,
   873                  disableMCB:  persistDisableMCBInChildTab});
   874   },
   876   // open URL in current tab
   877   openLinkInCurrent: function() {
   878     var doc = this.target.ownerDocument;
   879     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
   880     openLinkIn(this.linkURL, "current",
   881                { charset: doc.characterSet,
   882                  referrerURI: doc.documentURIObject });
   883   },
   885   // Open frame in a new tab.
   886   openFrameInTab: function() {
   887     var doc = this.target.ownerDocument;
   888     var frameURL = doc.location.href;
   889     var referrer = doc.referrer;
   890     openLinkIn(frameURL, "tab",
   891                { charset: doc.characterSet,
   892                  referrerURI: referrer ? makeURI(referrer) : null });
   893   },
   895   // Reload clicked-in frame.
   896   reloadFrame: function() {
   897     this.target.ownerDocument.location.reload();
   898   },
   900   // Open clicked-in frame in its own window.
   901   openFrame: function() {
   902     var doc = this.target.ownerDocument;
   903     var frameURL = doc.location.href;
   904     var referrer = doc.referrer;
   905     openLinkIn(frameURL, "window",
   906                { charset: doc.characterSet,
   907                  referrerURI: referrer ? makeURI(referrer) : null });
   908   },
   910   // Open clicked-in frame in the same window.
   911   showOnlyThisFrame: function() {
   912     var doc = this.target.ownerDocument;
   913     var frameURL = doc.location.href;
   915     urlSecurityCheck(frameURL,
   916                      this._unremotePrincipal(this.browser.contentPrincipal),
   917                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
   918     var referrer = doc.referrer;
   919     openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
   920                                         referrerURI: referrer ? makeURI(referrer) : null });
   921   },
   923   reload: function(event) {
   924     if (this.onSocial) {
   925       // full reload of social provider
   926       Social._getProviderFromOrigin(this.browser.getAttribute("origin")).reload();
   927     } else {
   928       BrowserReloadOrDuplicate(event);
   929     }
   930   },
   932   // View Partial Source
   933   viewPartialSource: function(aContext) {
   934     var focusedWindow = document.commandDispatcher.focusedWindow;
   935     if (focusedWindow == window)
   936       focusedWindow = content;
   938     var docCharset = null;
   939     if (focusedWindow)
   940       docCharset = "charset=" + focusedWindow.document.characterSet;
   942     // "View Selection Source" and others such as "View MathML Source"
   943     // are mutually exclusive, with the precedence given to the selection
   944     // when there is one
   945     var reference = null;
   946     if (aContext == "selection")
   947       reference = focusedWindow.getSelection();
   948     else if (aContext == "mathml")
   949       reference = this.target;
   950     else
   951       throw "not reached";
   953     // unused (and play nice for fragments generated via XSLT too)
   954     var docUrl = null;
   955     window.openDialog("chrome://global/content/viewPartialSource.xul",
   956                       "_blank", "scrollbars,resizable,chrome,dialog=no",
   957                       docUrl, docCharset, reference, aContext);
   958   },
   960   // Open new "view source" window with the frame's URL.
   961   viewFrameSource: function() {
   962     BrowserViewSourceOfDocument(this.target.ownerDocument);
   963   },
   965   viewInfo: function() {
   966     BrowserPageInfo(this.target.ownerDocument.defaultView.top.document);
   967   },
   969   viewImageInfo: function() {
   970     BrowserPageInfo(this.target.ownerDocument.defaultView.top.document,
   971                     "mediaTab", this.target);
   972   },
   974   viewImageDesc: function(e) {
   975     var doc = this.target.ownerDocument;
   976     urlSecurityCheck(this.imageDescURL,
   977                      this._unremotePrincipal(this.browser.contentPrincipal),
   978                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
   979     openUILink(this.imageDescURL, e, { disallowInheritPrincipal: true,
   980                              referrerURI: doc.documentURIObject });
   981   },
   983   viewFrameInfo: function() {
   984     BrowserPageInfo(this.target.ownerDocument);
   985   },
   987   reloadImage: function(e) {
   988     urlSecurityCheck(this.mediaURL,
   989                      this._unremotePrincipal(this.browser.contentPrincipal),
   990                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
   992     if (this.target instanceof Ci.nsIImageLoadingContent)
   993       this.target.forceReload();
   994   },
   996   // Change current window to the URL of the image, video, or audio.
   997   viewMedia: function(e) {
   998     var viewURL;
  1000     if (this.onCanvas)
  1001       viewURL = this.target.toDataURL();
  1002     else {
  1003       viewURL = this.mediaURL;
  1004       urlSecurityCheck(viewURL,
  1005                        this._unremotePrincipal(this.browser.contentPrincipal),
  1006                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  1009     var doc = this.target.ownerDocument;
  1010     openUILink(viewURL, e, { disallowInheritPrincipal: true,
  1011                              referrerURI: doc.documentURIObject });
  1012   },
  1014   saveVideoFrameAsImage: function () {
  1015     let name = "";
  1016     if (this.mediaURL) {
  1017       try {
  1018         let uri = makeURI(this.mediaURL);
  1019         let url = uri.QueryInterface(Ci.nsIURL);
  1020         if (url.fileBaseName)
  1021           name = decodeURI(url.fileBaseName) + ".jpg";
  1022       } catch (e) { }
  1024     if (!name)
  1025       name = "snapshot.jpg";
  1026     var video = this.target;
  1027     var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
  1028     canvas.width = video.videoWidth;
  1029     canvas.height = video.videoHeight;
  1030     var ctxDraw = canvas.getContext("2d");
  1031     ctxDraw.drawImage(video, 0, 0);
  1032     saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument);
  1033   },
  1035   fullScreenVideo: function () {
  1036     let video = this.target;
  1037     if (document.mozFullScreenEnabled)
  1038       video.mozRequestFullScreen();
  1039   },
  1041   leaveDOMFullScreen: function() {
  1042     document.mozCancelFullScreen();
  1043   },
  1045   // Change current window to the URL of the background image.
  1046   viewBGImage: function(e) {
  1047     urlSecurityCheck(this.bgImageURL,
  1048                      this._unremotePrincipal(this.browser.contentPrincipal),
  1049                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  1050     var doc = this.target.ownerDocument;
  1051     openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
  1052                                      referrerURI: doc.documentURIObject });
  1053   },
  1055   disableSetDesktopBackground: function() {
  1056     // Disable the Set as Desktop Background menu item if we're still trying
  1057     // to load the image or the load failed.
  1058     if (!(this.target instanceof Ci.nsIImageLoadingContent))
  1059       return true;
  1061     if (("complete" in this.target) && !this.target.complete)
  1062       return true;
  1064     if (this.target.currentURI.schemeIs("javascript"))
  1065       return true;
  1067     var request = this.target
  1068                       .QueryInterface(Ci.nsIImageLoadingContent)
  1069                       .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
  1070     if (!request)
  1071       return true;
  1073     return false;
  1074   },
  1076   setDesktopBackground: function() {
  1077     // Paranoia: check disableSetDesktopBackground again, in case the
  1078     // image changed since the context menu was initiated.
  1079     if (this.disableSetDesktopBackground())
  1080       return;
  1082     var doc = this.target.ownerDocument;
  1083     urlSecurityCheck(this.target.currentURI.spec,
  1084                      this._unremotePrincipal(doc.nodePrincipal));
  1086     // Confirm since it's annoying if you hit this accidentally.
  1087     const kDesktopBackgroundURL = 
  1088                   "chrome://browser/content/setDesktopBackground.xul";
  1089 #ifdef XP_MACOSX
  1090     // On Mac, the Set Desktop Background window is not modal.
  1091     // Don't open more than one Set Desktop Background window.
  1092     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1093                        .getService(Components.interfaces.nsIWindowMediator);
  1094     var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
  1095     if (dbWin) {
  1096       dbWin.gSetBackground.init(this.target);
  1097       dbWin.focus();
  1099     else {
  1100       openDialog(kDesktopBackgroundURL, "",
  1101                  "centerscreen,chrome,dialog=no,dependent,resizable=no",
  1102                  this.target);
  1104 #else
  1105     // On non-Mac platforms, the Set Wallpaper dialog is modal.
  1106     openDialog(kDesktopBackgroundURL, "",
  1107                "centerscreen,chrome,dialog,modal,dependent",
  1108                this.target);
  1109 #endif
  1110   },
  1112   // Save URL of clicked-on frame.
  1113   saveFrame: function () {
  1114     saveDocument(this.target.ownerDocument);
  1115   },
  1117   // Helper function to wait for appropriate MIME-type headers and
  1118   // then prompt the user with a file picker
  1119   saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) {
  1120     // canonical def in nsURILoader.h
  1121     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
  1123     // an object to proxy the data through to
  1124     // nsIExternalHelperAppService.doContent, which will wait for the
  1125     // appropriate MIME-type headers and then prompt the user with a
  1126     // file picker
  1127     function saveAsListener() {}
  1128     saveAsListener.prototype = {
  1129       extListener: null, 
  1131       onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
  1133         // if the timer fired, the error status will have been caused by that,
  1134         // and we'll be restarting in onStopRequest, so no reason to notify
  1135         // the user
  1136         if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
  1137           return;
  1139         timer.cancel();
  1141         // some other error occured; notify the user...
  1142         if (!Components.isSuccessCode(aRequest.status)) {
  1143           try {
  1144             const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  1145                         getService(Ci.nsIStringBundleService);
  1146             const bundle = sbs.createBundle(
  1147                     "chrome://mozapps/locale/downloads/downloads.properties");
  1149             const title = bundle.GetStringFromName("downloadErrorAlertTitle");
  1150             const msg = bundle.GetStringFromName("downloadErrorGeneric");
  1152             const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  1153                               getService(Ci.nsIPromptService);
  1154             promptSvc.alert(doc.defaultView, title, msg);
  1155           } catch (ex) {}
  1156           return;
  1159         var extHelperAppSvc = 
  1160           Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
  1161           getService(Ci.nsIExternalHelperAppService);
  1162         var channel = aRequest.QueryInterface(Ci.nsIChannel);
  1163         this.extListener = 
  1164           extHelperAppSvc.doContent(channel.contentType, aRequest, 
  1165                                     doc.defaultView, true);
  1166         this.extListener.onStartRequest(aRequest, aContext);
  1167       }, 
  1169       onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext, 
  1170                                                        aStatusCode) {
  1171         if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
  1172           // do it the old fashioned way, which will pick the best filename
  1173           // it can without waiting.
  1174           saveURL(linkURL, linkText, dialogTitle, bypassCache, false, doc.documentURIObject, doc);
  1176         if (this.extListener)
  1177           this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
  1178       },
  1180       onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
  1181                                                            aInputStream,
  1182                                                            aOffset, aCount) {
  1183         this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
  1184                                          aOffset, aCount);
  1188     function callbacks() {}
  1189     callbacks.prototype = {
  1190       getInterface: function sLA_callbacks_getInterface(aIID) {
  1191         if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
  1192           // If the channel demands authentication prompt, we must cancel it
  1193           // because the save-as-timer would expire and cancel the channel
  1194           // before we get credentials from user.  Both authentication dialog
  1195           // and save as dialog would appear on the screen as we fall back to
  1196           // the old fashioned way after the timeout.
  1197           timer.cancel();
  1198           channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
  1200         throw Cr.NS_ERROR_NO_INTERFACE;
  1204     // if it we don't have the headers after a short time, the user 
  1205     // won't have received any feedback from their click.  that's bad.  so
  1206     // we give up waiting for the filename. 
  1207     function timerCallback() {}
  1208     timerCallback.prototype = {
  1209       notify: function sLA_timer_notify(aTimer) {
  1210         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
  1211         return;
  1215     // set up a channel to do the saving
  1216     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1217                     getService(Ci.nsIIOService);
  1218     var channel = ioService.newChannelFromURI(makeURI(linkURL));
  1219     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
  1220       let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
  1221       channel.setPrivate(docIsPrivate);
  1223     channel.notificationCallbacks = new callbacks();
  1225     let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
  1227     if (bypassCache)
  1228       flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  1230     if (channel instanceof Ci.nsICachingChannel)
  1231       flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
  1233     channel.loadFlags |= flags;
  1235     if (channel instanceof Ci.nsIHttpChannel) {
  1236       channel.referrer = doc.documentURIObject;
  1237       if (channel instanceof Ci.nsIHttpChannelInternal)
  1238         channel.forceAllowThirdPartyCookie = true;
  1241     // fallback to the old way if we don't see the headers quickly 
  1242     var timeToWait = 
  1243       gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
  1244     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1245     timer.initWithCallback(new timerCallback(), timeToWait,
  1246                            timer.TYPE_ONE_SHOT);
  1248     // kick off the channel with our proxy object as the listener
  1249     channel.asyncOpen(new saveAsListener(), null);
  1250   },
  1252   // Save URL of clicked-on link.
  1253   saveLink: function() {
  1254     var doc =  this.target.ownerDocument;
  1255     var linkText;
  1256     // If selected text is found to match valid URL pattern.
  1257     if (this.onPlainTextLink)
  1258       linkText = this.focusedWindow.getSelection().toString().trim();
  1259     else
  1260       linkText = this.linkText();
  1261     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
  1263     this.saveHelper(this.linkURL, linkText, null, true, doc);
  1264   },
  1266   // Backwards-compatibility wrapper
  1267   saveImage : function() {
  1268     if (this.onCanvas || this.onImage)
  1269         this.saveMedia();
  1270   },
  1272   // Save URL of the clicked upon image, video, or audio.
  1273   saveMedia: function() {
  1274     var doc =  this.target.ownerDocument;
  1275     if (this.onCanvas) {
  1276       // Bypass cache, since it's a data: URL.
  1277       saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle",
  1278                    true, false, doc.documentURIObject, doc);
  1280     else if (this.onImage) {
  1281       urlSecurityCheck(this.mediaURL,
  1282                        this._unremotePrincipal(doc.nodePrincipal));
  1283       saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
  1284                    false, doc.documentURIObject, doc);
  1286     else if (this.onVideo || this.onAudio) {
  1287       urlSecurityCheck(this.mediaURL,
  1288                        this._unremotePrincipal(doc.nodePrincipal));
  1289       var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
  1290       this.saveHelper(this.mediaURL, null, dialogTitle, false, doc);
  1292   },
  1294   // Backwards-compatibility wrapper
  1295   sendImage : function() {
  1296     if (this.onCanvas || this.onImage)
  1297         this.sendMedia();
  1298   },
  1300   sendMedia: function() {
  1301     MailIntegration.sendMessage(this.mediaURL, "");
  1302   },
  1304   playPlugin: function() {
  1305     gPluginHandler._showClickToPlayNotification(this.browser, this.target, true);
  1306   },
  1308   hidePlugin: function() {
  1309     gPluginHandler.hideClickToPlayOverlay(this.target);
  1310   },
  1312   // Generate email address and put it on clipboard.
  1313   copyEmail: function() {
  1314     // Copy the comma-separated list of email addresses only.
  1315     // There are other ways of embedding email addresses in a mailto:
  1316     // link, but such complex parsing is beyond us.
  1317     var url = this.linkURL;
  1318     var qmark = url.indexOf("?");
  1319     var addresses;
  1321     // 7 == length of "mailto:"
  1322     addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
  1324     // Let's try to unescape it using a character set
  1325     // in case the address is not ASCII.
  1326     try {
  1327       var characterSet = this.target.ownerDocument.characterSet;
  1328       const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
  1329                            getService(Ci.nsITextToSubURI);
  1330       addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
  1332     catch(ex) {
  1333       // Do nothing.
  1336     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
  1337                     getService(Ci.nsIClipboardHelper);
  1338     clipboard.copyString(addresses, document);
  1339   },
  1341   copyLink: function() {
  1342     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
  1343                     getService(Ci.nsIClipboardHelper);
  1344     clipboard.copyString(this.linkURL, document);
  1345   },
  1347   ///////////////
  1348   // Utilities //
  1349   ///////////////
  1351   // Show/hide one item (specified via name or the item element itself).
  1352   showItem: function(aItemOrId, aShow) {
  1353     var item = aItemOrId.constructor == String ?
  1354       document.getElementById(aItemOrId) : aItemOrId;
  1355     if (item)
  1356       item.hidden = !aShow;
  1357   },
  1359   // Set given attribute of specified context-menu item.  If the
  1360   // value is null, then it removes the attribute (which works
  1361   // nicely for the disabled attribute).
  1362   setItemAttr: function(aID, aAttr, aVal ) {
  1363     var elem = document.getElementById(aID);
  1364     if (elem) {
  1365       if (aVal == null) {
  1366         // null indicates attr should be removed.
  1367         elem.removeAttribute(aAttr);
  1369       else {
  1370         // Set attr=val.
  1371         elem.setAttribute(aAttr, aVal);
  1374   },
  1376   // Set context menu attribute according to like attribute of another node
  1377   // (such as a broadcaster).
  1378   setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
  1379     var elem = document.getElementById(aOther_id);
  1380     if (elem && elem.getAttribute(aAttr) == "true")
  1381       this.setItemAttr(aItem_id, aAttr, "true");
  1382     else
  1383       this.setItemAttr(aItem_id, aAttr, null);
  1384   },
  1386   // Temporary workaround for DOM api not yet implemented by XUL nodes.
  1387   cloneNode: function(aItem) {
  1388     // Create another element like the one we're cloning.
  1389     var node = document.createElement(aItem.tagName);
  1391     // Copy attributes from argument item to the new one.
  1392     var attrs = aItem.attributes;
  1393     for (var i = 0; i < attrs.length; i++) {
  1394       var attr = attrs.item(i);
  1395       node.setAttribute(attr.nodeName, attr.nodeValue);
  1398     // Voila!
  1399     return node;
  1400   },
  1402   // Generate fully qualified URL for clicked-on link.
  1403   getLinkURL: function() {
  1404     var href = this.link.href;  
  1405     if (href)
  1406       return href;
  1408     href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
  1409                                     "href");
  1411     if (!href || !href.match(/\S/)) {
  1412       // Without this we try to save as the current doc,
  1413       // for example, HTML case also throws if empty
  1414       throw "Empty href";
  1417     return makeURLAbsolute(this.link.baseURI, href);
  1418   },
  1420   getLinkURI: function() {
  1421     try {
  1422       return makeURI(this.linkURL);
  1424     catch (ex) {
  1425      // e.g. empty URL string
  1428     return null;
  1429   },
  1431   getLinkProtocol: function() {
  1432     if (this.linkURI)
  1433       return this.linkURI.scheme; // can be |undefined|
  1435     return null;
  1436   },
  1438   // Get text of link.
  1439   linkText: function() {
  1440     var text = gatherTextUnder(this.link);
  1441     if (!text || !text.match(/\S/)) {
  1442       text = this.link.getAttribute("title");
  1443       if (!text || !text.match(/\S/)) {
  1444         text = this.link.getAttribute("alt");
  1445         if (!text || !text.match(/\S/))
  1446           text = this.linkURL;
  1450     return text;
  1451   },
  1453   // Returns true if anything is selected.
  1454   isContentSelection: function() {
  1455     return !this.focusedWindow.getSelection().isCollapsed;
  1456   },
  1458   toString: function () {
  1459     return "contextMenu.target     = " + this.target + "\n" +
  1460            "contextMenu.onImage    = " + this.onImage + "\n" +
  1461            "contextMenu.onLink     = " + this.onLink + "\n" +
  1462            "contextMenu.link       = " + this.link + "\n" +
  1463            "contextMenu.inFrame    = " + this.inFrame + "\n" +
  1464            "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
  1465   },
  1467   isDisabledForEvents: function(aNode) {
  1468     let ownerDoc = aNode.ownerDocument;
  1469     return
  1470       ownerDoc.defaultView &&
  1471       ownerDoc.defaultView
  1472               .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  1473               .getInterface(Components.interfaces.nsIDOMWindowUtils)
  1474               .isNodeDisabledForEvents(aNode);
  1475   },
  1477   isTargetATextBox: function(node) {
  1478     if (node instanceof HTMLInputElement)
  1479       return node.mozIsTextField(false);
  1481     return (node instanceof HTMLTextAreaElement);
  1482   },
  1484   isTargetAKeywordField: function(aNode) {
  1485     if (!(aNode instanceof HTMLInputElement))
  1486       return false;
  1488     var form = aNode.form;
  1489     if (!form || aNode.type == "password")
  1490       return false;
  1492     var method = form.method.toUpperCase();
  1494     // These are the following types of forms we can create keywords for:
  1495     //
  1496     // method   encoding type       can create keyword
  1497     // GET      *                                 YES
  1498     //          *                                 YES
  1499     // POST                                       YES
  1500     // POST     application/x-www-form-urlencoded YES
  1501     // POST     text/plain                        NO (a little tricky to do)
  1502     // POST     multipart/form-data               NO
  1503     // POST     everything else                   YES
  1504     return (method == "GET" || method == "") ||
  1505            (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
  1506   },
  1508   // Determines whether or not the separator with the specified ID should be
  1509   // shown or not by determining if there are any non-hidden items between it
  1510   // and the previous separator.
  1511   shouldShowSeparator: function (aSeparatorID) {
  1512     var separator = document.getElementById(aSeparatorID);
  1513     if (separator) {
  1514       var sibling = separator.previousSibling;
  1515       while (sibling && sibling.localName != "menuseparator") {
  1516         if (!sibling.hidden)
  1517           return true;
  1518         sibling = sibling.previousSibling;
  1521     return false;
  1522   },
  1524   addDictionaries: function() {
  1525     var uri = formatURL("browser.dictionaries.download.url", true);
  1527     var locale = "-";
  1528     try {
  1529       locale = gPrefService.getComplexValue("intl.accept_languages",
  1530                                             Ci.nsIPrefLocalizedString).data;
  1532     catch (e) { }
  1534     var version = "-";
  1535     try {
  1536       version = Cc["@mozilla.org/xre/app-info;1"].
  1537                 getService(Ci.nsIXULAppInfo).version;
  1539     catch (e) { }
  1541     uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
  1543     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
  1544     var where = newWindowPref == 3 ? "tab" : "window";
  1546     openUILinkIn(uri, where);
  1547   },
  1549   bookmarkThisPage: function CM_bookmarkThisPage() {
  1550     window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
  1551   },
  1553   bookmarkLink: function CM_bookmarkLink() {
  1554     var linkText;
  1555     // If selected text is found to match valid URL pattern.
  1556     if (this.onPlainTextLink)
  1557       linkText = this.focusedWindow.getSelection().toString().trim();
  1558     else
  1559       linkText = this.linkText();
  1560     window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL,
  1561                                               linkText);
  1562   },
  1564   addBookmarkForFrame: function CM_addBookmarkForFrame() {
  1565     var doc = this.target.ownerDocument;
  1566     var uri = doc.documentURIObject;
  1568     var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
  1569     if (itemId == -1) {
  1570       var title = doc.title;
  1571       var description = PlacesUIUtils.getDescriptionFromDocument(doc);
  1572       PlacesUIUtils.showBookmarkDialog({ action: "add"
  1573                                        , type: "bookmark"
  1574                                        , uri: uri
  1575                                        , title: title
  1576                                        , description: description
  1577                                        , hiddenRows: [ "description"
  1578                                                      , "location"
  1579                                                      , "loadInSidebar"
  1580                                                      , "keyword" ]
  1581                                        }, window.top);
  1583     else {
  1584       PlacesUIUtils.showBookmarkDialog({ action: "edit"
  1585                                        , type: "bookmark"
  1586                                        , itemId: itemId
  1587                                        }, window.top);
  1589   },
  1590   markLink: function CM_markLink(origin) {
  1591     // send link to social, if it is the page url linkURI will be null
  1592     SocialMarks.markLink(origin, this.linkURI ? this.linkURI.spec : null);
  1593   },
  1594   shareLink: function CM_shareLink() {
  1595     SocialShare.sharePage(null, { url: this.linkURI.spec });
  1596   },
  1598   shareImage: function CM_shareImage() {
  1599     SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] });
  1600   },
  1602   shareVideo: function CM_shareVideo() {
  1603     SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL });
  1604   },
  1606   shareSelect: function CM_shareSelect(selection) {
  1607     SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection });
  1608   },
  1610   savePageAs: function CM_savePageAs() {
  1611     saveDocument(this.browser.contentDocument);
  1612   },
  1614   printFrame: function CM_printFrame() {
  1615     PrintUtils.print(this.target.ownerDocument.defaultView);
  1616   },
  1618   switchPageDirection: function CM_switchPageDirection() {
  1619     SwitchDocumentDirection(this.browser.contentWindow);
  1620   },
  1622   mediaCommand : function CM_mediaCommand(command, data) {
  1623     var media = this.target;
  1625     switch (command) {
  1626       case "play":
  1627         media.play();
  1628         break;
  1629       case "pause":
  1630         media.pause();
  1631         break;
  1632       case "mute":
  1633         media.muted = true;
  1634         break;
  1635       case "unmute":
  1636         media.muted = false;
  1637         break;
  1638       case "playbackRate":
  1639         media.playbackRate = data;
  1640         break;
  1641       case "hidecontrols":
  1642         media.removeAttribute("controls");
  1643         break;
  1644       case "showcontrols":
  1645         media.setAttribute("controls", "true");
  1646         break;
  1647       case "hidestats":
  1648       case "showstats":
  1649         var event = media.ownerDocument.createEvent("CustomEvent");
  1650         event.initCustomEvent("media-showStatistics", false, true, command == "showstats");
  1651         media.dispatchEvent(event);
  1652         break;
  1654   },
  1656   copyMediaLocation : function () {
  1657     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
  1658                     getService(Ci.nsIClipboardHelper);
  1659     clipboard.copyString(this.mediaURL, document);
  1660   },
  1662   get imageURL() {
  1663     if (this.onImage)
  1664       return this.mediaURL;
  1665     return "";
  1666   },
  1668   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
  1669   formatSearchContextItem: function() {
  1670     var menuItem = document.getElementById("context-searchselect");
  1671     var selectedText = this.isTextSelected ? this.textSelected : this.linkText();
  1673     // Store searchTerms in context menu item so we know what to search onclick
  1674     menuItem.searchTerms = selectedText;
  1676     if (selectedText.length > 15)
  1677       selectedText = selectedText.substr(0,15) + this.ellipsis;
  1679     // Use the current engine if the search bar is visible, the default
  1680     // engine otherwise.
  1681     var engineName = "";
  1682     var ss = Cc["@mozilla.org/browser/search-service;1"].
  1683              getService(Ci.nsIBrowserSearchService);
  1684     if (isElementVisible(BrowserSearch.searchBar))
  1685       engineName = ss.currentEngine.name;
  1686     else
  1687       engineName = ss.defaultEngine.name;
  1689     // format "Search <engine> for <selection>" string to show in menu
  1690     var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
  1691                                                         [engineName,
  1692                                                          selectedText]);
  1693     menuItem.label = menuLabel;
  1694     menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
  1696 };

mercurial