forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1521 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1521 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | |
| /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetters(this, {
 | |
|   SpellCheckHelper: "resource://gre/modules/InlineSpellChecker.jsm",
 | |
|   LoginHelper: "resource://gre/modules/LoginHelper.jsm",
 | |
|   LoginManagerContextMenu: "resource://gre/modules/LoginManagerContextMenu.jsm",
 | |
|   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
 | |
|   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
 | |
|   DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
 | |
|   NetUtil: "resource://gre/modules/NetUtil.jsm",
 | |
| });
 | |
| 
 | |
| var gContextMenuContentData = null;
 | |
| 
 | |
| function setContextMenuContentData(data) {
 | |
|   gContextMenuContentData = data;
 | |
| }
 | |
| 
 | |
| function openContextMenu(aMessage) {
 | |
|   let data = aMessage.data;
 | |
|   let browser = aMessage.target;
 | |
|   let spellInfo = data.spellInfo;
 | |
| 
 | |
|   // ContextMenu.jsm sends us the target as a CPOW only so that
 | |
|   // we can send that CPOW back down to the content process and
 | |
|   // have it resolve to a DOM node. The parent should not attempt
 | |
|   // to access any properties on this CPOW (in fact, doing so
 | |
|   // will throw an exception).
 | |
|   data.context.targetAsCPOW = aMessage.objects.targetAsCPOW;
 | |
| 
 | |
|   if (spellInfo) {
 | |
|     spellInfo.target = aMessage.target.messageManager;
 | |
|   }
 | |
| 
 | |
|   let documentURIObject = makeURI(data.docLocation,
 | |
|                                   data.charSet,
 | |
|                                   makeURI(data.baseURI));
 | |
|   gContextMenuContentData = { context: data.context,
 | |
|                               isRemote: data.isRemote,
 | |
|                               popupNodeSelectors: data.popupNodeSelectors,
 | |
|                               browser,
 | |
|                               editFlags: data.editFlags,
 | |
|                               spellInfo,
 | |
|                               principal: data.principal,
 | |
|                               customMenuItems: data.customMenuItems,
 | |
|                               documentURIObject,
 | |
|                               docLocation: data.docLocation,
 | |
|                               charSet: data.charSet,
 | |
|                               referrer: data.referrer,
 | |
|                               referrerPolicy: data.referrerPolicy,
 | |
|                               contentType: data.contentType,
 | |
|                               contentDisposition: data.contentDisposition,
 | |
|                               frameOuterWindowID: data.frameOuterWindowID,
 | |
|                               selectionInfo: data.selectionInfo,
 | |
|                               disableSetDesktopBackground: data.disableSetDesktopBg,
 | |
|                               loginFillInfo: data.loginFillInfo,
 | |
|                               parentAllowsMixedContent: data.parentAllowsMixedContent,
 | |
|                               userContextId: data.userContextId,
 | |
|                             };
 | |
| 
 | |
|   let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
 | |
|   let context = gContextMenuContentData.context;
 | |
| 
 | |
|   // The event is a CPOW that can't be passed into the native openPopupAtScreen
 | |
|   // function. Therefore we synthesize a new MouseEvent to propagate the
 | |
|   // inputSource to the subsequently triggered popupshowing event.
 | |
|   var newEvent = document.createEvent("MouseEvent");
 | |
|   newEvent.initNSMouseEvent("contextmenu", true, true, null, 0, context.screenX, context.screenY,
 | |
|                             0, 0, false, false, false, false, 0, null, 0, context.mozInputSource);
 | |
| 
 | |
|   popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent);
 | |
| }
 | |
| 
 | |
| function nsContextMenu(aXulMenu, aIsShift) {
 | |
|   this.shouldDisplay = true;
 | |
|   this.initMenu(aXulMenu, aIsShift);
 | |
| }
 | |
| 
 | |
| // Prototype for nsContextMenu "class."
 | |
| nsContextMenu.prototype = {
 | |
|   initMenu: function CM_initMenu(aXulMenu, aIsShift) {
 | |
|     // Get contextual info.
 | |
|     this.setContext();
 | |
| 
 | |
|     if (!this.shouldDisplay)
 | |
|       return;
 | |
| 
 | |
|     this.hasPageMenu = false;
 | |
|     this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
 | |
|     if (!aIsShift) {
 | |
|       if (this.isRemote) {
 | |
|         this.hasPageMenu =
 | |
|           PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
 | |
|                                     this.browser, aXulMenu);
 | |
|       } else {
 | |
|         this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
 | |
|       }
 | |
| 
 | |
|       let subject = {
 | |
|         menu: aXulMenu,
 | |
|         tab: gBrowser ? gBrowser.getTabForBrowser(this.browser) : undefined,
 | |
|         timeStamp: this.timeStamp,
 | |
|         isContentSelected: this.isContentSelected,
 | |
|         inFrame: this.inFrame,
 | |
|         isTextSelected: this.isTextSelected,
 | |
|         onTextInput: this.onTextInput,
 | |
|         onLink: this.onLink,
 | |
|         onImage: this.onImage,
 | |
|         onVideo: this.onVideo,
 | |
|         onAudio: this.onAudio,
 | |
|         onCanvas: this.onCanvas,
 | |
|         onEditable: this.onEditable,
 | |
|         onSpellcheckable: this.onSpellcheckable,
 | |
|         onPassword: this.onPassword,
 | |
|         srcUrl: this.mediaURL,
 | |
|         frameUrl: gContextMenuContentData ? gContextMenuContentData.docLocation : undefined,
 | |
|         pageUrl: this.browser ? this.browser.currentURI.spec : undefined,
 | |
|         linkText: this.linkTextStr,
 | |
|         linkUrl: this.linkURL,
 | |
|         selectionText: this.isTextSelected ? this.selectionInfo.fullText : undefined,
 | |
|         frameId: this.frameOuterWindowID,
 | |
|       };
 | |
|       subject.wrappedJSObject = subject;
 | |
|       Services.obs.notifyObservers(subject, "on-build-contextmenu");
 | |
|     }
 | |
| 
 | |
|     this.viewFrameSourceElement =
 | |
|          document.getElementById("context-viewframesource");
 | |
|     this.ellipsis = "\u2026";
 | |
|     try {
 | |
|       this.ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
 | |
|                                                      Ci.nsIPrefLocalizedString).data;
 | |
|     } catch (e) { }
 | |
| 
 | |
|     // Reset after "on-build-contextmenu" notification in case selection was
 | |
|     // changed during the notification.
 | |
|     this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
 | |
|     this.onPlainTextLink = false;
 | |
| 
 | |
|     let bookmarkPage = document.getElementById("context-bookmarkpage");
 | |
|     if (bookmarkPage)
 | |
|       BookmarkingUI.onCurrentPageContextPopupShowing();
 | |
| 
 | |
|     // Initialize (disable/remove) menu items.
 | |
|     this.initItems();
 | |
|   },
 | |
| 
 | |
