|
1 // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
8 Components.utils.import("resource://gre/modules/CharsetMenu.jsm"); |
|
9 |
|
10 const Cc = Components.classes; |
|
11 const Ci = Components.interfaces; |
|
12 |
|
13 var gLastLineFound = ''; |
|
14 var gGoToLine = 0; |
|
15 |
|
16 [ |
|
17 ["gBrowser", "content"], |
|
18 ["gViewSourceBundle", "viewSourceBundle"], |
|
19 ["gContextMenu", "viewSourceContextMenu"] |
|
20 ].forEach(function ([name, id]) { |
|
21 window.__defineGetter__(name, function () { |
|
22 var element = document.getElementById(id); |
|
23 if (!element) |
|
24 return null; |
|
25 delete window[name]; |
|
26 return window[name] = element; |
|
27 }); |
|
28 }); |
|
29 |
|
30 // viewZoomOverlay.js uses this |
|
31 function getBrowser() { |
|
32 return gBrowser; |
|
33 } |
|
34 |
|
35 this.__defineGetter__("gPageLoader", function () { |
|
36 var webnav = getWebNavigation(); |
|
37 if (!webnav) |
|
38 return null; |
|
39 delete this.gPageLoader; |
|
40 return this.gPageLoader = webnav.QueryInterface(Ci.nsIWebPageDescriptor); |
|
41 }); |
|
42 |
|
43 var gSelectionListener = { |
|
44 timeout: 0, |
|
45 attached: false, |
|
46 notifySelectionChanged: function(doc, sel, reason) |
|
47 { |
|
48 // Coalesce notifications within 100ms intervals. |
|
49 if (!this.timeout) |
|
50 this.timeout = setTimeout(updateStatusBar, 100); |
|
51 } |
|
52 } |
|
53 |
|
54 function onLoadViewSource() |
|
55 { |
|
56 viewSource(window.arguments[0]); |
|
57 document.commandDispatcher.focusedWindow = content; |
|
58 gBrowser.droppedLinkHandler = function (event, url, name) { |
|
59 viewSource(url) |
|
60 event.preventDefault(); |
|
61 } |
|
62 |
|
63 if (!isHistoryEnabled()) { |
|
64 // Disable the BACK and FORWARD commands and hide the related menu items. |
|
65 var viewSourceNavigation = document.getElementById("viewSourceNavigation"); |
|
66 viewSourceNavigation.setAttribute("disabled", "true"); |
|
67 viewSourceNavigation.setAttribute("hidden", "true"); |
|
68 } |
|
69 } |
|
70 |
|
71 function isHistoryEnabled() { |
|
72 return !gBrowser.hasAttribute("disablehistory"); |
|
73 } |
|
74 |
|
75 function getSelectionController() { |
|
76 return gBrowser.docShell |
|
77 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
78 .getInterface(Ci.nsISelectionDisplay) |
|
79 .QueryInterface(Ci.nsISelectionController); |
|
80 } |
|
81 |
|
82 function viewSource(url) |
|
83 { |
|
84 if (!url) |
|
85 return; // throw Components.results.NS_ERROR_FAILURE; |
|
86 |
|
87 var viewSrcUrl = "view-source:" + url; |
|
88 |
|
89 gBrowser.addEventListener("pagehide", onUnloadContent, true); |
|
90 gBrowser.addEventListener("pageshow", onLoadContent, true); |
|
91 gBrowser.addEventListener("click", onClickContent, false); |
|
92 |
|
93 var loadFromURL = true; |
|
94 |
|
95 // Parse the 'arguments' supplied with the dialog. |
|
96 // arg[0] - URL string. |
|
97 // arg[1] - Charset value in the form 'charset=xxx'. |
|
98 // arg[2] - Page descriptor used to load content from the cache. |
|
99 // arg[3] - Line number to go to. |
|
100 // arg[4] - Whether charset was forced by the user |
|
101 |
|
102 if ("arguments" in window) { |
|
103 var arg; |
|
104 |
|
105 // Set the charset of the viewsource window... |
|
106 var charset; |
|
107 if (window.arguments.length >= 2) { |
|
108 arg = window.arguments[1]; |
|
109 |
|
110 try { |
|
111 if (typeof(arg) == "string" && arg.indexOf('charset=') != -1) { |
|
112 var arrayArgComponents = arg.split('='); |
|
113 if (arrayArgComponents) { |
|
114 // Remember the charset here so that it can be used below in case |
|
115 // the document had a forced charset. |
|
116 charset = arrayArgComponents[1]; |
|
117 } |
|
118 } |
|
119 } catch (ex) { |
|
120 // Ignore the failure and keep processing arguments... |
|
121 } |
|
122 } |
|
123 // If the document had a forced charset, set it here also |
|
124 if (window.arguments.length >= 5) { |
|
125 arg = window.arguments[4]; |
|
126 |
|
127 try { |
|
128 if (arg === true) { |
|
129 gBrowser.docShell.charset = charset; |
|
130 } |
|
131 } catch (ex) { |
|
132 // Ignore the failure and keep processing arguments... |
|
133 } |
|
134 } |
|
135 |
|
136 // Get any specified line to jump to. |
|
137 if (window.arguments.length >= 4) { |
|
138 arg = window.arguments[3]; |
|
139 gGoToLine = parseInt(arg); |
|
140 } |
|
141 |
|
142 // Use the page descriptor to load the content from the cache (if |
|
143 // available). |
|
144 if (window.arguments.length >= 3) { |
|
145 arg = window.arguments[2]; |
|
146 |
|
147 try { |
|
148 if (typeof(arg) == "object" && arg != null) { |
|
149 // Load the page using the page descriptor rather than the URL. |
|
150 // This allows the content to be fetched from the cache (if |
|
151 // possible) rather than the network... |
|
152 gPageLoader.loadPage(arg, gPageLoader.DISPLAY_AS_SOURCE); |
|
153 |
|
154 // The content was successfully loaded. |
|
155 loadFromURL = false; |
|
156 |
|
157 // Record the page load in the session history so <back> will work. |
|
158 var shEntrySource = arg.QueryInterface(Ci.nsISHEntry); |
|
159 var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry); |
|
160 shEntry.setURI(makeURI(viewSrcUrl, null, null)); |
|
161 shEntry.setTitle(viewSrcUrl); |
|
162 shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; |
|
163 shEntry.cacheKey = shEntrySource.cacheKey; |
|
164 gBrowser.sessionHistory |
|
165 .QueryInterface(Ci.nsISHistoryInternal) |
|
166 .addEntry(shEntry, true); |
|
167 } |
|
168 } catch(ex) { |
|
169 // Ignore the failure. The content will be loaded via the URL |
|
170 // that was supplied in arg[0]. |
|
171 } |
|
172 } |
|
173 } |
|
174 |
|
175 if (loadFromURL) { |
|
176 // Currently, an exception is thrown if the URL load fails... |
|
177 var loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; |
|
178 getWebNavigation().loadURI(viewSrcUrl, loadFlags, null, null, null); |
|
179 } |
|
180 |
|
181 // Check the view_source.wrap_long_lines pref and set the menuitem's checked |
|
182 // attribute accordingly. |
|
183 var wraplonglinesPrefValue = Services.prefs.getBoolPref("view_source.wrap_long_lines"); |
|
184 |
|
185 if (wraplonglinesPrefValue) |
|
186 document.getElementById("menu_wrapLongLines").setAttribute("checked", "true"); |
|
187 |
|
188 document.getElementById("menu_highlightSyntax") |
|
189 .setAttribute("checked", |
|
190 Services.prefs.getBoolPref("view_source.syntax_highlight")); |
|
191 |
|
192 window.addEventListener("AppCommand", HandleAppCommandEvent, true); |
|
193 window.addEventListener("MozSwipeGesture", HandleSwipeGesture, true); |
|
194 window.content.focus(); |
|
195 } |
|
196 |
|
197 function onLoadContent() |
|
198 { |
|
199 // If the view source was opened with a "go to line" argument. |
|
200 if (gGoToLine > 0) { |
|
201 goToLine(gGoToLine); |
|
202 gGoToLine = 0; |
|
203 } |
|
204 document.getElementById('cmd_goToLine').removeAttribute('disabled'); |
|
205 |
|
206 // Register a listener so that we can show the caret position on the status bar. |
|
207 window.content.getSelection() |
|
208 .QueryInterface(Ci.nsISelectionPrivate) |
|
209 .addSelectionListener(gSelectionListener); |
|
210 gSelectionListener.attached = true; |
|
211 |
|
212 if (isHistoryEnabled()) |
|
213 UpdateBackForwardCommands(); |
|
214 } |
|
215 |
|
216 function onUnloadContent() |
|
217 { |
|
218 // Disable "go to line" while reloading due to e.g. change of charset |
|
219 // or toggling of syntax highlighting. |
|
220 document.getElementById('cmd_goToLine').setAttribute('disabled', 'true'); |
|
221 |
|
222 // If we're not just unloading the initial "about:blank" which doesn't have |
|
223 // a selection listener, get rid of it so it doesn't try to fire after the |
|
224 // window has gone away. |
|
225 if (gSelectionListener.attached) { |
|
226 window.content.getSelection().QueryInterface(Ci.nsISelectionPrivate) |
|
227 .removeSelectionListener(gSelectionListener); |
|
228 gSelectionListener.attached = false; |
|
229 } |
|
230 } |
|
231 |
|
232 /** |
|
233 * Handle click events bubbling up from error page content |
|
234 */ |
|
235 function onClickContent(event) { |
|
236 // Don't trust synthetic events |
|
237 if (!event.isTrusted || event.target.localName != "button") |
|
238 return; |
|
239 |
|
240 var target = event.originalTarget; |
|
241 var errorDoc = target.ownerDocument; |
|
242 |
|
243 if (/^about:blocked/.test(errorDoc.documentURI)) { |
|
244 // The event came from a button on a malware/phishing block page |
|
245 // First check whether it's malware or phishing, so that we can |
|
246 // use the right strings/links |
|
247 var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI); |
|
248 |
|
249 if (target == errorDoc.getElementById('getMeOutButton')) { |
|
250 // Instead of loading some safe page, just close the window |
|
251 window.close(); |
|
252 } else if (target == errorDoc.getElementById('reportButton')) { |
|
253 // This is the "Why is this site blocked" button. For malware, |
|
254 // we can fetch a site-specific report, for phishing, we redirect |
|
255 // to the generic page describing phishing protection. |
|
256 |
|
257 if (isMalware) { |
|
258 // Get the stop badware "why is this blocked" report url, |
|
259 // append the current url, and go there. |
|
260 try { |
|
261 let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL", true); |
|
262 reportURL += errorDoc.location.href.slice(12); |
|
263 openURL(reportURL); |
|
264 } catch (e) { |
|
265 Components.utils.reportError("Couldn't get malware report URL: " + e); |
|
266 } |
|
267 } else { |
|
268 // It's a phishing site, just link to the generic information page |
|
269 let url = Services.urlFormatter.formatURLPref("app.support.baseURL"); |
|
270 openURL(url + "phishing-malware"); |
|
271 } |
|
272 } else if (target == errorDoc.getElementById('ignoreWarningButton')) { |
|
273 // Allow users to override and continue through to the site |
|
274 gBrowser.loadURIWithFlags(content.location.href, |
|
275 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, |
|
276 null, null, null); |
|
277 } |
|
278 } |
|
279 } |
|
280 |
|
281 function HandleAppCommandEvent(evt) |
|
282 { |
|
283 evt.stopPropagation(); |
|
284 switch (evt.command) { |
|
285 case "Back": |
|
286 BrowserBack(); |
|
287 break; |
|
288 case "Forward": |
|
289 BrowserForward(); |
|
290 break; |
|
291 } |
|
292 } |
|
293 |
|
294 function HandleSwipeGesture(evt) { |
|
295 evt.stopPropagation(); |
|
296 switch (evt.direction) { |
|
297 case SimpleGestureEvent.DIRECTION_LEFT: |
|
298 BrowserBack(); |
|
299 break; |
|
300 case SimpleGestureEvent.DIRECTION_RIGHT: |
|
301 BrowserForward(); |
|
302 break; |
|
303 case SimpleGestureEvent.DIRECTION_UP: |
|
304 goDoCommand("cmd_scrollTop"); |
|
305 break; |
|
306 case SimpleGestureEvent.DIRECTION_DOWN: |
|
307 goDoCommand("cmd_scrollBottom"); |
|
308 break; |
|
309 } |
|
310 } |
|
311 |
|
312 function ViewSourceClose() |
|
313 { |
|
314 window.close(); |
|
315 } |
|
316 |
|
317 function ViewSourceReload() |
|
318 { |
|
319 gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | |
|
320 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); |
|
321 } |
|
322 |
|
323 // Strips the |view-source:| for internalSave() |
|
324 function ViewSourceSavePage() |
|
325 { |
|
326 internalSave(window.content.location.href.substring(12), |
|
327 null, null, null, null, null, "SaveLinkTitle", |
|
328 null, null, window.content.document, null, gPageLoader); |
|
329 } |
|
330 |
|
331 var PrintPreviewListener = { |
|
332 getPrintPreviewBrowser: function () { |
|
333 var browser = document.getElementById("ppBrowser"); |
|
334 if (!browser) { |
|
335 browser = document.createElement("browser"); |
|
336 browser.setAttribute("id", "ppBrowser"); |
|
337 browser.setAttribute("flex", "1"); |
|
338 document.getElementById("appcontent"). |
|
339 insertBefore(browser, document.getElementById("FindToolbar")); |
|
340 } |
|
341 return browser; |
|
342 }, |
|
343 getSourceBrowser: function () { |
|
344 return gBrowser; |
|
345 }, |
|
346 getNavToolbox: function () { |
|
347 return document.getElementById("appcontent"); |
|
348 }, |
|
349 onEnter: function () { |
|
350 var toolbox = document.getElementById("viewSource-toolbox"); |
|
351 toolbox.hidden = true; |
|
352 gBrowser.collapsed = true; |
|
353 }, |
|
354 onExit: function () { |
|
355 document.getElementById("ppBrowser").collapsed = true; |
|
356 gBrowser.collapsed = false; |
|
357 document.getElementById("viewSource-toolbox").hidden = false; |
|
358 } |
|
359 } |
|
360 |
|
361 function getWebNavigation() |
|
362 { |
|
363 try { |
|
364 return gBrowser.webNavigation; |
|
365 } catch (e) { |
|
366 return null; |
|
367 } |
|
368 } |
|
369 |
|
370 function ViewSourceGoToLine() |
|
371 { |
|
372 var input = {value:gLastLineFound}; |
|
373 for (;;) { |
|
374 var ok = Services.prompt.prompt( |
|
375 window, |
|
376 gViewSourceBundle.getString("goToLineTitle"), |
|
377 gViewSourceBundle.getString("goToLineText"), |
|
378 input, |
|
379 null, |
|
380 {value:0}); |
|
381 |
|
382 if (!ok) |
|
383 return; |
|
384 |
|
385 var line = parseInt(input.value, 10); |
|
386 |
|
387 if (!(line > 0)) { |
|
388 Services.prompt.alert(window, |
|
389 gViewSourceBundle.getString("invalidInputTitle"), |
|
390 gViewSourceBundle.getString("invalidInputText")); |
|
391 |
|
392 continue; |
|
393 } |
|
394 |
|
395 var found = goToLine(line); |
|
396 |
|
397 if (found) |
|
398 break; |
|
399 |
|
400 Services.prompt.alert(window, |
|
401 gViewSourceBundle.getString("outOfRangeTitle"), |
|
402 gViewSourceBundle.getString("outOfRangeText")); |
|
403 } |
|
404 } |
|
405 |
|
406 function goToLine(line) |
|
407 { |
|
408 var viewsource = window.content.document.body; |
|
409 |
|
410 // The source document is made up of a number of pre elements with |
|
411 // id attributes in the format <pre id="line123">, meaning that |
|
412 // the first line in the pre element is number 123. |
|
413 // Do binary search to find the pre element containing the line. |
|
414 // However, in the plain text case, we have only one pre without an |
|
415 // attribute, so assume it begins on line 1. |
|
416 |
|
417 var pre; |
|
418 for (var lbound = 0, ubound = viewsource.childNodes.length; ; ) { |
|
419 var middle = (lbound + ubound) >> 1; |
|
420 pre = viewsource.childNodes[middle]; |
|
421 |
|
422 var firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1; |
|
423 |
|
424 if (lbound == ubound - 1) { |
|
425 break; |
|
426 } |
|
427 |
|
428 if (line >= firstLine) { |
|
429 lbound = middle; |
|
430 } else { |
|
431 ubound = middle; |
|
432 } |
|
433 } |
|
434 |
|
435 var result = {}; |
|
436 var found = findLocation(pre, line, null, -1, false, result); |
|
437 |
|
438 if (!found) { |
|
439 return false; |
|
440 } |
|
441 |
|
442 var selection = window.content.getSelection(); |
|
443 selection.removeAllRanges(); |
|
444 |
|
445 // In our case, the range's startOffset is after "\n" on the previous line. |
|
446 // Tune the selection at the beginning of the next line and do some tweaking |
|
447 // to position the focusNode and the caret at the beginning of the line. |
|
448 |
|
449 selection.QueryInterface(Ci.nsISelectionPrivate) |
|
450 .interlinePosition = true; |
|
451 |
|
452 selection.addRange(result.range); |
|
453 |
|
454 if (!selection.isCollapsed) { |
|
455 selection.collapseToEnd(); |
|
456 |
|
457 var offset = result.range.startOffset; |
|
458 var node = result.range.startContainer; |
|
459 if (offset < node.data.length) { |
|
460 // The same text node spans across the "\n", just focus where we were. |
|
461 selection.extend(node, offset); |
|
462 } |
|
463 else { |
|
464 // There is another tag just after the "\n", hook there. We need |
|
465 // to focus a safe point because there are edgy cases such as |
|
466 // <span>...\n</span><span>...</span> vs. |
|
467 // <span>...\n<span>...</span></span><span>...</span> |
|
468 node = node.nextSibling ? node.nextSibling : node.parentNode.nextSibling; |
|
469 selection.extend(node, 0); |
|
470 } |
|
471 } |
|
472 |
|
473 var selCon = getSelectionController(); |
|
474 selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON); |
|
475 selCon.setCaretVisibilityDuringSelection(true); |
|
476 |
|
477 // Scroll the beginning of the line into view. |
|
478 selCon.scrollSelectionIntoView( |
|
479 Ci.nsISelectionController.SELECTION_NORMAL, |
|
480 Ci.nsISelectionController.SELECTION_FOCUS_REGION, |
|
481 true); |
|
482 |
|
483 gLastLineFound = line; |
|
484 |
|
485 document.getElementById("statusbar-line-col").label = |
|
486 gViewSourceBundle.getFormattedString("statusBarLineCol", [line, 1]); |
|
487 |
|
488 return true; |
|
489 } |
|
490 |
|
491 function updateStatusBar() |
|
492 { |
|
493 // Reset the coalesce flag. |
|
494 gSelectionListener.timeout = 0; |
|
495 |
|
496 var statusBarField = document.getElementById("statusbar-line-col"); |
|
497 |
|
498 var selection = window.content.getSelection(); |
|
499 if (!selection.focusNode) { |
|
500 statusBarField.label = ''; |
|
501 return; |
|
502 } |
|
503 if (selection.focusNode.nodeType != Node.TEXT_NODE) { |
|
504 return; |
|
505 } |
|
506 |
|
507 var selCon = getSelectionController(); |
|
508 selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON); |
|
509 selCon.setCaretVisibilityDuringSelection(true); |
|
510 |
|
511 var interlinePosition = selection.QueryInterface(Ci.nsISelectionPrivate) |
|
512 .interlinePosition; |
|
513 |
|
514 var result = {}; |
|
515 findLocation(null, -1, |
|
516 selection.focusNode, selection.focusOffset, interlinePosition, result); |
|
517 |
|
518 statusBarField.label = gViewSourceBundle.getFormattedString( |
|
519 "statusBarLineCol", [result.line, result.col]); |
|
520 } |
|
521 |
|
522 // Loops through the text lines in the pre element. The arguments are either |
|
523 // (pre, line) or (node, offset, interlinePosition). result is an out |
|
524 // argument. If (pre, line) are specified (and node == null), result.range is |
|
525 // a range spanning the specified line. If the (node, offset, |
|
526 // interlinePosition) are specified, result.line and result.col are the line |
|
527 // and column number of the specified offset in the specified node relative to |
|
528 // the whole file. |
|
529 function findLocation(pre, line, node, offset, interlinePosition, result) |
|
530 { |
|
531 if (node && !pre) { |
|
532 // Look upwards to find the current pre element. |
|
533 for (pre = node; |
|
534 pre.nodeName != "PRE"; |
|
535 pre = pre.parentNode); |
|
536 } |
|
537 |
|
538 // The source document is made up of a number of pre elements with |
|
539 // id attributes in the format <pre id="line123">, meaning that |
|
540 // the first line in the pre element is number 123. |
|
541 // However, in the plain text case, there is only one <pre> without an id, |
|
542 // so assume line 1. |
|
543 var curLine = pre.id ? parseInt(pre.id.substring(4)) : 1; |
|
544 |
|
545 // Walk through each of the text nodes and count newlines. |
|
546 var treewalker = window.content.document |
|
547 .createTreeWalker(pre, NodeFilter.SHOW_TEXT, null); |
|
548 |
|
549 // The column number of the first character in the current text node. |
|
550 var firstCol = 1; |
|
551 |
|
552 var found = false; |
|
553 for (var textNode = treewalker.firstChild(); |
|
554 textNode && !found; |
|
555 textNode = treewalker.nextNode()) { |
|
556 |
|
557 // \r is not a valid character in the DOM, so we only check for \n. |
|
558 var lineArray = textNode.data.split(/\n/); |
|
559 var lastLineInNode = curLine + lineArray.length - 1; |
|
560 |
|
561 // Check if we can skip the text node without further inspection. |
|
562 if (node ? (textNode != node) : (lastLineInNode < line)) { |
|
563 if (lineArray.length > 1) { |
|
564 firstCol = 1; |
|
565 } |
|
566 firstCol += lineArray[lineArray.length - 1].length; |
|
567 curLine = lastLineInNode; |
|
568 continue; |
|
569 } |
|
570 |
|
571 // curPos is the offset within the current text node of the first |
|
572 // character in the current line. |
|
573 for (var i = 0, curPos = 0; |
|
574 i < lineArray.length; |
|
575 curPos += lineArray[i++].length + 1) { |
|
576 |
|
577 if (i > 0) { |
|
578 curLine++; |
|
579 } |
|
580 |
|
581 if (node) { |
|
582 if (offset >= curPos && offset <= curPos + lineArray[i].length) { |
|
583 // If we are right after the \n of a line and interlinePosition is |
|
584 // false, the caret looks as if it were at the end of the previous |
|
585 // line, so we display that line and column instead. |
|
586 |
|
587 if (i > 0 && offset == curPos && !interlinePosition) { |
|
588 result.line = curLine - 1; |
|
589 var prevPos = curPos - lineArray[i - 1].length; |
|
590 result.col = (i == 1 ? firstCol : 1) + offset - prevPos; |
|
591 } else { |
|
592 result.line = curLine; |
|
593 result.col = (i == 0 ? firstCol : 1) + offset - curPos; |
|
594 } |
|
595 found = true; |
|
596 |
|
597 break; |
|
598 } |
|
599 |
|
600 } else { |
|
601 if (curLine == line && !("range" in result)) { |
|
602 result.range = document.createRange(); |
|
603 result.range.setStart(textNode, curPos); |
|
604 |
|
605 // This will always be overridden later, except when we look for |
|
606 // the very last line in the file (this is the only line that does |
|
607 // not end with \n). |
|
608 result.range.setEndAfter(pre.lastChild); |
|
609 |
|
610 } else if (curLine == line + 1) { |
|
611 result.range.setEnd(textNode, curPos - 1); |
|
612 found = true; |
|
613 break; |
|
614 } |
|
615 } |
|
616 } |
|
617 } |
|
618 |
|
619 return found || ("range" in result); |
|
620 } |
|
621 |
|
622 // Toggle long-line wrapping and sets the view_source.wrap_long_lines |
|
623 // pref to persist the last state. |
|
624 function wrapLongLines() |
|
625 { |
|
626 var myWrap = window.content.document.body; |
|
627 myWrap.classList.toggle("wrap"); |
|
628 |
|
629 // Since multiple viewsource windows are possible, another window could have |
|
630 // affected the pref, so instead of determining the new pref value via the current |
|
631 // pref value, we use myWrap.classList. |
|
632 Services.prefs.setBoolPref("view_source.wrap_long_lines", myWrap.classList.contains("wrap")); |
|
633 } |
|
634 |
|
635 // Toggles syntax highlighting and sets the view_source.syntax_highlight |
|
636 // pref to persist the last state. |
|
637 function highlightSyntax() |
|
638 { |
|
639 var highlightSyntaxMenu = document.getElementById("menu_highlightSyntax"); |
|
640 var highlightSyntax = (highlightSyntaxMenu.getAttribute("checked") == "true"); |
|
641 Services.prefs.setBoolPref("view_source.syntax_highlight", highlightSyntax); |
|
642 |
|
643 gPageLoader.loadPage(gPageLoader.currentDescriptor, gPageLoader.DISPLAY_NORMAL); |
|
644 } |
|
645 |
|
646 // Reload after change to character encoding or autodetection |
|
647 // |
|
648 // Fix for bug 136322: this function overrides the function in |
|
649 // browser.js to call PageLoader.loadPage() instead of BrowserReloadWithFlags() |
|
650 function BrowserCharsetReload() |
|
651 { |
|
652 if (isHistoryEnabled()) { |
|
653 gPageLoader.loadPage(gPageLoader.currentDescriptor, |
|
654 gPageLoader.DISPLAY_NORMAL); |
|
655 } else { |
|
656 gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); |
|
657 } |
|
658 } |
|
659 |
|
660 function BrowserSetCharacterSet(aEvent) |
|
661 { |
|
662 if (aEvent.target.hasAttribute("charset")) |
|
663 gBrowser.docShell.charset = aEvent.target.getAttribute("charset"); |
|
664 BrowserCharsetReload(); |
|
665 } |
|
666 |
|
667 function BrowserForward(aEvent) { |
|
668 try { |
|
669 gBrowser.goForward(); |
|
670 } |
|
671 catch(ex) { |
|
672 } |
|
673 } |
|
674 |
|
675 function BrowserBack(aEvent) { |
|
676 try { |
|
677 gBrowser.goBack(); |
|
678 } |
|
679 catch(ex) { |
|
680 } |
|
681 } |
|
682 |
|
683 function UpdateBackForwardCommands() { |
|
684 var backBroadcaster = document.getElementById("Browser:Back"); |
|
685 var forwardBroadcaster = document.getElementById("Browser:Forward"); |
|
686 |
|
687 if (getWebNavigation().canGoBack) |
|
688 backBroadcaster.removeAttribute("disabled"); |
|
689 else |
|
690 backBroadcaster.setAttribute("disabled", "true"); |
|
691 |
|
692 if (getWebNavigation().canGoForward) |
|
693 forwardBroadcaster.removeAttribute("disabled"); |
|
694 else |
|
695 forwardBroadcaster.setAttribute("disabled", "true"); |
|
696 } |
|
697 |
|
698 function contextMenuShowing() { |
|
699 var isLink = false; |
|
700 var isEmail = false; |
|
701 if (gContextMenu.triggerNode && gContextMenu.triggerNode.localName == 'a') { |
|
702 if (gContextMenu.triggerNode.href.indexOf('view-source:') == 0) |
|
703 isLink = true; |
|
704 if (gContextMenu.triggerNode.href.indexOf('mailto:') == 0) |
|
705 isEmail = true; |
|
706 } |
|
707 document.getElementById('context-copyLink').hidden = !isLink; |
|
708 document.getElementById('context-copyEmail').hidden = !isEmail; |
|
709 } |
|
710 |
|
711 function contextMenuCopyLinkOrEmail() { |
|
712 if (!gContextMenu.triggerNode) |
|
713 return; |
|
714 |
|
715 var href = gContextMenu.triggerNode.href; |
|
716 var clipboard = Cc['@mozilla.org/widget/clipboardhelper;1']. |
|
717 getService(Ci.nsIClipboardHelper); |
|
718 clipboard.copyString(href.substring(href.indexOf(':') + 1), document); |
|
719 } |