browser/base/content/nsContextMenu.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

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

mercurial