toolkit/components/viewsource/content/viewSource.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial