!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 //@line 42 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/mail/base/content/nsContextMenu.js"
2
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
3 function nsContextMenu(aXulMenu) {
4 this.target = null;
5 this.menu = null;
6 this.onTextInput = false;
7 this.onImage = false;
8 this.onLoadedImage = false;
9 this.onLink = false;
10 this.onMailtoLink = false;
11 this.onSaveableLink = false;
12 this.onMetaDataItem = false;
13 this.onMathML = false;
14 this.link = false;
15 this.linkURL = "";
16 this.linkURI = null;
17 this.linkProtocol = null;
18 this.inFrame = false;
19 this.hasBGImage = false;
20 this.isTextSelected = false;
21 this.inDirList = false;
22 this.shouldDisplay = true;
23
24 this.initMenu(aXulMenu);
25 }
26
27 nsContextMenu.prototype = {
28 /**
29 * Init: set properties based on the clicked-on element and the state of
30 * the world, then determine which context menu items to show based on
31 * those properties.
32 */
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
33 initMenu : function CM_initMenu(aPopup) {
34 this.menu = aPopup;
35
36 // Get contextual info.
37 this.setTarget(document.popupNode);
38 this.isTextSelected = this.isTextSelection();
39
40 this.initItems();
41 },
CM_initItems
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
42 initItems : function CM_initItems() {
43 this.initSaveItems();
44 this.initClipboardItems();
45 },
CM_initSaveItems
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
46 initSaveItems : function CM_initSaveItems() {
47 this.showItem("context-savelink", this.onSaveableLink);
48 this.showItem("context-saveimage", this.onLoadedImage);
49 },
CM_initClipboardItems
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
50 initClipboardItems : function CM_initClipboardItems() {
51 // Copy depends on whether there is selected text.
52 // Enabling this context menu item is now done through the global
53 // command updating system.
54
55 goUpdateGlobalEditMenuItems();
56
57 this.showItem("context-copy", this.isTextSelected || this.onTextInput);
58 this.showItem("context-selectall", true);
59 this.showItem("context-copyemail", this.onMailtoLink);
60 this.showItem("context-copylink", this.onLink);
61 this.showItem("context-copyimage", this.onImage);
62 },
63
64 /**
65 * Set the nsContextMenu properties based on the selected node and
66 * its ancestors.
67 */
CM_setTarget
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
68 setTarget : function CM_setTarget(aNode) {
69 const xulNS =
70 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
71 if (aNode.namespaceURI == xulNS) {
72 this.shouldDisplay = false;
73 return;
74 }
75 this.onImage = false;
76 this.onLoadedImage = false;
77 this.onMetaDataItem = false;
78 this.onTextInput = false;
79 this.imageURL = "";
80 this.onLink = false;
81 this.linkURL = "";
82 this.linkURI = null;
83 this.linkProtocol = null;
84 this.onMathML = false;
85 this.inFrame = false;
86 this.hasBGImage = false;
87 this.bgImageURL = "";
88
89 this.target = aNode;
90
91 // First, do checks for nodes that never have children.
92 if (this.target.nodeType == Node.ELEMENT_NODE) {
93 if (this.target instanceof Components.interfaces.nsIImageLoadingContent &&
94 this.target.currentURI) {
95 this.onImage = true;
96 this.onMetaDataItem = true;
97
98 var request = this.target.getRequest(Components.interfaces.nsIImageLoadingContent.CURRENT_REQUEST);
99 if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
100 this.onLoadedImage = true;
101
102 this.imageURL = this.target.currentURI.spec;
103 } else if (this.target instanceof HTMLInputElement) {
104 this.onTextInput = this.isTargetATextBox(this.target);
105 } else if (this.target instanceof HTMLTextAreaElement) {
106 this.onTextInput = true;
107 } else if (this.target instanceof HTMLHtmlElement) {
108 var bodyElt = this.target.ownerDocument.body;
109 if (bodyElt) {
110 var computedURL = this.getComputedURL(bodyElt, "background-image");
111 if (computedURL) {
112 this.hasBGImage = true;
113 this.bgImageURL = this.makeURLAbsolute(bodyElt.baseURI,
114 computedURL);
115 }
116 }
117 } else if ("HTTPIndex" in content &&
118 content.HTTPIndex instanceof Components.interfaces.nsIHTTPIndex) {
119 this.inDirList = true;
120 // Bubble outward till we get to an element with URL attribute
121 // (which should be the href).
122 var root = this.target;
123 while (root && !this.link) {
124 if (root.tagName == "tree") {
125 // Hit root of tree; must have clicked in empty space;
126 // thus, no link.
127 break;
128 }
129 if (root.getAttribute("URL")) {
130 // Build pseudo link object so link-related functions work.
131 this.onLink = true;
132 this.link = { href : root.getAttribute("URL"),
getAttribute
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
133 getAttribute: function (aAttr) {
134 if (aAttr == "title") {
135 return root.firstChild.firstChild
136 .getAttribute("label");
137 }
138 return "";
139 }
140 };
141 // If element is a directory, then you can't save it.
142 this.onSaveableLink = root.getAttribute("container") != "true";
143 } else {
144 root = root.parentNode;
145 }
146 }
147 }
148 }
149
150 // Second, bubble out, looking for items of interest that might be
151 // parents of the click target, picking the innermost of each.
152 const XMLNS = "http://www.w3.org/XML/1998/namespace";
153 var elem = this.target;
154 while (elem) {
155 if (elem.nodeType == Node.ELEMENT_NODE) {
156 // Link?
157 if (!this.onLink &&
158 ((elem instanceof HTMLAnchorElement && elem.href) ||
159 elem instanceof HTMLAreaElement && elem.href ||
160 elem instanceof HTMLLinkElement ||
161 elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
162
163 // Target is a link or a descendant of a link.
164 this.onLink = true;
165 this.onMetaDataItem = true;
166 // Remember corresponding element.
167 this.link = elem;
168 this.linkURL = this.getLinkURL();
169 this.linkURI = this.getLinkURI();
170 this.linkProtocol = this.getLinkProtocol();
171 this.onMailtoLink = (this.linkProtocol == "mailto");
172 this.onSaveableLink = this.isLinkSaveable();
173 }
174
175 // Text input?
176 if (!this.onTextInput) {
177 this.onTextInput = this.isTargetATextBox(elem);
178 }
179
180 // Metadata item?
181 if (!this.onMetaDataItem) {
182 if ((elem instanceof HTMLQuoteElement && elem.cite) ||
183 (elem instanceof HTMLTableElement && elem.summary) ||
184 (elem instanceof HTMLModElement &&
185 (elem.cite || elem.dateTime)) ||
186 (elem instanceof HTMLElement &&
187 (elem.title || elem.lang)) ||
188 (elem.getAttributeNS(XMLNS, "lang"))) {
189 this.onMetaDataItem = true;
190 }
191 }
192
193 // Background image? Don't bother if we've already found a
194 // background image further down the hierarchy. Otherwise,
195 // we look for the computed background-image style.
196 if (!this.hasBGImage) {
197 var bgImgUrl = this.getComputedURL(elem, "background-image");
198 if (bgImgUrl) {
199 this.hasBGImage = true;
200 this.bgImageURL = this.makeURLAbsolute(elem.baseURI, bgImgUrl);
201 }
202 }
203 }
204 elem = elem.parentNode;
205 }
206
207 // See if the user clicked on MathML.
208 const NS_MathML = "http://www.w3.org/1998/Math/MathML";
209 if ((this.target.nodeType == Node.TEXT_NODE &&
210 this.target.parentNode.namespaceURI == NS_MathML) ||
211 (this.target.namespaceURI == NS_MathML))
212 this.onMathML = true;
213
214 // See if the user clicked in a frame.
215 if (this.target.ownerDocument != window.content.document) {
216 this.inFrame = true;
217 }
218 },
219
220 /**
221 * Get a computed style property for an element.
222 * @param aElem
223 * A DOM node
224 * @param aProp
225 * The desired CSS property
226 * @return the value of the property
227 */
CM_getComputedStyle
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
228 getComputedStyle: function CM_getComputedStyle(aElem, aProp) {
229 return aElem.ownerDocument.defaultView.getComputedStyle(aElem, "")
230 .getPropertyValue(aProp);
231 },
232
233 /**
234 * Generate a URL string from a computed style property, for things like
235 * |style="background-image:url(...)"|
236 * @return a "url"-type computed style attribute value, with the "url(" and
237 * ")" stripped.
238 */
CM_getComputedURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
239 getComputedURL: function CM_getComputedURL(aElem, aProp) {
240 var url = aElem.ownerDocument.defaultView.getComputedStyle(aElem, "")
241 .getPropertyCSSValue(aProp);
242 return (url.primitiveType == CSSPrimitiveValue.CSS_URI) ? url.getStringValue() : null;
243 },
244
245 /**
246 * Determine whether the clicked-on link can be saved, and whether it
247 * may be saved according to the ScriptSecurityManager.
248 * @return true if the protocol can be persisted and if the target has
249 * permission to link to the URL, false if not
250 */
CM_isLinkSaveable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
251 isLinkSaveable : function CM_isLinkSaveable() {
252 try {
253 const nsIScriptSecurityManager =
254 Components.interfaces.nsIScriptSecurityManager;
255 var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
256 .getService(nsIScriptSecurityManager);
257 secMan.checkLoadURIWithPrincipal(this.target.nodePrincipal, this.linkURI,
258 nsIScriptSecurityManager.STANDARD);
259 } catch (e) {
260 // Don't save things we can't link to.
261 return false;
262 }
263
264 // We don't do the Right Thing for news/snews yet, so turn them off
265 // until we do.
266 return this.linkProtocol && !(
267 this.linkProtocol == "mailto" ||
268 this.linkProtocol == "javascript" ||
269 this.linkProtocol == "news" ||
270 this.linkProtocol == "snews");
271 },
272
273 /**
274 * Save URL of clicked-on link.
275 */
CM_saveLink
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
276 saveLink : function CM_saveLink() {
277 saveURL(this.linkURL, this.linkText(), null, true);
278 },
279
280 /**
281 * Save a clicked-on image.
282 */
CM_saveImage
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
283 saveImage : function CM_saveImage() {
284 saveURL(this.imageURL, null, "SaveImageTitle", false);
285 },
286
287 /**
288 * Extract email addresses from a mailto: link and put them on the
289 * clipboard.
290 */
CM_copyEmail
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
291 copyEmail : function CM_copyEmail() {
292 // Copy the comma-separated list of email addresses only.
293 // There are other ways of embedding email addresses in a mailto:
294 // link, but such complex parsing is beyond us.
295
296 const kMailToLength = 7; // length of "mailto:"
297
298 var url = this.linkURL;
299 var qmark = url.indexOf("?");
300 var addresses;
301
302 if (qmark > kMailToLength) {
303 addresses = url.substring(kMailToLength, qmark);
304 } else {
305 addresses = url.substr(kMailToLength);
306 }
307
308 // Let's try to unescape it using a character set.
309 try {
310 var characterSet = this.target.ownerDocument.characterSet;
311 const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
312 .getService(Components.interfaces.nsITextToSubURI);
313 addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
314 }
315 catch(ex) {
316 // Do nothing.
317 }
318
319 var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
320 .getService(Components.interfaces.nsIClipboardHelper);
321 clipboard.copyString(addresses);
322 },
323
324 ///////////////
325 // Utilities //
326 ///////////////
327
328 /**
329 * Set a DOM node's hidden property by passing in the node's id or the
330 * element itself.
331 * @param aItemOrId
332 * a DOM node or the id of a DOM node
333 * @param aShow
334 * true to show, false to hide
335 */
CM_showItem
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
336 showItem : function CM_showItem(aItemOrId, aShow) {
337 var item = aItemOrId.constructor == String ? document.getElementById(aItemOrId) : aItemOrId;
338 if (item)
339 item.hidden = !aShow;
340 },
341
342 /**
343 * Set given attribute of specified context-menu item. If the
344 * value is null, then it removes the attribute (which works
345 * nicely for the disabled attribute).
346 * @param aId
347 * The id of an element
348 * @param aAttr
349 * The attribute name
350 * @param aVal
351 * The value to set the attribute to, or null to remove the attribute
352 */
CM_setItemAttr
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
353 setItemAttr : function CM_setItemAttr(aId, aAttr, aVal) {
354 var elem = document.getElementById(aId);
355 if (elem) {
356 if (aVal == null) {
357 // null indicates attr should be removed.
358 elem.removeAttribute(aAttr);
359 } else {
360 // Set attr=val.
361 elem.setAttribute(aAttr, aVal);
362 }
363 }
364 },
365
366 /**
367 * Get an absolute URL for clicked-on link, from the href property or by
368 * resolving an XLink URL by hand.
369 * @return the string absolute URL for the clicked-on link
370 */
CM_getLinkURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
371 getLinkURL : function CM_getLinkURL() {
372 if (this.link.href) {
373 return this.link.href;
374 }
375 var href = this.link.getAttributeNS("http://www.w3.org/1999/xlink","href");
376 if (!href || !href.match(/\S/)) {
377 // Without this we try to save as the current doc,
378 // for example, HTML case also throws if empty.
379 throw "Empty href";
380 }
381 href = this.makeURLAbsolute(this.link.baseURI,href);
382 return href;
383 },
384
385 /**
386 * Generate a URI object from the linkURL spec
387 * @return an nsIURI if possible, or null if not
388 */
CM_getLinkURI
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
389 getLinkURI: function CM_getLinkURI() {
390 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
391 .getService(Components.interfaces.nsIIOService);
392 try {
393 return ioService.newURI(this.linkURL, null, null);
394 } catch (ex) {
395 // e.g. empty URL string
396 }
397 return null;
398 },
399
400 /**
401 * Get the scheme for the clicked-on linkURI, if present.
402 * @return a scheme, possibly undefined, or null if there's no linkURI
403 */
CM_getLinkProtocol
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
404 getLinkProtocol: function CM_getLinkProtocol() {
405 if (this.linkURI)
406 return this.linkURI.scheme; // can be |undefined|
407
408 return null;
409 },
410
411 /**
412 * Get some text, any text, for the clicked-on link.
413 * @return the link text, title, alt, href, or "" if everything fails
414 */
CM_linkText
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
415 linkText : function CM_linkText() {
416 var text = gatherTextUnder(this.link);
417 if (!text || !text.match(/\S/)) {
418 text = this.link.getAttribute("title");
419 if (!text || !text.match(/\S/)) {
420 text = this.link.getAttribute("alt");
421 if (!text || !text.match(/\S/)) {
422 if (this.link.href) {
423 text = this.link.href;
424 } else {
425 text = getAttributeNS("http://www.w3.org/1999/xlink", "href");
426 if (text && text.match(/\S/)) {
427 text = this.makeURLAbsolute(this.link.baseURI, text);
428 }
429 }
430 }
431 }
432 }
433
434 return text;
435 },
436
437 /**
438 * Determines whether the focused window has selected text, and if so
439 * formats the first 15 characters for the label of the context-searchselect
440 * element according to the searchText string.
441 * @return true if there is selected text, false if not
442 */
CM_isTextSelection
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
443 isTextSelection : function CM_isTextSelection() {
444 var result = false;
445 var selection = this.searchSelected();
446
447 if (selection != "") {
448 var searchSelectText = selection.toString();
449 if (searchSelectText.length > 15)
450 searchSelectText = searchSelectText.substr(0,15) + "...";
451 result = true;
452
453 // Format "Search for <selection>" string to show in menu.
454 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
455 .getService(Components.interfaces.nsIStringBundleService);
456 var bundle = sbs.createBundle("chrome://communicator/locale/contentAreaCommands.properties");
457 searchSelectText = bundle.formatStringFromName("searchText",
458 [searchSelectText], 1);
459 this.setItemAttr("context-searchselect", "label", searchSelectText);
460 }
461 return result;
462 },
463
464 /**
465 * Get the currently selected text, with whitespace trimmed and
466 * newlines and tabs converted to spaces.
467 * @return the selection as a searchable string
468 */
CM_searchSelected
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
469 searchSelected : function CM_searchSelected() {
470 var focusedWindow = document.commandDispatcher.focusedWindow;
471 var searchStr = focusedWindow.getSelection();
472 searchStr = searchStr.toString();
473 searchStr = searchStr.replace(/^\s+/, "");
474 searchStr = searchStr.replace(/(\n|\r|\t)+/g, " ");
475 searchStr = searchStr.replace(/\s+$/,"");
476 return searchStr;
477 },
478
479 /**
480 * Convert relative URL to absolute, using a provided <base>.
481 * @param aBase
482 * The URL string to use as the base
483 * @param aUrl
484 * The possibly-relative URL string
485 * @return The string absolute URL
486 */
CM_makeURLAbsolute
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
487 makeURLAbsolute : function CM_makeURLAbsolute(aBase, aUrl) {
488 // Construct nsIURL.
489 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
490 .getService(Components.interfaces.nsIIOService);
491 var baseURI = ioService.newURI(aBase, null, null);
492
493 return ioService.newURI(baseURI.resolve(aUrl), null, null).spec;
494 },
495
496 /**
497 * Determine whether a DOM node is a text or password input, or a textarea.
498 * @param aNode
499 * The DOM node to check
500 * @return true for textboxes, false for other elements
501 */
CM_isTargetATextBox
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
502 isTargetATextBox : function CM_isTargetATextBox(aNode) {
503 if (aNode instanceof HTMLInputElement)
504 return (aNode.type == "text" || aNode.type == "password");
505
506 return (aNode instanceof HTMLTextAreaElement);
507 },
508
509 /**
510 * Determine whether a separator should be shown based on whether
511 * there are any non-hidden items between it and the previous separator.
512 * @param aSeparatorID
513 * The id of the separator element
514 * @return true if the separator should be shown, false if not
515 */
CM_shouldShowSeparator
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
516 shouldShowSeparator : function CM_shouldShowSeparator(aSeparatorID) {
517 var separator = document.getElementById(aSeparatorID);
518 if (separator) {
519 var sibling = separator.previousSibling;
520 while (sibling && sibling.localName != "menuseparator") {
521 if (sibling.getAttribute("hidden") != "true")
522 return true;
523 sibling = sibling.previousSibling;
524 }
525 }
526 return false;
527 }
528 };