~ubuntu-branches/ubuntu/precise/kompozer/precise

« back to all changes in this revision

Viewing changes to mozilla/xpfe/browser/resources/content/viewPartialSource.js

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Yarusso
  • Date: 2007-08-27 01:11:03 UTC
  • Revision ID: james.westby@ubuntu.com-20070827011103-2jgf4s6532gqu2ka
Tags: upstream-0.7.10
ImportĀ upstreamĀ versionĀ 0.7.10

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ***** BEGIN LICENSE BLOCK *****
 
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
3
 *
 
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/
 
8
 *
 
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
 
12
 * License.
 
13
 *
 
14
 * The Original Code is mozilla.org view-source front-end.
 
15
 *
 
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.
 
19
 *
 
20
 * Contributor(s):
 
21
 *   Roger B. Sidje <rbs@maths.uq.edu.au> (Original Author)
 
22
 *   Steve Swanson <steve.swanson@mackichan.com>
 
23
 *   Doron Rosenberg <doronr@naboonline.com>
 
24
 *
 
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.
 
36
 *
 
37
 * ***** END LICENSE BLOCK ***** */
 
38
 
 
39
var gDebug = 0;
 
40
var gLineCount = 0;
 
41
var gStartTargetLine = 0;
 
42
var gEndTargetLine = 0;
 
43
var gTargetNode = null;
 
44
 
 
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';
 
49
 
 
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';
 
57
 
 
58
function onLoadViewPartialSource()
 
59
{
 
60
  // check the view_source.wrap_long_lines pref and set the menuitem's checked attribute accordingly
 
61
  if (gPrefs) {
 
62
    try {
 
63
      var wraplonglinesPrefValue = gPrefs.getBoolPref('view_source.wrap_long_lines');
 
64
      if (wraplonglinesPrefValue) {
 
65
        document.getElementById('menu_wrapLongLines').setAttribute('checked', 'true');
 
66
        gWrapLongLines = true;
 
67
      }
 
68
    } catch (e) { }
 
69
    try {
 
70
      document.getElementById("menu_highlightSyntax").setAttribute("checked", gPrefs.getBoolPref("view_source.syntax_highlight"));
 
71
    } catch (e) {
 
72
    }
 
73
  } else {
 
74
    document.getElementById("menu_highlightSyntax").setAttribute("hidden", "true");
 
75
  }
 
76
 
 
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');
 
81
 
 
82
  if (window.arguments[3] == 'selection')
 
83
    viewPartialSourceForSelection(window.arguments[2]);
 
84
  else
 
85
    viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
 
86
 
 
87
  window._content.focus();
 
88
}
 
89
 
 
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)
 
94
{
 
95
  var range = selection.getRangeAt(0);
 
96
  var ancestorContainer = range.commonAncestorContainer;
 
97
  var doc = ancestorContainer.ownerDocument;
 
98
 
 
99
  var startContainer = range.startContainer;
 
100
  var endContainer = range.endContainer;
 
101
  var startOffset = range.startOffset;
 
102
  var endOffset = range.endOffset;
 
103
 
 
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;
 
108
 
 
109
  // for selectAll, let's use the entire document, including <html>...</html>
 
110
  // @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
 
111
  try {
 
112
    if (ancestorContainer == doc.body)
 
113
      ancestorContainer = doc.documentElement;
 
114
  } catch (e) { }
 
115
 
 
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);
 
120
 
 
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;
 
126
  var i;
 
127
  for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
 
128
    startContainer = startContainer.childNodes.item(startPath[i]);
 
129
  }
 
130
  for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
 
131
    endContainer = endContainer.childNodes.item(endPath[i]);
 
132
  }
 
133
 
 
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...)
 
138
  var tmpNode;
 
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);
 
149
    else {
 
150
      tmpNode = doc.createTextNode(MARK_SELECTION_END);
 
151
      endContainer = endContainer.parentNode;
 
152
      if (endOffset == 0)
 
153
        endContainer.parentNode.insertBefore(tmpNode, endContainer);
 
154
      else
 
155
        endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
 
156
    }
 
157
  }
 
158
  else {
 
159
    tmpNode = doc.createTextNode(MARK_SELECTION_END);
 
160
    endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
 
161
  }
 
162
 
 
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);
 
174
    else {
 
175
      tmpNode = doc.createTextNode(MARK_SELECTION_START);
 
176
      startContainer = startContainer.parentNode;
 
177
      if (startOffset == 0)
 
178
        startContainer.parentNode.insertBefore(tmpNode, startContainer);
 
179
      else
 
180
        startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
 
181
    }
 