|   setContext() {
 | |
|     let context = Object.create(null);
 | |
|     this.isRemote = false;
 | |
| 
 | |
|     if (gContextMenuContentData) {
 | |
|       context = gContextMenuContentData.context;
 | |
|       gContextMenuContentData.context = null;
 | |
|       this.isRemote = gContextMenuContentData.isRemote;
 | |
|     }
 | |
| 
 | |
|     this.shouldDisplay = context.shouldDisplay;
 | |
|     this.timeStamp = context.timeStamp;
 | |
| 
 | |
|     // Assign what's _possibly_ needed from `context` sent by ContextMenu.jsm
 | |
|     // Keep this consistent with the similar code in ContextMenu's _setContext
 | |
|     this.bgImageURL          = context.bgImageURL;
 | |
|     this.imageDescURL        = context.imageDescURL;
 | |
|     this.imageInfo           = context.imageInfo;
 | |
|     this.mediaURL            = context.mediaURL;
 | |
|     this.webExtBrowserType   = context.webExtBrowserType;
 | |
| 
 | |
|     this.canSpellCheck       = context.canSpellCheck;
 | |
|     this.hasBGImage          = context.hasBGImage;
 | |
|     this.hasMultipleBGImages = context.hasMultipleBGImages;
 | |
|     this.isDesignMode        = context.isDesignMode;
 | |
|     this.inFrame             = context.inFrame;
 | |
|     this.inSrcdocFrame       = context.inSrcdocFrame;
 | |
|     this.inSyntheticDoc      = context.inSyntheticDoc;
 | |
|     this.inTabBrowser        = context.inTabBrowser;
 | |
|     this.inWebExtBrowser     = context.inWebExtBrowser;
 | |
| 
 | |
|     this.link                = context.link;
 | |
|     this.linkDownload        = context.linkDownload;
 | |
|     this.linkHasNoReferrer   = context.linkHasNoReferrer;
 | |
|     this.linkProtocol        = context.linkProtocol;
 | |
|     this.linkTextStr         = context.linkTextStr;
 | |
|     this.linkURL             = context.linkURL;
 | |
|     this.linkURI             = this.getLinkURI(); // can't send; regenerate
 | |
| 
 | |
|     this.onAudio             = context.onAudio;
 | |
|     this.onCanvas            = context.onCanvas;
 | |
|     this.onCompletedImage    = context.onCompletedImage;
 | |
|     this.onCTPPlugin         = context.onCTPPlugin;
 | |
|     this.onDRMMedia          = context.onDRMMedia;
 | |
|     this.onEditable          = context.onEditable;
 | |
|     this.onImage             = context.onImage;
 | |
|     this.onKeywordField      = context.onKeywordField;
 | |
|     this.onLink              = context.onLink;
 | |
|     this.onLoadedImage       = context.onLoadedImage;
 | |
|     this.onMailtoLink        = context.onMailtoLink;
 | |
|     this.onMozExtLink        = context.onMozExtLink;
 | |
|     this.onNumeric           = context.onNumeric;
 | |
|     this.onPassword          = context.onPassword;
 | |
|     this.onSaveableLink      = context.onSaveableLink;
 | |
|     this.onSpellcheckable    = context.onSpellcheckable;
 | |
|     this.onTextInput         = context.onTextInput;
 | |
|     this.onVideo             = context.onVideo;
 | |
| 
 | |
|     this.target = this.isRemote ? context.target : document.popupNode;
 | |
|     this.targetAsCPOW = context.targetAsCPOW;
 | |
| 
 | |
|     this.principal = context.principal;
 | |
|     this.frameOuterWindowID = context.frameOuterWindowID;
 | |
| 
 | |
|     this.inSyntheticDoc = context.inSyntheticDoc;
 | |
| 
 | |
|     // Everything after this isn't sent directly from ContextMenu
 | |
|     this.ownerDoc = this.target.ownerDocument;
 | |
| 
 | |
|     // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
 | |
|     // can be null if the menu was triggered by tests in which case use an empty array.
 | |
|     this.targetSelectors = gContextMenuContentData
 | |
|                            ? gContextMenuContentData.popupNodeSelectors
 | |
|                            : [];
 | |
| 
 | |
|     if (this.isRemote) {
 | |
|       this.browser = gContextMenuContentData.browser;
 | |
|       this.selectionInfo = gContextMenuContentData.selectionInfo;
 | |
|     } else {
 | |
|       this.browser = this.ownerDoc.defaultView.docShell.chromeEventHandler;
 | |
|       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
 | |
|     }
 | |
| 
 | |
|     this.textSelected      = this.selectionInfo.text;
 | |
|     this.isTextSelected    = this.textSelected.length != 0;
 | |
|     this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
 | |
|     this.inWebExtBrowser   = !!this.webExtBrowserType;
 | |
|     this.inTabBrowser      = this.browser.ownerGlobal.gBrowser ?
 | |
|       !!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
 | |
| 
 | |
|     if (context.shouldInitInlineSpellCheckerUINoChildren) {
 | |
|       if (this.isRemote) {
 | |
|         InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
 | |
|       } else {
 | |
|         InlineSpellCheckerUI.init(this.target.editor);
 | |
|         InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
 | |
|                                            document.popupRangeOffset);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (context.shouldInitInlineSpellCheckerUIWithChildren) {
 | |
|       if (this.isRemote) {
 | |
|         InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
 | |
|       } else {
 | |
|         var targetWin = this.ownerDoc.defaultView;
 | |
|         var {editingSession} = targetWin.docShell;
 | |
| 
 | |
|         InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
 | |
|         InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
 | |
|                                            document.popupRangeOffset);
 | |
|       }
 | |
| 
 | |
|       let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
 | |
|       this.showItem("spell-check-enabled", canSpell);
 | |
|       this.showItem("spell-separator", canSpell);
 | |
|     }
 | |
|   },  // setContext
 | |
| 
 | |
|   hiding: function CM_hiding() {
 | |
|     if (this.browser) {
 | |
|       this.browser.messageManager.sendAsyncMessage("ContextMenu:Hiding");
 | |
|     }
 | |
| 
 | |
|     gContextMenuContentData = null;
 | |
|     InlineSpellCheckerUI.clearSuggestionsFromMenu();
 | |
|     InlineSpellCheckerUI.clearDictionaryListFromMenu();
 | |
|     InlineSpellCheckerUI.uninit();
 | |
|     if (Cu.isModuleLoaded("resource://gre/modules/LoginManagerContextMenu.jsm")) {
 | |
|       LoginManagerContextMenu.clearLoginsFromMenu(document);
 | |
|     }
 | |
| 
 | |
|     // This handler self-deletes, only run it if it is still there:
 | |
|     if (this._onPopupHiding) {
 | |
|       this._onPopupHiding();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   initItems: function CM_initItems() {
 | |
|     this.initPageMenuSeparator();
 | |
|     this.initOpenItems();
 | |
|     this.initNavigationItems();
 | |
|     this.initViewItems();
 | |
|     this.initMiscItems();
 | |
|     this.initSpellingItems();
 | |
|     this.initSaveItems();
 | |
|     this.initClipboardItems();
 | |
|     this.initMediaPlayerItems();
 | |
|     this.initLeaveDOMFullScreenItems();
 | |
|     this.initClickToPlayItems();
 | |
|     this.initPasswordManagerItems();
 | |
|     this.initSyncItems();
 | |
|   },
 | |
| 
 | |
|   initPageMenuSeparator: function CM_initPageMenuSeparator() {
 | |
|     this.showItem("page-menu-separator", this.hasPageMenu);
 | |
|   },
 | |
| 
 | |
|   initOpenItems: function CM_initOpenItems() {
 | |
|     var isMailtoInternal = false;
 | |
|     if (this.onMailtoLink) {
 | |
|       var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
 | |
|                           getService(Ci.nsIExternalProtocolService).
 | |
|                           getProtocolHandlerInfo("mailto");
 | |
|       isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
 | |
|                           mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
 | |
|                           (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
 | |
|     }
 | |
| 
 | |
|     if (this.isTextSelected && !this.onLink &&
 | |
|         this.selectionInfo && this.selectionInfo.linkURL) {
 | |
|       this.linkURL = this.selectionInfo.linkURL;
 | |
|       try {
 | |
|         this.linkURI = makeURI(this.linkURL);
 | |
|       } catch (ex) {}
 | |
| 
 | |
|       this.linkTextStr = this.selectionInfo.linkText;
 | |
|       this.onPlainTextLink = true;
 | |
|     }
 | |
| 
 | |
|     var inContainer = false;
 | |
|     if (gContextMenuContentData.userContextId) {
 | |
|       inContainer = true;
 | |
|       var item = document.getElementById("context-openlinkincontainertab");
 | |
| 
 | |
|       item.setAttribute("data-usercontextid", gContextMenuContentData.userContextId);
 | |
| 
 | |
|       var label =
 | |
|         ContextualIdentityService.getUserContextLabel(gContextMenuContentData.userContextId);
 | |
|       item.setAttribute("label",
 | |
|          gBrowserBundle.formatStringFromName("userContextOpenLink.label",
 | |
|                                              [label], 1));
 | |
|     }
 | |
| 
 | |
|     var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
 | |
|     var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
 | |
|     var showContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
 | |
|     this.showItem("context-openlink", shouldShow && !isWindowPrivate);
 | |
|     this.showItem("context-openlinkprivate", shouldShow && PrivateBrowsingUtils.enabled);
 | |
|     this.showItem("context-openlinkintab", shouldShow && !inContainer);
 | |
|     this.showItem("context-openlinkincontainertab", shouldShow && inContainer);
 | |
|     this.showItem("context-openlinkinusercontext-menu", shouldShow && !isWindowPrivate && showContainers);
 | |
|     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
 | |
|     this.showItem("context-sep-open", shouldShow);
 | |
|   },
 | |
| 
 | |
|   initNavigationItems: function CM_initNavigationItems() {
 | |
|     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
 | |
|                        this.onCanvas || this.onVideo || this.onAudio ||
 | |
|                        this.onTextInput) && this.inTabBrowser;
 | |
|     this.showItem("context-navigation", shouldShow);
 | |
|     this.showItem("context-sep-navigation", shouldShow);
 | |
| 
 | |
|     let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
 | |
| 
 | |
|     let stopReloadItem = "";
 | |
|     if (shouldShow || !this.inTabBrowser) {
 | |
|       stopReloadItem = (stopped || !this.inTabBrowser) ? "reload" : "stop";
 | |
|     }
 | |
| 
 | |
|     this.showItem("context-reload", stopReloadItem == "reload");
 | |
|     this.showItem("context-stop", stopReloadItem == "stop");
 | |
|   },
 | |
| 
 | |
|   initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
 | |
|     // only show the option if the user is in DOM fullscreen
 | |
|     var shouldShow = this.target.ownerDocument.fullscreen;
 | |
|     this.showItem("context-leave-dom-fullscreen", shouldShow);
 | |
| 
 | |
|     // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
 | |
|     if (shouldShow)
 | |
|         this.showItem("context-media-sep-commands", true);
 | |
|   },
 | |
| 
 | |
|   initSaveItems: function CM_initSaveItems() {
 | |
|     var shouldShow = !(this.onTextInput || this.onLink ||
 | |
|                        this.isContentSelected || this.onImage ||
 | |
|                        this.onCanvas || this.onVideo || this.onAudio);
 | |
|     this.showItem("context-savepage", shouldShow);
 | |
| 
 | |
|     // Save link depends on whether we're in a link, or selected text matches valid URL pattern.
 | |
|     this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
 | |
| 
 | |
|     // Save image depends on having loaded its content, video and audio don't.
 | |
|     this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
 | |
|     this.showItem("context-savevideo", this.onVideo);
 | |
|     this.showItem("context-saveaudio", this.onAudio);
 | |
|     this.showItem("context-video-saveimage", this.onVideo);
 | |
|     this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
 | |
|     this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
 | |
|     // Send media URL (but not for canvas, since it's a big data: URL)
 | |
|     this.showItem("context-sendimage", this.onImage);
 | |
|     this.showItem("context-sendvideo", this.onVideo);
 | |
|     this.showItem("context-sendaudio", this.onAudio);
 | |
|     let mediaIsBlob = this.mediaURL.startsWith("blob:");
 | |
|     this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL || mediaIsBlob);
 | |
|     this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL || mediaIsBlob);
 | |
