1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 ChromeUtils.defineESModuleGetters(lazy, {
9 ViewSourcePageChild: "resource://gre/actors/ViewSourcePageChild.sys.mjs",
12 export class ViewSourceChild extends JSWindowActorChild {
13 receiveMessage(message) {
14 let data = message.data;
15 switch (message.name) {
16 case "ViewSource:LoadSource":
17 this.viewSource(data.URL, data.outerWindowID, data.lineNumber);
19 case "ViewSource:LoadSourceWithSelection":
20 this.viewSourceWithSelection(
26 case "ViewSource:GetSelection":
29 selectionDetails = this.getSelection(this.document.ownerGlobal);
31 return selectionDetails;
38 * Called when the parent sends a message to view some source code.
40 * @param URL (required)
41 * The URL string of the source to be shown.
42 * @param outerWindowID (optional)
43 * The outerWindowID of the content window that has hosted
44 * the document, in case we want to retrieve it from the network
46 * @param lineNumber (optional)
47 * The line number to focus as soon as the source has finished
50 viewSource(URL, outerWindowID, lineNumber) {
52 let forceEncodingDetection = false;
55 let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
57 otherDocShell = contentWindow.docShell;
59 forceEncodingDetection = contentWindow.windowUtils.docCharsetIsForced;
63 this.loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection);
67 * Loads a view source selection showing the given view-source url and
68 * highlight the selection.
70 * @param uri view-source uri to show
71 * @param drawSelection true to highlight the selection
72 * @param baseURI base URI of the original document
74 viewSourceWithSelection(uri, drawSelection, baseURI) {
75 // This isn't ideal, but set a global in the view source page actor
76 // that indicates that a selection should be drawn. It will be read
77 // when by the page's pageshow listener. This should work as the
78 // view source page is always loaded in the same process.
79 lazy.ViewSourcePageChild.setNeedsDrawSelection(drawSelection);
81 // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
82 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
83 let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
84 let loadURIOptions = {
85 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
87 baseURI: Services.io.newURI(baseURI),
89 webNav.fixupAndLoadURIString(uri, loadURIOptions);
93 * Common utility function used by both the current and deprecated APIs
96 * @param URL (required)
97 * The URL string of the source to be shown.
98 * @param otherDocShell (optional)
99 * The docshell of the content window that is hosting the document.
100 * @param lineNumber (optional)
101 * The line number to focus as soon as the source has finished
103 * @param forceEncodingDetection (optional)
104 * Force autodetection of the character encoding.
106 loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection) {
107 const viewSrcURL = "view-source:" + URL;
109 if (forceEncodingDetection) {
110 this.docShell.forceEncodingDetection();
114 lazy.ViewSourcePageChild.setInitialLineNumber(lineNumber);
117 if (!otherDocShell) {
118 this.loadSourceFromURL(viewSrcURL);
123 let pageLoader = this.docShell.QueryInterface(Ci.nsIWebPageDescriptor);
124 pageLoader.loadPageAsViewSource(otherDocShell, viewSrcURL);
126 // We were not able to load the source from the network cache.
127 this.loadSourceFromURL(viewSrcURL);
132 * Load some URL in the browser.
135 * The URL string to load.
137 loadSourceFromURL(URL) {
138 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
139 let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
140 let loadURIOptions = {
141 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
144 webNav.fixupAndLoadURIString(URL, loadURIOptions);
148 * A helper to get a path like FIXptr, but with an array instead of the
149 * "tumbler" notation.
150 * See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
152 getPath(ancestor, node) {
154 var p = n.parentNode;
155 if (n == ancestor || !p) {
163 for (var i = 0; i < p.childNodes.length; i++) {
164 if (p.childNodes.item(i) == n) {
171 } while (n != ancestor && p);
175 getSelection(global) {
176 const { content } = global;
178 // These are markers used to delimit the selection during processing. They
179 // are removed from the final rendering.
180 // We use noncharacter Unicode codepoints to minimize the risk of clashing
181 // with anything that might legitimately be present in the document.
182 // U+FDD0..FDEF <noncharacters>
183 const MARK_SELECTION_START = "\uFDD0";
184 const MARK_SELECTION_END = "\uFDEF";
186 var focusedWindow = Services.focus.focusedWindow || content;
187 var selection = focusedWindow.getSelection();
189 var range = selection.getRangeAt(0);
190 var ancestorContainer = range.commonAncestorContainer;
191 var doc = ancestorContainer.ownerDocument;
193 var startContainer = range.startContainer;
194 var endContainer = range.endContainer;
195 var startOffset = range.startOffset;
196 var endOffset = range.endOffset;
198 // let the ancestor be an element
199 var Node = doc.defaultView.Node;
201 ancestorContainer.nodeType == Node.TEXT_NODE ||
202 ancestorContainer.nodeType == Node.CDATA_SECTION_NODE
204 ancestorContainer = ancestorContainer.parentNode;
207 // for selectAll, let's use the entire document, including <html>...</html>
208 // @see nsDocumentViewer::SelectAll() for how selectAll is implemented
210 if (ancestorContainer == doc.body) {
211 ancestorContainer = doc.documentElement;
215 // each path is a "child sequence" (a.k.a. "tumbler") that
216 // descends from the ancestor down to the boundary point
217 var startPath = this.getPath(ancestorContainer, startContainer);
218 var endPath = this.getPath(ancestorContainer, endContainer);
220 // clone the fragment of interest and reset everything to be relative to it
221 // note: it is with the clone that we operate/munge from now on. Also note
222 // that we clone into a data document to prevent images in the fragment from
223 // loading and the like. The use of importNode here, as opposed to adoptNode,
224 // is _very_ important.
225 // XXXbz wish there were a less hacky way to create an untrusted document here
226 var isHTML = doc.createElement("div").tagName == "DIV";
228 ? ancestorContainer.ownerDocument.implementation.createHTMLDocument("")
229 : ancestorContainer.ownerDocument.implementation.createDocument(
234 ancestorContainer = dataDoc.importNode(ancestorContainer, true);
235 startContainer = ancestorContainer;
236 endContainer = ancestorContainer;
238 // Only bother with the selection if it can be remapped. Don't mess with
239 // leaf elements (such as <isindex>) that secretly use anynomous content
240 // for their display appearance.
241 var canDrawSelection = ancestorContainer.hasChildNodes();
243 if (canDrawSelection) {
245 for (i = startPath ? startPath.length - 1 : -1; i >= 0; i--) {
246 startContainer = startContainer.childNodes.item(startPath[i]);
248 for (i = endPath ? endPath.length - 1 : -1; i >= 0; i--) {
249 endContainer = endContainer.childNodes.item(endPath[i]);
252 // add special markers to record the extent of the selection
253 // note: |startOffset| and |endOffset| are interpreted either as
254 // offsets in the text data or as child indices (see the Range spec)
255 // (here, munging the end point first to keep the start point safe...)
257 endContainer.nodeType == Node.TEXT_NODE ||
258 endContainer.nodeType == Node.CDATA_SECTION_NODE
260 // do some extra tweaks to try to avoid the view-source output to look like
261 // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
262 // To get a neat output, the idea here is to remap the end point from:
263 // 1. ...<tag>]... to ...]<tag>...
264 // 2. ...]</tag>... to ...</tag>]...
266 (endOffset > 0 && endOffset < endContainer.data.length) ||
267 !endContainer.parentNode ||
268 !endContainer.parentNode.parentNode
270 endContainer.insertData(endOffset, MARK_SELECTION_END);
272 tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
273 endContainer = endContainer.parentNode;
274 if (endOffset === 0) {
275 endContainer.parentNode.insertBefore(tmpNode, endContainer);
277 endContainer.parentNode.insertBefore(
279 endContainer.nextSibling
284 tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
285 endContainer.insertBefore(
287 endContainer.childNodes.item(endOffset)
292 startContainer.nodeType == Node.TEXT_NODE ||
293 startContainer.nodeType == Node.CDATA_SECTION_NODE
295 // do some extra tweaks to try to avoid the view-source output to look like
296 // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
297 // To get a neat output, the idea here is to remap the start point from:
298 // 1. ...<tag>[... to ...[<tag>...
299 // 2. ...[</tag>... to ...</tag>[...
301 (startOffset > 0 && startOffset < startContainer.data.length) ||
302 !startContainer.parentNode ||
303 !startContainer.parentNode.parentNode ||
304 startContainer != startContainer.parentNode.lastChild
306 startContainer.insertData(startOffset, MARK_SELECTION_START);
308 tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
309 startContainer = startContainer.parentNode;
310 if (startOffset === 0) {
311 startContainer.parentNode.insertBefore(tmpNode, startContainer);
313 startContainer.parentNode.insertBefore(
315 startContainer.nextSibling
320 tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
321 startContainer.insertBefore(
323 startContainer.childNodes.item(startOffset)
328 // now extract and display the syntax highlighted source
329 tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
330 tmpNode.appendChild(ancestorContainer);
335 ? "view-source:data:text/html;charset=utf-8,"
336 : "view-source:data:application/xml;charset=utf-8,") +
337 encodeURIComponent(tmpNode.innerHTML),
338 drawSelection: canDrawSelection,
339 baseURI: doc.baseURI,
343 get wrapLongLines() {
344 return Services.prefs.getBoolPref("view_source.wrap_long_lines");