michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: var gContextMenuContentData = null; michael@0: michael@0: function nsContextMenu(aXulMenu, aIsShift) { michael@0: this.shouldDisplay = true; michael@0: this.initMenu(aXulMenu, aIsShift); michael@0: } michael@0: michael@0: // Prototype for nsContextMenu "class." michael@0: nsContextMenu.prototype = { michael@0: initMenu: function CM_initMenu(aXulMenu, aIsShift) { michael@0: // Get contextual info. michael@0: this.setTarget(document.popupNode, document.popupRangeParent, michael@0: document.popupRangeOffset); michael@0: if (!this.shouldDisplay) michael@0: return; michael@0: michael@0: this.hasPageMenu = false; michael@0: if (!aIsShift) { michael@0: this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target, michael@0: aXulMenu); michael@0: } michael@0: michael@0: this.isFrameImage = document.getElementById("isFrameImage"); michael@0: this.ellipsis = "\u2026"; michael@0: try { michael@0: this.ellipsis = gPrefService.getComplexValue("intl.ellipsis", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: } catch (e) { } michael@0: michael@0: this.isContentSelected = this.isContentSelection(); michael@0: this.onPlainTextLink = false; michael@0: michael@0: // Initialize (disable/remove) menu items. michael@0: this.initItems(); michael@0: }, michael@0: michael@0: hiding: function CM_hiding() { michael@0: gContextMenuContentData = null; michael@0: InlineSpellCheckerUI.clearSuggestionsFromMenu(); michael@0: InlineSpellCheckerUI.clearDictionaryListFromMenu(); michael@0: InlineSpellCheckerUI.uninit(); michael@0: }, michael@0: michael@0: initItems: function CM_initItems() { michael@0: this.initPageMenuSeparator(); michael@0: this.initOpenItems(); michael@0: this.initNavigationItems(); michael@0: this.initViewItems(); michael@0: this.initMiscItems(); michael@0: this.initSpellingItems(); michael@0: this.initSaveItems(); michael@0: this.initClipboardItems(); michael@0: this.initMediaPlayerItems(); michael@0: this.initLeaveDOMFullScreenItems(); michael@0: this.initClickToPlayItems(); michael@0: }, michael@0: michael@0: initPageMenuSeparator: function CM_initPageMenuSeparator() { michael@0: this.showItem("page-menu-separator", this.hasPageMenu); michael@0: }, michael@0: michael@0: initOpenItems: function CM_initOpenItems() { michael@0: var isMailtoInternal = false; michael@0: if (this.onMailtoLink) { michael@0: var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService). michael@0: getProtocolHandlerInfo("mailto"); michael@0: isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling && michael@0: mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp && michael@0: (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp)); michael@0: } michael@0: michael@0: // Time to do some bad things and see if we've highlighted a URL that michael@0: // isn't actually linked. michael@0: if (this.isTextSelected && !this.onLink) { michael@0: // Ok, we have some text, let's figure out if it looks like a URL. michael@0: let selection = this.focusedWindow.getSelection(); michael@0: let linkText = selection.toString().trim(); michael@0: let uri; michael@0: if (/^(?:https?|ftp):/i.test(linkText)) { michael@0: try { michael@0: uri = makeURI(linkText); michael@0: } catch (ex) {} michael@0: } michael@0: // Check if this could be a valid url, just missing the protocol. michael@0: else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) { michael@0: // Now let's see if this is an intentional link selection. Our guess is michael@0: // based on whether the selection begins/ends with whitespace or is michael@0: // preceded/followed by a non-word character. michael@0: michael@0: // selection.toString() trims trailing whitespace, so we look for michael@0: // that explicitly in the first and last ranges. michael@0: let beginRange = selection.getRangeAt(0); michael@0: let delimitedAtStart = /^\s/.test(beginRange); michael@0: if (!delimitedAtStart) { michael@0: let container = beginRange.startContainer; michael@0: let offset = beginRange.startOffset; michael@0: if (container.nodeType == Node.TEXT_NODE && offset > 0) michael@0: delimitedAtStart = /\W/.test(container.textContent[offset - 1]); michael@0: else michael@0: delimitedAtStart = true; michael@0: } michael@0: michael@0: let delimitedAtEnd = false; michael@0: if (delimitedAtStart) { michael@0: let endRange = selection.getRangeAt(selection.rangeCount - 1); michael@0: delimitedAtEnd = /\s$/.test(endRange); michael@0: if (!delimitedAtEnd) { michael@0: let container = endRange.endContainer; michael@0: let offset = endRange.endOffset; michael@0: if (container.nodeType == Node.TEXT_NODE && michael@0: offset < container.textContent.length) michael@0: delimitedAtEnd = /\W/.test(container.textContent[offset]); michael@0: else michael@0: delimitedAtEnd = true; michael@0: } michael@0: } michael@0: michael@0: if (delimitedAtStart && delimitedAtEnd) { michael@0: let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"] michael@0: .getService(Ci.nsIURIFixup); michael@0: try { michael@0: uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE); michael@0: } catch (ex) {} michael@0: } michael@0: } michael@0: michael@0: if (uri && uri.host) { michael@0: this.linkURI = uri; michael@0: this.linkURL = this.linkURI.spec; michael@0: this.onPlainTextLink = true; michael@0: } michael@0: } michael@0: michael@0: var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink; michael@0: var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window); michael@0: this.showItem("context-openlink", shouldShow && !isWindowPrivate); michael@0: this.showItem("context-openlinkprivate", shouldShow); michael@0: this.showItem("context-openlinkintab", shouldShow); michael@0: this.showItem("context-openlinkincurrent", this.onPlainTextLink); michael@0: this.showItem("context-sep-open", shouldShow); michael@0: }, michael@0: michael@0: initNavigationItems: function CM_initNavigationItems() { michael@0: var shouldShow = !(this.isContentSelected || this.onLink || this.onImage || michael@0: this.onCanvas || this.onVideo || this.onAudio || michael@0: this.onTextInput || this.onSocial); michael@0: this.showItem("context-back", shouldShow); michael@0: this.showItem("context-forward", shouldShow); michael@0: michael@0: let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true"; michael@0: michael@0: let stopReloadItem = ""; michael@0: if (shouldShow || this.onSocial) { michael@0: stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop"; michael@0: } michael@0: michael@0: this.showItem("context-reload", stopReloadItem == "reload"); michael@0: this.showItem("context-stop", stopReloadItem == "stop"); michael@0: this.showItem("context-sep-stop", !!stopReloadItem); michael@0: michael@0: // XXX: Stop is determined in browser.js; the canStop broadcaster is broken michael@0: //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" ); michael@0: }, michael@0: michael@0: initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() { michael@0: // only show the option if the user is in DOM fullscreen michael@0: var shouldShow = (this.target.ownerDocument.mozFullScreenElement != null); michael@0: this.showItem("context-leave-dom-fullscreen", shouldShow); michael@0: michael@0: // Explicitly show if in DOM fullscreen, but do not hide it has already been shown michael@0: if (shouldShow) michael@0: this.showItem("context-media-sep-commands", true); michael@0: }, michael@0: michael@0: initSaveItems: function CM_initSaveItems() { michael@0: var shouldShow = !(this.onTextInput || this.onLink || michael@0: this.isContentSelected || this.onImage || michael@0: this.onCanvas || this.onVideo || this.onAudio); michael@0: this.showItem("context-savepage", shouldShow); michael@0: michael@0: // Save link depends on whether we're in a link, or selected text matches valid URL pattern. michael@0: this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink); michael@0: michael@0: // Save image depends on having loaded its content, video and audio don't. michael@0: this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas); michael@0: this.showItem("context-savevideo", this.onVideo); michael@0: this.showItem("context-saveaudio", this.onAudio); michael@0: this.showItem("context-video-saveimage", this.onVideo); michael@0: this.setItemAttr("context-savevideo", "disabled", !this.mediaURL); michael@0: this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL); michael@0: // Send media URL (but not for canvas, since it's a big data: URL) michael@0: this.showItem("context-sendimage", this.onImage); michael@0: this.showItem("context-sendvideo", this.onVideo); michael@0: this.showItem("context-sendaudio", this.onAudio); michael@0: this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL); michael@0: this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL); michael@0: }, michael@0: michael@0: initViewItems: function CM_initViewItems() { michael@0: // View source is always OK, unless in directory listing. michael@0: this.showItem("context-viewpartialsource-selection", michael@0: this.isContentSelected); michael@0: this.showItem("context-viewpartialsource-mathml", michael@0: this.onMathML && !this.isContentSelected); michael@0: michael@0: var shouldShow = !(this.isContentSelected || michael@0: this.onImage || this.onCanvas || michael@0: this.onVideo || this.onAudio || michael@0: this.onLink || this.onTextInput); michael@0: var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled"); michael@0: this.showItem("context-viewsource", shouldShow); michael@0: this.showItem("context-viewinfo", shouldShow); michael@0: this.showItem("inspect-separator", showInspect); michael@0: this.showItem("context-inspect", showInspect); michael@0: michael@0: this.showItem("context-sep-viewsource", shouldShow); michael@0: michael@0: // Set as Desktop background depends on whether an image was clicked on, michael@0: // and only works if we have a shell service. michael@0: var haveSetDesktopBackground = false; michael@0: #ifdef HAVE_SHELL_SERVICE michael@0: // Only enable Set as Desktop Background if we can get the shell service. michael@0: var shell = getShellService(); michael@0: if (shell) michael@0: haveSetDesktopBackground = shell.canSetDesktopBackground; michael@0: #endif michael@0: this.showItem("context-setDesktopBackground", michael@0: haveSetDesktopBackground && this.onLoadedImage); michael@0: michael@0: if (haveSetDesktopBackground && this.onLoadedImage) { michael@0: document.getElementById("context-setDesktopBackground") michael@0: .disabled = this.disableSetDesktopBackground(); michael@0: } michael@0: michael@0: // Reload image depends on an image that's not fully loaded michael@0: this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage)); michael@0: michael@0: // View image depends on having an image that's not standalone michael@0: // (or is in a frame), or a canvas. michael@0: this.showItem("context-viewimage", (this.onImage && michael@0: (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas); michael@0: michael@0: // View video depends on not having a standalone video. michael@0: this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame)); michael@0: this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL); michael@0: michael@0: // View background image depends on whether there is one, but don't make michael@0: // background images of a stand-alone media document available. michael@0: this.showItem("context-viewbgimage", shouldShow && michael@0: !this._hasMultipleBGImages && michael@0: !this.inSyntheticDoc); michael@0: this.showItem("context-sep-viewbgimage", shouldShow && michael@0: !this._hasMultipleBGImages && michael@0: !this.inSyntheticDoc); michael@0: document.getElementById("context-viewbgimage") michael@0: .disabled = !this.hasBGImage; michael@0: michael@0: this.showItem("context-viewimageinfo", this.onImage); michael@0: this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== ""); michael@0: }, michael@0: michael@0: initMiscItems: function CM_initMiscItems() { michael@0: // Use "Bookmark This Link" if on a link. michael@0: this.showItem("context-bookmarkpage", michael@0: !(this.isContentSelected || this.onTextInput || this.onLink || michael@0: this.onImage || this.onVideo || this.onAudio || this.onSocial)); michael@0: this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink && michael@0: !this.onSocial) || this.onPlainTextLink); michael@0: this.showItem("context-keywordfield", michael@0: this.onTextInput && this.onKeywordField); michael@0: this.showItem("frame", this.inFrame); michael@0: michael@0: let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage; michael@0: this.showItem("context-searchselect", showSearchSelect); michael@0: if (showSearchSelect) { michael@0: this.formatSearchContextItem(); michael@0: } michael@0: michael@0: // srcdoc cannot be opened separately due to concerns about web michael@0: // content with about:srcdoc in location bar masquerading as trusted michael@0: // chrome/addon content. michael@0: // No need to also test for this.inFrame as this is checked in the parent michael@0: // submenu. michael@0: this.showItem("context-showonlythisframe", !this.inSrcdocFrame); michael@0: this.showItem("context-openframeintab", !this.inSrcdocFrame); michael@0: this.showItem("context-openframe", !this.inSrcdocFrame); michael@0: this.showItem("context-bookmarkframe", !this.inSrcdocFrame); michael@0: this.showItem("open-frame-sep", !this.inSrcdocFrame); michael@0: michael@0: this.showItem("frame-sep", this.inFrame && this.isTextSelected); michael@0: michael@0: // Hide menu entries for images, show otherwise michael@0: if (this.inFrame) { michael@0: if (mimeTypeIsTextBased(this.target.ownerDocument.contentType)) michael@0: this.isFrameImage.removeAttribute('hidden'); michael@0: else michael@0: this.isFrameImage.setAttribute('hidden', 'true'); michael@0: } michael@0: michael@0: // BiDi UI michael@0: this.showItem("context-sep-bidi", top.gBidiUI); michael@0: this.showItem("context-bidi-text-direction-toggle", michael@0: this.onTextInput && top.gBidiUI); michael@0: this.showItem("context-bidi-page-direction-toggle", michael@0: !this.onTextInput && top.gBidiUI); michael@0: michael@0: // SocialMarks. Marks does not work with text selections, only links. If michael@0: // there is more than MENU_LIMIT providers, we show a submenu for them, michael@0: // otherwise we have a menuitem per provider (added in SocialMarks class). michael@0: let markProviders = SocialMarks.getProviders(); michael@0: let enablePageMarks = markProviders.length > 0 && !(this.onLink || this.onImage michael@0: || this.onVideo || this.onAudio); michael@0: this.showItem("context-markpageMenu", enablePageMarks && markProviders.length > SocialMarks.MENU_LIMIT); michael@0: let enablePageMarkItems = enablePageMarks && markProviders.length <= SocialMarks.MENU_LIMIT; michael@0: let linkmenus = document.getElementsByClassName("context-markpage"); michael@0: [m.hidden = !enablePageMarkItems for (m of linkmenus)]; michael@0: michael@0: let enableLinkMarks = markProviders.length > 0 && michael@0: ((this.onLink && !this.onMailtoLink) || this.onPlainTextLink); michael@0: this.showItem("context-marklinkMenu", enableLinkMarks && markProviders.length > SocialMarks.MENU_LIMIT); michael@0: let enableLinkMarkItems = enableLinkMarks && markProviders.length <= SocialMarks.MENU_LIMIT; michael@0: linkmenus = document.getElementsByClassName("context-marklink"); michael@0: [m.hidden = !enableLinkMarkItems for (m of linkmenus)]; michael@0: michael@0: // SocialShare michael@0: let shareButton = SocialShare.shareButton; michael@0: let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial; michael@0: let pageShare = shareEnabled && !(this.isContentSelected || michael@0: this.onTextInput || this.onLink || this.onImage || michael@0: this.onVideo || this.onAudio); michael@0: this.showItem("context-sharepage", pageShare); michael@0: this.showItem("context-shareselect", shareEnabled && this.isContentSelected); michael@0: this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink); michael@0: this.showItem("context-shareimage", shareEnabled && this.onImage); michael@0: this.showItem("context-sharevideo", shareEnabled && this.onVideo); michael@0: this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL); michael@0: }, michael@0: michael@0: initSpellingItems: function() { michael@0: var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck; michael@0: var onMisspelling = InlineSpellCheckerUI.overMisspelling; michael@0: var showUndo = canSpell && InlineSpellCheckerUI.canUndo(); michael@0: this.showItem("spell-check-enabled", canSpell); michael@0: this.showItem("spell-separator", canSpell || this.onEditableArea); michael@0: document.getElementById("spell-check-enabled") michael@0: .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled); michael@0: michael@0: this.showItem("spell-add-to-dictionary", onMisspelling); michael@0: this.showItem("spell-undo-add-to-dictionary", showUndo); michael@0: michael@0: // suggestion list michael@0: this.showItem("spell-suggestions-separator", onMisspelling || showUndo); michael@0: if (onMisspelling) { michael@0: var suggestionsSeparator = michael@0: document.getElementById("spell-add-to-dictionary"); michael@0: var numsug = michael@0: InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode, michael@0: suggestionsSeparator, 5); michael@0: this.showItem("spell-no-suggestions", numsug == 0); michael@0: } michael@0: else michael@0: this.showItem("spell-no-suggestions", false); michael@0: michael@0: // dictionary list michael@0: this.showItem("spell-dictionaries", canSpell && InlineSpellCheckerUI.enabled); michael@0: if (canSpell) { michael@0: var dictMenu = document.getElementById("spell-dictionaries-menu"); michael@0: var dictSep = document.getElementById("spell-language-separator"); michael@0: InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep); michael@0: this.showItem("spell-add-dictionaries-main", false); michael@0: } michael@0: else if (this.onEditableArea) { michael@0: // when there is no spellchecker but we might be able to spellcheck michael@0: // add the add to dictionaries item. This will ensure that people michael@0: // with no dictionaries will be able to download them michael@0: this.showItem("spell-add-dictionaries-main", true); michael@0: } michael@0: else michael@0: this.showItem("spell-add-dictionaries-main", false); michael@0: }, michael@0: michael@0: initClipboardItems: function() { michael@0: // Copy depends on whether there is selected text. michael@0: // Enabling this context menu item is now done through the global michael@0: // command updating system michael@0: // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() ); michael@0: goUpdateGlobalEditMenuItems(); michael@0: michael@0: this.showItem("context-undo", this.onTextInput); michael@0: this.showItem("context-sep-undo", this.onTextInput); michael@0: this.showItem("context-cut", this.onTextInput); michael@0: this.showItem("context-copy", michael@0: this.isContentSelected || this.onTextInput); michael@0: this.showItem("context-paste", this.onTextInput); michael@0: this.showItem("context-delete", this.onTextInput); michael@0: this.showItem("context-sep-paste", this.onTextInput); michael@0: this.showItem("context-selectall", !(this.onLink || this.onImage || michael@0: this.onVideo || this.onAudio || michael@0: this.inSyntheticDoc) || michael@0: this.isDesignMode); michael@0: this.showItem("context-sep-selectall", this.isContentSelected ); michael@0: michael@0: // XXX dr michael@0: // ------ michael@0: // nsDocumentViewer.cpp has code to determine whether we're michael@0: // on a link or an image. we really ought to be using that... michael@0: michael@0: // Copy email link depends on whether we're on an email link. michael@0: this.showItem("context-copyemail", this.onMailtoLink); michael@0: michael@0: // Copy link location depends on whether we're on a non-mailto link. michael@0: this.showItem("context-copylink", this.onLink && !this.onMailtoLink); michael@0: this.showItem("context-sep-copylink", this.onLink && michael@0: (this.onImage || this.onVideo || this.onAudio)); michael@0: michael@0: #ifdef CONTEXT_COPY_IMAGE_CONTENTS michael@0: // Copy image contents depends on whether we're on an image. michael@0: this.showItem("context-copyimage-contents", this.onImage); michael@0: #endif michael@0: // Copy image location depends on whether we're on an image. michael@0: this.showItem("context-copyimage", this.onImage); michael@0: this.showItem("context-copyvideourl", this.onVideo); michael@0: this.showItem("context-copyaudiourl", this.onAudio); michael@0: this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL); michael@0: this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL); michael@0: this.showItem("context-sep-copyimage", this.onImage || michael@0: this.onVideo || this.onAudio); michael@0: }, michael@0: michael@0: initMediaPlayerItems: function() { michael@0: var onMedia = (this.onVideo || this.onAudio); michael@0: // Several mutually exclusive items... play/pause, mute/unmute, show/hide michael@0: this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended)); michael@0: this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended); michael@0: this.showItem("context-media-mute", onMedia && !this.target.muted); michael@0: this.showItem("context-media-unmute", onMedia && this.target.muted); michael@0: this.showItem("context-media-playbackrate", onMedia); michael@0: this.showItem("context-media-showcontrols", onMedia && !this.target.controls); michael@0: this.showItem("context-media-hidecontrols", onMedia && this.target.controls); michael@0: this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null); michael@0: var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing; michael@0: this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing); michael@0: this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing); michael@0: michael@0: // Disable them when there isn't a valid media source loaded. michael@0: if (onMedia) { michael@0: this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5); michael@0: this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0); michael@0: this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5); michael@0: this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0); michael@0: var hasError = this.target.error != null || michael@0: this.target.networkState == this.target.NETWORK_NO_SOURCE; michael@0: this.setItemAttr("context-media-play", "disabled", hasError); michael@0: this.setItemAttr("context-media-pause", "disabled", hasError); michael@0: this.setItemAttr("context-media-mute", "disabled", hasError); michael@0: this.setItemAttr("context-media-unmute", "disabled", hasError); michael@0: this.setItemAttr("context-media-playbackrate", "disabled", hasError); michael@0: this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError); michael@0: this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError); michael@0: this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError); michael@0: this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError); michael@0: this.setItemAttr("context-media-showcontrols", "disabled", hasError); michael@0: this.setItemAttr("context-media-hidecontrols", "disabled", hasError); michael@0: if (this.onVideo) { michael@0: let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA; michael@0: this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot); michael@0: this.setItemAttr("context-video-fullscreen", "disabled", hasError); michael@0: this.setItemAttr("context-video-showstats", "disabled", hasError); michael@0: this.setItemAttr("context-video-hidestats", "disabled", hasError); michael@0: } michael@0: } michael@0: this.showItem("context-media-sep-commands", onMedia); michael@0: }, michael@0: michael@0: initClickToPlayItems: function() { michael@0: this.showItem("context-ctp-play", this.onCTPPlugin); michael@0: this.showItem("context-ctp-hide", this.onCTPPlugin); michael@0: this.showItem("context-sep-ctp", this.onCTPPlugin); michael@0: }, michael@0: michael@0: inspectNode: function CM_inspectNode() { michael@0: let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: let gBrowser = this.browser.ownerDocument.defaultView.gBrowser; michael@0: let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab); michael@0: return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) { michael@0: let inspector = toolbox.getCurrentPanel(); michael@0: inspector.selection.setNode(this.target, "browser-context-menu"); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: // Set various context menu attributes based on the state of the world. michael@0: setTarget: function (aNode, aRangeParent, aRangeOffset) { michael@0: // If gContextMenuContentData is not null, this event was forwarded from a michael@0: // child process, so use that information instead. michael@0: if (gContextMenuContentData) { michael@0: this.isRemote = true; michael@0: aNode = gContextMenuContentData.event.target; michael@0: } else { michael@0: this.isRemote = false; michael@0: } michael@0: michael@0: const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: if (aNode.namespaceURI == xulNS || michael@0: aNode.nodeType == Node.DOCUMENT_NODE || michael@0: this.isDisabledForEvents(aNode)) { michael@0: this.shouldDisplay = false; michael@0: return; michael@0: } michael@0: michael@0: // Initialize contextual info. michael@0: this.onImage = false; michael@0: this.onLoadedImage = false; michael@0: this.onCompletedImage = false; michael@0: this.imageDescURL = ""; michael@0: this.onCanvas = false; michael@0: this.onVideo = false; michael@0: this.onAudio = false; michael@0: this.onTextInput = false; michael@0: this.onKeywordField = false; michael@0: this.mediaURL = ""; michael@0: this.onLink = false; michael@0: this.onMailtoLink = false; michael@0: this.onSaveableLink = false; michael@0: this.link = null; michael@0: this.linkURL = ""; michael@0: this.linkURI = null; michael@0: this.linkProtocol = ""; michael@0: this.onMathML = false; michael@0: this.inFrame = false; michael@0: this.inSrcdocFrame = false; michael@0: this.inSyntheticDoc = false; michael@0: this.hasBGImage = false; michael@0: this.bgImageURL = ""; michael@0: this.onEditableArea = false; michael@0: this.isDesignMode = false; michael@0: this.onCTPPlugin = false; michael@0: this.canSpellCheck = false; michael@0: this.textSelected = getBrowserSelection(); michael@0: this.isTextSelected = this.textSelected.length != 0; michael@0: michael@0: // Remember the node that was clicked. michael@0: this.target = aNode; michael@0: michael@0: let [elt, win] = BrowserUtils.getFocusSync(document); michael@0: this.focusedWindow = win; michael@0: this.focusedElement = elt; michael@0: michael@0: // If this is a remote context menu event, use the information from michael@0: // gContextMenuContentData instead. michael@0: if (this.isRemote) { michael@0: this.browser = gContextMenuContentData.browser; michael@0: } else { michael@0: this.browser = this.target.ownerDocument.defaultView michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell) michael@0: .chromeEventHandler; michael@0: } michael@0: this.onSocial = !!this.browser.getAttribute("origin"); michael@0: michael@0: // Check if we are in a synthetic document (stand alone image, video, etc.). michael@0: this.inSyntheticDoc = this.target.ownerDocument.mozSyntheticDocument; michael@0: // First, do checks for nodes that never have children. michael@0: if (this.target.nodeType == Node.ELEMENT_NODE) { michael@0: // See if the user clicked on an image. michael@0: if (this.target instanceof Ci.nsIImageLoadingContent && michael@0: this.target.currentURI) { michael@0: this.onImage = true; michael@0: michael@0: var request = michael@0: this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); michael@0: if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)) michael@0: this.onLoadedImage = true; michael@0: if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE)) michael@0: this.onCompletedImage = true; michael@0: michael@0: this.mediaURL = this.target.currentURI.spec; michael@0: michael@0: var descURL = this.target.getAttribute("longdesc"); michael@0: if (descURL) { michael@0: this.imageDescURL = makeURLAbsolute(this.target.ownerDocument.body.baseURI, descURL); michael@0: } michael@0: } michael@0: else if (this.target instanceof HTMLCanvasElement) { michael@0: this.onCanvas = true; michael@0: } michael@0: else if (this.target instanceof HTMLVideoElement) { michael@0: this.mediaURL = this.target.currentSrc || this.target.src; michael@0: // Firefox always creates a HTMLVideoElement when loading an ogg file michael@0: // directly. If the media is actually audio, be smarter and provide a michael@0: // context menu with audio operations. michael@0: if (this.target.readyState >= this.target.HAVE_METADATA && michael@0: (this.target.videoWidth == 0 || this.target.videoHeight == 0)) { michael@0: this.onAudio = true; michael@0: } else { michael@0: this.onVideo = true; michael@0: } michael@0: } michael@0: else if (this.target instanceof HTMLAudioElement) { michael@0: this.onAudio = true; michael@0: this.mediaURL = this.target.currentSrc || this.target.src; michael@0: } michael@0: else if (this.target instanceof HTMLInputElement ) { michael@0: this.onTextInput = this.isTargetATextBox(this.target); michael@0: // Allow spellchecking UI on all text and search inputs. michael@0: if (this.onTextInput && ! this.target.readOnly && michael@0: (this.target.type == "text" || this.target.type == "search")) { michael@0: this.onEditableArea = true; michael@0: InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor); michael@0: InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset); michael@0: } michael@0: this.onKeywordField = this.isTargetAKeywordField(this.target); michael@0: } michael@0: else if (this.target instanceof HTMLTextAreaElement) { michael@0: this.onTextInput = true; michael@0: if (!this.target.readOnly) { michael@0: this.onEditableArea = true; michael@0: InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor); michael@0: InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset); michael@0: } michael@0: } michael@0: else if (this.target instanceof HTMLHtmlElement) { michael@0: var bodyElt = this.target.ownerDocument.body; michael@0: if (bodyElt) { michael@0: let computedURL; michael@0: try { michael@0: computedURL = this.getComputedURL(bodyElt, "background-image"); michael@0: this._hasMultipleBGImages = false; michael@0: } catch (e) { michael@0: this._hasMultipleBGImages = true; michael@0: } michael@0: if (computedURL) { michael@0: this.hasBGImage = true; michael@0: this.bgImageURL = makeURLAbsolute(bodyElt.baseURI, michael@0: computedURL); michael@0: } michael@0: } michael@0: } michael@0: else if ((this.target instanceof HTMLEmbedElement || michael@0: this.target instanceof HTMLObjectElement || michael@0: this.target instanceof HTMLAppletElement) && michael@0: this.target.mozMatchesSelector(":-moz-handler-clicktoplay")) { michael@0: this.onCTPPlugin = true; michael@0: } michael@0: michael@0: this.canSpellCheck = this._isSpellCheckEnabled(this.target); michael@0: } michael@0: else if (this.target.nodeType == Node.TEXT_NODE) { michael@0: // For text nodes, look at the parent node to determine the spellcheck attribute. michael@0: this.canSpellCheck = this.target.parentNode && michael@0: this._isSpellCheckEnabled(this.target); michael@0: } michael@0: michael@0: // Second, bubble out, looking for items of interest that can have childen. michael@0: // Always pick the innermost link, background image, etc. michael@0: const XMLNS = "http://www.w3.org/XML/1998/namespace"; michael@0: var elem = this.target; michael@0: while (elem) { michael@0: if (elem.nodeType == Node.ELEMENT_NODE) { michael@0: // Link? michael@0: if (!this.onLink && michael@0: // Be consistent with what hrefAndLinkNodeForClickEvent michael@0: // does in browser.js michael@0: ((elem instanceof HTMLAnchorElement && elem.href) || michael@0: (elem instanceof HTMLAreaElement && elem.href) || michael@0: elem instanceof HTMLLinkElement || michael@0: elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) { michael@0: michael@0: // Target is a link or a descendant of a link. michael@0: this.onLink = true; michael@0: michael@0: // Remember corresponding element. michael@0: this.link = elem; michael@0: this.linkURL = this.getLinkURL(); michael@0: this.linkURI = this.getLinkURI(); michael@0: this.linkProtocol = this.getLinkProtocol(); michael@0: this.onMailtoLink = (this.linkProtocol == "mailto"); michael@0: this.onSaveableLink = this.isLinkSaveable( this.link ); michael@0: } michael@0: michael@0: // Background image? Don't bother if we've already found a michael@0: // background image further down the hierarchy. Otherwise, michael@0: // we look for the computed background-image style. michael@0: if (!this.hasBGImage && michael@0: !this._hasMultipleBGImages) { michael@0: let bgImgUrl; michael@0: try { michael@0: bgImgUrl = this.getComputedURL(elem, "background-image"); michael@0: this._hasMultipleBGImages = false; michael@0: } catch (e) { michael@0: this._hasMultipleBGImages = true; michael@0: } michael@0: if (bgImgUrl) { michael@0: this.hasBGImage = true; michael@0: this.bgImageURL = makeURLAbsolute(elem.baseURI, michael@0: bgImgUrl); michael@0: } michael@0: } michael@0: } michael@0: michael@0: elem = elem.parentNode; michael@0: } michael@0: michael@0: // See if the user clicked on MathML michael@0: const NS_MathML = "http://www.w3.org/1998/Math/MathML"; michael@0: if ((this.target.nodeType == Node.TEXT_NODE && michael@0: this.target.parentNode.namespaceURI == NS_MathML) michael@0: || (this.target.namespaceURI == NS_MathML)) michael@0: this.onMathML = true; michael@0: michael@0: // See if the user clicked in a frame. michael@0: var docDefaultView = this.target.ownerDocument.defaultView; michael@0: if (docDefaultView != docDefaultView.top) { michael@0: this.inFrame = true; michael@0: michael@0: if (this.target.ownerDocument.isSrcdocDocument) { michael@0: this.inSrcdocFrame = true; michael@0: } michael@0: } michael@0: michael@0: // if the document is editable, show context menu like in text inputs michael@0: if (!this.onEditableArea) { michael@0: var win = this.target.ownerDocument.defaultView; michael@0: if (win) { michael@0: var isEditable = false; michael@0: try { michael@0: var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIEditingSession); michael@0: if (editingSession.windowIsEditable(win) && michael@0: this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") { michael@0: isEditable = true; michael@0: } michael@0: } michael@0: catch(ex) { michael@0: // If someone built with composer disabled, we can't get an editing session. michael@0: } michael@0: michael@0: if (isEditable) { michael@0: this.onTextInput = true; michael@0: this.onKeywordField = false; michael@0: this.onImage = false; michael@0: this.onLoadedImage = false; michael@0: this.onCompletedImage = false; michael@0: this.onMathML = false; michael@0: this.inFrame = false; michael@0: this.inSrcdocFrame = false; michael@0: this.hasBGImage = false; michael@0: this.isDesignMode = true; michael@0: this.onEditableArea = true; michael@0: InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win)); michael@0: var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck; michael@0: InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset); michael@0: this.showItem("spell-check-enabled", canSpell); michael@0: this.showItem("spell-separator", canSpell); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Returns the computed style attribute for the given element. michael@0: getComputedStyle: function(aElem, aProp) { michael@0: return aElem.ownerDocument michael@0: .defaultView michael@0: .getComputedStyle(aElem, "").getPropertyValue(aProp); michael@0: }, michael@0: michael@0: // Returns a "url"-type computed style attribute value, with the url() stripped. michael@0: getComputedURL: function(aElem, aProp) { michael@0: var url = aElem.ownerDocument michael@0: .defaultView.getComputedStyle(aElem, "") michael@0: .getPropertyCSSValue(aProp); michael@0: if (url instanceof CSSValueList) { michael@0: if (url.length != 1) michael@0: throw "found multiple URLs"; michael@0: url = url[0]; michael@0: } michael@0: return url.primitiveType == CSSPrimitiveValue.CSS_URI ? michael@0: url.getStringValue() : null; michael@0: }, michael@0: michael@0: // Returns true if clicked-on link targets a resource that can be saved. michael@0: isLinkSaveable: function(aLink) { michael@0: // We don't do the Right Thing for news/snews yet, so turn them off michael@0: // until we do. michael@0: return this.linkProtocol && !( michael@0: this.linkProtocol == "mailto" || michael@0: this.linkProtocol == "javascript" || michael@0: this.linkProtocol == "news" || michael@0: this.linkProtocol == "snews" ); michael@0: }, michael@0: michael@0: _unremotePrincipal: function(aRemotePrincipal) { michael@0: if (this.isRemote) { michael@0: return Cc["@mozilla.org/scriptsecuritymanager;1"] michael@0: .getService(Ci.nsIScriptSecurityManager) michael@0: .getAppCodebasePrincipal(aRemotePrincipal.URI, michael@0: aRemotePrincipal.appId, michael@0: aRemotePrincipal.isInBrowserElement); michael@0: } michael@0: michael@0: return aRemotePrincipal; michael@0: }, michael@0: michael@0: _isSpellCheckEnabled: function(aNode) { michael@0: // We can always force-enable spellchecking on textboxes michael@0: if (this.isTargetATextBox(aNode)) { michael@0: return true; michael@0: } michael@0: // We can never spell check something which is not content editable michael@0: var editable = aNode.isContentEditable; michael@0: if (!editable && aNode.ownerDocument) { michael@0: editable = aNode.ownerDocument.designMode == "on"; michael@0: } michael@0: if (!editable) { michael@0: return false; michael@0: } michael@0: // Otherwise make sure that nothing in the parent chain disables spellchecking michael@0: return aNode.spellcheck; michael@0: }, michael@0: michael@0: // Open linked-to URL in a new window. michael@0: openLink : function () { michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); michael@0: openLinkIn(this.linkURL, "window", michael@0: { charset: doc.characterSet, michael@0: referrerURI: doc.documentURIObject }); michael@0: }, michael@0: michael@0: // Open linked-to URL in a new private window. michael@0: openLinkInPrivateWindow : function () { michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); michael@0: openLinkIn(this.linkURL, "window", michael@0: { charset: doc.characterSet, michael@0: referrerURI: doc.documentURIObject, michael@0: private: true }); michael@0: }, michael@0: michael@0: // Open linked-to URL in a new tab. michael@0: openLinkInTab: function() { michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); michael@0: var referrerURI = doc.documentURIObject; michael@0: michael@0: // if the mixedContentChannel is present and the referring URI passes michael@0: // a same origin check with the target URI, we can preserve the users michael@0: // decision of disabling MCB on a page for it's child tabs. michael@0: var persistDisableMCBInChildTab = false; michael@0: michael@0: if (this.browser.docShell && this.browser.docShell.mixedContentChannel) { michael@0: const sm = Services.scriptSecurityManager; michael@0: try { michael@0: var targetURI = this.linkURI; michael@0: sm.checkSameOriginURI(referrerURI, targetURI, false); michael@0: persistDisableMCBInChildTab = true; michael@0: } michael@0: catch (e) { } michael@0: } michael@0: michael@0: openLinkIn(this.linkURL, "tab", michael@0: { charset: doc.characterSet, michael@0: referrerURI: referrerURI, michael@0: disableMCB: persistDisableMCBInChildTab}); michael@0: }, michael@0: michael@0: // open URL in current tab michael@0: openLinkInCurrent: function() { michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); michael@0: openLinkIn(this.linkURL, "current", michael@0: { charset: doc.characterSet, michael@0: referrerURI: doc.documentURIObject }); michael@0: }, michael@0: michael@0: // Open frame in a new tab. michael@0: openFrameInTab: function() { michael@0: var doc = this.target.ownerDocument; michael@0: var frameURL = doc.location.href; michael@0: var referrer = doc.referrer; michael@0: openLinkIn(frameURL, "tab", michael@0: { charset: doc.characterSet, michael@0: referrerURI: referrer ? makeURI(referrer) : null }); michael@0: }, michael@0: michael@0: // Reload clicked-in frame. michael@0: reloadFrame: function() { michael@0: this.target.ownerDocument.location.reload(); michael@0: }, michael@0: michael@0: // Open clicked-in frame in its own window. michael@0: openFrame: function() { michael@0: var doc = this.target.ownerDocument; michael@0: var frameURL = doc.location.href; michael@0: var referrer = doc.referrer; michael@0: openLinkIn(frameURL, "window", michael@0: { charset: doc.characterSet, michael@0: referrerURI: referrer ? makeURI(referrer) : null }); michael@0: }, michael@0: michael@0: // Open clicked-in frame in the same window. michael@0: showOnlyThisFrame: function() { michael@0: var doc = this.target.ownerDocument; michael@0: var frameURL = doc.location.href; michael@0: michael@0: urlSecurityCheck(frameURL, michael@0: this._unremotePrincipal(this.browser.contentPrincipal), michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: var referrer = doc.referrer; michael@0: openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true, michael@0: referrerURI: referrer ? makeURI(referrer) : null }); michael@0: }, michael@0: michael@0: reload: function(event) { michael@0: if (this.onSocial) { michael@0: // full reload of social provider michael@0: Social._getProviderFromOrigin(this.browser.getAttribute("origin")).reload(); michael@0: } else { michael@0: BrowserReloadOrDuplicate(event); michael@0: } michael@0: }, michael@0: michael@0: // View Partial Source michael@0: viewPartialSource: function(aContext) { michael@0: var focusedWindow = document.commandDispatcher.focusedWindow; michael@0: if (focusedWindow == window) michael@0: focusedWindow = content; michael@0: michael@0: var docCharset = null; michael@0: if (focusedWindow) michael@0: docCharset = "charset=" + focusedWindow.document.characterSet; michael@0: michael@0: // "View Selection Source" and others such as "View MathML Source" michael@0: // are mutually exclusive, with the precedence given to the selection michael@0: // when there is one michael@0: var reference = null; michael@0: if (aContext == "selection") michael@0: reference = focusedWindow.getSelection(); michael@0: else if (aContext == "mathml") michael@0: reference = this.target; michael@0: else michael@0: throw "not reached"; michael@0: michael@0: // unused (and play nice for fragments generated via XSLT too) michael@0: var docUrl = null; michael@0: window.openDialog("chrome://global/content/viewPartialSource.xul", michael@0: "_blank", "scrollbars,resizable,chrome,dialog=no", michael@0: docUrl, docCharset, reference, aContext); michael@0: }, michael@0: michael@0: // Open new "view source" window with the frame's URL. michael@0: viewFrameSource: function() { michael@0: BrowserViewSourceOfDocument(this.target.ownerDocument); michael@0: }, michael@0: michael@0: viewInfo: function() { michael@0: BrowserPageInfo(this.target.ownerDocument.defaultView.top.document); michael@0: }, michael@0: michael@0: viewImageInfo: function() { michael@0: BrowserPageInfo(this.target.ownerDocument.defaultView.top.document, michael@0: "mediaTab", this.target); michael@0: }, michael@0: michael@0: viewImageDesc: function(e) { michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.imageDescURL, michael@0: this._unremotePrincipal(this.browser.contentPrincipal), michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: openUILink(this.imageDescURL, e, { disallowInheritPrincipal: true, michael@0: referrerURI: doc.documentURIObject }); michael@0: }, michael@0: michael@0: viewFrameInfo: function() { michael@0: BrowserPageInfo(this.target.ownerDocument); michael@0: }, michael@0: michael@0: reloadImage: function(e) { michael@0: urlSecurityCheck(this.mediaURL, michael@0: this._unremotePrincipal(this.browser.contentPrincipal), michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: michael@0: if (this.target instanceof Ci.nsIImageLoadingContent) michael@0: this.target.forceReload(); michael@0: }, michael@0: michael@0: // Change current window to the URL of the image, video, or audio. michael@0: viewMedia: function(e) { michael@0: var viewURL; michael@0: michael@0: if (this.onCanvas) michael@0: viewURL = this.target.toDataURL(); michael@0: else { michael@0: viewURL = this.mediaURL; michael@0: urlSecurityCheck(viewURL, michael@0: this._unremotePrincipal(this.browser.contentPrincipal), michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: } michael@0: michael@0: var doc = this.target.ownerDocument; michael@0: openUILink(viewURL, e, { disallowInheritPrincipal: true, michael@0: referrerURI: doc.documentURIObject }); michael@0: }, michael@0: michael@0: saveVideoFrameAsImage: function () { michael@0: let name = ""; michael@0: if (this.mediaURL) { michael@0: try { michael@0: let uri = makeURI(this.mediaURL); michael@0: let url = uri.QueryInterface(Ci.nsIURL); michael@0: if (url.fileBaseName) michael@0: name = decodeURI(url.fileBaseName) + ".jpg"; michael@0: } catch (e) { } michael@0: } michael@0: if (!name) michael@0: name = "snapshot.jpg"; michael@0: var video = this.target; michael@0: var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); michael@0: canvas.width = video.videoWidth; michael@0: canvas.height = video.videoHeight; michael@0: var ctxDraw = canvas.getContext("2d"); michael@0: ctxDraw.drawImage(video, 0, 0); michael@0: saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument); michael@0: }, michael@0: michael@0: fullScreenVideo: function () { michael@0: let video = this.target; michael@0: if (document.mozFullScreenEnabled) michael@0: video.mozRequestFullScreen(); michael@0: }, michael@0: michael@0: leaveDOMFullScreen: function() { michael@0: document.mozCancelFullScreen(); michael@0: }, michael@0: michael@0: // Change current window to the URL of the background image. michael@0: viewBGImage: function(e) { michael@0: urlSecurityCheck(this.bgImageURL, michael@0: this._unremotePrincipal(this.browser.contentPrincipal), michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: var doc = this.target.ownerDocument; michael@0: openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true, michael@0: referrerURI: doc.documentURIObject }); michael@0: }, michael@0: michael@0: disableSetDesktopBackground: function() { michael@0: // Disable the Set as Desktop Background menu item if we're still trying michael@0: // to load the image or the load failed. michael@0: if (!(this.target instanceof Ci.nsIImageLoadingContent)) michael@0: return true; michael@0: michael@0: if (("complete" in this.target) && !this.target.complete) michael@0: return true; michael@0: michael@0: if (this.target.currentURI.schemeIs("javascript")) michael@0: return true; michael@0: michael@0: var request = this.target michael@0: .QueryInterface(Ci.nsIImageLoadingContent) michael@0: .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); michael@0: if (!request) michael@0: return true; michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: setDesktopBackground: function() { michael@0: // Paranoia: check disableSetDesktopBackground again, in case the michael@0: // image changed since the context menu was initiated. michael@0: if (this.disableSetDesktopBackground()) michael@0: return; michael@0: michael@0: var doc = this.target.ownerDocument; michael@0: urlSecurityCheck(this.target.currentURI.spec, michael@0: this._unremotePrincipal(doc.nodePrincipal)); michael@0: michael@0: // Confirm since it's annoying if you hit this accidentally. michael@0: const kDesktopBackgroundURL = michael@0: "chrome://browser/content/setDesktopBackground.xul"; michael@0: #ifdef XP_MACOSX michael@0: // On Mac, the Set Desktop Background window is not modal. michael@0: // Don't open more than one Set Desktop Background window. michael@0: var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] michael@0: .getService(Components.interfaces.nsIWindowMediator); michael@0: var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground"); michael@0: if (dbWin) { michael@0: dbWin.gSetBackground.init(this.target); michael@0: dbWin.focus(); michael@0: } michael@0: else { michael@0: openDialog(kDesktopBackgroundURL, "", michael@0: "centerscreen,chrome,dialog=no,dependent,resizable=no", michael@0: this.target); michael@0: } michael@0: #else michael@0: // On non-Mac platforms, the Set Wallpaper dialog is modal. michael@0: openDialog(kDesktopBackgroundURL, "", michael@0: "centerscreen,chrome,dialog,modal,dependent", michael@0: this.target); michael@0: #endif michael@0: }, michael@0: michael@0: // Save URL of clicked-on frame. michael@0: saveFrame: function () { michael@0: saveDocument(this.target.ownerDocument); michael@0: }, michael@0: michael@0: // Helper function to wait for appropriate MIME-type headers and michael@0: // then prompt the user with a file picker michael@0: saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) { michael@0: // canonical def in nsURILoader.h michael@0: const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020; michael@0: michael@0: // an object to proxy the data through to michael@0: // nsIExternalHelperAppService.doContent, which will wait for the michael@0: // appropriate MIME-type headers and then prompt the user with a michael@0: // file picker michael@0: function saveAsListener() {} michael@0: saveAsListener.prototype = { michael@0: extListener: null, michael@0: michael@0: onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) { michael@0: michael@0: // if the timer fired, the error status will have been caused by that, michael@0: // and we'll be restarting in onStopRequest, so no reason to notify michael@0: // the user michael@0: if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT) michael@0: return; michael@0: michael@0: timer.cancel(); michael@0: michael@0: // some other error occured; notify the user... michael@0: if (!Components.isSuccessCode(aRequest.status)) { michael@0: try { michael@0: const sbs = Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService); michael@0: const bundle = sbs.createBundle( michael@0: "chrome://mozapps/locale/downloads/downloads.properties"); michael@0: michael@0: const title = bundle.GetStringFromName("downloadErrorAlertTitle"); michael@0: const msg = bundle.GetStringFromName("downloadErrorGeneric"); michael@0: michael@0: const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]. michael@0: getService(Ci.nsIPromptService); michael@0: promptSvc.alert(doc.defaultView, title, msg); michael@0: } catch (ex) {} michael@0: return; michael@0: } michael@0: michael@0: var extHelperAppSvc = michael@0: Cc["@mozilla.org/uriloader/external-helper-app-service;1"]. michael@0: getService(Ci.nsIExternalHelperAppService); michael@0: var channel = aRequest.QueryInterface(Ci.nsIChannel); michael@0: this.extListener = michael@0: extHelperAppSvc.doContent(channel.contentType, aRequest, michael@0: doc.defaultView, true); michael@0: this.extListener.onStartRequest(aRequest, aContext); michael@0: }, michael@0: michael@0: onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext, michael@0: aStatusCode) { michael@0: if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) { michael@0: // do it the old fashioned way, which will pick the best filename michael@0: // it can without waiting. michael@0: saveURL(linkURL, linkText, dialogTitle, bypassCache, false, doc.documentURIObject, doc); michael@0: } michael@0: if (this.extListener) michael@0: this.extListener.onStopRequest(aRequest, aContext, aStatusCode); michael@0: }, michael@0: michael@0: onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext, michael@0: aInputStream, michael@0: aOffset, aCount) { michael@0: this.extListener.onDataAvailable(aRequest, aContext, aInputStream, michael@0: aOffset, aCount); michael@0: } michael@0: } michael@0: michael@0: function callbacks() {} michael@0: callbacks.prototype = { michael@0: getInterface: function sLA_callbacks_getInterface(aIID) { michael@0: if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) { michael@0: // If the channel demands authentication prompt, we must cancel it michael@0: // because the save-as-timer would expire and cancel the channel michael@0: // before we get credentials from user. Both authentication dialog michael@0: // and save as dialog would appear on the screen as we fall back to michael@0: // the old fashioned way after the timeout. michael@0: timer.cancel(); michael@0: channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); michael@0: } michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: } michael@0: michael@0: // if it we don't have the headers after a short time, the user michael@0: // won't have received any feedback from their click. that's bad. so michael@0: // we give up waiting for the filename. michael@0: function timerCallback() {} michael@0: timerCallback.prototype = { michael@0: notify: function sLA_timer_notify(aTimer) { michael@0: channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // set up a channel to do the saving michael@0: var ioService = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: var channel = ioService.newChannelFromURI(makeURI(linkURL)); michael@0: if (channel instanceof Ci.nsIPrivateBrowsingChannel) { michael@0: let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView); michael@0: channel.setPrivate(docIsPrivate); michael@0: } michael@0: channel.notificationCallbacks = new callbacks(); michael@0: michael@0: let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; michael@0: michael@0: if (bypassCache) michael@0: flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; michael@0: michael@0: if (channel instanceof Ci.nsICachingChannel) michael@0: flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; michael@0: michael@0: channel.loadFlags |= flags; michael@0: michael@0: if (channel instanceof Ci.nsIHttpChannel) { michael@0: channel.referrer = doc.documentURIObject; michael@0: if (channel instanceof Ci.nsIHttpChannelInternal) michael@0: channel.forceAllowThirdPartyCookie = true; michael@0: } michael@0: michael@0: // fallback to the old way if we don't see the headers quickly michael@0: var timeToWait = michael@0: gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout"); michael@0: var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: timer.initWithCallback(new timerCallback(), timeToWait, michael@0: timer.TYPE_ONE_SHOT); michael@0: michael@0: // kick off the channel with our proxy object as the listener michael@0: channel.asyncOpen(new saveAsListener(), null); michael@0: }, michael@0: michael@0: // Save URL of clicked-on link. michael@0: saveLink: function() { michael@0: var doc = this.target.ownerDocument; michael@0: var linkText; michael@0: // If selected text is found to match valid URL pattern. michael@0: if (this.onPlainTextLink) michael@0: linkText = this.focusedWindow.getSelection().toString().trim(); michael@0: else michael@0: linkText = this.linkText(); michael@0: urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); michael@0: michael@0: this.saveHelper(this.linkURL, linkText, null, true, doc); michael@0: }, michael@0: michael@0: // Backwards-compatibility wrapper michael@0: saveImage : function() { michael@0: if (this.onCanvas || this.onImage) michael@0: this.saveMedia(); michael@0: }, michael@0: michael@0: // Save URL of the clicked upon image, video, or audio. michael@0: saveMedia: function() { michael@0: var doc = this.target.ownerDocument; michael@0: if (this.onCanvas) { michael@0: // Bypass cache, since it's a data: URL. michael@0: saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle", michael@0: true, false, doc.documentURIObject, doc); michael@0: } michael@0: else if (this.onImage) { michael@0: urlSecurityCheck(this.mediaURL, michael@0: this._unremotePrincipal(doc.nodePrincipal)); michael@0: saveImageURL(this.mediaURL, null, "SaveImageTitle", false, michael@0: false, doc.documentURIObject, doc); michael@0: } michael@0: else if (this.onVideo || this.onAudio) { michael@0: urlSecurityCheck(this.mediaURL, michael@0: this._unremotePrincipal(doc.nodePrincipal)); michael@0: var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle"; michael@0: this.saveHelper(this.mediaURL, null, dialogTitle, false, doc); michael@0: } michael@0: }, michael@0: michael@0: // Backwards-compatibility wrapper michael@0: sendImage : function() { michael@0: if (this.onCanvas || this.onImage) michael@0: this.sendMedia(); michael@0: }, michael@0: michael@0: sendMedia: function() { michael@0: MailIntegration.sendMessage(this.mediaURL, ""); michael@0: }, michael@0: michael@0: playPlugin: function() { michael@0: gPluginHandler._showClickToPlayNotification(this.browser, this.target, true); michael@0: }, michael@0: michael@0: hidePlugin: function() { michael@0: gPluginHandler.hideClickToPlayOverlay(this.target); michael@0: }, michael@0: michael@0: // Generate email address and put it on clipboard. michael@0: copyEmail: function() { michael@0: // Copy the comma-separated list of email addresses only. michael@0: // There are other ways of embedding email addresses in a mailto: michael@0: // link, but such complex parsing is beyond us. michael@0: var url = this.linkURL; michael@0: var qmark = url.indexOf("?"); michael@0: var addresses; michael@0: michael@0: // 7 == length of "mailto:" michael@0: addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7); michael@0: michael@0: // Let's try to unescape it using a character set michael@0: // in case the address is not ASCII. michael@0: try { michael@0: var characterSet = this.target.ownerDocument.characterSet; michael@0: const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"]. michael@0: getService(Ci.nsITextToSubURI); michael@0: addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses); michael@0: } michael@0: catch(ex) { michael@0: // Do nothing. michael@0: } michael@0: michael@0: var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Ci.nsIClipboardHelper); michael@0: clipboard.copyString(addresses, document); michael@0: }, michael@0: michael@0: copyLink: function() { michael@0: var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Ci.nsIClipboardHelper); michael@0: clipboard.copyString(this.linkURL, document); michael@0: }, michael@0: michael@0: /////////////// michael@0: // Utilities // michael@0: /////////////// michael@0: michael@0: // Show/hide one item (specified via name or the item element itself). michael@0: showItem: function(aItemOrId, aShow) { michael@0: var item = aItemOrId.constructor == String ? michael@0: document.getElementById(aItemOrId) : aItemOrId; michael@0: if (item) michael@0: item.hidden = !aShow; michael@0: }, michael@0: michael@0: // Set given attribute of specified context-menu item. If the michael@0: // value is null, then it removes the attribute (which works michael@0: // nicely for the disabled attribute). michael@0: setItemAttr: function(aID, aAttr, aVal ) { michael@0: var elem = document.getElementById(aID); michael@0: if (elem) { michael@0: if (aVal == null) { michael@0: // null indicates attr should be removed. michael@0: elem.removeAttribute(aAttr); michael@0: } michael@0: else { michael@0: // Set attr=val. michael@0: elem.setAttribute(aAttr, aVal); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Set context menu attribute according to like attribute of another node michael@0: // (such as a broadcaster). michael@0: setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) { michael@0: var elem = document.getElementById(aOther_id); michael@0: if (elem && elem.getAttribute(aAttr) == "true") michael@0: this.setItemAttr(aItem_id, aAttr, "true"); michael@0: else michael@0: this.setItemAttr(aItem_id, aAttr, null); michael@0: }, michael@0: michael@0: // Temporary workaround for DOM api not yet implemented by XUL nodes. michael@0: cloneNode: function(aItem) { michael@0: // Create another element like the one we're cloning. michael@0: var node = document.createElement(aItem.tagName); michael@0: michael@0: // Copy attributes from argument item to the new one. michael@0: var attrs = aItem.attributes; michael@0: for (var i = 0; i < attrs.length; i++) { michael@0: var attr = attrs.item(i); michael@0: node.setAttribute(attr.nodeName, attr.nodeValue); michael@0: } michael@0: michael@0: // Voila! michael@0: return node; michael@0: }, michael@0: michael@0: // Generate fully qualified URL for clicked-on link. michael@0: getLinkURL: function() { michael@0: var href = this.link.href; michael@0: if (href) michael@0: return href; michael@0: michael@0: href = this.link.getAttributeNS("http://www.w3.org/1999/xlink", michael@0: "href"); michael@0: michael@0: if (!href || !href.match(/\S/)) { michael@0: // Without this we try to save as the current doc, michael@0: // for example, HTML case also throws if empty michael@0: throw "Empty href"; michael@0: } michael@0: michael@0: return makeURLAbsolute(this.link.baseURI, href); michael@0: }, michael@0: michael@0: getLinkURI: function() { michael@0: try { michael@0: return makeURI(this.linkURL); michael@0: } michael@0: catch (ex) { michael@0: // e.g. empty URL string michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: getLinkProtocol: function() { michael@0: if (this.linkURI) michael@0: return this.linkURI.scheme; // can be |undefined| michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: // Get text of link. michael@0: linkText: function() { michael@0: var text = gatherTextUnder(this.link); michael@0: if (!text || !text.match(/\S/)) { michael@0: text = this.link.getAttribute("title"); michael@0: if (!text || !text.match(/\S/)) { michael@0: text = this.link.getAttribute("alt"); michael@0: if (!text || !text.match(/\S/)) michael@0: text = this.linkURL; michael@0: } michael@0: } michael@0: michael@0: return text; michael@0: }, michael@0: michael@0: // Returns true if anything is selected. michael@0: isContentSelection: function() { michael@0: return !this.focusedWindow.getSelection().isCollapsed; michael@0: }, michael@0: michael@0: toString: function () { michael@0: return "contextMenu.target = " + this.target + "\n" + michael@0: "contextMenu.onImage = " + this.onImage + "\n" + michael@0: "contextMenu.onLink = " + this.onLink + "\n" + michael@0: "contextMenu.link = " + this.link + "\n" + michael@0: "contextMenu.inFrame = " + this.inFrame + "\n" + michael@0: "contextMenu.hasBGImage = " + this.hasBGImage + "\n"; michael@0: }, michael@0: michael@0: isDisabledForEvents: function(aNode) { michael@0: let ownerDoc = aNode.ownerDocument; michael@0: return michael@0: ownerDoc.defaultView && michael@0: ownerDoc.defaultView michael@0: .QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIDOMWindowUtils) michael@0: .isNodeDisabledForEvents(aNode); michael@0: }, michael@0: michael@0: isTargetATextBox: function(node) { michael@0: if (node instanceof HTMLInputElement) michael@0: return node.mozIsTextField(false); michael@0: michael@0: return (node instanceof HTMLTextAreaElement); michael@0: }, michael@0: michael@0: isTargetAKeywordField: function(aNode) { michael@0: if (!(aNode instanceof HTMLInputElement)) michael@0: return false; michael@0: michael@0: var form = aNode.form; michael@0: if (!form || aNode.type == "password") michael@0: return false; michael@0: michael@0: var method = form.method.toUpperCase(); michael@0: michael@0: // These are the following types of forms we can create keywords for: michael@0: // michael@0: // method encoding type can create keyword michael@0: // GET * YES michael@0: // * YES michael@0: // POST YES michael@0: // POST application/x-www-form-urlencoded YES michael@0: // POST text/plain NO (a little tricky to do) michael@0: // POST multipart/form-data NO michael@0: // POST everything else YES michael@0: return (method == "GET" || method == "") || michael@0: (form.enctype != "text/plain") && (form.enctype != "multipart/form-data"); michael@0: }, michael@0: michael@0: // Determines whether or not the separator with the specified ID should be michael@0: // shown or not by determining if there are any non-hidden items between it michael@0: // and the previous separator. michael@0: shouldShowSeparator: function (aSeparatorID) { michael@0: var separator = document.getElementById(aSeparatorID); michael@0: if (separator) { michael@0: var sibling = separator.previousSibling; michael@0: while (sibling && sibling.localName != "menuseparator") { michael@0: if (!sibling.hidden) michael@0: return true; michael@0: sibling = sibling.previousSibling; michael@0: } michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: addDictionaries: function() { michael@0: var uri = formatURL("browser.dictionaries.download.url", true); michael@0: michael@0: var locale = "-"; michael@0: try { michael@0: locale = gPrefService.getComplexValue("intl.accept_languages", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: } michael@0: catch (e) { } michael@0: michael@0: var version = "-"; michael@0: try { michael@0: version = Cc["@mozilla.org/xre/app-info;1"]. michael@0: getService(Ci.nsIXULAppInfo).version; michael@0: } michael@0: catch (e) { } michael@0: michael@0: uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version); michael@0: michael@0: var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow"); michael@0: var where = newWindowPref == 3 ? "tab" : "window"; michael@0: michael@0: openUILinkIn(uri, where); michael@0: }, michael@0: michael@0: bookmarkThisPage: function CM_bookmarkThisPage() { michael@0: window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true); michael@0: }, michael@0: michael@0: bookmarkLink: function CM_bookmarkLink() { michael@0: var linkText; michael@0: // If selected text is found to match valid URL pattern. michael@0: if (this.onPlainTextLink) michael@0: linkText = this.focusedWindow.getSelection().toString().trim(); michael@0: else michael@0: linkText = this.linkText(); michael@0: window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL, michael@0: linkText); michael@0: }, michael@0: michael@0: addBookmarkForFrame: function CM_addBookmarkForFrame() { michael@0: var doc = this.target.ownerDocument; michael@0: var uri = doc.documentURIObject; michael@0: michael@0: var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); michael@0: if (itemId == -1) { michael@0: var title = doc.title; michael@0: var description = PlacesUIUtils.getDescriptionFromDocument(doc); michael@0: PlacesUIUtils.showBookmarkDialog({ action: "add" michael@0: , type: "bookmark" michael@0: , uri: uri michael@0: , title: title michael@0: , description: description michael@0: , hiddenRows: [ "description" michael@0: , "location" michael@0: , "loadInSidebar" michael@0: , "keyword" ] michael@0: }, window.top); michael@0: } michael@0: else { michael@0: PlacesUIUtils.showBookmarkDialog({ action: "edit" michael@0: , type: "bookmark" michael@0: , itemId: itemId michael@0: }, window.top); michael@0: } michael@0: }, michael@0: markLink: function CM_markLink(origin) { michael@0: // send link to social, if it is the page url linkURI will be null michael@0: SocialMarks.markLink(origin, this.linkURI ? this.linkURI.spec : null); michael@0: }, michael@0: shareLink: function CM_shareLink() { michael@0: SocialShare.sharePage(null, { url: this.linkURI.spec }); michael@0: }, michael@0: michael@0: shareImage: function CM_shareImage() { michael@0: SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] }); michael@0: }, michael@0: michael@0: shareVideo: function CM_shareVideo() { michael@0: SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL }); michael@0: }, michael@0: michael@0: shareSelect: function CM_shareSelect(selection) { michael@0: SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection }); michael@0: }, michael@0: michael@0: savePageAs: function CM_savePageAs() { michael@0: saveDocument(this.browser.contentDocument); michael@0: }, michael@0: michael@0: printFrame: function CM_printFrame() { michael@0: PrintUtils.print(this.target.ownerDocument.defaultView); michael@0: }, michael@0: michael@0: switchPageDirection: function CM_switchPageDirection() { michael@0: SwitchDocumentDirection(this.browser.contentWindow); michael@0: }, michael@0: michael@0: mediaCommand : function CM_mediaCommand(command, data) { michael@0: var media = this.target; michael@0: michael@0: switch (command) { michael@0: case "play": michael@0: media.play(); michael@0: break; michael@0: case "pause": michael@0: media.pause(); michael@0: break; michael@0: case "mute": michael@0: media.muted = true; michael@0: break; michael@0: case "unmute": michael@0: media.muted = false; michael@0: break; michael@0: case "playbackRate": michael@0: media.playbackRate = data; michael@0: break; michael@0: case "hidecontrols": michael@0: media.removeAttribute("controls"); michael@0: break; michael@0: case "showcontrols": michael@0: media.setAttribute("controls", "true"); michael@0: break; michael@0: case "hidestats": michael@0: case "showstats": michael@0: var event = media.ownerDocument.createEvent("CustomEvent"); michael@0: event.initCustomEvent("media-showStatistics", false, true, command == "showstats"); michael@0: media.dispatchEvent(event); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: copyMediaLocation : function () { michael@0: var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Ci.nsIClipboardHelper); michael@0: clipboard.copyString(this.mediaURL, document); michael@0: }, michael@0: michael@0: get imageURL() { michael@0: if (this.onImage) michael@0: return this.mediaURL; michael@0: return ""; michael@0: }, michael@0: michael@0: // Formats the 'Search for ""' context menu. michael@0: formatSearchContextItem: function() { michael@0: var menuItem = document.getElementById("context-searchselect"); michael@0: var selectedText = this.isTextSelected ? this.textSelected : this.linkText(); michael@0: michael@0: // Store searchTerms in context menu item so we know what to search onclick michael@0: menuItem.searchTerms = selectedText; michael@0: michael@0: if (selectedText.length > 15) michael@0: selectedText = selectedText.substr(0,15) + this.ellipsis; michael@0: michael@0: // Use the current engine if the search bar is visible, the default michael@0: // engine otherwise. michael@0: var engineName = ""; michael@0: var ss = Cc["@mozilla.org/browser/search-service;1"]. michael@0: getService(Ci.nsIBrowserSearchService); michael@0: if (isElementVisible(BrowserSearch.searchBar)) michael@0: engineName = ss.currentEngine.name; michael@0: else michael@0: engineName = ss.defaultEngine.name; michael@0: michael@0: // format "Search for " string to show in menu michael@0: var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch", michael@0: [engineName, michael@0: selectedText]); michael@0: menuItem.label = menuLabel; michael@0: menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey"); michael@0: } michael@0: };