1
/* ***** BEGIN LICENSE BLOCK *****
2
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
* The contents of this file are subject to the Mozilla Public License Version
5
* 1.1 (the "License"); you may not use this file except in compliance with
6
* the License. You may obtain a copy of the License at
7
* http://www.mozilla.org/MPL/
9
* Software distributed under the License is distributed on an "AS IS" basis,
10
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
* for the specific language governing rights and limitations under the
14
* The Original Code is mozilla.org view-source front-end.
16
* The Initial Developer of the Original Code is mozilla.org.
17
* Portions created by the Initial Developer are Copyright (C) 2002
18
* the Initial Developer. All Rights Reserved.
21
* Roger B. Sidje <rbs@maths.uq.edu.au> (Original Author)
22
* Steve Swanson <steve.swanson@mackichan.com>
23
* Doron Rosenberg <doronr@naboonline.com>
25
* Alternatively, the contents of this file may be used under the terms of
26
* either the GNU General Public License Version 2 or later (the "GPL"), or
27
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
* in which case the provisions of the GPL or the LGPL are applicable instead
29
* of those above. If you wish to allow use of your version of this file only
30
* under the terms of either the GPL or the LGPL, and not to allow others to
31
* use your version of this file under the terms of the MPL, indicate your
32
* decision by deleting the provisions above and replace them with the notice
33
* and other provisions required by the GPL or the LGPL. If you do not delete
34
* the provisions above, a recipient may use your version of this file under
35
* the terms of any one of the MPL, the GPL or the LGPL.
37
* ***** END LICENSE BLOCK ***** */
41
var gStartTargetLine = 0;
42
var gEndTargetLine = 0;
43
var gTargetNode = null;
45
var gEntityConverter = null;
46
var gWrapLongLines = false;
47
const gViewSourceCSS = 'resource://gre/res/viewsource.css';
48
const NS_XHTML = 'http://www.w3.org/1999/xhtml';
50
// These are markers used to delimit the selection during processing. They
51
// are removed from the final rendering, but we pick space-like characters for
52
// safety (and futhermore, these are known to be mapped to a 0-length string
53
// in transliterate.properties). It is okay to set start=end, we use findNext()
54
// U+200B ZERO WIDTH SPACE
55
const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
56
const MARK_SELECTION_END = '\u200B\u200B\u200B\u200B\u200B';
58
function onLoadViewPartialSource()
60
// check the view_source.wrap_long_lines pref and set the menuitem's checked attribute accordingly
63
var wraplonglinesPrefValue = gPrefs.getBoolPref('view_source.wrap_long_lines');
64
if (wraplonglinesPrefValue) {
65
document.getElementById('menu_wrapLongLines').setAttribute('checked', 'true');
66
gWrapLongLines = true;
70
document.getElementById("menu_highlightSyntax").setAttribute("checked", gPrefs.getBoolPref("view_source.syntax_highlight"));
74
document.getElementById("menu_highlightSyntax").setAttribute("hidden", "true");
77
// disable menu items that don't work since the selection is munged and
78
// the editor doesn't work for MathML
79
document.getElementById('cmd_savePage').setAttribute('disabled', 'true');
80
document.getElementById('cmd_editPage').setAttribute('disabled', 'true');
82
if (window.arguments[3] == 'selection')
83
viewPartialSourceForSelection(window.arguments[2]);
85
viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
87
window._content.focus();
90
////////////////////////////////////////////////////////////////////////////////
91
// view-source of a selection with the special effect of remapping the selection
92
// to the underlying view-source output
93
function viewPartialSourceForSelection(selection)
95
var range = selection.getRangeAt(0);
96
var ancestorContainer = range.commonAncestorContainer;
97
var doc = ancestorContainer.ownerDocument;
99
var startContainer = range.startContainer;
100
var endContainer = range.endContainer;
101
var startOffset = range.startOffset;
102
var endOffset = range.endOffset;
104
// let the ancestor be an element
105
if (ancestorContainer.nodeType == Node.TEXT_NODE ||
106
ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
107
ancestorContainer = ancestorContainer.parentNode;
109
// for selectAll, let's use the entire document, including <html>...</html>
110
// @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
112
if (ancestorContainer == doc.body)
113
ancestorContainer = doc.documentElement;
116
// each path is a "child sequence" (a.k.a. "tumbler") that
117
// descends from the ancestor down to the boundary point
118
var startPath = getPath(ancestorContainer, startContainer);
119
var endPath = getPath(ancestorContainer, endContainer);
121
// clone the fragment of interest and reset everything to be relative to it
122
// note: it is with the clone that we operate from now on
123
ancestorContainer = ancestorContainer.cloneNode(true);
124
startContainer = ancestorContainer;
125
endContainer = ancestorContainer;
127
for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
128
startContainer = startContainer.childNodes.item(startPath[i]);
130
for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
131
endContainer = endContainer.childNodes.item(endPath[i]);
134
// add special markers to record the extent of the selection
135
// note: |startOffset| and |endOffset| are interpreted either as
136
// offsets in the text data or as child indices (see the Range spec)
137
// (here, munging the end point first to keep the start point safe...)
139
if (endContainer.nodeType == Node.TEXT_NODE ||
140
endContainer.nodeType == Node.CDATA_SECTION_NODE) {
141
// do some extra tweaks to try to avoid the view-source output to look like
142
// ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
143
// To get a neat output, the idea here is to remap the end point from:
144
// 1. ...<tag>]... to ...]<tag>...
145
// 2. ...]</tag>... to ...</tag>]...
146
if ((endOffset > 0 && endOffset < endContainer.data.length) ||
147
!endContainer.parentNode || !endContainer.parentNode.parentNode)
148
endContainer.insertData(endOffset, MARK_SELECTION_END);
150
tmpNode = doc.createTextNode(MARK_SELECTION_END);
151
endContainer = endContainer.parentNode;
153
endContainer.parentNode.insertBefore(tmpNode, endContainer);
155
endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
159
tmpNode = doc.createTextNode(MARK_SELECTION_END);
160
endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
163
if (startContainer.nodeType == Node.TEXT_NODE ||
164
startContainer.nodeType == Node.CDATA_SECTION_NODE) {
165
// do some extra tweaks to try to avoid the view-source output to look like
166
// ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
167
// To get a neat output, the idea here is to remap the start point from:
168
// 1. ...<tag>[... to ...[<tag>...
169
// 2. ...[</tag>... to ...</tag>[...
170
if ((startOffset > 0 && startOffset < startContainer.data.length) ||
171
!startContainer.parentNode || !startContainer.parentNode.parentNode ||
172
startContainer != startContainer.parentNode.lastChild)
173
startContainer.insertData(startOffset, MARK_SELECTION_START);
175
tmpNode = doc.createTextNode(MARK_SELECTION_START);
176
startContainer = startContainer.parentNode;
177
if (startOffset == 0)
178
startContainer.parentNode.insertBefore(tmpNode, startContainer);
180
startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
184
tmpNode = doc.createTextNode(MARK_SELECTION_START);
185
startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
188
// now extract and display the syntax highlighted source
189
tmpNode = doc.createElementNS(NS_XHTML, 'div');
190
tmpNode.appendChild(ancestorContainer);
192
// the load is aynchronous and so we will wait until the view-source DOM is done
193
// before drawing the selection.
194
window.document.getElementById("appcontent").addEventListener("load", drawSelection, true);
196
// all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
197
var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
198
getBrowser().webNavigation
199
.loadURI("view-source:data:text/html;charset=utf-8," + encodeURIComponent(tmpNode.innerHTML),
200
loadFlags, null, null, null);
203
////////////////////////////////////////////////////////////////////////////////
204
// helper to get a path like FIXptr, but with an array instead of the "tumbler" notation
205
// see FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
206
function getPath(ancestor, node)
209
var p = n.parentNode;
210
if (n == ancestor || !p)
212
var path = new Array();
216
for (var i = 0; i < p.childNodes.length; i++) {
217
if (p.childNodes.item(i) == n) {
224
} while (n != ancestor && p);
228
////////////////////////////////////////////////////////////////////////////////
229
// using special markers left in the serialized source, this helper makes the
230
// underlying markup of the selected fragment to automatically appear as selected
231
// on the inflated view-source DOM
232
function drawSelection()
234
// find the special selection markers that we added earlier, and
235
// draw the selection between the two...
236
var findService = null;
238
// get the find service which stores the global find state
239
findService = Components.classes["@mozilla.org/find/find_service;1"]
240
.getService(Components.interfaces.nsIFindService);
245
// cache the current global find state
246
var matchCase = findService.matchCase;
247
var entireWord = findService.entireWord;
248
var wrapFind = findService.wrapFind;
249
var findBackwards = findService.findBackwards;
250
var searchString = findService.searchString;
251
var replaceString = findService.replaceString;
253
// setup our find instance
254
var findInst = getBrowser().webBrowserFind;
255
findInst.matchCase = true;
256
findInst.entireWord = false;
257
findInst.wrapFind = true;
258
findInst.findBackwards = false;
260
// ...lookup the start mark
261
findInst.searchString = MARK_SELECTION_START;
262
var startLength = MARK_SELECTION_START.length;
265
var contentWindow = getBrowser().contentDocument.defaultView;
266
var selection = contentWindow.getSelection();
267
var range = selection.getRangeAt(0);
269
var startContainer = range.startContainer;
270
var startOffset = range.startOffset;
272
// ...lookup the end mark
273
findInst.searchString = MARK_SELECTION_END;
274
var endLength = MARK_SELECTION_END.length;
277
var endContainer = selection.anchorNode;
278
var endOffset = selection.anchorOffset;
280
// reset the selection that find has left
281
selection.removeAllRanges();
283
// delete the special markers now...
284
endContainer.deleteData(endOffset, endLength);
285
startContainer.deleteData(startOffset, startLength);
286
if (startContainer == endContainer)
287
endOffset -= startLength; // has shrunk if on same text node...
288
range.setEnd(endContainer, endOffset);
290
// show the selection and scroll it into view
291
selection.addRange(range);
292
// the default behavior of the selection is to scroll at the end of
293
// the selection, whereas in this situation, it is more user-friendly
294
// to scroll at the beginning. So we override the default behavior here
296
getBrowser().docShell
297
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
298
.getInterface(Components.interfaces.nsISelectionDisplay)
299
.QueryInterface(Components.interfaces.nsISelectionController)
300
.scrollSelectionIntoView(Components.interfaces.nsISelectionController.SELECTION_NORMAL,
301
Components.interfaces.nsISelectionController.SELECTION_ANCHOR_REGION,
306
// restore the current find state
307
findService.matchCase = matchCase;
308
findService.entireWord = entireWord;
309
findService.wrapFind = wrapFind;
310
findService.findBackwards = findBackwards;
311
findService.searchString = searchString;
312
findService.replaceString = replaceString;
314
findInst.matchCase = matchCase;
315
findInst.entireWord = entireWord;
316
findInst.wrapFind = wrapFind;
317
findInst.findBackwards = findBackwards;
318
findInst.searchString = searchString;
321
////////////////////////////////////////////////////////////////////////////////
322
// special handler for markups such as MathML where reformatting the output is
324
function viewPartialSourceForFragment(node, context)
327
if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
328
gTargetNode = gTargetNode.parentNode;
330
// walk up the tree to the top-level element (e.g., <math>, <svg>)
332
if (context == 'mathml')
336
var topNode = gTargetNode;
337
while (topNode && topNode.localName != topTag)
338
topNode = topNode.parentNode;
342
// serialize (note: the main window overrides the title set here)
343
var wrapClass = gWrapLongLines ? ' class="wrap"' : '';
346
+ '<head><title>Mozilla</title>'
347
+ '<link rel="stylesheet" type="text/css" href="' + gViewSourceCSS + '">'
348
+ '<style type="text/css">'
349
+ '#target { border: dashed 1px; background-color: lightyellow; }'
352
+ '<body id="viewsource"' + wrapClass
353
+ ' onload="document.getElementById(\'target\').scrollIntoView(true)">'
355
+ getOuterMarkup(topNode, 0)
356
+ '</pre></body></html>'
360
var doc = getBrowser().contentDocument;
361
doc.open("text/html", "replace");
366
////////////////////////////////////////////////////////////////////////////////
367
function getInnerMarkup(node, indent) {
369
for (var i = 0; i < node.childNodes.length; i++) {
370
str += getOuterMarkup(node.childNodes.item(i), indent);
375
////////////////////////////////////////////////////////////////////////////////
376
function getOuterMarkup(node, indent) {
380
if (node == gTargetNode) {
381
gStartTargetLine = gLineCount;
382
str += '</pre><pre id="target">';
385
switch (node.nodeType) {
386
case Node.ELEMENT_NODE: // Element
387
// to avoid the wide gap problem, '\n' is not emitted on the first
388
// line and the lines before & after the <pre id="target">...</pre>
389
if (gLineCount > 0 &&
390
gLineCount != gStartTargetLine &&
391
gLineCount != gEndTargetLine) {
396
newline += gLineCount;
398
for (var k = 0; k < indent; k++) {
401
str += newline + padding
402
+ '<<span class="start-tag">' + node.nodeName + '</span>';
403
for (var i = 0; i < node.attributes.length; i++) {
404
var attr = node.attributes.item(i);
405
if (!gDebug && attr.nodeName.match(/^[-_]moz/)) {
408
str += ' <span class="attribute-name">'
410
+ '</span>=<span class="attribute-value">"'
411
+ unicodeTOentity(attr.nodeValue)
414
if (!node.hasChildNodes()) {
419
var oldLine = gLineCount;
420
str += getInnerMarkup(node, indent + 2);
421
if (oldLine == gLineCount) {
426
newline = (gLineCount == gEndTargetLine) ? '' : '\n';
429
newline += gLineCount;
432
str += newline + padding
433
+ '</<span class="end-tag">' + node.nodeName + '</span>>';
436
case Node.TEXT_NODE: // Text
437
var tmp = node.nodeValue;
438
tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
439
tmp = tmp.replace(/^ +/, "");
440
tmp = tmp.replace(/ +$/, "");
441
if (tmp.length != 0) {
442
str += '<span class="text">' + unicodeTOentity(tmp) + '</span>';
449
if (node == gTargetNode) {
450
gEndTargetLine = gLineCount;
451
str += '</pre><pre>';
456
////////////////////////////////////////////////////////////////////////////////
457
function unicodeTOentity(text)
460
'&': '&<span class="entity">amp;</span>',
461
'<': '&<span class="entity">lt;</span>',
462
'>': '&<span class="entity">gt;</span>',
463
'"': '&<span class="entity">quot;</span>'
466
function charTableLookup(letter) {
467
return charTable[letter];
470
function convertEntity(letter) {
472
var unichar = gEntityConverter.ConvertToEntity(letter, entityVersion);
473
var entity = unichar.substring(1); // extract '&'
474
return '&<span class="entity">' + entity + '</span>';
480
if (!gEntityConverter) {
483
Components.classes["@mozilla.org/intl/entityconverter;1"]
484
.createInstance(Components.interfaces.nsIEntityConverter);
488
const entityVersion = Components.interfaces.nsIEntityConverter.entityW3C;
492
// replace chars in our charTable
493
str = str.replace(/[<>&"]/g, charTableLookup);
495
// replace chars > 0x7f via nsIEntityConverter
496
str = str.replace(/[^\0-\u007f]/g, convertEntity);