nsContextMenu.js
Go to the documentation of this file.
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 #
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
13 #
14 # The Original Code is mozilla.org code.
15 #
16 # The Initial Developer of the Original Code is
17 # Netscape Communications Corporation.
18 # Portions created by the Initial Developer are Copyright (C) 1998
19 # the Initial Developer. All Rights Reserved.
20 #
21 # Contributor(s):
22 # Blake Ross <blake@cs.stanford.edu>
23 # David Hyatt <hyatt@mozilla.org>
24 # Peter Annema <disttsc@bart.nl>
25 # Dean Tessman <dean_tessman@hotmail.com>
26 # Kevin Puetz <puetzk@iastate.edu>
27 # Ben Goodger <ben@netscape.com>
28 # Pierre Chanial <chanial@noos.fr>
29 # Jason Eager <jce2@po.cwru.edu>
30 # Joe Hewitt <hewitt@netscape.com>
31 # Alec Flett <alecf@netscape.com>
32 # Asaf Romano <mozilla.mano@sent.com>
33 # Jason Barnabe <jason_barnabe@fastmail.fm>
34 # Peter Parente <parente@cs.unc.edu>
35 # Giorgio Maone <g.maone@informaction.com>
36 # Tom Germeau <tom.germeau@epigoon.com>
37 # Jesse Ruderman <jruderman@gmail.com>
38 # Joe Hughes <joe@retrovirus.com>
39 # Pamela Greene <pamg.bugs@gmail.com>
40 # Michael Ventnor <ventnors_dogs234@yahoo.com.au>
41 # Simon Bünzli <zeniko@gmail.com>
42 # Gijs Kruitbosch <gijskruitbosch@gmail.com>
43 # Ehsan Akhgari <ehsan.akhgari@gmail.com>
44 # Dan Mosedale <dmose@mozilla.org>
45 # Justin Dolske <dolske@mozilla.com>
46 # Kathleen Brade <brade@pearlcrescent.com>
47 # Mark Smith <mcs@pearlcrescent.com>
48 #
49 # Alternatively, the contents of this file may be used under the terms of
50 # either the GNU General Public License Version 2 or later (the "GPL"), or
51 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
52 # in which case the provisions of the GPL or the LGPL are applicable instead
53 # of those above. If you wish to allow use of your version of this file only
54 # under the terms of either the GPL or the LGPL, and not to allow others to
55 # use your version of this file under the terms of the MPL, indicate your
56 # decision by deleting the provisions above and replace them with the notice
57 # and other provisions required by the GPL or the LGPL. If you do not delete
58 # the provisions above, a recipient may use your version of this file under
59 # the terms of any one of the MPL, the GPL or the LGPL.
60 #
61 # ***** END LICENSE BLOCK *****
62 
63 function nsContextMenu(aXulMenu, aBrowser) {
64  this.target = null;
65  this.browser = null;
66  this.menu = null;
67  this.isFrameImage = false;
68  this.onTextInput = false;
69  this.onKeywordField = false;
70  this.onImage = false;
71  this.onLoadedImage = false;
72  this.onCompletedImage = false;
73  this.onCanvas = false;
74  this.onVideo = false;
75  this.onAudio = false;
76  this.onLink = false;
77  this.onMailtoLink = false;
78  this.onSaveableLink = false;
79  this.onMathML = false;
80  this.link = false;
81  this.linkURL = "";
82  this.linkURI = null;
83  this.linkProtocol = null;
84  this.inFrame = false;
85  this.hasBGImage = false;
86  this.isTextSelected = false;
87  this.isContentSelected = false;
88  this.shouldDisplay = true;
89  this.isDesignMode = false;
90  this.possibleSpellChecking = false;
91  this.ellipsis = "\u2026";
92  try {
93  this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
94  Ci.nsIPrefLocalizedString).data;
95  } catch (e) { }
96 
97  // Initialize new menu.
98  this.initMenu(aXulMenu, aBrowser);
99 }
100 
101 // Prototype for nsContextMenu "class."
102 nsContextMenu.prototype = {
103  // onDestroy is a no-op at this point.
104  onDestroy: function () {
105  },
106 
107  // Initialize context menu.
108  initMenu: function CM_initMenu(aPopup, aBrowser) {
109  this.menu = aPopup;
110  this.browser = aBrowser;
111 
112  this.isFrameImage = document.getElementById("isFrameImage");
113 
114  // Get contextual info.
115  this.setTarget(document.popupNode, document.popupRangeParent,
116  document.popupRangeOffset);
117 
118  this.isTextSelected = this.isTextSelection();
119  this.isContentSelected = this.isContentSelection();
120 
121  // Initialize (disable/remove) menu items.
122  this.initItems();
123  },
124 
125  initItems: function CM_initItems() {
126  this.initOpenItems();
127  this.initNavigationItems();
128  this.initViewItems();
129  this.initMiscItems();
130  this.initSpellingItems();
131  this.initSaveItems();
132  this.initClipboardItems();
133  this.initMediaPlayerItems();
134  },
135 
136  initOpenItems: function CM_initOpenItems() {
137  var isMailtoInternal = false;
138  if (this.onMailtoLink) {
139  var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
140  getService(Ci.nsIExternalProtocolService).
141  getProtocolHandlerInfo("mailto");
142  isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
143  mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
144  (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
145  }
146  var shouldShow = this.onSaveableLink || isMailtoInternal;
147  this.showItem("context-openlink", shouldShow);
148  this.showItem("context-openlinkintab", shouldShow);
149  this.showItem("context-sep-open", shouldShow);
150  },
151 
152  initNavigationItems: function CM_initNavigationItems() {
153  var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
154  this.onCanvas || this.onVideo || this.onAudio ||
155  this.onTextInput);
156  this.showItem("context-back", shouldShow);
157  this.showItem("context-forward", shouldShow);
158  this.showItem("context-reload", shouldShow);
159  this.showItem("context-stop", shouldShow);
160  this.showItem("context-sep-stop", shouldShow);
161 
162  // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
163  //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
164  },
165 
166  initSaveItems: function CM_initSaveItems() {
167  var shouldShow = !(this.onTextInput || this.onLink ||
168  this.isContentSelected || this.onImage ||
169  this.onCanvas || this.onVideo || this.onAudio);
170  this.showItem("context-savepage", shouldShow);
171  this.showItem("context-sendpage", shouldShow);
172 
173  // Save+Send link depends on whether we're in a link.
174  this.showItem("context-savelink", this.onSaveableLink);
175  this.showItem("context-sendlink", this.onSaveableLink);
176 
177  // Save image depends on having loaded its content, video and audio don't.
178  this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
179  this.showItem("context-savevideo", this.onVideo);
180  this.showItem("context-saveaudio", this.onAudio);
181  this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
182  this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
183  // Send media URL (but not for canvas, since it's a big data: URL)
184  this.showItem("context-sendimage", this.onImage);
185  this.showItem("context-sendvideo", this.onVideo);
186  this.showItem("context-sendaudio", this.onAudio);
187  this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
188  this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
189  },
190 
191  initViewItems: function CM_initViewItems() {
192  // View source is always OK, unless in directory listing.
193  this.showItem("context-viewpartialsource-selection",
194  this.isContentSelected);
195  this.showItem("context-viewpartialsource-mathml",
196  this.onMathML && !this.isContentSelected);
197 
198  var shouldShow = !(this.isContentSelected ||
199  this.onImage || this.onCanvas ||
200  this.onVideo || this.onAudio ||
201  this.onLink || this.onTextInput);
202  this.showItem("context-viewsource", shouldShow);
203  this.showItem("context-viewinfo", shouldShow);
204 
205  this.showItem("context-sep-viewsource", shouldShow);
206 
207  // Set as Desktop background depends on whether an image was clicked on,
208  // and only works if we have a shell service.
209  var haveSetDesktopBackground = false;
210 #ifdef HAVE_SHELL_SERVICE
211  // Only enable Set as Desktop Background if we can get the shell service.
212  var shell = getShellService();
213  if (shell)
214  haveSetDesktopBackground = true;
215 #endif
216  this.showItem("context-setDesktopBackground",
217  haveSetDesktopBackground && this.onLoadedImage);
218 
219  if (haveSetDesktopBackground && this.onLoadedImage) {
220  document.getElementById("context-setDesktopBackground")
221  .disabled = this.disableSetDesktopBackground();
222  }
223 
224  // Reload image depends on an image that's not fully loaded
225  this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
226 
227  // View image depends on having an image that's not standalone
228  // (or is in a frame), or a canvas.
229  this.showItem("context-viewimage", (this.onImage &&
230  (!this.onStandaloneImage || this.inFrame)) || this.onCanvas);
231 
232  this.showItem("context-viewvideo", this.onVideo);
233  this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
234 
235  // View background image depends on whether there is one.
236  this.showItem("context-viewbgimage", shouldShow && !this._hasMultipleBGImages);
237  this.showItem("context-sep-viewbgimage", shouldShow && !this._hasMultipleBGImages);
238  document.getElementById("context-viewbgimage")
239  .disabled = !this.hasBGImage;
240 
241  this.showItem("context-viewimageinfo", this.onImage);
242  },
243 
244  initMiscItems: function CM_initMiscItems() {
245  var isTextSelected = this.isTextSelected;
246 
247  // Use "Bookmark This Link" if on a link.
248  this.showItem("context-bookmarkpage",
249  !(this.isContentSelected || this.onTextInput || this.onLink ||
250  this.onImage || this.onVideo || this.onAudio));
251  this.showItem("context-bookmarklink", this.onLink && !this.onMailtoLink);
252  this.showItem("context-searchselect", isTextSelected);
253  this.showItem("context-keywordfield",
254  this.onTextInput && this.onKeywordField);
255  this.showItem("frame", this.inFrame);
256  this.showItem("frame-sep", this.inFrame && isTextSelected);
257 
258  // Hide menu entries for images, show otherwise
259  if (this.inFrame) {
260  if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
261  this.isFrameImage.removeAttribute('hidden');
262  else
263  this.isFrameImage.setAttribute('hidden', 'true');
264  }
265 
266  // BiDi UI
267  this.showItem("context-sep-bidi", top.gBidiUI);
268  this.showItem("context-bidi-text-direction-toggle",
269  this.onTextInput && top.gBidiUI);
270  this.showItem("context-bidi-page-direction-toggle",
271  !this.onTextInput && top.gBidiUI);
272 
273  if (this.onImage) {
274  var blockImage = document.getElementById("context-blockimage");
275 
276  var uri = this.target
277  .QueryInterface(Ci.nsIImageLoadingContent)
278  .currentURI;
279 
280  // this throws if the image URI doesn't have a host (eg, data: image URIs)
281  // see bug 293758 for details
282  var hostLabel = "";
283  try {
284  hostLabel = uri.host;
285  } catch (ex) { }
286 
287  if (hostLabel) {
288  var shortenedUriHost = hostLabel.replace(/^www\./i,"");
289  if (shortenedUriHost.length > 15)
290  shortenedUriHost = shortenedUriHost.substr(0,15) + this.ellipsis;
291  blockImage.label = gNavigatorBundle.getFormattedString("blockImages", [shortenedUriHost]);
292 
293  if (this.isImageBlocked())
294  blockImage.setAttribute("checked", "true");
295  else
296  blockImage.removeAttribute("checked");
297  }
298  }
299 
300  // Only show the block image item if the image can be blocked
301  this.showItem("context-blockimage", this.onImage && hostLabel &&
302  !gPrivateBrowsingUI.privateBrowsingEnabled);
303  },
304 
305  initSpellingItems: function() {
306  var canSpell = InlineSpellCheckerUI.canSpellCheck;
307  var onMisspelling = InlineSpellCheckerUI.overMisspelling;
308  this.showItem("spell-check-enabled", canSpell);
309  this.showItem("spell-separator", canSpell || this.possibleSpellChecking);
310  if (canSpell) {
311  document.getElementById("spell-check-enabled")
312  .setAttribute("checked", InlineSpellCheckerUI.enabled);
313  }
314 
315  this.showItem("spell-add-to-dictionary", onMisspelling);
316 
317  // suggestion list
318  this.showItem("spell-suggestions-separator", onMisspelling);
319  if (onMisspelling) {
320  var menu = document.getElementById("contentAreaContextMenu");
321  var suggestionsSeparator =
322  document.getElementById("spell-add-to-dictionary");
323  var numsug = InlineSpellCheckerUI.addSuggestionsToMenu(menu, suggestionsSeparator, 5);
324  this.showItem("spell-no-suggestions", numsug == 0);
325  }
326  else
327  this.showItem("spell-no-suggestions", false);
328 
329  // dictionary list
330  this.showItem("spell-dictionaries", InlineSpellCheckerUI.enabled);
331  if (canSpell) {
332  var dictMenu = document.getElementById("spell-dictionaries-menu");
333  var dictSep = document.getElementById("spell-language-separator");
334  InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
335  this.showItem("spell-add-dictionaries-main", false);
336  }
337  else if (this.possibleSpellChecking) {
338  // when there is no spellchecker but we might be able to spellcheck
339  // add the add to dictionaries item. This will ensure that people
340  // with no dictionaries will be able to download them
341  this.showItem("spell-add-dictionaries-main", true);
342  }
343  else
344  this.showItem("spell-add-dictionaries-main", false);
345  },
346 
347  initClipboardItems: function() {
348  // Copy depends on whether there is selected text.
349  // Enabling this context menu item is now done through the global
350  // command updating system
351  // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
352  goUpdateGlobalEditMenuItems();
353 
354  this.showItem("context-undo", this.onTextInput);
355  this.showItem("context-sep-undo", this.onTextInput);
356  this.showItem("context-cut", this.onTextInput);
357  this.showItem("context-copy",
358  this.isContentSelected || this.onTextInput);
359  this.showItem("context-paste", this.onTextInput);
360  this.showItem("context-delete", this.onTextInput);
361  this.showItem("context-sep-paste", this.onTextInput);
362  this.showItem("context-selectall", !(this.onLink || this.onImage ||
363  this.onVideo || this.onAudio) || this.isDesignMode);
364  this.showItem("context-sep-selectall", this.isContentSelected );
365 
366  // XXX dr
367  // ------
368  // nsDocumentViewer.cpp has code to determine whether we're
369  // on a link or an image. we really ought to be using that...
370 
371  // Copy email link depends on whether we're on an email link.
372  this.showItem("context-copyemail", this.onMailtoLink);
373 
374  // Copy link location depends on whether we're on a non-mailto link.
375  this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
376  this.showItem("context-sep-copylink", this.onLink &&
377  (this.onImage || this.onVideo || this.onAudio));
378 
379 #ifdef CONTEXT_COPY_IMAGE_CONTENTS
380  // Copy image contents depends on whether we're on an image.
381  this.showItem("context-copyimage-contents", this.onImage);
382 #endif
383  // Copy image location depends on whether we're on an image.
384  this.showItem("context-copyimage", this.onImage);
385  this.showItem("context-copyvideourl", this.onVideo);
386  this.showItem("context-copyaudiourl", this.onAudio);
387  this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
388  this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
389  this.showItem("context-sep-copyimage", this.onImage ||
390  this.onVideo || this.onAudio);
391  },
392 
393  initMediaPlayerItems: function() {
394  var onMedia = (this.onVideo || this.onAudio);
395  // Several mutually exclusive items... play/pause, mute/unmute, show/hide
396  this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended));
397  this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
398  this.showItem("context-media-mute", onMedia && !this.target.muted);
399  this.showItem("context-media-unmute", onMedia && this.target.muted);
400  this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
401  this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
402  this.showItem("context-video-fullscreen", this.onVideo);
403  // Disable them when there isn't a valid media source loaded.
404  if (onMedia) {
405  var hasError = (this.target.error != null);
406  this.setItemAttr("context-media-play", "disabled", hasError);
407  this.setItemAttr("context-media-pause", "disabled", hasError);
408  this.setItemAttr("context-media-mute", "disabled", hasError);
409  this.setItemAttr("context-media-unmute", "disabled", hasError);
410  this.setItemAttr("context-media-showcontrols", "disabled", hasError);
411  this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
412  if (this.onVideo)
413  this.setItemAttr("context-video-fullscreen", "disabled", hasError);
414  }
415  this.showItem("context-media-sep-commands", onMedia);
416  },
417 
418  // Set various context menu attributes based on the state of the world.
419  setTarget: function (aNode, aRangeParent, aRangeOffset) {
420  const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
421  if (aNode.namespaceURI == xulNS ||
422  this.isTargetAFormControl(aNode)) {
423  this.shouldDisplay = false;
424  }
425 
426  // Initialize contextual info.
427  this.onImage = false;
428  this.onLoadedImage = false;
429  this.onCompletedImage = false;
430  this.onStandaloneImage = false;
431  this.onCanvas = false;
432  this.onVideo = false;
433  this.onAudio = false;
434  this.onTextInput = false;
435  this.onKeywordField = false;
436  this.mediaURL = "";
437  this.onLink = false;
438  this.linkURL = "";
439  this.linkURI = null;
440  this.linkProtocol = "";
441  this.onMathML = false;
442  this.inFrame = false;
443  this.hasBGImage = false;
444  this.bgImageURL = "";
445  this.possibleSpellChecking = false;
446 
447  // Clear any old spellchecking items from the menu, this used to
448  // be in the menu hiding code but wasn't getting called in all
449  // situations. Here, we can ensure it gets cleaned up any time the
450  // menu is shown. Note: must be before uninit because that clears the
451  // internal vars
452  InlineSpellCheckerUI.clearSuggestionsFromMenu();
453  InlineSpellCheckerUI.clearDictionaryListFromMenu();
454 
455  InlineSpellCheckerUI.uninit();
456 
457  // Remember the node that was clicked.
458  this.target = aNode;
459 
460  // First, do checks for nodes that never have children.
461  if (this.target.nodeType == Node.ELEMENT_NODE) {
462  // See if the user clicked on an image.
463  if (this.target instanceof Ci.nsIImageLoadingContent &&
464  this.target.currentURI) {
465  this.onImage = true;
466 
467  var request =
468  this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
469  if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
470  this.onLoadedImage = true;
471  if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
472  this.onCompletedImage = true;
473 
474  this.mediaURL = this.target.currentURI.spec;
475  if (this.target.ownerDocument instanceof ImageDocument)
476  this.onStandaloneImage = true;
477  }
478  else if (this.target instanceof HTMLCanvasElement) {
479  this.onCanvas = true;
480  }
481  else if (this.target instanceof HTMLVideoElement) {
482  this.onVideo = true;
483  this.mediaURL = this.target.currentSrc || this.target.src;
484  }
485  else if (this.target instanceof HTMLAudioElement) {
486  this.onAudio = true;
487  this.mediaURL = this.target.currentSrc || this.target.src;
488  }
489  else if (this.target instanceof HTMLInputElement ) {
490  this.onTextInput = this.isTargetATextBox(this.target);
491  // allow spellchecking UI on all writable text boxes except passwords
492  if (this.onTextInput && ! this.target.readOnly &&
493  this.target.type != "password") {
494  this.possibleSpellChecking = true;
495  InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
496  InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
497  }
498  this.onKeywordField = this.isTargetAKeywordField(this.target);
499  }
500  else if (this.target instanceof HTMLTextAreaElement) {
501  this.onTextInput = true;
502  if (!this.target.readOnly) {
503  this.possibleSpellChecking = true;
504  InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
505  InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
506  }
507  }
508  else if (this.target instanceof HTMLHtmlElement) {
509  var bodyElt = this.target.ownerDocument.body;
510  if (bodyElt) {
511  let computedURL;
512  try {
513  computedURL = this.getComputedURL(bodyElt, "background-image");
514  this._hasMultipleBGImages = false;
515  } catch (e) {
516  this._hasMultipleBGImages = true;
517  }
518  if (computedURL) {
519  this.hasBGImage = true;
520  this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
521  computedURL);
522  }
523  }
524  }
525  }
526 
527  // Second, bubble out, looking for items of interest that can have childen.
528  // Always pick the innermost link, background image, etc.
529  const XMLNS = "http://www.w3.org/XML/1998/namespace";
530  var elem = this.target;
531  while (elem) {
532  if (elem.nodeType == Node.ELEMENT_NODE) {
533  // Link?
534  if (!this.onLink &&
535  ((elem instanceof HTMLAnchorElement && elem.href) ||
536  (elem instanceof HTMLAreaElement && elem.href) ||
537  elem instanceof HTMLLinkElement ||
538  elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
539 
540  // Target is a link or a descendant of a link.
541  this.onLink = true;
542 
543  // xxxmpc: this is kind of a hack to work around a Gecko bug (see bug 266932)
544  // we're going to walk up the DOM looking for a parent link node,
545  // this shouldn't be necessary, but we're matching the existing behaviour for left click
546  var realLink = elem;
547  var parent = elem;
548  while ((parent = parent.parentNode) &&
549  (parent.nodeType == Node.ELEMENT_NODE)) {
550  try {
551  if ((parent instanceof HTMLAnchorElement && parent.href) ||
552  (parent instanceof HTMLAreaElement && parent.href) ||
553  parent instanceof HTMLLinkElement ||
554  parent.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")
555  realLink = parent;
556  } catch (e) { }
557  }
558 
559  // Remember corresponding element.
560  this.link = realLink;
561  this.linkURL = this.getLinkURL();
562  this.linkURI = this.getLinkURI();
563  this.linkProtocol = this.getLinkProtocol();
564  this.onMailtoLink = (this.linkProtocol == "mailto");
565  this.onSaveableLink = this.isLinkSaveable( this.link );
566  }
567 
568  // Background image? Don't bother if we've already found a
569  // background image further down the hierarchy. Otherwise,
570  // we look for the computed background-image style.
571  if (!this.hasBGImage &&
572  !this._hasMultipleBGImages) {
573  let bgImgUrl;
574  try {
575  bgImgUrl = this.getComputedURL(elem, "background-image");
576  this._hasMultipleBGImages = false;
577  } catch (e) {
578  this._hasMultipleBGImages = true;
579  }
580  if (bgImgUrl) {
581  this.hasBGImage = true;
582  this.bgImageURL = makeURLAbsolute(elem.baseURI,
583  bgImgUrl);
584  }
585  }
586  }
587 
588  elem = elem.parentNode;
589  }
590 
591  // See if the user clicked on MathML
592  const NS_MathML = "http://www.w3.org/1998/Math/MathML";
593  if ((this.target.nodeType == Node.TEXT_NODE &&
594  this.target.parentNode.namespaceURI == NS_MathML)
595  || (this.target.namespaceURI == NS_MathML))
596  this.onMathML = true;
597 
598  // See if the user clicked in a frame.
599  var docDefaultView = this.target.ownerDocument.defaultView;
600  if (docDefaultView != docDefaultView.top)
601  this.inFrame = true;
602 
603  // if the document is editable, show context menu like in text inputs
604  var win = this.target.ownerDocument.defaultView;
605  if (win) {
606  var isEditable = false;
607  try {
608  var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
609  .getInterface(Ci.nsIWebNavigation)
610  .QueryInterface(Ci.nsIInterfaceRequestor)
611  .getInterface(Ci.nsIEditingSession);
612  if (editingSession.windowIsEditable(win) &&
613  this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
614  isEditable = true;
615  }
616  }
617  catch(ex) {
618  // If someone built with composer disabled, we can't get an editing session.
619  }
620 
621  if (isEditable) {
622  this.onTextInput = true;
623  this.onKeywordField = false;
624  this.onImage = false;
625  this.onLoadedImage = false;
626  this.onCompletedImage = false;
627  this.onMathML = false;
628  this.inFrame = false;
629  this.hasBGImage = false;
630  this.isDesignMode = true;
631  this.possibleSpellChecking = true;
632  InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
633  var canSpell = InlineSpellCheckerUI.canSpellCheck;
634  InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
635  this.showItem("spell-check-enabled", canSpell);
636  this.showItem("spell-separator", canSpell);
637  }
638  }
639  },
640 
641  // Returns the computed style attribute for the given element.
642  getComputedStyle: function(aElem, aProp) {
643  return aElem.ownerDocument
644  .defaultView
645  .getComputedStyle(aElem, "").getPropertyValue(aProp);
646  },
647 
648  // Returns a "url"-type computed style attribute value, with the url() stripped.
649  getComputedURL: function(aElem, aProp) {
650  var url = aElem.ownerDocument
651  .defaultView.getComputedStyle(aElem, "")
652  .getPropertyCSSValue(aProp);
653  if (url instanceof CSSValueList) {
654  if (url.length != 1)
655  throw "found multiple URLs";
656  url = url[0];
657  }
658  return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
659  url.getStringValue() : null;
660  },
661 
662  // Returns true if clicked-on link targets a resource that can be saved.
663  isLinkSaveable: function(aLink) {
664  // We don't do the Right Thing for news/snews yet, so turn them off
665  // until we do.
666  return this.linkProtocol && !(
667  this.linkProtocol == "mailto" ||
668  this.linkProtocol == "javascript" ||
669  this.linkProtocol == "news" ||
670  this.linkProtocol == "snews" );
671  },
672 
673  // Open linked-to URL in a new window.
674  openLink : function () {
675  openNewWindowWith(this.linkURL, this.target.ownerDocument, null, false);
676  },
677 
678  // Open linked-to URL in a new tab.
679  openLinkInTab: function() {
680  openNewTabWith(this.linkURL, this.target.ownerDocument, null, null, false);
681  },
682 
683  // Open frame in a new tab.
684  openFrameInTab: function() {
685  var doc = this.target.ownerDocument;
686  var frameURL = doc.location.href;
687  var referrer = doc.referrer;
688 
689  return openNewTabWith(frameURL, null, null, null, false,
690  referrer ? makeURI(referrer) : null);
691  },
692 
693  // Reload clicked-in frame.
694  reloadFrame: function() {
695  this.target.ownerDocument.location.reload();
696  },
697 
698  // Open clicked-in frame in its own window.
699  openFrame: function() {
700  var doc = this.target.ownerDocument;
701  var frameURL = doc.location.href;
702  var referrer = doc.referrer;
703 
704  return openNewWindowWith(frameURL, null, null, false,
705  referrer ? makeURI(referrer) : null);
706  },
707 
708  // Open clicked-in frame in the same window.
709  showOnlyThisFrame: function() {
710  var doc = this.target.ownerDocument;
711  var frameURL = doc.location.href;
712 
713  urlSecurityCheck(frameURL, this.browser.contentPrincipal,
714  Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
715  var referrer = doc.referrer;
716  this.browser.loadURI(frameURL, referrer ? makeURI(referrer) : null);
717  },
718 
719  // View Partial Source
720  viewPartialSource: function(aContext) {
721  var focusedWindow = document.commandDispatcher.focusedWindow;
722  if (focusedWindow == window)
723  focusedWindow = content;
724 
725  var docCharset = null;
726  if (focusedWindow)
727  docCharset = "charset=" + focusedWindow.document.characterSet;
728 
729  // "View Selection Source" and others such as "View MathML Source"
730  // are mutually exclusive, with the precedence given to the selection
731  // when there is one
732  var reference = null;
733  if (aContext == "selection")
734  reference = focusedWindow.getSelection();
735  else if (aContext == "mathml")
736  reference = this.target;
737  else
738  throw "not reached";
739 
740  // unused (and play nice for fragments generated via XSLT too)
741  var docUrl = null;
742  window.openDialog("chrome://global/content/viewPartialSource.xul",
743  "_blank", "scrollbars,resizable,chrome,dialog=no",
744  docUrl, docCharset, reference, aContext);
745  },
746 
747  // Open new "view source" window with the frame's URL.
748  viewFrameSource: function() {
749  BrowserViewSourceOfDocument(this.target.ownerDocument);
750  },
751 
752  viewInfo: function() {
753  BrowserPageInfo(this.target.ownerDocument.defaultView.top.document);
754  },
755 
756  viewImageInfo: function() {
757  BrowserPageInfo(this.target.ownerDocument.defaultView.top.document,
758  "mediaTab", this.target);
759  },
760 
761  viewFrameInfo: function() {
762  BrowserPageInfo(this.target.ownerDocument);
763  },
764 
765  reloadImage: function(e) {
766  urlSecurityCheck(this.mediaURL,
767  this.browser.contentPrincipal,
768  Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
769 
770  if (this.target instanceof Ci.nsIImageLoadingContent)
771  this.target.forceReload();
772  },
773 
774  // Change current window to the URL of the image, video, or audio.
775  viewMedia: function(e) {
776  var viewURL;
777 
778  if (this.onCanvas)
779  viewURL = this.target.toDataURL();
780  else {
781  viewURL = this.mediaURL;
782  urlSecurityCheck(viewURL,
783  this.browser.contentPrincipal,
784  Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
785  }
786 
787  var doc = this.target.ownerDocument;
788  openUILink(viewURL, e, null, null, null, null, doc.documentURIObject );
789  },
790 
791  fullScreenVideo: function () {
792  this.target.pause();
793 
794  openDialog("chrome://browser/content/fullscreen-video.xhtml",
795  "", "chrome,dialog=no", this.target);
796  },
797 
798  // Change current window to the URL of the background image.
799  viewBGImage: function(e) {
800  urlSecurityCheck(this.bgImageURL,
801  this.browser.contentPrincipal,
802  Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
803  var doc = this.target.ownerDocument;
804  openUILink(this.bgImageURL, e, null, null, null, null, doc.documentURIObject );
805  },
806 
807  disableSetDesktopBackground: function() {
808  // Disable the Set as Desktop Background menu item if we're still trying
809  // to load the image or the load failed.
810  if (!(this.target instanceof Ci.nsIImageLoadingContent))
811  return true;
812 
813  if (("complete" in this.target) && !this.target.complete)
814  return true;
815 
816  if (this.target.currentURI.schemeIs("javascript"))
817  return true;
818 
819  var request = this.target
820  .QueryInterface(Ci.nsIImageLoadingContent)
821  .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
822  if (!request)
823  return true;
824 
825  return false;
826  },
827 
828  setDesktopBackground: function() {
829  // Paranoia: check disableSetDesktopBackground again, in case the
830  // image changed since the context menu was initiated.
831  if (this.disableSetDesktopBackground())
832  return;
833 
834  urlSecurityCheck(this.target.currentURI.spec,
835  this.target.ownerDocument.nodePrincipal);
836 
837  // Confirm since it's annoying if you hit this accidentally.
838  const kDesktopBackgroundURL =
839  "chrome://browser/content/setDesktopBackground.xul";
840 #ifdef XP_MACOSX
841  // On Mac, the Set Desktop Background window is not modal.
842  // Don't open more than one Set Desktop Background window.
843  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
844  .getService(Components.interfaces.nsIWindowMediator);
845  var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
846  if (dbWin) {
847  dbWin.gSetBackground.init(this.target);
848  dbWin.focus();
849  }
850  else {
851  openDialog(kDesktopBackgroundURL, "",
852  "centerscreen,chrome,dialog=no,dependent,resizable=no",
853  this.target);
854  }
855 #else
856  // On non-Mac platforms, the Set Wallpaper dialog is modal.
857  openDialog(kDesktopBackgroundURL, "",
858  "centerscreen,chrome,dialog,modal,dependent",
859  this.target);
860 #endif
861  },
862 
863  // Save URL of clicked-on frame.
864  saveFrame: function () {
865  saveDocument(this.target.ownerDocument);
866  },
867 
868  // Save URL of clicked-on link.
869  saveLink: function() {
870  // canonical def in nsURILoader.h
871  const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
872 
873  var doc = this.target.ownerDocument;
874  urlSecurityCheck(this.linkURL, doc.nodePrincipal);
875  var linkText = this.linkText();
876  var linkURL = this.linkURL;
877 
878 
879  // an object to proxy the data through to
880  // nsIExternalHelperAppService.doContent, which will wait for the
881  // appropriate MIME-type headers and then prompt the user with a
882  // file picker
883  function saveAsListener() {}
884  saveAsListener.prototype = {
885  extListener: null,
886 
887  onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
888 
889  // if the timer fired, the error status will have been caused by that,
890  // and we'll be restarting in onStopRequest, so no reason to notify
891  // the user
892  if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
893  return;
894 
895  timer.cancel();
896 
897  // some other error occured; notify the user...
898  if (!Components.isSuccessCode(aRequest.status)) {
899  try {
900  const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
901  getService(Ci.nsIStringBundleService);
902  const bundle = sbs.createBundle(
903  "chrome://mozapps/locale/downloads/downloads.properties");
904 
905  const title = bundle.GetStringFromName("downloadErrorAlertTitle");
906  const msg = bundle.GetStringFromName("downloadErrorGeneric");
907 
908  const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
909  getService(Ci.nsIPromptService);
910  promptSvc.alert(doc.defaultView, title, msg);
911  } catch (ex) {}
912  return;
913  }
914 
915  var extHelperAppSvc =
916  Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
917  getService(Ci.nsIExternalHelperAppService);
918  var channel = aRequest.QueryInterface(Ci.nsIChannel);
919  this.extListener =
920  extHelperAppSvc.doContent(channel.contentType, aRequest,
921  doc.defaultView, true);
922  this.extListener.onStartRequest(aRequest, aContext);
923  },
924 
925  onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
926  aStatusCode) {
927  if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
928  // do it the old fashioned way, which will pick the best filename
929  // it can without waiting.
930  saveURL(linkURL, linkText, null, true, false, doc.documentURIObject);
931  }
932  if (this.extListener)
933  this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
934  },
935 
936  onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
937  aInputStream,
938  aOffset, aCount) {
939  this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
940  aOffset, aCount);
941  }
942  }
943 
944  // in case we need to prompt the user for authentication
945  function callbacks() {}
946  callbacks.prototype = {
947  getInterface: function sLA_callbacks_getInterface(aIID) {
948  if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
949  var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
950  getService(Ci.nsIPromptFactory);
951  return ww.getPrompt(doc.defaultView, aIID);
952  }
953  throw Cr.NS_ERROR_NO_INTERFACE;
954  }
955  }
956 
957  // if it we don't have the headers after a short time, the user
958  // won't have received any feedback from their click. that's bad. so
959  // we give up waiting for the filename.
960  function timerCallback() {}
961  timerCallback.prototype = {
962  notify: function sLA_timer_notify(aTimer) {
963  channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
964  return;
965  }
966  }
967 
968  // set up a channel to do the saving
969  var ioService = Cc["@mozilla.org/network/io-service;1"].
970  getService(Ci.nsIIOService);
971  var channel = ioService.newChannelFromURI(this.getLinkURI());
972  channel.notificationCallbacks = new callbacks();
973  channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
974  Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
975  if (channel instanceof Ci.nsIHttpChannel) {
976  channel.referrer = doc.documentURIObject;
977  if (channel instanceof Ci.nsIHttpChannelInternal)
978  channel.forceAllowThirdPartyCookie = true;
979  }
980 
981  // fallback to the old way if we don't see the headers quickly
982  var timeToWait =
983  gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
984  var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
985  timer.initWithCallback(new timerCallback(), timeToWait,
986  timer.TYPE_ONE_SHOT);
987 
988  // kick off the channel with our proxy object as the listener
989  channel.asyncOpen(new saveAsListener(), null);
990  },
991 
992  sendLink: function() {
993  // we don't know the title of the link so pass in an empty string
994  MailIntegration.sendMessage( this.linkURL, "" );
995  },
996 
997  // Backwards-compatability wrapper
998  saveImage : function() {
999  if (this.onCanvas || this.onImage)
1000  this.saveMedia();
1001  },
1002 
1003  // Save URL of the clicked upon image, video, or audio.
1004  saveMedia: function() {
1005  var doc = this.target.ownerDocument;
1006  if (this.onCanvas) {
1007  // Bypass cache, since it's a data: URL.
1008  saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle",
1009  true, false, doc.documentURIObject);
1010  }
1011  else if (this.onImage) {
1012  urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
1013  saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
1014  false, doc.documentURIObject);
1015  }
1016  else if (this.onVideo || this.onAudio) {
1017  urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
1018  var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
1019  saveURL(this.mediaURL, null, dialogTitle, false,
1020  false, doc.documentURIObject);
1021  }
1022  },
1023 
1024  // Backwards-compatability wrapper
1025  sendImage : function() {
1026  if (this.onCanvas || this.onImage)
1027  this.sendMedia();
1028  },
1029 
1030  sendMedia: function() {
1031  MailIntegration.sendMessage(this.mediaURL, "");
1032  },
1033 
1034  toggleImageBlocking: function(aBlock) {
1035  var permissionmanager = Cc["@mozilla.org/permissionmanager;1"].
1036  getService(Ci.nsIPermissionManager);
1037 
1038  var uri = this.target.QueryInterface(Ci.nsIImageLoadingContent).currentURI;
1039 
1040  if (aBlock)
1041  permissionmanager.add(uri, "image", Ci.nsIPermissionManager.DENY_ACTION);
1042  else
1043  permissionmanager.remove(uri.host, "image");
1044 
1045  var brandBundle = document.getElementById("bundle_brand");
1046  var app = brandBundle.getString("brandShortName");
1047  var message = gNavigatorBundle.getFormattedString(aBlock ?
1048  "imageBlockedWarning" : "imageAllowedWarning", [app, uri.host]);
1049 
1050  var notificationBox = this.browser.getNotificationBox();
1051  var notification = notificationBox.getNotificationWithValue("images-blocked");
1052 
1053  if (notification)
1054  notification.label = message;
1055  else {
1056  var self = this;
1057  var buttons = [{
1058  label: gNavigatorBundle.getString("undo"),
1059  accessKey: gNavigatorBundle.getString("undo.accessKey"),
1060  callback: function() { self.toggleImageBlocking(!aBlock); }
1061  }];
1062  const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
1063  notificationBox.appendNotification(message, "images-blocked",
1064  "chrome://browser/skin/Info.png",
1065  priority, buttons);
1066  }
1067 
1068  // Reload the page to show the effect instantly
1069  BrowserReload();
1070  },
1071 
1072  isImageBlocked: function() {
1073  var permissionmanager = Cc["@mozilla.org/permissionmanager;1"].
1074  getService(Ci.nsIPermissionManager);
1075 
1076  var uri = this.target.QueryInterface(Ci.nsIImageLoadingContent).currentURI;
1077 
1078  return permissionmanager.testPermission(uri, "image") == Ci.nsIPermissionManager.DENY_ACTION;
1079  },
1080 
1081  // Generate email address and put it on clipboard.
1082  copyEmail: function() {
1083  // Copy the comma-separated list of email addresses only.
1084  // There are other ways of embedding email addresses in a mailto:
1085  // link, but such complex parsing is beyond us.
1086  var url = this.linkURL;
1087  var qmark = url.indexOf("?");
1088  var addresses;
1089 
1090  // 7 == length of "mailto:"
1091  addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
1092 
1093  // Let's try to unescape it using a character set
1094  // in case the address is not ASCII.
1095  try {
1096  var characterSet = this.target.ownerDocument.characterSet;
1097  const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
1098  getService(Ci.nsITextToSubURI);
1099  addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
1100  }
1101  catch(ex) {
1102  // Do nothing.
1103  }
1104 
1105  var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
1106  getService(Ci.nsIClipboardHelper);
1107  clipboard.copyString(addresses);
1108  },
1109 
1111  // Utilities //
1113 
1114  // Show/hide one item (specified via name or the item element itself).
1115  showItem: function(aItemOrId, aShow) {
1116  var item = aItemOrId.constructor == String ?
1117  document.getElementById(aItemOrId) : aItemOrId;
1118  if (item)
1119  item.hidden = !aShow;
1120  },
1121 
1122  // Set given attribute of specified context-menu item. If the
1123  // value is null, then it removes the attribute (which works
1124  // nicely for the disabled attribute).
1125  setItemAttr: function(aID, aAttr, aVal ) {
1126  var elem = document.getElementById(aID);
1127  if (elem) {
1128  if (aVal == null) {
1129  // null indicates attr should be removed.
1130  elem.removeAttribute(aAttr);
1131  }
1132  else {
1133  // Set attr=val.
1134  elem.setAttribute(aAttr, aVal);
1135  }
1136  }
1137  },
1138 
1139  // Set context menu attribute according to like attribute of another node
1140  // (such as a broadcaster).
1141  setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
1142  var elem = document.getElementById(aOther_id);
1143  if (elem && elem.getAttribute(aAttr) == "true")
1144  this.setItemAttr(aItem_id, aAttr, "true");
1145  else
1146  this.setItemAttr(aItem_id, aAttr, null);
1147  },
1148 
1149  // Temporary workaround for DOM api not yet implemented by XUL nodes.
1150  cloneNode: function(aItem) {
1151  // Create another element like the one we're cloning.
1152  var node = document.createElement(aItem.tagName);
1153 
1154  // Copy attributes from argument item to the new one.
1155  var attrs = aItem.attributes;
1156  for (var i = 0; i < attrs.length; i++) {
1157  var attr = attrs.item(i);
1158  node.setAttribute(attr.nodeName, attr.nodeValue);
1159  }
1160 
1161  // Voila!
1162  return node;
1163  },
1164 
1165  // Generate fully qualified URL for clicked-on link.
1166  getLinkURL: function() {
1167  var href = this.link.href;
1168  if (href)
1169  return href;
1170 
1171  href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
1172  "href");
1173 
1174  if (!href || !href.match(/\S/)) {
1175  // Without this we try to save as the current doc,
1176  // for example, HTML case also throws if empty
1177  throw "Empty href";
1178  }
1179 
1180  return makeURLAbsolute(this.link.baseURI, href);
1181  },
1182 
1183  getLinkURI: function() {
1184  try {
1185  return makeURI(this.linkURL);
1186  }
1187  catch (ex) {
1188  // e.g. empty URL string
1189  }
1190 
1191  return null;
1192  },
1193 
1194  getLinkProtocol: function() {
1195  if (this.linkURI)
1196  return this.linkURI.scheme; // can be |undefined|
1197 
1198  return null;
1199  },
1200 
1201  // Get text of link.
1202  linkText: function() {
1203  var text = gatherTextUnder(this.link);
1204  if (!text || !text.match(/\S/)) {
1205  text = this.link.getAttribute("title");
1206  if (!text || !text.match(/\S/)) {
1207  text = this.link.getAttribute("alt");
1208  if (!text || !text.match(/\S/))
1209  text = this.linkURL;
1210  }
1211  }
1212 
1213  return text;
1214  },
1215 
1216  // Get selected text. Only display the first 15 chars.
1217  isTextSelection: function() {
1218  // Get 16 characters, so that we can trim the selection if it's greater
1219  // than 15 chars
1220  var selectedText = getBrowserSelection(16);
1221 
1222  if (!selectedText)
1223  return false;
1224 
1225  if (selectedText.length > 15)
1226  selectedText = selectedText.substr(0,15) + this.ellipsis;
1227 
1228  // Use the current engine if the search bar is visible, the default
1229  // engine otherwise.
1230  var engineName = "";
1231  var ss = Cc["@mozilla.org/browser/search-service;1"].
1232  getService(Ci.nsIBrowserSearchService);
1233  if (isElementVisible(BrowserSearch.searchBar))
1234  engineName = ss.currentEngine.name;
1235  else
1236  engineName = ss.defaultEngine.name;
1237 
1238  // format "Search <engine> for <selection>" string to show in menu
1239  var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearchText",
1240  [engineName,
1241  selectedText]);
1242  document.getElementById("context-searchselect").label = menuLabel;
1243  document.getElementById("context-searchselect").accessKey =
1244  gNavigatorBundle.getString("contextMenuSearchText.accesskey");
1245 
1246  return true;
1247  },
1248 
1249  // Returns true if anything is selected.
1250  isContentSelection: function() {
1251  return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
1252  },
1253 
1254  toString: function () {
1255  return "contextMenu.target = " + this.target + "\n" +
1256  "contextMenu.onImage = " + this.onImage + "\n" +
1257  "contextMenu.onLink = " + this.onLink + "\n" +
1258  "contextMenu.link = " + this.link + "\n" +
1259  "contextMenu.inFrame = " + this.inFrame + "\n" +
1260  "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
1261  },
1262 
1263  // Returns true if aNode is a from control (except text boxes and images).
1264  // This is used to disable the context menu for form controls.
1265  isTargetAFormControl: function(aNode) {
1266  if (aNode instanceof HTMLInputElement)
1267  return (aNode.type != "text" && aNode.type != "password" &&
1268  aNode.type != "image");
1269 
1270  return (aNode instanceof HTMLButtonElement) ||
1271  (aNode instanceof HTMLSelectElement) ||
1272  (aNode instanceof HTMLOptionElement) ||
1273  (aNode instanceof HTMLOptGroupElement);
1274  },
1275 
1276  isTargetATextBox: function(node) {
1277  if (node instanceof HTMLInputElement)
1278  return (node.type == "text" || node.type == "password")
1279 
1280  return (node instanceof HTMLTextAreaElement);
1281  },
1282 
1283  isTargetAKeywordField: function(aNode) {
1284  if (!(aNode instanceof HTMLInputElement))
1285  return false;
1286 
1287  var form = aNode.form;
1288  if (!form || aNode.type == "password")
1289  return false;
1290 
1291  var method = form.method.toUpperCase();
1292 
1293  // These are the following types of forms we can create keywords for:
1294  //
1295  // method encoding type can create keyword
1296  // GET * YES
1297  // * YES
1298  // POST YES
1299  // POST application/x-www-form-urlencoded YES
1300  // POST text/plain NO (a little tricky to do)
1301  // POST multipart/form-data NO
1302  // POST everything else YES
1303  return (method == "GET" || method == "") ||
1304  (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
1305  },
1306 
1307  // Determines whether or not the separator with the specified ID should be
1308  // shown or not by determining if there are any non-hidden items between it
1309  // and the previous separator.
1310  shouldShowSeparator: function (aSeparatorID) {
1311  var separator = document.getElementById(aSeparatorID);
1312  if (separator) {
1313  var sibling = separator.previousSibling;
1314  while (sibling && sibling.localName != "menuseparator") {
1315  if (!sibling.hidden)
1316  return true;
1317  sibling = sibling.previousSibling;
1318  }
1319  }
1320  return false;
1321  },
1322 
1323  addDictionaries: function() {
1324  var uri = formatURL("browser.dictionaries.download.url", true);
1325 
1326  var locale = "-";
1327  try {
1328  locale = gPrefService.getComplexValue("intl.accept_languages",
1329  Ci.nsIPrefLocalizedString).data;
1330  }
1331  catch (e) { }
1332 
1333  var version = "-";
1334  try {
1335  version = Cc["@mozilla.org/xre/app-info;1"].
1336  getService(Ci.nsIXULAppInfo).version;
1337  }
1338  catch (e) { }
1339 
1340  uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
1341 
1342  var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
1343  var where = newWindowPref == 3 ? "tab" : "window";
1344 
1345  openUILinkIn(uri, where);
1346  },
1347 
1348  bookmarkThisPage: function CM_bookmarkThisPage() {
1349  window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
1350  },
1351 
1352  bookmarkLink: function CM_bookmarkLink() {
1353  window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL,
1354  this.linkText());
1355  },
1356 
1357  addBookmarkForFrame: function CM_addBookmarkForFrame() {
1358  var doc = this.target.ownerDocument;
1359  var uri = doc.documentURIObject;
1360 
1361  var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
1362  if (itemId == -1) {
1363  var title = doc.title;
1364  var description = PlacesUIUtils.getDescriptionFromDocument(doc);
1365 
1366  var descAnno = { name: DESCRIPTION_ANNO, value: description };
1367  var txn = PlacesUIUtils.ptm.createItem(uri,
1368  PlacesUtils.bookmarksMenuFolderId,
1369  -1, title, null, [descAnno]);
1370  PlacesUIUtils.ptm.doTransaction(txn);
1371  itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
1372  StarUI.beginBatch();
1373  }
1374 
1375  window.top.StarUI.showEditBookmarkPopup(itemId, this.browser, "overlap");
1376  },
1377 
1378  savePageAs: function CM_savePageAs() {
1379  saveDocument(this.browser.contentDocument);
1380  },
1381 
1382  sendPage: function CM_sendPage() {
1383  MailIntegration.sendLinkForWindow(this.browser.contentWindow);
1384  },
1385 
1386  printFrame: function CM_printFrame() {
1387  PrintUtils.print(this.target.ownerDocument.defaultView);
1388  },
1389 
1390  switchPageDirection: function CM_switchPageDirection() {
1391  SwitchDocumentDirection(this.browser.contentWindow);
1392  },
1393 
1394  mediaCommand : function CM_mediaCommand(command) {
1395  var media = this.target;
1396 
1397  switch (command) {
1398  case "play":
1399  media.play();
1400  break;
1401  case "pause":
1402  media.pause();
1403  break;
1404  case "mute":
1405  media.muted = true;
1406  break;
1407  case "unmute":
1408  media.muted = false;
1409  break;
1410  case "hidecontrols":
1411  media.removeAttribute("controls");
1412  break;
1413  case "showcontrols":
1414  media.setAttribute("controls", "true");
1415  break;
1416  }
1417  },
1418 
1419  copyMediaLocation : function () {
1420  var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
1421  getService(Ci.nsIClipboardHelper);
1422  clipboard.copyString(this.mediaURL);
1423  },
1424 
1425  get imageURL() {
1426  if (this.onImage)
1427  return this.mediaURL;
1428  return "";
1429  }
1430 };
var PlacesUIUtils
Definition: utils.js:85
function saveMedia()
Definition: pageInfo.js:715
const Cc
function openNewTabWith(aURL, aDocument, aPostData, aEvent, aAllowThirdPartyFixup, aReferrer)
function BrowserReload()
function mimeTypeIsTextBased(aMimeType)
Definition: browser.js:3839
function gatherTextUnder(root)
#define DESCRIPTION_ANNO
function openUILinkIn(url, where, allowThirdPartyFixup, postData, referrerUrl)
function doc() browser.contentDocument
return elem[name]
var MailIntegration
Definition: browser.js:5773
function makeURLAbsolute(aBase, aUrl)
var menu
function formatURL(aFormat, aIsPref)
Definition: browser.js:6398
function getBrowserSelection(aCharLen)
Definition: browser.js:4749
var ioService
version(170)
function BrowserPageInfo(doc, initialTab, imageElement)
Definition: browser.js:2128
getService(Ci.sbIFaceplateManager)
let window
function BrowserViewSourceOfDocument(aDocument)
Definition: browser.js:2080
TimerLoop prototype notify
this _contentSandbox label
Definition: FeedWriter.js:814
var bundle
function makeURI(aURLSpec, aCharset)
Definition: FeedWriter.js:71
function isElementVisible(aElement)
Element Properties href
function openUILink(url, e, ignoreButton, ignoreAlt, allowKeywordFixup, postData, referrerUrl)
grep callback
let gPrivateBrowsingUI
Definition: browser.js:6953
GstMessage * message
return null
Definition: FeedWriter.js:1143
let node
var gPrefService
Definition: overlay.js:34
function url(spec)
var uri
Definition: FeedWriter.js:1135
_updateTextAndScrollDataForTab aBrowser
countRef value
Definition: FeedWriter.js:1423
const Cr
const XMLNS
Definition: pageInfo.js:221
function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer)
const Ci
var gNavigatorBundle
function getShellService()
restoreHistoryPrecursor aCount
function nsContextMenu(aXulMenu, aBrowser)
function msg
var browser
Definition: openLocation.js:42
function SwitchDocumentDirection(aWindow)
Definition: browser.js:5899
_getSelectedPageStyle s i
var brandBundle
Definition: xpInstallHat.js:37
var StarUI