|   },
 | |
| 
 | |
|   initViewItems: function CM_initViewItems() {
 | |
|     // View source is always OK, unless in directory listing.
 | |
|     this.showItem("context-viewpartialsource-selection",
 | |
|                   this.isContentSelected);
 | |
| 
 | |
|     var shouldShow = !(this.isContentSelected ||
 | |
|                        this.onImage || this.onCanvas ||
 | |
|                        this.onVideo || this.onAudio ||
 | |
|                        this.onLink || this.onTextInput);
 | |
| 
 | |
|     var showInspect = this.inTabBrowser &&
 | |
|                       Services.prefs.getBoolPref("devtools.inspector.enabled", true) &&
 | |
|                       !Services.prefs.getBoolPref("devtools.policy.disabled", false);
 | |
| 
 | |
|     var showInspectA11Y = showInspect &&
 | |
|                           // Only when accessibility service started.
 | |
|                           Services.appinfo.accessibilityEnabled &&
 | |
|                           this.inTabBrowser &&
 | |
|                           Services.prefs.getBoolPref("devtools.enabled", true) &&
 | |
|                           Services.prefs.getBoolPref("devtools.accessibility.enabled", true) &&
 | |
|                           !Services.prefs.getBoolPref("devtools.policy.disabled", false);
 | |
| 
 | |
|     this.showItem("context-viewsource", shouldShow);
 | |
|     this.showItem("context-viewinfo", shouldShow);
 | |
|     // The page info is broken for WebExtension popups, as the browser is
 | |
|     // destroyed when the popup is closed.
 | |
|     this.setItemAttr("context-viewinfo", "disabled", this.webExtBrowserType === "popup");
 | |
|     this.showItem("inspect-separator", showInspect);
 | |
|     this.showItem("context-inspect", showInspect);
 | |
| 
 | |
|     this.showItem("context-inspect-a11y", showInspectA11Y);
 | |
| 
 | |
|     this.showItem("context-sep-viewsource", shouldShow);
 | |
| 
 | |
|     // Set as Desktop background depends on whether an image was clicked on,
 | |
|     // and only works if we have a shell service.
 | |
|     var haveSetDesktopBackground = false;
 | |
| 
 | |
|     if (AppConstants.HAVE_SHELL_SERVICE &&
 | |
|         Services.policies.isAllowed("setDesktopBackground")) {
 | |
|       // Only enable Set as Desktop Background if we can get the shell service.
 | |
|       var shell = getShellService();
 | |
|       if (shell)
 | |
|         haveSetDesktopBackground = shell.canSetDesktopBackground;
 | |
|     }
 | |
| 
 | |
|     this.showItem("context-setDesktopBackground",
 | |
|                   haveSetDesktopBackground && this.onLoadedImage);
 | |
| 
 | |
|     if (haveSetDesktopBackground && this.onLoadedImage) {
 | |
|       document.getElementById("context-setDesktopBackground")
 | |
|               .disabled = gContextMenuContentData.disableSetDesktopBackground;
 | |
|     }
 | |
| 
 | |
|     // Reload image depends on an image that's not fully loaded
 | |
|     this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
 | |
| 
 | |
|     // View image depends on having an image that's not standalone
 | |
|     // (or is in a frame), or a canvas.
 | |
|     this.showItem("context-viewimage", (this.onImage &&
 | |
|                   (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
 | |
| 
 | |
|     // View video depends on not having a standalone video.
 | |
|     this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
 | |
|     this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
 | |
| 
 | |
|     // View background image depends on whether there is one, but don't make
 | |
|     // background images of a stand-alone media document available.
 | |
|     this.showItem("context-viewbgimage", shouldShow &&
 | |
|                                          !this.hasMultipleBGImages &&
 | |
|                                          !this.inSyntheticDoc);
 | |
|     this.showItem("context-sep-viewbgimage", shouldShow &&
 | |
|                                              !this.hasMultipleBGImages &&
 | |
|                                              !this.inSyntheticDoc);
 | |
|     document.getElementById("context-viewbgimage")
 | |
|             .disabled = !this.hasBGImage;
 | |
| 
 | |
|     this.showItem("context-viewimageinfo", this.onImage);
 | |
|     // The image info popup is broken for WebExtension popups, since the browser
 | |
|     // is destroyed when the popup is closed.
 | |
|     this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
 | |
|     this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
 | |
|   },
 | |
| 
 | |
|   initMiscItems: function CM_initMiscItems() {
 | |
|     // Use "Bookmark This Link" if on a link.
 | |
|     let bookmarkPage = document.getElementById("context-bookmarkpage");
 | |
|     this.showItem(bookmarkPage,
 | |
|                   !(this.isContentSelected || this.onTextInput || this.onLink ||
 | |
|                     this.onImage || this.onVideo || this.onAudio ||
 | |
|                     this.onCanvas || this.inWebExtBrowser));
 | |
| 
 | |
|     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
 | |
|                                            !this.onMozExtLink) || this.onPlainTextLink);
 | |
|     this.showItem("context-keywordfield",
 | |
|                   this.onTextInput && this.onKeywordField);
 | |
|     this.showItem("frame", this.inFrame);
 | |
| 
 | |
|     let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
 | |
|     this.showItem("context-searchselect", showSearchSelect);
 | |
|     if (showSearchSelect) {
 | |
|       this.formatSearchContextItem();
 | |
|     }
 | |
| 
 | |
|     // srcdoc cannot be opened separately due to concerns about web
 | |
|     // content with about:srcdoc in location bar masquerading as trusted
 | |
|     // chrome/addon content.
 | |
|     // No need to also test for this.inFrame as this is checked in the parent
 | |
|     // submenu.
 | |
|     this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
 | |
|     this.showItem("context-openframeintab", !this.inSrcdocFrame);
 | |
|     this.showItem("context-openframe", !this.inSrcdocFrame);
 | |
|     this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
 | |
|     this.showItem("open-frame-sep", !this.inSrcdocFrame);
 | |
| 
 | |
|     this.showItem("frame-sep", this.inFrame && this.isTextSelected);
 | |
| 
 | |
|     // Hide menu entries for images, show otherwise
 | |
|     if (this.inFrame) {
 | |
|       if (BrowserUtils.mimeTypeIsTextBased(this.target.ownerDocument.contentType))
 | |
|         this.viewFrameSourceElement.removeAttribute("hidden");
 | |
|       else
 | |
|         this.viewFrameSourceElement.setAttribute("hidden", "true");
 | |
|     }
 | |
| 
 | |
|     // BiDi UI
 | |
|     this.showItem("context-sep-bidi", !this.onNumeric && top.gBidiUI);
 | |
|     this.showItem("context-bidi-text-direction-toggle",
 | |
|                   this.onTextInput && !this.onNumeric && top.gBidiUI);
 | |
|     this.showItem("context-bidi-page-direction-toggle",
 | |
|                   !this.onTextInput && top.gBidiUI);
 | |
|   },
 | |
| 
 | |
|   initSpellingItems() {
 | |
|     var canSpell = InlineSpellCheckerUI.canSpellCheck &&
 | |
|                    !InlineSpellCheckerUI.initialSpellCheckPending &&
 | |
|                    this.canSpellCheck;
 | |
|     let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
 | |
|     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
 | |
|     var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
 | |
|     this.showItem("spell-check-enabled", canSpell);
 | |
|     this.showItem("spell-separator", canSpell);
 | |
|     document.getElementById("spell-check-enabled")
 | |
|             .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
 | |
| 
 | |
|     this.showItem("spell-add-to-dictionary", onMisspelling);
 | |
|     this.showItem("spell-undo-add-to-dictionary", showUndo);
 | |
| 
 | |
|     // suggestion list
 | |
|     this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
 | |
|     if (onMisspelling) {
 | |
|       var suggestionsSeparator =
 | |
|         document.getElementById("spell-add-to-dictionary");
 | |
|       var numsug =
 | |
|         InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
 | |
|                                                   suggestionsSeparator, 5);
 | |
|       this.showItem("spell-no-suggestions", numsug == 0);
 | |
|     } else {
 | |
|       this.showItem("spell-no-suggestions", false);
 | |
|     }
 | |
| 
 | |
|     // dictionary list
 | |
|     this.showItem("spell-dictionaries", showDictionaries);
 | |
|     if (canSpell) {
 | |
|       var dictMenu = document.getElementById("spell-dictionaries-menu");
 | |
|       var dictSep = document.getElementById("spell-language-separator");
 | |
|       let count = InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
 | |
|       this.showItem(dictSep, count > 0);
 | |
|       this.showItem("spell-add-dictionaries-main", false);
 | |
|     } else if (this.onSpellcheckable) {
 | |
|       // when there is no spellchecker but we might be able to spellcheck
 | |
|       // add the add to dictionaries item. This will ensure that people
 | |
|       // with no dictionaries will be able to download them
 | |
|       this.showItem("spell-language-separator", showDictionaries);
 | |
|       this.showItem("spell-add-dictionaries-main", showDictionaries);
 | |
|     } else {
 | |
|       this.showItem("spell-add-dictionaries-main", false);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   initClipboardItems() {
 | |
|     // Copy depends on whether there is selected text.
 | |
|     // Enabling this context menu item is now done through the global
 | |
|     // command updating system
 | |
|     // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
 | |
|     goUpdateGlobalEditMenuItems();
 | |
| 
 | |
|     this.showItem("context-undo", this.onTextInput);
 | |
|     this.showItem("context-sep-undo", this.onTextInput);
 | |
|     this.showItem("context-cut", this.onTextInput);
 | |
|     this.showItem("context-copy",
 | |
|                   this.isContentSelected || this.onTextInput);
 | |
|     this.showItem("context-paste", this.onTextInput);
 | |
|     this.showItem("context-delete", this.onTextInput);
 | |
|     this.showItem("context-sep-paste", this.onTextInput);
 | |
|     this.showItem("context-selectall", !(this.onLink || this.onImage ||
 | |
|                                          this.onVideo || this.onAudio ||
 | |
|                                          this.inSyntheticDoc) ||
 | |
|                                        this.isDesignMode);
 | |
|     this.showItem("context-sep-selectall", this.isContentSelected );
 | |
| 
 | |
|     // XXX dr
 | |
|     // ------
 | |
|     // nsDocumentViewer.cpp has code to determine whether we're
 | |
|     // on a link or an image. we really ought to be using that...
 | |
| 
 | |
|     // Copy email link depends on whether we're on an email link.
 | |
|     this.showItem("context-copyemail", this.onMailtoLink);
 | |
| 
 | |
|     // Copy link location depends on whether we're on a non-mailto link.
 | |
|     this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
 | |
|     this.showItem("context-sep-copylink", this.onLink &&
 | |
|                   (this.onImage || this.onVideo || this.onAudio));
 | |
| 
 | |
|     // Copy image contents depends on whether we're on an image.
 | |
|     // Note: the element doesn't exist on all platforms, but showItem() takes
 | |
|     // care of that by itself.
 | |
|     this.showItem("context-copyimage-contents", this.onImage);
 | |
| 
 | |
|     // Copy image location depends on whether we're on an image.
 | |
|     this.showItem("context-copyimage", this.onImage);
 | |
|     this.showItem("context-copyvideourl", this.onVideo);
 | |
|     this.showItem("context-copyaudiourl", this.onAudio);
 | |
|     this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
 | |
|     this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
 | |
|     this.showItem("context-sep-copyimage", this.onImage ||
 | |
|                   this.onVideo || this.onAudio);
 | |
|   },
 | |
| 
 | |
|   initMediaPlayerItems() {
 | |
|     var onMedia = (this.onVideo || this.onAudio);
 | |
|     // Several mutually exclusive items... play/pause, mute/unmute, show/hide
 | |
|     this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended));
 | |
|     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
 | |
|     this.showItem("context-media-mute", onMedia && !this.target.muted);
 | |
|     this.showItem("context-media-unmute", onMedia && this.target.muted);
 | |
|     this.showItem("context-media-playbackrate", onMedia && this.target.duration != Number.POSITIVE_INFINITY);
 | |
|     this.showItem("context-media-loop", onMedia);
 | |
|     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
 | |
|     this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
 | |
|     this.showItem("context-video-fullscreen", this.onVideo && !this.target.ownerDocument.fullscreen);
 | |
|     this.showItem("context-media-eme-learnmore", this.onDRMMedia);
 | |
|     this.showItem("context-media-eme-separator", this.onDRMMedia);
 | |
| 
 | |
|     // Disable them when there isn't a valid media source loaded.
 | |
|     if (onMedia) {
 | |
|       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
 | |
|       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
 | |
|       this.setItemAttr("context-media-playbackrate-125x", "checked", this.target.playbackRate == 1.25);
 | |
|       this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
 | |
|       this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
 | |
|       this.setItemAttr("context-media-loop", "checked", this.target.loop);
 | |
|       var hasError = this.target.error != null ||
 | |
|                      this.target.networkState == this.target.NETWORK_NO_SOURCE;
 | |
|       this.setItemAttr("context-media-play", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-pause", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-mute", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-unmute", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate-125x", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-showcontrols", "disabled", hasError);
 | |
|       this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
 | |
|       if (this.onVideo) {
 | |
|         let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
 | |
|         this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
 | |
|         this.setItemAttr("context-video-fullscreen", "disabled", hasError);
 | |
|       }
 | |
|     }
 | |
|     this.showItem("context-media-sep-commands", onMedia);
 | |
|   },
 | |
| 
 | |
|   initClickToPlayItems() {
 | |
|     this.showItem("context-ctp-play", this.onCTPPlugin);
 | |
|     this.showItem("context-ctp-hide", this.onCTPPlugin);
 | |
|     this.showItem("context-sep-ctp", this.onCTPPlugin);
 | |
|   },
 | |
| 
 | |
|   initPasswordManagerItems() {
 | |
|     let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo;
 | |
| 
 | |
|     // If we could not find a password field we
 | |
|     // don't want to show the form fill option.
 | |
|     let showFill = loginFillInfo && loginFillInfo.passwordField.found;
 | |
| 
 | |
|     // Disable the fill option if the user has set a master password
 | |
|     // or if the password field or target field are disabled.
 | |
|     let disableFill = !loginFillInfo ||
 | |
|                       !Services.logins ||
 | |
|                       !Services.logins.isLoggedIn ||
 | |
|                       loginFillInfo.passwordField.disabled ||
 | |
|                       (!this.onPassword && loginFillInfo.usernameField.disabled);
 | |
| 
 | |
|     this.showItem("fill-login-separator", showFill);
 | |
|     this.showItem("fill-login", showFill);
 | |
|     this.setItemAttr("fill-login", "disabled", disableFill);
 | |
| 
 | |
|     // Set the correct label for the fill menu
 | |
|     let fillMenu = document.getElementById("fill-login");
 | |
|     if (this.onPassword) {
 | |
|       fillMenu.setAttribute("label", fillMenu.getAttribute("label-password"));
 | |
|       fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-password"));
 | |
|     } else {
 | |
|       fillMenu.setAttribute("label", fillMenu.getAttribute("label-login"));
 | |
|       fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login"));
 | |
|     }
 | |
| 
 | |
|     if (!showFill || disableFill) {
 | |
|       return;
 | |
|     }
 | |
|     let documentURI = gContextMenuContentData.documentURIObject;
 | |
|     let fragment = LoginManagerContextMenu.addLoginsToMenu(this.targetAsCPOW, this.browser, documentURI);
 | |
| 
 | |
|     this.showItem("fill-login-no-logins", !fragment);
 | |
| 
 | |
|     if (!fragment) {
 | |
|       return;
 | |
|     }
 | |
|     let popup = document.getElementById("fill-login-popup");
 | |
|     let insertBeforeElement = document.getElementById("fill-login-no-logins");
 | |
|     popup.insertBefore(fragment, insertBeforeElement);
 | |
|   },
 | |
| 
 | |
|   initSyncItems() {
 | |
|     gSync.updateContentContextMenu(this);
 | |
|   },
 | |
| 
 | |
|   openPasswordManager() {
 | |
|     LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
 | |
|   },
 | |
| 
 | |
|   inspectNode() {
 | |
|     return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
 | |
|   },
 | |
| 
 | |
|   inspectA11Y() {
 | |
|     return DevToolsShim.inspectA11Y(gBrowser.selectedTab, this.targetSelectors);
 | |
|   },
 | |
| 
 | |
|   _openLinkInParameters(extra) {
 | |
|     let params = { charset: gContextMenuContentData.charSet,
 | |
|                    originPrincipal: this.principal,
 | |
|                    triggeringPrincipal: this.principal,
 | |
|                    referrerURI: gContextMenuContentData.documentURIObject,
 | |
|                    referrerPolicy: gContextMenuContentData.referrerPolicy,
 | |
|                    frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
 | |
|                    noReferrer: this.linkHasNoReferrer || this.onPlainTextLink };
 | |
|     for (let p in extra) {
 | |
|       params[p] = extra[p];
 | |
|     }
 | |
| 
 | |
|     if (!this.isRemote) {
 | |
|       // Propagate the frameOuterWindowID value saved when
 | |
|       // the context menu has been opened.
 | |
|       params.frameOuterWindowID = this.frameOuterWindowID;
 | |
|     }
 | |
| 
 | |
|     // If we want to change userContextId, we must be sure that we don't
 | |
|     // propagate the referrer.
 | |
|     if ("userContextId" in params &&
 | |
|         params.userContextId != gContextMenuContentData.userContextId) {
 | |
|       params.noReferrer = true;
 | |
|     }
 | |
| 
 | |
|     return params;
 | |
|   },
 | |
| 
 | |
|   // Open linked-to URL in a new window.
 | |
|   openLink() {
 | |
|     openLinkIn(this.linkURL, "window", this._openLinkInParameters());
 | |
|   },
 | |
| 
 | |
|   // Open linked-to URL in a new private window.
 | |
|   openLinkInPrivateWindow() {
 | |
|     openLinkIn(this.linkURL, "window",
 | |
|                this._openLinkInParameters({ private: true }));
 | |
|   },
 | |
| 
 | |
|   // Open linked-to URL in a new tab.
 | |
|   openLinkInTab(event) {
 | |
|     let referrerURI = gContextMenuContentData.documentURIObject;
 | |
| 
 | |
|     // if its parent allows mixed content and the referring URI passes
 | |
|     // a same origin check with the target URI, we can preserve the users
 | |
|     // decision of disabling MCB on a page for it's child tabs.
 | |
|     let persistAllowMixedContentInChildTab = false;
 | |
| 
 | |
|     if (gContextMenuContentData.parentAllowsMixedContent) {
 | |
|       const sm = Services.scriptSecurityManager;
 | |
|       try {
 | |
|         let targetURI = this.linkURI;
 | |
|         let isPrivateWin = this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
 | |
|         sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
 | |
|         persistAllowMixedContentInChildTab = true;
 | |
|       } catch (e) { }
 | |
|     }
 | |
| 
 | |
|     let params = {
 | |
|       allowMixedContent: persistAllowMixedContentInChildTab,
 | |
|       userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
 | |
|     };
 | |
| 
 | |
|     openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
 | |
|   },
 | |
| 
 | |
|   // open URL in current tab
 | |
|   openLinkInCurrent() {
 | |
|     openLinkIn(this.linkURL, "current", this._openLinkInParameters());
 | |
|   },
 | |
| 
 | |
|   // Open frame in a new tab.
 | |
|   openFrameInTab() {
 | |
|     let referrer = gContextMenuContentData.referrer;
 | |
|     openLinkIn(gContextMenuContentData.docLocation, "tab",
 | |
|                { charset: gContextMenuContentData.charSet,
 | |
|                  triggeringPrincipal: this.browser.contentPrincipal,
 | |
|                  referrerURI: referrer ? makeURI(referrer) : null });
 | |
|   },
 | |
| 
 | |
|   // Reload clicked-in frame.
 | |
|   reloadFrame(aEvent) {
 | |
|     let forceReload = aEvent.shiftKey;
 | |
|     this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
 | |
|                                                  null, { target: this.target, forceReload });
 | |
|   },
 | |