182
  }
 
183
  else {
 
184
    tmpNode = doc.createTextNode(MARK_SELECTION_START);
 
185
    startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
 
186
  }
 
187
 
 
188
  // now extract and display the syntax highlighted source
 
189
  tmpNode = doc.createElementNS(NS_XHTML, 'div');
 
190
  tmpNode.appendChild(ancestorContainer);
 
191
 
 
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);
 
195
 
 
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);
 
201
}
 
202
 
 
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)
 
207
{
 
208
  var n = node;
 
209
  var p = n.parentNode;
 
210
  if (n == ancestor || !p)
 
211
    return null;
 
212
  var path = new Array();
 
213
  if (!path)
 
214
    return null;
 
215
  do {
 
216
    for (var i = 0; i < p.childNodes.length; i++) {
 
217
      if (p.childNodes.item(i) == n) {
 
218
        path.push(i);
 
219
        break;
 
220
      }
 
221
    }
 
222
    n = p;
 
223
    p = n.parentNode;
 
224
  } while (n != ancestor && p);
 
225
  return path;
 
226
}
 
227
 
 
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()
 
233
{
 
234
  // find the special selection markers that we added earlier, and
 
235
  // draw the selection between the two...
 
236
  var findService = null;
 
237
  try {
 
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);
 
241
  } catch(e) { }
 
242
  if (!findService)
 
243
    return;
 
244
 
 
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;
 
252
 
 
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;
 
259
 
 
260
  // ...lookup the start mark
 
261
  findInst.searchString = MARK_SELECTION_START;
 
262
  var startLength = MARK_SELECTION_START.length;
 
263
  findInst.findNext();
 
264
 
 
265
  var contentWindow = getBrowser().contentDocument.defaultView;
 
266
  var selection = contentWindow.getSelection();
 
267
  var range = selection.getRangeAt(0);
 
268
 
 
269
  var startContainer = range.startContainer;
 
270
  var startOffset = range.startOffset;
 
271
 
 
272
  // ...lookup the end mark
 
273
  findInst.searchString = MARK_SELECTION_END;
 
274
  var endLength = MARK_SELECTION_END.length;
 
275
  findInst.findNext();
 
276
 
 
277
  var endContainer = selection.anchorNode;
 
278
  var endOffset = selection.anchorOffset;
 
279
 
 
280
  // reset the selection that find has left
 
281
  selection.removeAllRanges();
 
282
 
 
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);
 
289
 
 
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
 
295
  try {
 
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,
 
302
                                         true);
 
303
  }
 
304
  catch(e) { }
 
305
 
 
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;
 
313
 
 
314
  findInst.matchCase     = matchCase;
 
315
  findInst.entireWord    = entireWord;
 
316
  findInst.wrapFind      = wrapFind;
 
317
  findInst.findBackwards = findBackwards;
 
318
  findInst.searchString  = searchString;
 
319
}
 
320
 
 
321
////////////////////////////////////////////////////////////////////////////////
 
322
// special handler for markups such as MathML where reformatting the output is
 
323
// helpful
 
324
function viewPartialSourceForFragment(node, context)
 
325
{
 
326
  gTargetNode = node;
 
327
  if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
 
328
    gTargetNode = gTargetNode.parentNode;
 
329
 
 
330
  // walk up the tree to the top-level element (e.g., <math>, <svg>)
 
331
  var topTag;
 
332
  if (context == 'mathml')
 
333
    topTag = 'math';
 
334
  else
 
335
    throw 'not reached';
 
336
  var topNode = gTargetNode;
 
337
  while (topNode && topNode.localName != topTag)
 
338
    topNode = topNode.parentNode;
 
339
  if (!topNode)
 
340
    return;
 
341
 
 
342
  // serialize (note: the main window overrides the title set here)
 
343
  var wrapClass = gWrapLongLines ? ' class="wrap"' : '';
 
344
  var source =
 
345
    '<html>'
 
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; }'
 
350
  + '</style>'
 
351
  + '</head>'
 
352
  + '<body id="viewsource"' + wrapClass
 
353
  +        ' onload="document.getElementById(\'target\').scrollIntoView(true)">'
 
354
  + '<pre>'
 
355
  + getOuterMarkup(topNode, 0)
 
356
  + '</pre></body></html>'
 
357
  ; // end
 
358
 
 
359
  // display
 
360
  var doc = getBrowser().contentDocument;
 
