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