diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/actors/ViewSourceChild.sys.mjs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/actors/ViewSourceChild.sys.mjs')
-rw-r--r-- | toolkit/actors/ViewSourceChild.sys.mjs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/toolkit/actors/ViewSourceChild.sys.mjs b/toolkit/actors/ViewSourceChild.sys.mjs new file mode 100644 index 0000000000..4c573865b7 --- /dev/null +++ b/toolkit/actors/ViewSourceChild.sys.mjs @@ -0,0 +1,346 @@ +/* -*- 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + ViewSourcePageChild: "resource://gre/actors/ViewSourcePageChild.sys.mjs", +}); + +export 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 otherDocShell; + let forceEncodingDetection = false; + + if (outerWindowID) { + let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID); + if (contentWindow) { + otherDocShell = contentWindow.docShell; + + forceEncodingDetection = contentWindow.windowUtils.docCharsetIsForced; + } + } + + this.loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection); + } + + /** + * 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. + lazy.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.fixupAndLoadURIString(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 otherDocShell (optional) + * The docshell of the content window that is hosting the document. + * @param lineNumber (optional) + * The line number to focus as soon as the source has finished + * loading. + * @param forceEncodingDetection (optional) + * Force autodetection of the character encoding. + */ + loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection) { + const viewSrcURL = "view-source:" + URL; + + if (forceEncodingDetection) { + this.docShell.forceEncodingDetection(); + } + + if (lineNumber) { + lazy.ViewSourcePageChild.setInitialLineNumber(lineNumber); + } + + if (!otherDocShell) { + this.loadSourceFromURL(viewSrcURL); + return; + } + + try { + let pageLoader = this.docShell.QueryInterface(Ci.nsIWebPageDescriptor); + pageLoader.loadPageAsViewSource(otherDocShell, viewSrcURL); + } catch (e) { + // We were not able to load the source from the network cache. + this.loadSourceFromURL(viewSrcURL); + } + } + + /** + * 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.fixupAndLoadURIString(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"); + } +} |