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