1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/viewsource/content/viewSource.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,719 @@ 1.4 +// -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.11 +Components.utils.import("resource://gre/modules/CharsetMenu.jsm"); 1.12 + 1.13 +const Cc = Components.classes; 1.14 +const Ci = Components.interfaces; 1.15 + 1.16 +var gLastLineFound = ''; 1.17 +var gGoToLine = 0; 1.18 + 1.19 +[ 1.20 + ["gBrowser", "content"], 1.21 + ["gViewSourceBundle", "viewSourceBundle"], 1.22 + ["gContextMenu", "viewSourceContextMenu"] 1.23 +].forEach(function ([name, id]) { 1.24 + window.__defineGetter__(name, function () { 1.25 + var element = document.getElementById(id); 1.26 + if (!element) 1.27 + return null; 1.28 + delete window[name]; 1.29 + return window[name] = element; 1.30 + }); 1.31 +}); 1.32 + 1.33 +// viewZoomOverlay.js uses this 1.34 +function getBrowser() { 1.35 + return gBrowser; 1.36 +} 1.37 + 1.38 +this.__defineGetter__("gPageLoader", function () { 1.39 + var webnav = getWebNavigation(); 1.40 + if (!webnav) 1.41 + return null; 1.42 + delete this.gPageLoader; 1.43 + return this.gPageLoader = webnav.QueryInterface(Ci.nsIWebPageDescriptor); 1.44 +}); 1.45 + 1.46 +var gSelectionListener = { 1.47 + timeout: 0, 1.48 + attached: false, 1.49 + notifySelectionChanged: function(doc, sel, reason) 1.50 + { 1.51 + // Coalesce notifications within 100ms intervals. 1.52 + if (!this.timeout) 1.53 + this.timeout = setTimeout(updateStatusBar, 100); 1.54 + } 1.55 +} 1.56 + 1.57 +function onLoadViewSource() 1.58 +{ 1.59 + viewSource(window.arguments[0]); 1.60 + document.commandDispatcher.focusedWindow = content; 1.61 + gBrowser.droppedLinkHandler = function (event, url, name) { 1.62 + viewSource(url) 1.63 + event.preventDefault(); 1.64 + } 1.65 + 1.66 + if (!isHistoryEnabled()) { 1.67 + // Disable the BACK and FORWARD commands and hide the related menu items. 1.68 + var viewSourceNavigation = document.getElementById("viewSourceNavigation"); 1.69 + viewSourceNavigation.setAttribute("disabled", "true"); 1.70 + viewSourceNavigation.setAttribute("hidden", "true"); 1.71 + } 1.72 +} 1.73 + 1.74 +function isHistoryEnabled() { 1.75 + return !gBrowser.hasAttribute("disablehistory"); 1.76 +} 1.77 + 1.78 +function getSelectionController() { 1.79 + return gBrowser.docShell 1.80 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.81 + .getInterface(Ci.nsISelectionDisplay) 1.82 + .QueryInterface(Ci.nsISelectionController); 1.83 +} 1.84 + 1.85 +function viewSource(url) 1.86 +{ 1.87 + if (!url) 1.88 + return; // throw Components.results.NS_ERROR_FAILURE; 1.89 + 1.90 + var viewSrcUrl = "view-source:" + url; 1.91 + 1.92 + gBrowser.addEventListener("pagehide", onUnloadContent, true); 1.93 + gBrowser.addEventListener("pageshow", onLoadContent, true); 1.94 + gBrowser.addEventListener("click", onClickContent, false); 1.95 + 1.96 + var loadFromURL = true; 1.97 + 1.98 + // Parse the 'arguments' supplied with the dialog. 1.99 + // arg[0] - URL string. 1.100 + // arg[1] - Charset value in the form 'charset=xxx'. 1.101 + // arg[2] - Page descriptor used to load content from the cache. 1.102 + // arg[3] - Line number to go to. 1.103 + // arg[4] - Whether charset was forced by the user 1.104 + 1.105 + if ("arguments" in window) { 1.106 + var arg; 1.107 + 1.108 + // Set the charset of the viewsource window... 1.109 + var charset; 1.110 + if (window.arguments.length >= 2) { 1.111 + arg = window.arguments[1]; 1.112 + 1.113 + try { 1.114 + if (typeof(arg) == "string" && arg.indexOf('charset=') != -1) { 1.115 + var arrayArgComponents = arg.split('='); 1.116 + if (arrayArgComponents) { 1.117 + // Remember the charset here so that it can be used below in case 1.118 + // the document had a forced charset. 1.119 + charset = arrayArgComponents[1]; 1.120 + } 1.121 + } 1.122 + } catch (ex) { 1.123 + // Ignore the failure and keep processing arguments... 1.124 + } 1.125 + } 1.126 + // If the document had a forced charset, set it here also 1.127 + if (window.arguments.length >= 5) { 1.128 + arg = window.arguments[4]; 1.129 + 1.130 + try { 1.131 + if (arg === true) { 1.132 + gBrowser.docShell.charset = charset; 1.133 + } 1.134 + } catch (ex) { 1.135 + // Ignore the failure and keep processing arguments... 1.136 + } 1.137 + } 1.138 + 1.139 + // Get any specified line to jump to. 1.140 + if (window.arguments.length >= 4) { 1.141 + arg = window.arguments[3]; 1.142 + gGoToLine = parseInt(arg); 1.143 + } 1.144 + 1.145 + // Use the page descriptor to load the content from the cache (if 1.146 + // available). 1.147 + if (window.arguments.length >= 3) { 1.148 + arg = window.arguments[2]; 1.149 + 1.150 + try { 1.151 + if (typeof(arg) == "object" && arg != null) { 1.152 + // Load the page using the page descriptor rather than the URL. 1.153 + // This allows the content to be fetched from the cache (if 1.154 + // possible) rather than the network... 1.155 + gPageLoader.loadPage(arg, gPageLoader.DISPLAY_AS_SOURCE); 1.156 + 1.157 + // The content was successfully loaded. 1.158 + loadFromURL = false; 1.159 + 1.160 + // Record the page load in the session history so <back> will work. 1.161 + var shEntrySource = arg.QueryInterface(Ci.nsISHEntry); 1.162 + var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry); 1.163 + shEntry.setURI(makeURI(viewSrcUrl, null, null)); 1.164 + shEntry.setTitle(viewSrcUrl); 1.165 + shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; 1.166 + shEntry.cacheKey = shEntrySource.cacheKey; 1.167 + gBrowser.sessionHistory 1.168 + .QueryInterface(Ci.nsISHistoryInternal) 1.169 + .addEntry(shEntry, true); 1.170 + } 1.171 + } catch(ex) { 1.172 + // Ignore the failure. The content will be loaded via the URL 1.173 + // that was supplied in arg[0]. 1.174 + } 1.175 + } 1.176 + } 1.177 + 1.178 + if (loadFromURL) { 1.179 + // Currently, an exception is thrown if the URL load fails... 1.180 + var loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; 1.181 + getWebNavigation().loadURI(viewSrcUrl, loadFlags, null, null, null); 1.182 + } 1.183 + 1.184 + // Check the view_source.wrap_long_lines pref and set the menuitem's checked 1.185 + // attribute accordingly. 1.186 + var wraplonglinesPrefValue = Services.prefs.getBoolPref("view_source.wrap_long_lines"); 1.187 + 1.188 + if (wraplonglinesPrefValue) 1.189 + document.getElementById("menu_wrapLongLines").setAttribute("checked", "true"); 1.190 + 1.191 + document.getElementById("menu_highlightSyntax") 1.192 + .setAttribute("checked", 1.193 + Services.prefs.getBoolPref("view_source.syntax_highlight")); 1.194 + 1.195 + window.addEventListener("AppCommand", HandleAppCommandEvent, true); 1.196 + window.addEventListener("MozSwipeGesture", HandleSwipeGesture, true); 1.197 + window.content.focus(); 1.198 +} 1.199 + 1.200 +function onLoadContent() 1.201 +{ 1.202 + // If the view source was opened with a "go to line" argument. 1.203 + if (gGoToLine > 0) { 1.204 + goToLine(gGoToLine); 1.205 + gGoToLine = 0; 1.206 + } 1.207 + document.getElementById('cmd_goToLine').removeAttribute('disabled'); 1.208 + 1.209 + // Register a listener so that we can show the caret position on the status bar. 1.210 + window.content.getSelection() 1.211 + .QueryInterface(Ci.nsISelectionPrivate) 1.212 + .addSelectionListener(gSelectionListener); 1.213 + gSelectionListener.attached = true; 1.214 + 1.215 + if (isHistoryEnabled()) 1.216 + UpdateBackForwardCommands(); 1.217 +} 1.218 + 1.219 +function onUnloadContent() 1.220 +{ 1.221 + // Disable "go to line" while reloading due to e.g. change of charset 1.222 + // or toggling of syntax highlighting. 1.223 + document.getElementById('cmd_goToLine').setAttribute('disabled', 'true'); 1.224 + 1.225 + // If we're not just unloading the initial "about:blank" which doesn't have 1.226 + // a selection listener, get rid of it so it doesn't try to fire after the 1.227 + // window has gone away. 1.228 + if (gSelectionListener.attached) { 1.229 + window.content.getSelection().QueryInterface(Ci.nsISelectionPrivate) 1.230 + .removeSelectionListener(gSelectionListener); 1.231 + gSelectionListener.attached = false; 1.232 + } 1.233 +} 1.234 + 1.235 +/** 1.236 + * Handle click events bubbling up from error page content 1.237 + */ 1.238 +function onClickContent(event) { 1.239 + // Don't trust synthetic events 1.240 + if (!event.isTrusted || event.target.localName != "button") 1.241 + return; 1.242 + 1.243 + var target = event.originalTarget; 1.244 + var errorDoc = target.ownerDocument; 1.245 + 1.246 + if (/^about:blocked/.test(errorDoc.documentURI)) { 1.247 + // The event came from a button on a malware/phishing block page 1.248 + // First check whether it's malware or phishing, so that we can 1.249 + // use the right strings/links 1.250 + var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI); 1.251 + 1.252 + if (target == errorDoc.getElementById('getMeOutButton')) { 1.253 + // Instead of loading some safe page, just close the window 1.254 + window.close(); 1.255 + } else if (target == errorDoc.getElementById('reportButton')) { 1.256 + // This is the "Why is this site blocked" button. For malware, 1.257 + // we can fetch a site-specific report, for phishing, we redirect 1.258 + // to the generic page describing phishing protection. 1.259 + 1.260 + if (isMalware) { 1.261 + // Get the stop badware "why is this blocked" report url, 1.262 + // append the current url, and go there. 1.263 + try { 1.264 + let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL", true); 1.265 + reportURL += errorDoc.location.href.slice(12); 1.266 + openURL(reportURL); 1.267 + } catch (e) { 1.268 + Components.utils.reportError("Couldn't get malware report URL: " + e); 1.269 + } 1.270 + } else { 1.271 + // It's a phishing site, just link to the generic information page 1.272 + let url = Services.urlFormatter.formatURLPref("app.support.baseURL"); 1.273 + openURL(url + "phishing-malware"); 1.274 + } 1.275 + } else if (target == errorDoc.getElementById('ignoreWarningButton')) { 1.276 + // Allow users to override and continue through to the site 1.277 + gBrowser.loadURIWithFlags(content.location.href, 1.278 + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, 1.279 + null, null, null); 1.280 + } 1.281 + } 1.282 +} 1.283 + 1.284 +function HandleAppCommandEvent(evt) 1.285 +{ 1.286 + evt.stopPropagation(); 1.287 + switch (evt.command) { 1.288 + case "Back": 1.289 + BrowserBack(); 1.290 + break; 1.291 + case "Forward": 1.292 + BrowserForward(); 1.293 + break; 1.294 + } 1.295 +} 1.296 + 1.297 +function HandleSwipeGesture(evt) { 1.298 + evt.stopPropagation(); 1.299 + switch (evt.direction) { 1.300 + case SimpleGestureEvent.DIRECTION_LEFT: 1.301 + BrowserBack(); 1.302 + break; 1.303 + case SimpleGestureEvent.DIRECTION_RIGHT: 1.304 + BrowserForward(); 1.305 + break; 1.306 + case SimpleGestureEvent.DIRECTION_UP: 1.307 + goDoCommand("cmd_scrollTop"); 1.308 + break; 1.309 + case SimpleGestureEvent.DIRECTION_DOWN: 1.310 + goDoCommand("cmd_scrollBottom"); 1.311 + break; 1.312 + } 1.313 +} 1.314 + 1.315 +function ViewSourceClose() 1.316 +{ 1.317 + window.close(); 1.318 +} 1.319 + 1.320 +function ViewSourceReload() 1.321 +{ 1.322 + gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | 1.323 + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); 1.324 +} 1.325 + 1.326 +// Strips the |view-source:| for internalSave() 1.327 +function ViewSourceSavePage() 1.328 +{ 1.329 + internalSave(window.content.location.href.substring(12), 1.330 + null, null, null, null, null, "SaveLinkTitle", 1.331 + null, null, window.content.document, null, gPageLoader); 1.332 +} 1.333 + 1.334 +var PrintPreviewListener = { 1.335 + getPrintPreviewBrowser: function () { 1.336 + var browser = document.getElementById("ppBrowser"); 1.337 + if (!browser) { 1.338 + browser = document.createElement("browser"); 1.339 + browser.setAttribute("id", "ppBrowser"); 1.340 + browser.setAttribute("flex", "1"); 1.341 + document.getElementById("appcontent"). 1.342 + insertBefore(browser, document.getElementById("FindToolbar")); 1.343 + } 1.344 + return browser; 1.345 + }, 1.346 + getSourceBrowser: function () { 1.347 + return gBrowser; 1.348 + }, 1.349 + getNavToolbox: function () { 1.350 + return document.getElementById("appcontent"); 1.351 + }, 1.352 + onEnter: function () { 1.353 + var toolbox = document.getElementById("viewSource-toolbox"); 1.354 + toolbox.hidden = true; 1.355 + gBrowser.collapsed = true; 1.356 + }, 1.357 + onExit: function () { 1.358 + document.getElementById("ppBrowser").collapsed = true; 1.359 + gBrowser.collapsed = false; 1.360 + document.getElementById("viewSource-toolbox").hidden = false; 1.361 + } 1.362 +} 1.363 + 1.364 +function getWebNavigation() 1.365 +{ 1.366 + try { 1.367 + return gBrowser.webNavigation; 1.368 + } catch (e) { 1.369 + return null; 1.370 + } 1.371 +} 1.372 + 1.373 +function ViewSourceGoToLine() 1.374 +{ 1.375 + var input = {value:gLastLineFound}; 1.376 + for (;;) { 1.377 + var ok = Services.prompt.prompt( 1.378 + window, 1.379 + gViewSourceBundle.getString("goToLineTitle"), 1.380 + gViewSourceBundle.getString("goToLineText"), 1.381 + input, 1.382 + null, 1.383 + {value:0}); 1.384 + 1.385 + if (!ok) 1.386 + return; 1.387 + 1.388 + var line = parseInt(input.value, 10); 1.389 + 1.390 + if (!(line > 0)) { 1.391 + Services.prompt.alert(window, 1.392 + gViewSourceBundle.getString("invalidInputTitle"), 1.393 + gViewSourceBundle.getString("invalidInputText")); 1.394 + 1.395 + continue; 1.396 + } 1.397 + 1.398 + var found = goToLine(line); 1.399 + 1.400 + if (found) 1.401 + break; 1.402 + 1.403 + Services.prompt.alert(window, 1.404 + gViewSourceBundle.getString("outOfRangeTitle"), 1.405 + gViewSourceBundle.getString("outOfRangeText")); 1.406 + } 1.407 +} 1.408 + 1.409 +function goToLine(line) 1.410 +{ 1.411 + var viewsource = window.content.document.body; 1.412 + 1.413 + // The source document is made up of a number of pre elements with 1.414 + // id attributes in the format <pre id="line123">, meaning that 1.415 + // the first line in the pre element is number 123. 1.416 + // Do binary search to find the pre element containing the line. 1.417 + // However, in the plain text case, we have only one pre without an 1.418 + // attribute, so assume it begins on line 1. 1.419 + 1.420 + var pre; 1.421 + for (var lbound = 0, ubound = viewsource.childNodes.length; ; ) { 1.422 + var middle = (lbound + ubound) >> 1; 1.423 + pre = viewsource.childNodes[middle]; 1.424 + 1.425 + var firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1; 1.426 + 1.427 + if (lbound == ubound - 1) { 1.428 + break; 1.429 + } 1.430 + 1.431 + if (line >= firstLine) { 1.432 + lbound = middle; 1.433 + } else { 1.434 + ubound = middle; 1.435 + } 1.436 + } 1.437 + 1.438 + var result = {}; 1.439 + var found = findLocation(pre, line, null, -1, false, result); 1.440 + 1.441 + if (!found) { 1.442 + return false; 1.443 + } 1.444 + 1.445 + var selection = window.content.getSelection(); 1.446 + selection.removeAllRanges(); 1.447 + 1.448 + // In our case, the range's startOffset is after "\n" on the previous line. 1.449 + // Tune the selection at the beginning of the next line and do some tweaking 1.450 + // to position the focusNode and the caret at the beginning of the line. 1.451 + 1.452 + selection.QueryInterface(Ci.nsISelectionPrivate) 1.453 + .interlinePosition = true; 1.454 + 1.455 + selection.addRange(result.range); 1.456 + 1.457 + if (!selection.isCollapsed) { 1.458 + selection.collapseToEnd(); 1.459 + 1.460 + var offset = result.range.startOffset; 1.461 + var node = result.range.startContainer; 1.462 + if (offset < node.data.length) { 1.463 + // The same text node spans across the "\n", just focus where we were. 1.464 + selection.extend(node, offset); 1.465 + } 1.466 + else { 1.467 + // There is another tag just after the "\n", hook there. We need 1.468 + // to focus a safe point because there are edgy cases such as 1.469 + // <span>...\n</span><span>...</span> vs. 1.470 + // <span>...\n<span>...</span></span><span>...</span> 1.471 + node = node.nextSibling ? node.nextSibling : node.parentNode.nextSibling; 1.472 + selection.extend(node, 0); 1.473 + } 1.474 + } 1.475 + 1.476 + var selCon = getSelectionController(); 1.477 + selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON); 1.478 + selCon.setCaretVisibilityDuringSelection(true); 1.479 + 1.480 + // Scroll the beginning of the line into view. 1.481 + selCon.scrollSelectionIntoView( 1.482 + Ci.nsISelectionController.SELECTION_NORMAL, 1.483 + Ci.nsISelectionController.SELECTION_FOCUS_REGION, 1.484 + true); 1.485 + 1.486 + gLastLineFound = line; 1.487 + 1.488 + document.getElementById("statusbar-line-col").label = 1.489 + gViewSourceBundle.getFormattedString("statusBarLineCol", [line, 1]); 1.490 + 1.491 + return true; 1.492 +} 1.493 + 1.494 +function updateStatusBar() 1.495 +{ 1.496 + // Reset the coalesce flag. 1.497 + gSelectionListener.timeout = 0; 1.498 + 1.499 + var statusBarField = document.getElementById("statusbar-line-col"); 1.500 + 1.501 + var selection = window.content.getSelection(); 1.502 + if (!selection.focusNode) { 1.503 + statusBarField.label = ''; 1.504 + return; 1.505 + } 1.506 + if (selection.focusNode.nodeType != Node.TEXT_NODE) { 1.507 + return; 1.508 + } 1.509 + 1.510 + var selCon = getSelectionController(); 1.511 + selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON); 1.512 + selCon.setCaretVisibilityDuringSelection(true); 1.513 + 1.514 + var interlinePosition = selection.QueryInterface(Ci.nsISelectionPrivate) 1.515 + .interlinePosition; 1.516 + 1.517 + var result = {}; 1.518 + findLocation(null, -1, 1.519 + selection.focusNode, selection.focusOffset, interlinePosition, result); 1.520 + 1.521 + statusBarField.label = gViewSourceBundle.getFormattedString( 1.522 + "statusBarLineCol", [result.line, result.col]); 1.523 +} 1.524 + 1.525 +// Loops through the text lines in the pre element. The arguments are either 1.526 +// (pre, line) or (node, offset, interlinePosition). result is an out 1.527 +// argument. If (pre, line) are specified (and node == null), result.range is 1.528 +// a range spanning the specified line. If the (node, offset, 1.529 +// interlinePosition) are specified, result.line and result.col are the line 1.530 +// and column number of the specified offset in the specified node relative to 1.531 +// the whole file. 1.532 +function findLocation(pre, line, node, offset, interlinePosition, result) 1.533 +{ 1.534 + if (node && !pre) { 1.535 + // Look upwards to find the current pre element. 1.536 + for (pre = node; 1.537 + pre.nodeName != "PRE"; 1.538 + pre = pre.parentNode); 1.539 + } 1.540 + 1.541 + // The source document is made up of a number of pre elements with 1.542 + // id attributes in the format <pre id="line123">, meaning that 1.543 + // the first line in the pre element is number 123. 1.544 + // However, in the plain text case, there is only one <pre> without an id, 1.545 + // so assume line 1. 1.546 + var curLine = pre.id ? parseInt(pre.id.substring(4)) : 1; 1.547 + 1.548 + // Walk through each of the text nodes and count newlines. 1.549 + var treewalker = window.content.document 1.550 + .createTreeWalker(pre, NodeFilter.SHOW_TEXT, null); 1.551 + 1.552 + // The column number of the first character in the current text node. 1.553 + var firstCol = 1; 1.554 + 1.555 + var found = false; 1.556 + for (var textNode = treewalker.firstChild(); 1.557 + textNode && !found; 1.558 + textNode = treewalker.nextNode()) { 1.559 + 1.560 + // \r is not a valid character in the DOM, so we only check for \n. 1.561 + var lineArray = textNode.data.split(/\n/); 1.562 + var lastLineInNode = curLine + lineArray.length - 1; 1.563 + 1.564 + // Check if we can skip the text node without further inspection. 1.565 + if (node ? (textNode != node) : (lastLineInNode < line)) { 1.566 + if (lineArray.length > 1) { 1.567 + firstCol = 1; 1.568 + } 1.569 + firstCol += lineArray[lineArray.length - 1].length; 1.570 + curLine = lastLineInNode; 1.571 + continue; 1.572 + } 1.573 + 1.574 + // curPos is the offset within the current text node of the first 1.575 + // character in the current line. 1.576 + for (var i = 0, curPos = 0; 1.577 + i < lineArray.length; 1.578 + curPos += lineArray[i++].length + 1) { 1.579 + 1.580 + if (i > 0) { 1.581 + curLine++; 1.582 + } 1.583 + 1.584 + if (node) { 1.585 + if (offset >= curPos && offset <= curPos + lineArray[i].length) { 1.586 + // If we are right after the \n of a line and interlinePosition is 1.587 + // false, the caret looks as if it were at the end of the previous 1.588 + // line, so we display that line and column instead. 1.589 + 1.590 + if (i > 0 && offset == curPos && !interlinePosition) { 1.591 + result.line = curLine - 1; 1.592 + var prevPos = curPos - lineArray[i - 1].length; 1.593 + result.col = (i == 1 ? firstCol : 1) + offset - prevPos; 1.594 + } else { 1.595 + result.line = curLine; 1.596 + result.col = (i == 0 ? firstCol : 1) + offset - curPos; 1.597 + } 1.598 + found = true; 1.599 + 1.600 + break; 1.601 + } 1.602 + 1.603 + } else { 1.604 + if (curLine == line && !("range" in result)) { 1.605 + result.range = document.createRange(); 1.606 + result.range.setStart(textNode, curPos); 1.607 + 1.608 + // This will always be overridden later, except when we look for 1.609 + // the very last line in the file (this is the only line that does 1.610 + // not end with \n). 1.611 + result.range.setEndAfter(pre.lastChild); 1.612 + 1.613 + } else if (curLine == line + 1) { 1.614 + result.range.setEnd(textNode, curPos - 1); 1.615 + found = true; 1.616 + break; 1.617 + } 1.618 + } 1.619 + } 1.620 + } 1.621 + 1.622 + return found || ("range" in result); 1.623 +} 1.624 + 1.625 +// Toggle long-line wrapping and sets the view_source.wrap_long_lines 1.626 +// pref to persist the last state. 1.627 +function wrapLongLines() 1.628 +{ 1.629 + var myWrap = window.content.document.body; 1.630 + myWrap.classList.toggle("wrap"); 1.631 + 1.632 + // Since multiple viewsource windows are possible, another window could have 1.633 + // affected the pref, so instead of determining the new pref value via the current 1.634 + // pref value, we use myWrap.classList. 1.635 + Services.prefs.setBoolPref("view_source.wrap_long_lines", myWrap.classList.contains("wrap")); 1.636 +} 1.637 + 1.638 +// Toggles syntax highlighting and sets the view_source.syntax_highlight 1.639 +// pref to persist the last state. 1.640 +function highlightSyntax() 1.641 +{ 1.642 + var highlightSyntaxMenu = document.getElementById("menu_highlightSyntax"); 1.643 + var highlightSyntax = (highlightSyntaxMenu.getAttribute("checked") == "true"); 1.644 + Services.prefs.setBoolPref("view_source.syntax_highlight", highlightSyntax); 1.645 + 1.646 + gPageLoader.loadPage(gPageLoader.currentDescriptor, gPageLoader.DISPLAY_NORMAL); 1.647 +} 1.648 + 1.649 +// Reload after change to character encoding or autodetection 1.650 +// 1.651 +// Fix for bug 136322: this function overrides the function in 1.652 +// browser.js to call PageLoader.loadPage() instead of BrowserReloadWithFlags() 1.653 +function BrowserCharsetReload() 1.654 +{ 1.655 + if (isHistoryEnabled()) { 1.656 + gPageLoader.loadPage(gPageLoader.currentDescriptor, 1.657 + gPageLoader.DISPLAY_NORMAL); 1.658 + } else { 1.659 + gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); 1.660 + } 1.661 +} 1.662 + 1.663 +function BrowserSetCharacterSet(aEvent) 1.664 +{ 1.665 + if (aEvent.target.hasAttribute("charset")) 1.666 + gBrowser.docShell.charset = aEvent.target.getAttribute("charset"); 1.667 + BrowserCharsetReload(); 1.668 +} 1.669 + 1.670 +function BrowserForward(aEvent) { 1.671 + try { 1.672 + gBrowser.goForward(); 1.673 + } 1.674 + catch(ex) { 1.675 + } 1.676 +} 1.677 + 1.678 +function BrowserBack(aEvent) { 1.679 + try { 1.680 + gBrowser.goBack(); 1.681 + } 1.682 + catch(ex) { 1.683 + } 1.684 +} 1.685 + 1.686 +function UpdateBackForwardCommands() { 1.687 + var backBroadcaster = document.getElementById("Browser:Back"); 1.688 + var forwardBroadcaster = document.getElementById("Browser:Forward"); 1.689 + 1.690 + if (getWebNavigation().canGoBack) 1.691 + backBroadcaster.removeAttribute("disabled"); 1.692 + else 1.693 + backBroadcaster.setAttribute("disabled", "true"); 1.694 + 1.695 + if (getWebNavigation().canGoForward) 1.696 + forwardBroadcaster.removeAttribute("disabled"); 1.697 + else 1.698 + forwardBroadcaster.setAttribute("disabled", "true"); 1.699 +} 1.700 + 1.701 +function contextMenuShowing() { 1.702 + var isLink = false; 1.703 + var isEmail = false; 1.704 + if (gContextMenu.triggerNode && gContextMenu.triggerNode.localName == 'a') { 1.705 + if (gContextMenu.triggerNode.href.indexOf('view-source:') == 0) 1.706 + isLink = true; 1.707 + if (gContextMenu.triggerNode.href.indexOf('mailto:') == 0) 1.708 + isEmail = true; 1.709 + } 1.710 + document.getElementById('context-copyLink').hidden = !isLink; 1.711 + document.getElementById('context-copyEmail').hidden = !isEmail; 1.712 +} 1.713 + 1.714 +function contextMenuCopyLinkOrEmail() { 1.715 + if (!gContextMenu.triggerNode) 1.716 + return; 1.717 + 1.718 + var href = gContextMenu.triggerNode.href; 1.719 + var clipboard = Cc['@mozilla.org/widget/clipboardhelper;1']. 1.720 + getService(Ci.nsIClipboardHelper); 1.721 + clipboard.copyString(href.substring(href.indexOf(':') + 1), document); 1.722 +}