toolkit/components/viewsource/content/viewSource.js

changeset 0
6474c204b198
     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 +}

mercurial