361
  doc.open("text/html", "replace");
 
362
  doc.write(source);
 
363
  doc.close();
 
364
}
 
365
 
 
366
////////////////////////////////////////////////////////////////////////////////
 
367
function getInnerMarkup(node, indent) {
 
368
  var str = '';
 
369
  for (var i = 0; i < node.childNodes.length; i++) {
 
370
    str += getOuterMarkup(node.childNodes.item(i), indent);
 
371
  }
 
372
  return str;
 
373
}
 
374
 
 
375
////////////////////////////////////////////////////////////////////////////////
 
376
function getOuterMarkup(node, indent) {
 
377
  var newline = '';
 
378
  var padding = '';
 
379
  var str = '';
 
380
  if (node == gTargetNode) {
 
381
    gStartTargetLine = gLineCount;
 
382
    str += '</pre><pre id="target">';
 
383
  }
 
384
 
 
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) {
 
392
      newline = '\n';
 
393
    }
 
394
    gLineCount++;
 
395
    if (gDebug) {
 
396
      newline += gLineCount;
 
397
    }
 
398
    for (var k = 0; k < indent; k++) {
 
399
      padding += ' ';
 
400
    }
 
401
    str += newline + padding
 
402
        +  '&lt;<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/)) {
 
406
        continue;
 
407
      }
 
408
      str += ' <span class="attribute-name">'
 
409
          +  attr.nodeName
 
410
          +  '</span>=<span class="attribute-value">"'
 
411
          +  unicodeTOentity(attr.nodeValue)
 
412
          +  '"</span>';
 
413
    }
 
414
    if (!node.hasChildNodes()) {
 
415
      str += '/&gt;';
 
416
    }
 
417
    else {
 
418
      str += '&gt;';
 
419
      var oldLine = gLineCount;
 
420
      str += getInnerMarkup(node, indent + 2);
 
421
      if (oldLine == gLineCount) {
 
422
        newline = '';
 
423
        padding = '';
 
424
      }
 
425
      else {
 
426
        newline = (gLineCount == gEndTargetLine) ? '' : '\n';
 
427
        gLineCount++;
 
428
        if (gDebug) {
 
429
          newline += gLineCount;
 
430
        }
 
431
      }
 
432
      str += newline + padding
 
433
          +  '&lt;/<span class="end-tag">' + node.nodeName + '</span>&gt;';
 
434
    }
 
435
    break;
 
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>';
 
443
    }
 
444
    break;
 
445
  default:
 
446
    break;
 
447
  }
 
448
 
 
449
  if (node == gTargetNode) {
 
450
    gEndTargetLine = gLineCount;
 
451
    str += '</pre><pre>';
 
452
  }
 
453
  return str;
 
454
}
 
455
 
 
456
////////////////////////////////////////////////////////////////////////////////
 
457
function unicodeTOentity(text)
 
458
{
 
459
  const charTable = {
 
460
    '&': '&amp;<span class="entity">amp;</span>',
 
461
    '<': '&amp;<span class="entity">lt;</span>',
 
462
    '>': '&amp;<span class="entity">gt;</span>',
 
463
    '"': '&amp;<span class="entity">quot;</span>'
 
464
  };
 
465
 
 
466
  function charTableLookup(letter) {
 
467
    return charTable[letter];
 
468
  }
 
469
 
 
470
  function convertEntity(letter) {
 
471
    try {
 
472
      var unichar = gEntityConverter.ConvertToEntity(letter, entityVersion);
 
473
      var entity = unichar.substring(1); // extract '&'
 
474
      return '&amp;<span class="entity">' + entity + '</span>';
 
475
    } catch (ex) {
 
476
      return letter;
 
477
    }
 
478
  }
 
479
 
 
480
  if (!gEntityConverter) {
 
481
    try {
 
482
      gEntityConverter =
 
483
        Components.classes["@mozilla.org/intl/entityconverter;1"]
 
484
                  .createInstance(Components.interfaces.nsIEntityConverter);
 
485
    } catch(e) { }
 
486
  }
 
487
 
 
488
  const entityVersion = Components.interfaces.nsIEntityConverter.entityW3C;
 
489
 
 
490
  var str = text;
 
491
 
 
492
  // replace chars in our charTable
 
493
  str = str.replace(/[<>&"]/g, charTableLookup);
 
494
 
 
495
  // replace chars > 0x7f via nsIEntityConverter
 
496
  str = str.replace(/[^\0-\u007f]/g, convertEntity);
 
497
 
 
498
  return str;
 
499
}