| 
 | |
|   // Open clicked-in frame in its own window.
 | |
|   openFrame() {
 | |
|     let referrer = gContextMenuContentData.referrer;
 | |
|     openLinkIn(gContextMenuContentData.docLocation, "window",
 | |
|                { charset: gContextMenuContentData.charSet,
 | |
|                  triggeringPrincipal: this.browser.contentPrincipal,
 | |
|                  referrerURI: referrer ? makeURI(referrer) : null });
 | |
|   },
 | |
| 
 | |
|   // Open clicked-in frame in the same window.
 | |
|   showOnlyThisFrame() {
 | |
|     urlSecurityCheck(gContextMenuContentData.docLocation,
 | |
|                      this.browser.contentPrincipal,
 | |
|                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 | |
|     let referrer = gContextMenuContentData.referrer;
 | |
|     openWebLinkIn(gContextMenuContentData.docLocation, "current", {
 | |
|       referrerURI: referrer ? makeURI(referrer) : null,
 | |
|       triggeringPrincipal: this.browser.contentPrincipal,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   reload(event) {
 | |
|     BrowserReloadOrDuplicate(event);
 | |
|   },
 | |
| 
 | |
|   // View Partial Source
 | |
|   viewPartialSource() {
 | |
|     let {browser} = this;
 | |
|     let openSelectionFn = function() {
 | |
|       let tabBrowser = gBrowser;
 | |
|       // In the case of popups, we need to find a non-popup browser window.
 | |
|       // We might also not have a tabBrowser reference (if this isn't in a
 | |
|       // a tabbrowser scope) or might have a fake/stub tabbrowser reference
 | |
|       // (in the sidebar). Deal with those cases:
 | |
|       if (!tabBrowser || !tabBrowser.loadOneTab || !window.toolbar.visible) {
 | |
|         // This returns only non-popup browser windows by default.
 | |
|         let browserWindow = BrowserWindowTracker.getTopWindow();
 | |
|         tabBrowser = browserWindow.gBrowser;
 | |
|       }
 | |
|       let relatedToCurrent = gBrowser && gBrowser.selectedBrowser == browser;
 | |
|       let tab = tabBrowser.loadOneTab("about:blank", {
 | |
|         relatedToCurrent,
 | |
|         inBackground: false,
 | |
|         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
 | |
|       });
 | |
|       return tabBrowser.getBrowserForTab(tab);
 | |
|     };
 | |
| 
 | |
|     top.gViewSourceUtils.viewPartialSourceInBrowser(browser, openSelectionFn);
 | |
|   },
 | |
| 
 | |
|   // Open new "view source" window with the frame's URL.
 | |
|   viewFrameSource() {
 | |
|     BrowserViewSourceOfDocument({
 | |
|       browser: this.browser,
 | |
|       URL: gContextMenuContentData.docLocation,
 | |
|       outerWindowID: this.frameOuterWindowID,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   viewInfo() {
 | |
|     BrowserPageInfo(gContextMenuContentData.docLocation, null, null, null, this.browser);
 | |
|   },
 | |
| 
 | |
|   viewImageInfo() {
 | |
|     BrowserPageInfo(gContextMenuContentData.docLocation, "mediaTab",
 | |
|                     this.imageInfo, null, this.browser);
 | |
|   },
 | |
| 
 | |
|   viewImageDesc(e) {
 | |
|     urlSecurityCheck(this.imageDescURL,
 | |
|                      this.principal,
 | |
|                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 | |
|     openUILink(this.imageDescURL, e, { referrerURI: gContextMenuContentData.documentURIObject,
 | |
|                                        triggeringPrincipal: this.principal,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   viewFrameInfo() {
 | |
|     BrowserPageInfo(gContextMenuContentData.docLocation, null, null,
 | |
|                     this.frameOuterWindowID, this.browser);
 | |
|   },
 | |
| 
 | |
|   reloadImage() {
 | |
|     urlSecurityCheck(this.mediaURL,
 | |
|                      this.principal,
 | |
|                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 | |
| 
 | |
|     this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
 | |
|                                                  null, { target: this.target });
 | |
|   },
 | |
| 
 | |
|   _canvasToBlobURL(target) {
 | |
|     let mm = this.browser.messageManager;
 | |
|     return new Promise(function(resolve) {
 | |
|       mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL", {}, { target });
 | |
| 
 | |
|       let onMessage = (message) => {
 | |
|         mm.removeMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
 | |
|         resolve(message.data.blobURL);
 | |
|       };
 | |
|       mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   // Change current window to the URL of the image, video, or audio.
 | |
|   viewMedia(e) {
 | |
|     let referrerURI = gContextMenuContentData.documentURIObject;
 | |
|     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 | |
|     if (this.onCanvas) {
 | |
|       this._canvasToBlobURL(this.target).then(function(blobURL) {
 | |
|         openUILink(blobURL, e, { referrerURI,
 | |
|                                  triggeringPrincipal: systemPrincipal});
 | |
|       }, Cu.reportError);
 | |
|     } else {
 | |
|       urlSecurityCheck(this.mediaURL,
 | |
|                        this.principal,
 | |
|                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 | |
|       openUILink(this.mediaURL, e, { referrerURI,
 | |
|                                      forceAllowDataURI: true,
 | |
|                                      triggeringPrincipal: this.principal,
 | |
|       });
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   saveVideoFrameAsImage() {
 | |
|     let mm = this.browser.messageManager;
 | |
|     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 | |
| 
 | |
|     let name = "";
 | |
|     if (this.mediaURL) {
 | |
|       try {
 | |
|         let uri = makeURI(this.mediaURL);
 | |
|         let url = uri.QueryInterface(Ci.nsIURL);
 | |
|         if (url.fileBaseName)
 | |
|           name = decodeURI(url.fileBaseName) + ".jpg";
 | |
|       } catch (e) { }
 | |
|     }
 | |
|     if (!name)
 | |
|       name = "snapshot.jpg";
 | |
| 
 | |
|     mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, {
 | |
|       target: this.target,
 | |
|     });
 | |
| 
 | |
|     // Cache this because we fetch the data async
 | |
|     let {documentURIObject} = gContextMenuContentData;
 | |
| 
 | |
|     let onMessage = (message) => {
 | |
|       mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
 | |
|       // FIXME can we switch this to a blob URL?
 | |
|       let dataURL = message.data.dataURL;
 | |
|       saveImageURL(dataURL, name, "SaveImageTitle",
 | |
|                    true, // bypass cache
 | |
|                    false, // don't skip prompt for where to save
 | |
|                    documentURIObject, // referrer
 | |
|                    null, // document
 | |
|                    null, // content type
 | |
|                    null, // content disposition
 | |
|                    isPrivate,
 | |
|                    this.principal);
 | |
|     };
 | |
|     mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
 | |
|   },
 | |
| 
 | |
|   leaveDOMFullScreen() {
 | |
|     document.exitFullscreen();
 | |
|   },
 | |
| 
 | |
|   // Change current window to the URL of the background image.
 | |
|   viewBGImage(e) {
 | |
|     urlSecurityCheck(this.bgImageURL,
 | |
|                      this.principal,
 | |
|                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 | |
| 
 | |
|     openUILink(this.bgImageURL, e, { referrerURI: gContextMenuContentData.documentURIObject,
 | |
|                                      triggeringPrincipal: this.principal,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   setDesktopBackground() {
 | |
|     let mm = this.browser.messageManager;
 | |
| 
 | |
|     mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
 | |
|                         { target: this.target });
 | |
| 
 | |
|     let onMessage = (message) => {
 | |
|       mm.removeMessageListener("ContextMenu:SetAsDesktopBackground:Result",
 | |
|                                onMessage);
 | |
| 
 | |
|       if (message.data.disable ||
 | |
|           !Services.policies.isAllowed("setDesktopBackground")) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
 | |
|       image.src = message.data.dataUrl;
 | |
|       let imageName = message.data.imageName;
 | |
| 
 | |
|       // Confirm since it's annoying if you hit this accidentally.
 | |
|       const kDesktopBackgroundURL =
 | |
|                     "chrome://browser/content/setDesktopBackground.xul";
 | |
| 
 | |
|       if (AppConstants.platform == "macosx") {
 | |
|         // On Mac, the Set Desktop Background window is not modal.
 | |
|         // Don't open more than one Set Desktop Background window.
 | |
|         let dbWin = Services.wm.getMostRecentWindow("Shell:SetDesktopBackground");
 | |
|         if (dbWin) {
 | |
|           dbWin.gSetBackground.init(image, imageName);
 | |
|           dbWin.focus();
 | |
|         } else {
 | |
|           openDialog(kDesktopBackgroundURL, "",
 | |
|                      "centerscreen,chrome,dialog=no,dependent,resizable=no",
 | |
|                      image, imageName);
 | |
|         }
 | |
|       } else {
 | |
|         // On non-Mac platforms, the Set Wallpaper dialog is modal.
 | |
|         openDialog(kDesktopBackgroundURL, "",
 | |
|                    "centerscreen,chrome,dialog,modal,dependent",
 | |
|                    image, imageName);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
 | |
|   },
 | |
| 
 | |
|   // Save URL of clicked-on frame.
 | |
|   saveFrame() {
 | |
|     saveBrowser(this.browser, false, this.frameOuterWindowID);
 | |
|   },
 | |
| 
 | |
|   // Helper function to wait for appropriate MIME-type headers and
 | |
|   // then prompt the user with a file picker
 | |
|   saveHelper(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
 | |
|              windowID, linkDownload, isContentWindowPrivate) {
 | |
|     // canonical def in nsURILoader.h
 | |
|     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
 | |
| 
 | |
|     // an object to proxy the data through to
 | |
|     // nsIExternalHelperAppService.doContent, which will wait for the
 | |
|     // appropriate MIME-type headers and then prompt the user with a
 | |
|     // file picker
 | |
|     function saveAsListener() {}
 | |
|     saveAsListener.prototype = {
 | |
|       extListener: null,
 | |
| 
 | |
|       onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
 | |
| 
 | |
|         // if the timer fired, the error status will have been caused by that,
 | |
|         // and we'll be restarting in onStopRequest, so no reason to notify
 | |
|         // the user
 | |
|         if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
 | |
|           return;
 | |
| 
 | |
|         timer.cancel();
 | |
| 
 | |
|         // some other error occured; notify the user...
 | |
|         if (!Components.isSuccessCode(aRequest.status)) {
 | |
|           try {
 | |
|             const bundle = Services.strings.createBundle(
 | |
|                     "chrome://mozapps/locale/downloads/downloads.properties");
 | |
| 
 | |
|             const title = bundle.GetStringFromName("downloadErrorAlertTitle");
 | |
|             const msg = bundle.GetStringFromName("downloadErrorGeneric");
 | |
| 
 | |
|             let window = Services.wm.getOuterWindowWithId(windowID);
 | |
|             Services.prompt.alert(window, title, msg);
 | |
|           } catch (ex) {}
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         let extHelperAppSvc =
 | |
|           Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
 | |
|           getService(Ci.nsIExternalHelperAppService);
 | |
|         let channel = aRequest.QueryInterface(Ci.nsIChannel);
 | |
|         this.extListener =
 | |
|           extHelperAppSvc.doContent(channel.contentType, aRequest,
 | |
|                                     null, true, window);
 | |
|         this.extListener.onStartRequest(aRequest, aContext);
 | |
|       },
 | |
| 
 | |
|       onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
 | |
|                                                        aStatusCode) {
 | |
|         if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
 | |
|           // do it the old fashioned way, which will pick the best filename
 | |
|           // it can without waiting.
 | |
|           saveURL(linkURL, linkText, dialogTitle, bypassCache, false, docURI,
 | |
|                   doc, isContentWindowPrivate);
 | |
|         }
 | |
|         if (this.extListener)
 | |
|           this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
 | |
|       },
 | |
| 
 | |
|       onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
 | |
|                                                            aInputStream,
 | |
|                                                            aOffset, aCount) {
 | |
|         this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
 | |
|                                          aOffset, aCount);
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     function callbacks() {}
 | |
|     callbacks.prototype = {
 | |
|       getInterface: function sLA_callbacks_getInterface(aIID) {
 | |
|         if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
 | |
|           // If the channel demands authentication prompt, we must cancel it
 | |
|           // because the save-as-timer would expire and cancel the channel
 | |
|           // before we get credentials from user.  Both authentication dialog
 | |
|           // and save as dialog would appear on the screen as we fall back to
 | |
|           // the old fashioned way after the timeout.
 | |
|           timer.cancel();
 | |
|           channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
 | |
|         }
 | |
|         throw Cr.NS_ERROR_NO_INTERFACE;
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     // if it we don't have the headers after a short time, the user
 | |
|     // won't have received any feedback from their click.  that's bad.  so
 | |
|     // we give up waiting for the filename.
 | |
|     function timerCallback() {}
 | |
|     timerCallback.prototype = {
 | |
|       notify: function sLA_timer_notify(aTimer) {
 | |
|         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     // setting up a new channel for 'right click - save link as ...'
 | |
|     var channel = NetUtil.newChannel({
 | |
|                     uri: makeURI(linkURL),
 | |
|                     loadingPrincipal: this.principal,
 | |
|                     contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
 | |
|                     securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
 | |
|                   });
 | |
| 
 | |
|     if (linkDownload)
 | |
|       channel.contentDispositionFilename = linkDownload;
 | |
|     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
 | |
|       let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 | |
|       channel.setPrivate(docIsPrivate);
 | |
|     }
 | |
|     channel.notificationCallbacks = new callbacks();
 | |
| 
 | |
|     let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
 | |
| 
 | |
|     if (bypassCache)
 | |
|       flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
 | |
| 
 | |
|     if (channel instanceof Ci.nsICachingChannel)
 | |
|       flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
 | |
| 
 | |
|     channel.loadFlags |= flags;
 | |
| 
 | |
|     if (channel instanceof Ci.nsIHttpChannel) {
 | |
|       channel.referrer = docURI;
 | |
|       if (channel instanceof Ci.nsIHttpChannelInternal)
 | |
|         channel.forceAllowThirdPartyCookie = true;
 | |
|     }
 | |
| 
 | |
|     // fallback to the old way if we don't see the headers quickly
 | |
|     var timeToWait =
 | |
|       Services.prefs.getIntPref("browser.download.saveLinkAsFilenameTimeout");
 | |
|     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 | |
|     timer.initWithCallback(new timerCallback(), timeToWait,
 | |
|                            timer.TYPE_ONE_SHOT);
 | |
| 
 | |
|     // kick off the channel with our proxy object as the listener
 | |
|     channel.asyncOpen2(new saveAsListener());
 | |
|   },
 | |
| 
 | |
|   // Save URL of clicked-on link.
 | |
|   saveLink() {
 | |
|     let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
 | |
|     this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
 | |
|                     gContextMenuContentData.documentURIObject,
 | |
|                     this.frameOuterWindowID,
 | |
|                     this.linkDownload,
 | |
|                     isContentWindowPrivate);
 | |
|   },
 | |
| 
 | |
|   // Backwards-compatibility wrapper
 | |
|   saveImage() {
 | |
|     if (this.onCanvas || this.onImage)
 | |
|         this.saveMedia();
 | |
|   },
 | |
| 
 | |
|   // Save URL of the clicked upon image, video, or audio.
 | |
|   saveMedia() {
 | |
|     let doc = this.ownerDoc;
 | |
|     let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
 | |
|     let referrerURI = gContextMenuContentData.documentURIObject;
 | |
|     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 | |
|     if (this.onCanvas) {
 | |
|       // Bypass cache, since it's a data: URL.
 | |
|       this._canvasToBlobURL(this.target).then(function(blobURL) {
 | |
|         saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
 | |
|                      true, false, referrerURI, null, null, null,
 | |
|                      isPrivate,
 | |
|                      document.nodePrincipal /* system, because blob: */);
 | |
|       }, Cu.reportError);
 | |
|     } else if (this.onImage) {
 | |
|       urlSecurityCheck(this.mediaURL, this.principal);
 | |
|       saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
 | |
|                    false, referrerURI, null, gContextMenuContentData.contentType,
 | |
|                    gContextMenuContentData.contentDisposition, isPrivate,
 | |
|                    this.principal);
 | |
|     } else if (this.onVideo || this.onAudio) {
 | |
|       var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
 | |
|       this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI,
 | |
|                       this.frameOuterWindowID, "", isContentWindowPrivate);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Backwards-compatibility wrapper
 | |
|   sendImage() {
 | |
|     if (this.onCanvas || this.onImage)
 | |
|         this.sendMedia();
 | |
|   },
 | |
| 
 | |
|   sendMedia() {
 | |
|     MailIntegration.sendMessage(this.mediaURL, "");
 | |
|   },
 | |
| 
 | |
|   playPlugin() {
 | |
|     gPluginHandler.contextMenuCommand(this.browser, this.target, "play");
 | |
|   },
 | |
| 
 | |
|   hidePlugin() {
 | |
|     gPluginHandler.contextMenuCommand(this.browser, this.target, "hide");
 | |
|   },
 | |
| 
 | |
|   // Generate email address and put it on clipboard.
 | |
|   copyEmail() {
 | |
|     // Copy the comma-separated list of email addresses only.
 | |
|     // There are other ways of embedding email addresses in a mailto:
 | |
|     // link, but such complex parsing is beyond us.
 | |
|     var url = this.linkURL;
 | |
|     var qmark = url.indexOf("?");
 | |
|     var addresses;
 | |
| 
 | |
|     // 7 == length of "mailto:"
 | |
|     addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
 | |
| 
 | |
|     // Let's try to unescape it using a character set
 | |
|     // in case the address is not ASCII.
 | |
|     try {
 | |
|       addresses = Services.textToSubURI.unEscapeURIForUI(gContextMenuContentData.charSet,
 | |
|                                                          addresses);
 | |
|     } catch (ex) {
 | |
|       // Do nothing.
 | |
|     }
 | |
| 
 | |
|     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
 | |
|                     getService(Ci.nsIClipboardHelper);
 | |
|     clipboard.copyString(addresses);
 | |
|   },
 | |
| 
 | |
|   copyLink() {
 | |
|     // If we're in a view source tab, remove the view-source: prefix
 | |
|     let linkURL = this.linkURL.replace(/^view-source:/, "");
 | |
|     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
 | |
|                     getService(Ci.nsIClipboardHelper);
 | |
|     clipboard.copyString(linkURL);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Utilities
 | |
|    */
 | |
| 
 | |
|   /**
 | |
|    * Show/hide one item (specified via name or the item element itself).
 | |
|    * If the element is not found, then this function finishes silently.
 | |
|    *
 | |
|    * @param {Element|String} aItemOrId The item element or the name of the element
 | |
|    *                                   to show.
 | |
|    * @param {Boolean} aShow Set to true to show the item, false to hide it.
 | |
|    */
 | |
|   showItem(aItemOrId, aShow) {
 | |
|     var item = aItemOrId.constructor == String ?
 | |
|       document.getElementById(aItemOrId) : aItemOrId;
 | |
|     if (item)
 | |
|       item.hidden = !aShow;
 | |
|   },
 | |
| 
 | |
|   // Set given attribute of specified context-menu item.  If the
 | |
|   // value is null, then it removes the attribute (which works
 | |
|   // nicely for the disabled attribute).
 | |
|   setItemAttr(aID, aAttr, aVal ) {
 | |
|     var elem = document.getElementById(aID);
 | |
|     if (elem) {
 | |
|       if (aVal == null) {
 | |
|         // null indicates attr should be removed.
 | |
|         elem.removeAttribute(aAttr);
 | |
|       } else {
 | |
|         // Set attr=val.
 | |
|         elem.setAttribute(aAttr, aVal);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Temporary workaround for DOM api not yet implemented by XUL nodes.
 | |
|   cloneNode(aItem) {
 | |
|     // Create another element like the one we're cloning.
 | |
|     var node = document.createElement(aItem.tagName);
 | |
| 
 | |
|     // Copy attributes from argument item to the new one.
 | |
|     var attrs = aItem.attributes;
 | |
|     for (var i = 0; i < attrs.length; i++) {
 | |
|       var attr = attrs.item(i);
 | |
|       node.setAttribute(attr.nodeName, attr.nodeValue);
 | |
|     }
 | |
| 
 | |
|     // Voila!
 | |
|     return node;
 | |
|   },
 | |
| 
 | |
|   getLinkURI() {
 | |
|     try {
 | |
|       return makeURI(this.linkURL);
 | |
|     } catch (ex) {
 | |
|      // e.g. empty URL string
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   // Kept for addon compat
 | |
|   linkText() {
 | |
|     return this.linkTextStr;
 | |
|   },
 | |
| 
 | |
|   // Determines whether or not the separator with the specified ID should be
 | |
|   // shown or not by determining if there are any non-hidden items between it
 | |
|   // and the previous separator.
 | |
|   shouldShowSeparator(aSeparatorID) {
 | |
|     var separator = document.getElementById(aSeparatorID);
 | |
|     if (separator) {
 | |
|       var sibling = separator.previousSibling;
 | |
|       while (sibling && sibling.localName != "menuseparator") {
 | |
|         if (!sibling.hidden)
 | |
|           return true;
 | |
|         sibling = sibling.previousSibling;
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   addDictionaries() {
 | |
|     var uri = formatURL("browser.dictionaries.download.url", true);
 | |
| 
 | |
|     var locale = "-";
 | |
|     try {
 | |
|       locale = Services.prefs.getComplexValue("intl.accept_languages",
 | |
|                                               Ci.nsIPrefLocalizedString).data;
 | |
|     } catch (e) { }
 | |
| 
 | |
|     var version = "-";
 | |
|     try {
 | |
|       version = Services.appinfo.version;
 | |
|     } catch (e) { }
 | |
| 
 | |
|     uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
 | |
| 
 | |
|     var newWindowPref = Services.prefs.getIntPref("browser.link.open_newwindow");
 | |
|     var where = newWindowPref == 3 ? "tab" : "window";
 | |
| 
 | |
|     openTrustedLinkIn(uri, where);
 | |
|   },
 | |
| 
 | |
|   bookmarkThisPage: function CM_bookmarkThisPage() {
 | |
|     window.top.PlacesCommandHook
 | |
|               .bookmarkPage()
 | |
|               .catch(Cu.reportError);
 | |
|   },
 | |
| 
 | |
|   bookmarkLink: function CM_bookmarkLink() {
 | |
|     window.top.PlacesCommandHook.bookmarkLink(this.linkURL, this.linkTextStr)
 | |
|                                 .catch(Cu.reportError);
 | |
|   },
 | |
| 
 | |
|   addBookmarkForFrame: function CM_addBookmarkForFrame() {
 | |
|     let uri = gContextMenuContentData.documentURIObject;
 | |
|     let mm = this.browser.messageManager;
 | |
| 
 | |
|     let onMessage = (message) => {
 | |
|       mm.removeMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
 | |
| 
 | |
|       window.top.PlacesCommandHook.bookmarkLink(uri.spec, message.data.title)
 | |
|                                   .catch(Cu.reportError);
 | |
|     };
 | |
|     mm.addMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
 | |
| 
 | |
|     mm.sendAsyncMessage("ContextMenu:BookmarkFrame", null, { target: this.target });
 | |
|   },
 | |
| 
 | |
|   savePageAs: function CM_savePageAs() {
 | |
|     saveBrowser(this.browser);
 | |
|   },
 | |
| 
 | |
|   printFrame: function CM_printFrame() {
 | |
|     PrintUtils.printWindow(this.frameOuterWindowID, this.browser);
 | |
|   },
 | |
| 
 | |
|   switchPageDirection: function CM_switchPageDirection() {
 | |
|     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
 | |
|   },
 | |
| 
 | |
|   mediaCommand: function CM_mediaCommand(command, data) {
 | |
|     let mm = this.browser.messageManager;
 | |
|     let win = this.browser.ownerGlobal;
 | |
|     let windowUtils = win.windowUtils;
 | |
|     mm.sendAsyncMessage("ContextMenu:MediaCommand",
 | |
|                         {command,
 | |
|                          data,
 | |
|                          handlingUserInput: windowUtils.isHandlingUserInput},
 | |
|                         {element: this.target});
 | |
|   },
 | |
| 
 | |
|   copyMediaLocation() {
 | |
|     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
 | |
|                     getService(Ci.nsIClipboardHelper);
 | |
|     clipboard.copyString(this.mediaURL);
 | |
|   },
 | |
| 
 | |
|   drmLearnMore(aEvent) {
 | |
|     let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
 | |
|     let dest = whereToOpenLink(aEvent);
 | |
|     // Don't ever want this to open in the same tab as it'll unload the
 | |
|     // DRM'd video, which is going to be a bad idea in most cases.
 | |
|     if (dest == "current") {
 | |
|       dest = "tab";
 | |
|     }
 | |
|     openTrustedLinkIn(drmInfoURL, dest);
 | |
|   },
 | |
| 
 | |
|   get imageURL() {
 | |
|     if (this.onImage)
 | |
|       return this.mediaURL;
 | |
|     return "";
 | |
|   },
 | |
| 
 | |
|   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
 | |
|   formatSearchContextItem() {
 | |
|     var menuItem = document.getElementById("context-searchselect");
 | |
|     let selectedText = this.isTextSelected ? this.textSelected : this.linkTextStr;
 | |
| 
 | |
|     // Store searchTerms in context menu item so we know what to search onclick
 | |
|     menuItem.searchTerms = selectedText;
 | |
|     menuItem.principal = this.principal;
 | |
| 
 | |
|     // Copied to alert.js' prefillAlertInfo().
 | |
|     // If the JS character after our truncation point is a trail surrogate,
 | |
|     // include it in the truncated string to avoid splitting a surrogate pair.
 | |
|     if (selectedText.length > 15) {
 | |
|       let truncLength = 15;
 | |
|       let truncChar = selectedText[15].charCodeAt(0);
 | |
|       if (truncChar >= 0xDC00 && truncChar <= 0xDFFF)
 | |
|         truncLength++;
 | |
|       selectedText = selectedText.substr(0, truncLength) + this.ellipsis;
 | |
|     }
 | |
| 
 | |
|     // format "Search <engine> for <selection>" string to show in menu
 | |
|     let engineName = Services.search.currentEngine.name;
 | |
|     var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
 | |
|                                                         [engineName,
 | |
|                                                          selectedText]);
 | |
|     menuItem.label = menuLabel;
 | |
|     menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
 | |
|   },
 | |
| 
 | |
|   createContainerMenu(aEvent) {
 | |
|     let createMenuOptions = {
 | |
|       isContextMenu: true,
 | |
|       excludeUserContextId: gContextMenuContentData.userContextId,
 | |
|     };
 | |
|     return createUserContextMenu(aEvent, createMenuOptions);
 | |
|   },
 | |
| };
 | 
