зеркало из https://github.com/mozilla/gecko-dev.git
380 строки
13 KiB
JavaScript
380 строки
13 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["ViewSourceChild"];
|
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"ViewSourcePageChild",
|
|
"resource://gre/actors/ViewSourcePageChild.jsm"
|
|
);
|
|
|
|
class ViewSourceChild extends JSWindowActorChild {
|
|
receiveMessage(message) {
|
|
let data = message.data;
|
|
switch (message.name) {
|
|
case "ViewSource:LoadSource":
|
|
this.viewSource(data.URL, data.outerWindowID, data.lineNumber);
|
|
break;
|
|
case "ViewSource:LoadSourceWithSelection":
|
|
this.viewSourceWithSelection(
|
|
data.URL,
|
|
data.drawSelection,
|
|
data.baseURI
|
|
);
|
|
break;
|
|
case "ViewSource:GetSelection":
|
|
let selectionDetails;
|
|
try {
|
|
selectionDetails = this.getSelection(this.document.ownerGlobal);
|
|
} catch (e) {}
|
|
return selectionDetails;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Called when the parent sends a message to view some source code.
|
|
*
|
|
* @param URL (required)
|
|
* The URL string of the source to be shown.
|
|
* @param outerWindowID (optional)
|
|
* The outerWindowID of the content window that has hosted
|
|
* the document, in case we want to retrieve it from the network
|
|
* cache.
|
|
* @param lineNumber (optional)
|
|
* The line number to focus as soon as the source has finished
|
|
* loading.
|
|
*/
|
|
viewSource(URL, outerWindowID, lineNumber) {
|
|
let pageDescriptor, forcedCharSet;
|
|
|
|
if (outerWindowID) {
|
|
let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
|
|
if (contentWindow) {
|
|
let otherDocShell = contentWindow.docShell;
|
|
|
|
try {
|
|
pageDescriptor = otherDocShell.QueryInterface(Ci.nsIWebPageDescriptor)
|
|
.currentDescriptor;
|
|
} catch (e) {
|
|
// We couldn't get the page descriptor, so we'll probably end up re-retrieving
|
|
// this document off of the network.
|
|
}
|
|
|
|
let utils = contentWindow.windowUtils;
|
|
let doc = contentWindow.document;
|
|
forcedCharSet = utils.docCharsetIsForced ? doc.characterSet : null;
|
|
}
|
|
}
|
|
|
|
this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
|
|
}
|
|
|
|
/**
|
|
* Loads a view source selection showing the given view-source url and
|
|
* highlight the selection.
|
|
*
|
|
* @param uri view-source uri to show
|
|
* @param drawSelection true to highlight the selection
|
|
* @param baseURI base URI of the original document
|
|
*/
|
|
viewSourceWithSelection(uri, drawSelection, baseURI) {
|
|
// This isn't ideal, but set a global in the view source page actor
|
|
// that indicates that a selection should be drawn. It will be read
|
|
// when by the page's pageshow listener. This should work as the
|
|
// view source page is always loaded in the same process.
|
|
ViewSourcePageChild.setNeedsDrawSelection(drawSelection);
|
|
|
|
// all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
|
|
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
let loadURIOptions = {
|
|
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
loadFlags,
|
|
baseURI: Services.io.newURI(baseURI),
|
|
};
|
|
webNav.loadURI(uri, loadURIOptions);
|
|
}
|
|
|
|
/**
|
|
* Common utility function used by both the current and deprecated APIs
|
|
* for loading source.
|
|
*
|
|
* @param URL (required)
|
|
* The URL string of the source to be shown.
|
|
* @param pageDescriptor (optional)
|
|
* The currentDescriptor off of an nsIWebPageDescriptor, in the
|
|
* event that the caller wants to try to load the source out of
|
|
* the network cache.
|
|
* @param lineNumber (optional)
|
|
* The line number to focus as soon as the source has finished
|
|
* loading.
|
|
* @param forcedCharSet (optional)
|
|
* The document character set to use instead of the default one.
|
|
*/
|
|
loadSource(URL, pageDescriptor, lineNumber, forcedCharSet) {
|
|
const viewSrcURL = "view-source:" + URL;
|
|
|
|
if (forcedCharSet) {
|
|
try {
|
|
this.docShell.charset = forcedCharSet;
|
|
} catch (e) {
|
|
/* invalid charset */
|
|
}
|
|
}
|
|
|
|
ViewSourcePageChild.setInitialLineNumber(lineNumber);
|
|
|
|
if (!pageDescriptor) {
|
|
this.loadSourceFromURL(viewSrcURL);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let pageLoader = this.docShell.QueryInterface(Ci.nsIWebPageDescriptor);
|
|
pageLoader.loadPage(
|
|
pageDescriptor,
|
|
Ci.nsIWebPageDescriptor.DISPLAY_AS_SOURCE
|
|
);
|
|
} catch (e) {
|
|
// We were not able to load the source from the network cache.
|
|
this.loadSourceFromURL(viewSrcURL);
|
|
return;
|
|
}
|
|
|
|
let shEntrySource = pageDescriptor.QueryInterface(Ci.nsISHEntry);
|
|
let shistory = this.docShell.QueryInterface(Ci.nsIWebNavigation)
|
|
.sessionHistory.legacySHistory;
|
|
let shEntry = shistory.createEntry();
|
|
shEntry.URI = Services.io.newURI(viewSrcURL);
|
|
shEntry.title = viewSrcURL;
|
|
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
|
shEntry.triggeringPrincipal = systemPrincipal;
|
|
shEntry.setLoadTypeAsHistory();
|
|
shEntry.cacheKey = shEntrySource.cacheKey;
|
|
shistory.addEntry(shEntry, true);
|
|
}
|
|
|
|
/**
|
|
* Load some URL in the browser.
|
|
*
|
|
* @param URL
|
|
* The URL string to load.
|
|
*/
|
|
loadSourceFromURL(URL) {
|
|
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
let loadURIOptions = {
|
|
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
loadFlags,
|
|
};
|
|
webNav.loadURI(URL, loadURIOptions);
|
|
}
|
|
|
|
/**
|
|
* A 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
|
|
*/
|
|
getPath(ancestor, node) {
|
|
var n = node;
|
|
var p = n.parentNode;
|
|
if (n == ancestor || !p) {
|
|
return null;
|
|
}
|
|
var path = [];
|
|
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;
|
|
}
|
|
|
|
getSelection(global) {
|
|
const { content } = global;
|
|
|
|
// These are markers used to delimit the selection during processing. They
|
|
// are removed from the final rendering.
|
|
// We use noncharacter Unicode codepoints to minimize the risk of clashing
|
|
// with anything that might legitimately be present in the document.
|
|
// U+FDD0..FDEF <noncharacters>
|
|
const MARK_SELECTION_START = "\uFDD0";
|
|
const MARK_SELECTION_END = "\uFDEF";
|
|
|
|
var focusedWindow = Services.focus.focusedWindow || content;
|
|
var selection = focusedWindow.getSelection();
|
|
|
|
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
|
|
var Node = doc.defaultView.Node;
|
|
if (
|
|
ancestorContainer.nodeType == Node.TEXT_NODE ||
|
|
ancestorContainer.nodeType == Node.CDATA_SECTION_NODE
|
|
) {
|
|
ancestorContainer = ancestorContainer.parentNode;
|
|
}
|
|
|
|
// for selectAll, let's use the entire document, including <html>...</html>
|
|
// @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 = this.getPath(ancestorContainer, startContainer);
|
|
var endPath = this.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 <isindex>) that secretly use anynomous content
|
|
// for their display appearance.
|
|
var canDrawSelection = ancestorContainer.hasChildNodes();
|
|
var tmpNode;
|
|
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...)
|
|
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
|
|
// ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
|
|
// To get a neat output, the idea here is to remap the end point from:
|
|
// 1. ...<tag>]... to ...]<tag>...
|
|
// 2. ...]</tag>... to ...</tag>]...
|
|
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
|
|
// ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
|
|
// To get a neat output, the idea here is to remap the start point from:
|
|
// 1. ...<tag>[... to ...[<tag>...
|
|
// 2. ...[</tag>... to ...</tag>[...
|
|
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("http://www.w3.org/1999/xhtml", "div");
|
|
tmpNode.appendChild(ancestorContainer);
|
|
|
|
return {
|
|
URL:
|
|
(isHTML
|
|
? "view-source:data:text/html;charset=utf-8,"
|
|
: "view-source:data:application/xml;charset=utf-8,") +
|
|
encodeURIComponent(tmpNode.innerHTML),
|
|
drawSelection: canDrawSelection,
|
|
baseURI: doc.baseURI,
|
|
};
|
|
}
|
|
|
|
get wrapLongLines() {
|
|
return Services.prefs.getBoolPref("view_source.wrap_long_lines");
|
|
}
|
|
}
|