forked from mirrors/gecko-dev
		
	 f84227e740
			
		
	
	
		f84227e740
		
	
	
	
	
		
			
			* Skip showing the password manager items in the context menu from an about: page Differential Revision: https://phabricator.services.mozilla.com/D42963 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			2005 lines
		
	
	
	
		
			62 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2005 lines
		
	
	
	
		
			62 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/. */
 | |
| 
 | |
| var { PrivateBrowsingUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/PrivateBrowsingUtils.jsm"
 | |
| );
 | |
| var { BrowserUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/BrowserUtils.jsm"
 | |
| );
 | |
| var { XPCOMUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/XPCOMUtils.jsm"
 | |
| );
 | |
| var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetters(this, {
 | |
|   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
 | |
|   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",
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
 | |
|   Components.Constructor(
 | |
|     "@mozilla.org/referrer-info;1",
 | |
|     "nsIReferrerInfo",
 | |
|     "init"
 | |
|   )
 | |
| );
 | |
| 
 | |
| var gContextMenuContentData = null;
 | |
| 
 | |
| function openContextMenu(aMessage, aBrowser, aActor) {
 | |
|   let data = aMessage.data;
 | |
|   let browser = aBrowser;
 | |
|   let actor = aActor;
 | |
|   let spellInfo = data.spellInfo;
 | |
|   let frameReferrerInfo = data.frameReferrerInfo;
 | |
|   let targetReferrerInfo = data.targetReferrerInfo;
 | |
|   let principal = data.principal;
 | |
|   let storagePrincipal = data.storagePrincipal;
 | |
| 
 | |
|   if (spellInfo) {
 | |
|     spellInfo.target = browser.messageManager;
 | |
|   }
 | |
| 
 | |
|   let documentURIObject = makeURI(
 | |
|     data.docLocation,
 | |
|     data.charSet,
 | |
|     makeURI(data.baseURI)
 | |
|   );
 | |
| 
 | |
|   if (frameReferrerInfo) {
 | |
|     frameReferrerInfo = E10SUtils.deserializeReferrerInfo(frameReferrerInfo);
 | |
|   }
 | |
| 
 | |
|   if (targetReferrerInfo) {
 | |
|     targetReferrerInfo = E10SUtils.deserializeReferrerInfo(targetReferrerInfo);
 | |
|   }
 | |
| 
 | |
|   // For now, JS Window Actors don't deserialize Principals automatically, so we
 | |
|   // have to do it ourselves. See bug 1557852.
 | |
|   if (principal) {
 | |
|     principal = E10SUtils.deserializePrincipal(principal);
 | |
|   }
 | |
|   if (storagePrincipal) {
 | |
|     storagePrincipal = E10SUtils.deserializePrincipal(storagePrincipal);
 | |
|   }
 | |
| 
 | |
|   if (data.context.principal) {
 | |
|     data.context.principal = E10SUtils.deserializePrincipal(
 | |
|       data.context.principal
 | |
|     );
 | |
|   }
 | |
|   if (data.context.storagePrincipal) {
 | |
|     data.context.storagePrincipal = E10SUtils.deserializePrincipal(
 | |
|       data.context.storagePrincipal
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   gContextMenuContentData = {
 | |
|     context: data.context,
 | |
|     popupNodeSelectors: data.popupNodeSelectors,
 | |
|     browser,
 | |
|     actor,
 | |
|     editFlags: data.editFlags,
 | |
|     spellInfo,
 | |
|     principal,
 | |
|     storagePrincipal,
 | |
|     customMenuItems: data.customMenuItems,
 | |
|     documentURIObject,
 | |
|     docLocation: data.docLocation,
 | |
|     charSet: data.charSet,
 | |
|     referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
 | |
|     frameReferrerInfo,
 | |
|     targetReferrerInfo,
 | |
|     contentType: data.contentType,
 | |
|     contentDisposition: data.contentDisposition,
 | |
|     frameOuterWindowID: data.frameOuterWindowID,
 | |
|     selectionInfo: data.selectionInfo,
 | |
|     disableSetDesktopBackground: data.disableSetDesktopBackground,
 | |
|     loginFillInfo: data.loginFillInfo,
 | |
|     parentAllowsMixedContent: data.parentAllowsMixedContent,
 | |
|     userContextId: data.userContextId,
 | |
|     webExtContextData: data.webExtContextData,
 | |
|   };
 | |
| 
 | |
|   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) {
 | |
|       this.hasPageMenu = PageMenuParent.addToPopup(
 | |
|         gContextMenuContentData.customMenuItems,
 | |
|         this.browser,
 | |
|         aXulMenu
 | |
|       );
 | |
| 
 | |
|       let tab =
 | |
|         gBrowser && gBrowser.getTabForBrowser
 | |
|           ? gBrowser.getTabForBrowser(this.browser)
 | |
|           : undefined;
 | |
| 
 | |
|       let subject = {
 | |
|         menu: aXulMenu,
 | |
|         tab,
 | |
|         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,
 | |
|         webExtBrowserType: this.webExtBrowserType,
 | |
|         webExtContextData: gContextMenuContentData
 | |
|           ? gContextMenuContentData.webExtContextData
 | |
|           : undefined,
 | |
|       };
 | |
|       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);
 | |
| 
 | |
|     if (gContextMenuContentData) {
 | |
|       context = gContextMenuContentData.context;
 | |
|       gContextMenuContentData.context = null;
 | |
|     }
 | |
| 
 | |
|     this.shouldDisplay = context.shouldDisplay;
 | |
|     this.timeStamp = context.timeStamp;
 | |
| 
 | |
|     // Assign what's _possibly_ needed from `context` sent by ContextMenuChild.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.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.onPiPVideo = context.onPiPVideo;
 | |
|     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 = context.target;
 | |
|     this.targetIdentifier = context.targetIdentifier;
 | |
| 
 | |
|     this.principal = context.principal;
 | |
|     this.storagePrincipal = context.storagePrincipal;
 | |
|     this.frameOuterWindowID = context.frameOuterWindowID;
 | |
| 
 | |
|     this.inSyntheticDoc = context.inSyntheticDoc;
 | |
|     this.inAboutDevtoolsToolbox = context.inAboutDevtoolsToolbox;
 | |
| 
 | |
|     // Everything after this isn't sent directly from ContextMenu
 | |
|     if (this.target) {
 | |
|       this.ownerDoc = this.target.ownerDocument;
 | |
|     }
 | |
| 
 | |
|     this.csp = E10SUtils.deserializeCSP(context.csp);
 | |
| 
 | |
|     // 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 (gContextMenuContentData) {
 | |
|       this.browser = gContextMenuContentData.browser;
 | |
|       this.selectionInfo = gContextMenuContentData.selectionInfo;
 | |
|       this.actor = gContextMenuContentData.actor;
 | |
|     } else {
 | |
|       this.browser = this.ownerDoc.defaultView.docShell.chromeEventHandler;
 | |
|       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
 | |
|       this.actor = this.browser.browsingContext.currentWindowGlobal.getActor(
 | |
|         "ContextMenu"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const { gBrowser } = this.browser.ownerGlobal;
 | |
| 
 | |
|     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 =
 | |
|       gBrowser && gBrowser.getTabForBrowser
 | |
|         ? !!gBrowser.getTabForBrowser(this.browser)
 | |
|         : false;
 | |
| 
 | |
|     if (context.shouldInitInlineSpellCheckerUINoChildren) {
 | |
|       InlineSpellCheckerUI.initFromRemote(
 | |
|         gContextMenuContentData.spellInfo,
 | |
|         this.actor.manager
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (context.shouldInitInlineSpellCheckerUIWithChildren) {
 | |
|       InlineSpellCheckerUI.initFromRemote(
 | |
|         gContextMenuContentData.spellInfo,
 | |
|         this.actor.manager
 | |
|       );
 | |
|       let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
 | |
|       this.showItem("spell-check-enabled", canSpell);
 | |
|       this.showItem("spell-separator", canSpell);
 | |
|     }
 | |
|   }, // setContext
 | |
| 
 | |
|   hiding: function CM_hiding() {
 | |
|     if (this.actor) {
 | |
|       this.actor.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,
 | |
|         ])
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     var shouldShow =
 | |
|       this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
 | |
|     var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
 | |
|     let showContainers =
 | |
|       Services.prefs.getBoolPref("privacy.userContext.enabled") &&
 | |
|       ContextualIdentityService.getPublicIdentities().length;
 | |
|     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.inAboutDevtoolsToolbox && this.isContentSelected
 | |
|     );
 | |
| 
 | |
|     var shouldShow = !(
 | |
|       this.isContentSelected ||
 | |
|       this.onImage ||
 | |
|       this.onCanvas ||
 | |
|       this.onVideo ||
 | |
|       this.onAudio ||
 | |
|       this.onLink ||
 | |
|       this.onTextInput
 | |
|     );
 | |
| 
 | |
|     var showInspect =
 | |
|       this.inTabBrowser &&
 | |
|       !this.inAboutDevtoolsToolbox &&
 | |
|       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);
 | |
| 
 | |
|     if (this.inFrame) {
 | |
|       // To make it easier to debug the browser running with out-of-process iframes, we
 | |
|       // display the process PID of the iframe in the context menu for the subframe.
 | |
|       let frameOsPid = this.actor.manager.browsingContext.currentWindowGlobal
 | |
|         .osPid;
 | |
|       this.setItemAttr("context-frameOsPid", "label", "PID: " + frameOsPid);
 | |
|     }
 | |
| 
 | |
|     let showSearchSelect =
 | |
|       !this.inAboutDevtoolsToolbox &&
 | |
|       (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.inAboutDevtoolsToolbox && 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
 | |
|     );
 | |
|     {
 | |
|       let shouldDisplay =
 | |
|         Services.prefs.getBoolPref(
 | |
|           "media.videocontrols.picture-in-picture.enabled"
 | |
|         ) &&
 | |
|         this.onVideo &&
 | |
|         !this.target.ownerDocument.fullscreen;
 | |
|       this.showItem("context-video-pictureinpicture", shouldDisplay);
 | |
|     }
 | |
|     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.setItemAttr(
 | |
|           "context-video-pictureinpicture",
 | |
|           "checked",
 | |
|           this.onPiPVideo
 | |
|         );
 | |
|         this.setItemAttr(
 | |
|           "context-video-pictureinpicture",
 | |
|           "disabled",
 | |
|           !this.onPiPVideo && 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;
 | |
|     let documentURI = gContextMenuContentData.documentURIObject;
 | |
| 
 | |
|     // If we could not find a password field we
 | |
|     // don't want to show the form fill option.
 | |
|     let showFill =
 | |
|       loginFillInfo &&
 | |
|       loginFillInfo.passwordField.found &&
 | |
|       !documentURI.schemeIs("about");
 | |
| 
 | |
|     // Disable the fill option if the user hasn't unlocked with their master password
 | |
|     // or if the password field or target field are disabled.
 | |
|     // XXX: Bug 1529025 to respect signon.rememberSignons
 | |
|     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 formOrigin = LoginHelper.getLoginOrigin(documentURI.spec);
 | |
|     let fragment = LoginManagerContextMenu.addLoginsToMenu(
 | |
|       this.targetIdentifier,
 | |
|       this.browser,
 | |
|       formOrigin
 | |
|     );
 | |
|     let isGeneratedPasswordEnabled =
 | |
|       LoginHelper.generationAvailable && LoginHelper.generationEnabled;
 | |
|     let canFillGeneratedPassword =
 | |
|       this.onPassword &&
 | |
|       isGeneratedPasswordEnabled &&
 | |
|       Services.logins.getLoginSavingEnabled(formOrigin);
 | |
| 
 | |
|     this.showItem("fill-login-no-logins", !fragment);
 | |
|     this.showItem("fill-login-generated-password", canFillGeneratedPassword);
 | |
|     this.showItem("generated-password-separator", canFillGeneratedPassword);
 | |
| 
 | |
|     this.setItemAttr(
 | |
|       "fill-login-generated-password",
 | |
|       "disabled",
 | |
|       PrivateBrowsingUtils.isWindowPrivate(window)
 | |
|     );
 | |
| 
 | |
|     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, {
 | |
|       filterString: gContextMenuContentData.documentURIObject.host,
 | |
|       entryPoint: "contextmenu",
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   fillGeneratedPassword() {
 | |
|     LoginManagerContextMenu.fillGeneratedPassword(
 | |
|       this.targetIdentifier,
 | |
|       gContextMenuContentData.documentURIObject,
 | |
|       this.browser
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   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,
 | |
|       originStoragePrincipal: this.storagePrincipal,
 | |
|       triggeringPrincipal: this.principal,
 | |
|       csp: this.csp,
 | |
|       frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
 | |
|     };
 | |
|     for (let p in extra) {
 | |
|       params[p] = extra[p];
 | |
|     }
 | |
| 
 | |
|     let referrerInfo = gContextMenuContentData.referrerInfo;
 | |
|     // 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) ||
 | |
|       this.onPlainTextLink
 | |
|     ) {
 | |
|       referrerInfo = new ReferrerInfo(
 | |
|         referrerInfo.referrerPolicy,
 | |
|         false,
 | |
|         referrerInfo.originalReferrer
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     params.referrerInfo = referrerInfo;
 | |
|     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() {
 | |
|     openLinkIn(gContextMenuContentData.docLocation, "tab", {
 | |
|       charset: gContextMenuContentData.charSet,
 | |
|       triggeringPrincipal: this.browser.contentPrincipal,
 | |
|       csp: this.browser.csp,
 | |
|       referrerInfo: gContextMenuContentData.frameReferrerInfo,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   // Reload clicked-in frame.
 | |
|   reloadFrame(aEvent) {
 | |
|     let forceReload = aEvent.shiftKey;
 | |
|     this.actor.reloadFrame(this.targetIdentifier, forceReload);
 | |
|   },
 | |
| 
 | |
|   // Open clicked-in frame in its own window.
 | |
|   openFrame() {
 | |
|     openLinkIn(gContextMenuContentData.docLocation, "window", {
 | |
|       charset: gContextMenuContentData.charSet,
 | |
|       triggeringPrincipal: this.browser.contentPrincipal,
 | |
|       csp: this.browser.csp,
 | |
|       referrerInfo: gContextMenuContentData.frameReferrerInfo,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   // Open clicked-in frame in the same window.
 | |
|   showOnlyThisFrame() {
 | |
|     urlSecurityCheck(
 | |
|       gContextMenuContentData.docLocation,
 | |
|       this.browser.contentPrincipal,
 | |
|       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
 | |
|     );
 | |
|     openWebLinkIn(gContextMenuContentData.docLocation, "current", {
 | |
|       referrerInfo: gContextMenuContentData.frameReferrerInfo,
 | |
|       triggeringPrincipal: this.browser.contentPrincipal,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   // View Partial Source
 | |
|   viewPartialSource() {
 | |
|     let { browser } = this;
 | |
|     let openSelectionFn = function() {
 | |
|       let tabBrowser = gBrowser;
 | |
|       const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
 | |
|       // 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: inNewWindow,
 | |
|         skipAnimation: inNewWindow,
 | |
|         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
 | |
|       });
 | |
|       const viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
 | |
|       if (inNewWindow) {
 | |
|         tabBrowser.hideTab(tab);
 | |
|         tabBrowser.replaceTabsWithWindow(tab);
 | |
|       }
 | |
|       return viewSourceBrowser;
 | |
|     };
 | |
| 
 | |
|     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, {
 | |
|       referrerInfo: gContextMenuContentData.referrerInfo,
 | |
|       triggeringPrincipal: this.principal,
 | |
|       csp: this.csp,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   viewFrameInfo() {
 | |
|     BrowserPageInfo(
 | |
|       gContextMenuContentData.docLocation,
 | |
|       null,
 | |
|       null,
 | |
|       this.frameOuterWindowID,
 | |
|       this.browser
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   reloadImage() {
 | |
|     urlSecurityCheck(
 | |
|       this.mediaURL,
 | |
|       this.principal,
 | |
|       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
 | |
|     );
 | |
|     this.actor.reloadImage(this.targetIdentifier);
 | |
|   },
 | |
| 
 | |
|   _canvasToBlobURL(targetIdentifier) {
 | |
|     return this.actor.canvasToBlobURL(targetIdentifier);
 | |
|   },
 | |
| 
 | |
|   // Change current window to the URL of the image, video, or audio.
 | |
|   viewMedia(e) {
 | |
|     let referrerInfo = gContextMenuContentData.referrerInfo;
 | |
|     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 | |
|     if (this.onCanvas) {
 | |
|       this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
 | |
|         openUILink(blobURL, e, {
 | |
|           referrerInfo,
 | |
|           triggeringPrincipal: systemPrincipal,
 | |
|         });
 | |
|       }, Cu.reportError);
 | |
|     } else {
 | |
|       urlSecurityCheck(
 | |
|         this.mediaURL,
 | |
|         this.principal,
 | |
|         Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
 | |
|       );
 | |
|       openUILink(this.mediaURL, e, {
 | |
|         referrerInfo,
 | |
|         forceAllowDataURI: true,
 | |
|         triggeringPrincipal: this.principal,
 | |
|         csp: this.csp,
 | |
|       });
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   saveVideoFrameAsImage() {
 | |
|     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";
 | |
|     }
 | |
| 
 | |
|     // Cache this because we fetch the data async
 | |
|     let { targetReferrerInfo } = gContextMenuContentData;
 | |
| 
 | |
|     this.actor.saveVideoFrameAsImage(this.targetIdentifier).then(dataURL => {
 | |
|       // FIXME can we switch this to a blob URL?
 | |
|       saveImageURL(
 | |
|         dataURL,
 | |
|         name,
 | |
|         "SaveImageTitle",
 | |
|         true, // bypass cache
 | |
|         false, // don't skip prompt for where to save
 | |
|         targetReferrerInfo, // referrer info
 | |
|         null, // document
 | |
|         null, // content type
 | |
|         null, // content disposition
 | |
|         isPrivate,
 | |
|         this.principal
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   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, {
 | |
|       referrerInfo: gContextMenuContentData.referrerInfo,
 | |
|       triggeringPrincipal: this.principal,
 | |
|       csp: this.csp,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   setDesktopBackground() {
 | |
|     if (!Services.policies.isAllowed("setDesktopBackground")) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.actor
 | |
|       .setAsDesktopBackground(this.targetIdentifier)
 | |
|       .then(({ failed, dataURL, imageName }) => {
 | |
|         if (failed) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         let image = document.createElementNS(
 | |
|           "http://www.w3.org/1999/xhtml",
 | |
|           "img"
 | |
|         );
 | |
|         image.src = dataURL;
 | |
| 
 | |
|         // 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
 | |
|           );
 | |
|         }
 | |
|       });
 | |
|   },
 | |
| 
 | |
|   // 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,
 | |
|     referrerInfo,
 | |
|     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(principal) {
 | |
|       this._triggeringPrincipal = principal;
 | |
|     }
 | |
|     saveAsListener.prototype = {
 | |
|       extListener: null,
 | |
| 
 | |
|       onStartRequest: function saveLinkAs_onStartRequest(aRequest) {
 | |
|         // 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);
 | |
|       },
 | |
| 
 | |
|       onStopRequest: function saveLinkAs_onStopRequest(aRequest, 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,
 | |
|             referrerInfo,
 | |
|             doc,
 | |
|             isContentWindowPrivate,
 | |
|             this._triggeringPrincipal
 | |
|           );
 | |
|         }
 | |
|         if (this.extListener) {
 | |
|           this.extListener.onStopRequest(aRequest, aStatusCode);
 | |
|         }
 | |
|       },
 | |
| 
 | |
|       onDataAvailable: function saveLinkAs_onDataAvailable(
 | |
|         aRequest,
 | |
|         aInputStream,
 | |
|         aOffset,
 | |
|         aCount
 | |
|       ) {
 | |
|         this.extListener.onDataAvailable(
 | |
|           aRequest,
 | |
|           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.referrerInfo = referrerInfo;
 | |
|       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.asyncOpen(new saveAsListener(this.principal));
 | |
|   },
 | |
| 
 | |
|   // Save URL of clicked-on link.
 | |
|   saveLink() {
 | |
|     let isContentWindowPrivate = this.ownerDoc.isPrivate;
 | |
|     this.saveHelper(
 | |
|       this.linkURL,
 | |
|       this.linkTextStr,
 | |
|       null,
 | |
|       true,
 | |
|       this.ownerDoc,
 | |
|       gContextMenuContentData.referrerInfo,
 | |
|       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.ownerDoc.isPrivate;
 | |
|     let referrerInfo = gContextMenuContentData.targetReferrerInfo;
 | |
|     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 | |
|     if (this.onCanvas) {
 | |
|       // Bypass cache, since it's a data: URL.
 | |
|       this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
 | |
|         saveImageURL(
 | |
|           blobURL,
 | |
|           "canvas.png",
 | |
|           "SaveImageTitle",
 | |
|           true,
 | |
|           false,
 | |
|           referrerInfo,
 | |
|           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,
 | |
|         referrerInfo,
 | |
|         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,
 | |
|         referrerInfo,
 | |
|         this.frameOuterWindowID,
 | |
|         "",
 | |
|         isContentWindowPrivate
 | |
|       );
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Backwards-compatibility wrapper
 | |
|   sendImage() {
 | |
|     if (this.onCanvas || this.onImage) {
 | |
|       this.sendMedia();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   sendMedia() {
 | |
|     MailIntegration.sendMessage(this.mediaURL, "");
 | |
|   },
 | |
| 
 | |
|   playPlugin() {
 | |
|     this.actor.pluginCommand("play", this.targetIdentifier);
 | |
|   },
 | |
| 
 | |
|   hidePlugin() {
 | |
|     this.actor.pluginCommand("hide", this.targetIdentifier);
 | |
|   },
 | |
| 
 | |
|   // 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);
 | |
|   },
 | |
| 
 | |
|   addKeywordForSearchField() {
 | |
|     this.actor.getSearchFieldBookmarkData(this.targetIdentifier).then(data => {
 | |
|       let title = gNavigatorBundle.getFormattedString(
 | |
|         "addKeywordTitleAutoFill",
 | |
|         [data.title]
 | |
|       );
 | |
|       PlacesUIUtils.showBookmarkDialog(
 | |
|         {
 | |
|           action: "add",
 | |
|           type: "bookmark",
 | |
|           uri: makeURI(data.spec),
 | |
|           title,
 | |
|           keyword: "",
 | |
|           postData: data.postData,
 | |
|           charSet: data.charset,
 | |
|           hiddenRows: ["location", "tags"],
 | |
|         },
 | |
|         window
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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;
 | |
| 
 | |
|     this.actor.getFrameTitle(this.targetIdentifier).then(title => {
 | |
|       window.top.PlacesCommandHook.bookmarkLink(uri.spec, title).catch(
 | |
|         Cu.reportError
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   savePageAs: function CM_savePageAs() {
 | |
|     saveBrowser(this.browser);
 | |
|   },
 | |
| 
 | |
|   printFrame: function CM_printFrame() {
 | |
|     PrintUtils.printWindow(this.frameOuterWindowID, this.browser);
 | |
|   },
 | |
| 
 | |
|   switchPageDirection: function CM_switchPageDirection() {
 | |
|     gBrowser.selectedBrowser.sendMessageToActor(
 | |
|       "SwitchDocumentDirection",
 | |
|       {},
 | |
|       "SwitchDocumentDirection",
 | |
|       true
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   mediaCommand: function CM_mediaCommand(command, data) {
 | |
|     this.actor.mediaCommand(this.targetIdentifier, command, data);
 | |
|   },
 | |
| 
 | |
|   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;
 | |
|     menuItem.csp = this.csp;
 | |
| 
 | |
|     // 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.defaultEngine.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);
 | |
|   },
 | |
| 
 | |
|   doCustomCommand(generatedItemId, handlingUserInput) {
 | |
|     this.actor.doCustomCommand(generatedItemId, handlingUserInput);
 | |
|   },
 | |
| };
 |