diff -r 000000000000 -r 6474c204b198 toolkit/components/viewsource/content/viewPartialSource.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/toolkit/components/viewsource/content/viewPartialSource.js Wed Dec 31 06:09:35 2014 +0100
@@ -0,0 +1,481 @@
+// -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gDebug = 0;
+var gLineCount = 0;
+var gStartTargetLine = 0;
+var gEndTargetLine = 0;
+var gTargetNode = null;
+
+var gEntityConverter = null;
+var gWrapLongLines = false;
+const gViewSourceCSS = 'resource://gre-resources/viewsource.css';
+const NS_XHTML = 'http://www.w3.org/1999/xhtml';
+
+// These are markers used to delimit the selection during processing. They
+// are removed from the final rendering, but we pick space-like characters for
+// safety (and futhermore, these are known to be mapped to a 0-length string
+// in transliterate.properties). It is okay to set start=end, we use findNext()
+// U+200B ZERO WIDTH SPACE
+const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
+const MARK_SELECTION_END = '\u200B\u200B\u200B\u200B\u200B';
+
+function onLoadViewPartialSource()
+{
+ // check the view_source.wrap_long_lines pref
+ // and set the menuitem's checked attribute accordingly
+ gWrapLongLines = Services.prefs.getBoolPref("view_source.wrap_long_lines");
+ document.getElementById("menu_wrapLongLines").setAttribute("checked", gWrapLongLines);
+ document.getElementById("menu_highlightSyntax")
+ .setAttribute("checked",
+ Services.prefs.getBoolPref("view_source.syntax_highlight"));
+
+ if (window.arguments[3] == 'selection')
+ viewPartialSourceForSelection(window.arguments[2]);
+ else
+ viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
+
+ gBrowser.droppedLinkHandler = function (event, url, name) {
+ viewSource(url)
+ event.preventDefault();
+ }
+
+ window.content.focus();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// view-source of a selection with the special effect of remapping the selection
+// to the underlying view-source output
+function viewPartialSourceForSelection(selection)
+{
+ var range = selection.getRangeAt(0);
+ var ancestorContainer = range.commonAncestorContainer;
+ var doc = ancestorContainer.ownerDocument;
+
+ var startContainer = range.startContainer;
+ var endContainer = range.endContainer;
+ var startOffset = range.startOffset;
+ var endOffset = range.endOffset;
+
+ // let the ancestor be an element
+ if (ancestorContainer.nodeType == Node.TEXT_NODE ||
+ ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
+ ancestorContainer = ancestorContainer.parentNode;
+
+ // for selectAll, let's use the entire document, including ...
+ // @see nsDocumentViewer::SelectAll() for how selectAll is implemented
+ try {
+ if (ancestorContainer == doc.body)
+ ancestorContainer = doc.documentElement;
+ } catch (e) { }
+
+ // each path is a "child sequence" (a.k.a. "tumbler") that
+ // descends from the ancestor down to the boundary point
+ var startPath = getPath(ancestorContainer, startContainer);
+ var endPath = getPath(ancestorContainer, endContainer);
+
+ // clone the fragment of interest and reset everything to be relative to it
+ // note: it is with the clone that we operate/munge from now on. Also note
+ // that we clone into a data document to prevent images in the fragment from
+ // loading and the like. The use of importNode here, as opposed to adoptNode,
+ // is _very_ important.
+ // XXXbz wish there were a less hacky way to create an untrusted document here
+ var isHTML = (doc.createElement("div").tagName == "DIV");
+ var dataDoc = isHTML ?
+ ancestorContainer.ownerDocument.implementation.createHTMLDocument("") :
+ ancestorContainer.ownerDocument.implementation.createDocument("", "", null);
+ ancestorContainer = dataDoc.importNode(ancestorContainer, true);
+ startContainer = ancestorContainer;
+ endContainer = ancestorContainer;
+
+ // Only bother with the selection if it can be remapped. Don't mess with
+ // leaf elements (such as ) that secretly use anynomous content
+ // for their display appearance.
+ var canDrawSelection = ancestorContainer.hasChildNodes();
+ if (canDrawSelection) {
+ var i;
+ for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
+ startContainer = startContainer.childNodes.item(startPath[i]);
+ }
+ for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
+ endContainer = endContainer.childNodes.item(endPath[i]);
+ }
+
+ // add special markers to record the extent of the selection
+ // note: |startOffset| and |endOffset| are interpreted either as
+ // offsets in the text data or as child indices (see the Range spec)
+ // (here, munging the end point first to keep the start point safe...)
+ var tmpNode;
+ if (endContainer.nodeType == Node.TEXT_NODE ||
+ endContainer.nodeType == Node.CDATA_SECTION_NODE) {
+ // do some extra tweaks to try to avoid the view-source output to look like
+ // ...]... or ...]... (where ']' marks the end of the selection).
+ // To get a neat output, the idea here is to remap the end point from:
+ // 1. ...]... to ...]...
+ // 2. ...]... to ...]...
+ if ((endOffset > 0 && endOffset < endContainer.data.length) ||
+ !endContainer.parentNode || !endContainer.parentNode.parentNode)
+ endContainer.insertData(endOffset, MARK_SELECTION_END);
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
+ endContainer = endContainer.parentNode;
+ if (endOffset == 0)
+ endContainer.parentNode.insertBefore(tmpNode, endContainer);
+ else
+ endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
+ }
+ }
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
+ endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
+ }
+
+ if (startContainer.nodeType == Node.TEXT_NODE ||
+ startContainer.nodeType == Node.CDATA_SECTION_NODE) {
+ // do some extra tweaks to try to avoid the view-source output to look like
+ // ...[... or ...[... (where '[' marks the start of the selection).
+ // To get a neat output, the idea here is to remap the start point from:
+ // 1. ...[... to ...[...
+ // 2. ...[... to ...[...
+ if ((startOffset > 0 && startOffset < startContainer.data.length) ||
+ !startContainer.parentNode || !startContainer.parentNode.parentNode ||
+ startContainer != startContainer.parentNode.lastChild)
+ startContainer.insertData(startOffset, MARK_SELECTION_START);
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
+ startContainer = startContainer.parentNode;
+ if (startOffset == 0)
+ startContainer.parentNode.insertBefore(tmpNode, startContainer);
+ else
+ startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
+ }
+ }
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
+ startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
+ }
+ }
+
+ // now extract and display the syntax highlighted source
+ tmpNode = dataDoc.createElementNS(NS_XHTML, 'div');
+ tmpNode.appendChild(ancestorContainer);
+
+ // the load is aynchronous and so we will wait until the view-source DOM is done
+ // before drawing the selection.
+ if (canDrawSelection) {
+ window.document.getElementById("content").addEventListener("load", drawSelection, true);
+ }
+
+ // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
+ var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
+ getWebNavigation().loadURIWithBase((isHTML ?
+ "view-source:data:text/html;charset=utf-8," :
+ "view-source:data:application/xml;charset=utf-8,")
+ + encodeURIComponent(tmpNode.innerHTML),
+ loadFlags, null, null, null,
+ Services.io.newURI(doc.baseURI, null, null));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// helper to get a path like FIXptr, but with an array instead of the "tumbler" notation
+// see FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
+function getPath(ancestor, node)
+{
+ var n = node;
+ var p = n.parentNode;
+ if (n == ancestor || !p)
+ return null;
+ var path = new Array();
+ if (!path)
+ return null;
+ do {
+ for (var i = 0; i < p.childNodes.length; i++) {
+ if (p.childNodes.item(i) == n) {
+ path.push(i);
+ break;
+ }
+ }
+ n = p;
+ p = n.parentNode;
+ } while (n != ancestor && p);
+ return path;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// using special markers left in the serialized source, this helper makes the
+// underlying markup of the selected fragment to automatically appear as selected
+// on the inflated view-source DOM
+function drawSelection()
+{
+ gBrowser.contentDocument.title =
+ gViewSourceBundle.getString("viewSelectionSourceTitle");
+
+ // find the special selection markers that we added earlier, and
+ // draw the selection between the two...
+ var findService = null;
+ try {
+ // get the find service which stores the global find state
+ findService = Components.classes["@mozilla.org/find/find_service;1"]
+ .getService(Components.interfaces.nsIFindService);
+ } catch(e) { }
+ if (!findService)
+ return;
+
+ // cache the current global find state
+ var matchCase = findService.matchCase;
+ var entireWord = findService.entireWord;
+ var wrapFind = findService.wrapFind;
+ var findBackwards = findService.findBackwards;
+ var searchString = findService.searchString;
+ var replaceString = findService.replaceString;
+
+ // setup our find instance
+ var findInst = gBrowser.webBrowserFind;
+ findInst.matchCase = true;
+ findInst.entireWord = false;
+ findInst.wrapFind = true;
+ findInst.findBackwards = false;
+
+ // ...lookup the start mark
+ findInst.searchString = MARK_SELECTION_START;
+ var startLength = MARK_SELECTION_START.length;
+ findInst.findNext();
+
+ var selection = content.getSelection();
+ if (!selection.rangeCount)
+ return;
+
+ var range = selection.getRangeAt(0);
+
+ var startContainer = range.startContainer;
+ var startOffset = range.startOffset;
+
+ // ...lookup the end mark
+ findInst.searchString = MARK_SELECTION_END;
+ var endLength = MARK_SELECTION_END.length;
+ findInst.findNext();
+
+ var endContainer = selection.anchorNode;
+ var endOffset = selection.anchorOffset;
+
+ // reset the selection that find has left
+ selection.removeAllRanges();
+
+ // delete the special markers now...
+ endContainer.deleteData(endOffset, endLength);
+ startContainer.deleteData(startOffset, startLength);
+ if (startContainer == endContainer)
+ endOffset -= startLength; // has shrunk if on same text node...
+ range.setEnd(endContainer, endOffset);
+
+ // show the selection and scroll it into view
+ selection.addRange(range);
+ // the default behavior of the selection is to scroll at the end of
+ // the selection, whereas in this situation, it is more user-friendly
+ // to scroll at the beginning. So we override the default behavior here
+ try {
+ getSelectionController().scrollSelectionIntoView(
+ Ci.nsISelectionController.SELECTION_NORMAL,
+ Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
+ true);
+ }
+ catch(e) { }
+
+ // restore the current find state
+ findService.matchCase = matchCase;
+ findService.entireWord = entireWord;
+ findService.wrapFind = wrapFind;
+ findService.findBackwards = findBackwards;
+ findService.searchString = searchString;
+ findService.replaceString = replaceString;
+
+ findInst.matchCase = matchCase;
+ findInst.entireWord = entireWord;
+ findInst.wrapFind = wrapFind;
+ findInst.findBackwards = findBackwards;
+ findInst.searchString = searchString;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// special handler for markups such as MathML where reformatting the output is
+// helpful
+function viewPartialSourceForFragment(node, context)
+{
+ gTargetNode = node;
+ if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
+ gTargetNode = gTargetNode.parentNode;
+
+ // walk up the tree to the top-level element (e.g.,