mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1134585, remove cpow usage from view selection source, r=mconley
This commit is contained in:
parent
37caec30be
commit
6c2b4c2577
@ -1005,27 +1005,8 @@ nsContextMenu.prototype = {
|
||||
|
||||
// View Partial Source
|
||||
viewPartialSource: function(aContext) {
|
||||
var focusedWindow = document.commandDispatcher.focusedWindow;
|
||||
if (focusedWindow == window)
|
||||
focusedWindow = gBrowser.selectedBrowser.contentWindowAsCPOW;
|
||||
|
||||
var docCharset = null;
|
||||
if (focusedWindow)
|
||||
docCharset = "charset=" + focusedWindow.document.characterSet;
|
||||
|
||||
// "View Selection Source" and others such as "View MathML Source"
|
||||
// are mutually exclusive, with the precedence given to the selection
|
||||
// when there is one
|
||||
var reference = null;
|
||||
if (aContext == "selection")
|
||||
reference = focusedWindow.getSelection();
|
||||
else if (aContext == "mathml")
|
||||
reference = this.target;
|
||||
else
|
||||
throw "not reached";
|
||||
|
||||
let inTab = Services.prefs.getBoolPref("view_source.tab");
|
||||
if (inTab) {
|
||||
let inWindow = !Services.prefs.getBoolPref("view_source.tab");
|
||||
let openSelectionFn = inWindow ? null : function() {
|
||||
let tabBrowser = gBrowser;
|
||||
// In the case of sidebars and chat windows, gBrowser is defined but null,
|
||||
// because no #content element exists. For these cases, we need to find
|
||||
@ -1040,22 +1021,11 @@ nsContextMenu.prototype = {
|
||||
relatedToCurrent: true,
|
||||
inBackground: false
|
||||
});
|
||||
let viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
|
||||
if (aContext == "selection") {
|
||||
top.gViewSourceUtils
|
||||
.viewSourceFromSelectionInBrowser(reference, viewSourceBrowser);
|
||||
} else {
|
||||
top.gViewSourceUtils
|
||||
.viewSourceFromFragmentInBrowser(reference, aContext,
|
||||
viewSourceBrowser);
|
||||
}
|
||||
} else {
|
||||
// unused (and play nice for fragments generated via XSLT too)
|
||||
var docUrl = null;
|
||||
window.openDialog("chrome://global/content/viewPartialSource.xul",
|
||||
"_blank", "scrollbars,resizable,chrome,dialog=no",
|
||||
docUrl, docCharset, reference, aContext);
|
||||
return tabBrowser.getBrowserForTab(tab);
|
||||
}
|
||||
|
||||
let target = aContext == "mathml" ? this.target : null;
|
||||
top.gViewSourceUtils.viewPartialSourceInBrowser(gBrowser.selectedBrowser, target, openSelectionFn);
|
||||
},
|
||||
|
||||
// Open new "view source" window with the frame's URL.
|
||||
|
@ -161,7 +161,7 @@ this.BrowserTestUtils = {
|
||||
* @param {tabbrowser} tabbrowser
|
||||
* The tabbrowser to look for the next new tab in.
|
||||
* @param {string} url
|
||||
* A string URL to look for in the new tab.
|
||||
* A string URL to look for in the new tab. If null, allows any non-blank URL.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves With the {xul:tab} when a tab is opened and its location changes to the given URL.
|
||||
@ -174,7 +174,8 @@ this.BrowserTestUtils = {
|
||||
let progressListener = {
|
||||
onLocationChange(aBrowser) {
|
||||
if (aBrowser != openEvent.target.linkedBrowser ||
|
||||
aBrowser.currentURI.spec != url) {
|
||||
(url && aBrowser.currentURI.spec != url) ||
|
||||
(!url && aBrowser.currentURI.spec == "about:blank")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -13,18 +13,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
|
||||
const NS_XHTML = "http://www.w3.org/1999/xhtml";
|
||||
const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css";
|
||||
const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
|
||||
|
||||
// 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";
|
||||
|
||||
const FRAME_SCRIPT = "chrome://global/content/viewSource-content.js";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"];
|
||||
@ -158,20 +148,6 @@ ViewSourceBrowser.prototype = {
|
||||
this.browser.messageManager.sendAsyncMessage(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the nsIWebNavigation of the view source browser.
|
||||
*/
|
||||
get webNav() {
|
||||
return this.browser.webNavigation;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for whether long lines should be wrapped.
|
||||
*/
|
||||
get wrapLongLines() {
|
||||
return Services.prefs.getBoolPref("view_source.wrap_long_lines");
|
||||
},
|
||||
|
||||
/**
|
||||
* A getter for the view source string bundle.
|
||||
*/
|
||||
@ -223,6 +199,19 @@ ViewSourceBrowser.prototype = {
|
||||
{ URL, outerWindowID, lineNumber });
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
loadViewSourceFromSelection(URL, drawSelection, baseURI) {
|
||||
this.sendAsyncMessage("ViewSource:LoadSourceWithSelection",
|
||||
{ URL, drawSelection, baseURI });
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the "remote" attribute of the view source browser. This
|
||||
* will remove the browser from the DOM, and then re-add it in the
|
||||
@ -243,351 +232,6 @@ ViewSourceBrowser.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the view source browser from a selection in some document.
|
||||
*
|
||||
* @param selection
|
||||
* A Selection object for the content of interest.
|
||||
*/
|
||||
loadViewSourceFromSelection(selection) {
|
||||
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(NS_XHTML, "div");
|
||||
tmpNode.appendChild(ancestorContainer);
|
||||
|
||||
// Tell content to draw a selection after the load below
|
||||
if (canDrawSelection) {
|
||||
this.sendAsyncMessage("ViewSource:ScheduleDrawSelection");
|
||||
}
|
||||
|
||||
// all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
|
||||
var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||||
var referrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
|
||||
this.webNav.loadURIWithOptions((isHTML ?
|
||||
"view-source:data:text/html;charset=utf-8," :
|
||||
"view-source:data:application/xml;charset=utf-8,")
|
||||
+ encodeURIComponent(tmpNode.innerHTML),
|
||||
loadFlags,
|
||||
null, referrerPolicy, // referrer
|
||||
null, null, // postData, headers
|
||||
Services.io.newURI(doc.baseURI, null, null));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 = new Array();
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the view source browser from a fragment of some document, as in
|
||||
* markups such as MathML where reformatting the output is helpful.
|
||||
*
|
||||
* @param aNode
|
||||
* Some element within the fragment of interest.
|
||||
* @param aContext
|
||||
* A string denoting the type of fragment. Currently, "mathml" is the
|
||||
* only accepted value.
|
||||
*/
|
||||
loadViewSourceFromFragment(node, context) {
|
||||
var Node = node.ownerDocument.defaultView.Node;
|
||||
this._lineCount = 0;
|
||||
this._startTargetLine = 0;
|
||||
this._endTargetLine = 0;
|
||||
this._targetNode = node;
|
||||
if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE)
|
||||
this._targetNode = this._targetNode.parentNode;
|
||||
|
||||
// walk up the tree to the top-level element (e.g., <math>, <svg>)
|
||||
var topTag;
|
||||
if (context == "mathml")
|
||||
topTag = "math";
|
||||
else
|
||||
throw "not reached";
|
||||
var topNode = this._targetNode;
|
||||
while (topNode && topNode.localName != topTag)
|
||||
topNode = topNode.parentNode;
|
||||
if (!topNode)
|
||||
return;
|
||||
|
||||
// serialize
|
||||
var title = this.bundle.GetStringFromName("viewMathMLSourceTitle");
|
||||
var wrapClass = this.wrapLongLines ? ' class="wrap"' : '';
|
||||
var source =
|
||||
'<!DOCTYPE html>'
|
||||
+ '<html>'
|
||||
+ '<head><title>' + title + '</title>'
|
||||
+ '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">'
|
||||
+ '<style type="text/css">'
|
||||
+ '#target { border: dashed 1px; background-color: lightyellow; }'
|
||||
+ '</style>'
|
||||
+ '</head>'
|
||||
+ '<body id="viewsource"' + wrapClass
|
||||
+ ' onload="document.title=\''+title+'\'; document.getElementById(\'target\').scrollIntoView(true)">'
|
||||
+ '<pre>'
|
||||
+ this._getOuterMarkup(topNode, 0)
|
||||
+ '</pre></body></html>'
|
||||
; // end
|
||||
|
||||
// display
|
||||
this.browser.loadURI("data:text/html;charset=utf-8," +
|
||||
encodeURIComponent(source));
|
||||
},
|
||||
|
||||
_getInnerMarkup(node, indent) {
|
||||
var str = '';
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
str += this._getOuterMarkup(node.childNodes.item(i), indent);
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
_getOuterMarkup(node, indent) {
|
||||
var Node = node.ownerDocument.defaultView.Node;
|
||||
var newline = "";
|
||||
var padding = "";
|
||||
var str = "";
|
||||
if (node == this._targetNode) {
|
||||
this._startTargetLine = this._lineCount;
|
||||
str += '</pre><pre id="target">';
|
||||
}
|
||||
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE: // Element
|
||||
// to avoid the wide gap problem, '\n' is not emitted on the first
|
||||
// line and the lines before & after the <pre id="target">...</pre>
|
||||
if (this._lineCount > 0 &&
|
||||
this._lineCount != this._startTargetLine &&
|
||||
this._lineCount != this._endTargetLine) {
|
||||
newline = "\n";
|
||||
}
|
||||
this._lineCount++;
|
||||
for (var k = 0; k < indent; k++) {
|
||||
padding += " ";
|
||||
}
|
||||
str += newline + padding
|
||||
+ '<<span class="start-tag">' + node.nodeName + '</span>';
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
var attr = node.attributes.item(i);
|
||||
if (attr.nodeName.match(/^[-_]moz/)) {
|
||||
continue;
|
||||
}
|
||||
str += ' <span class="attribute-name">'
|
||||
+ attr.nodeName
|
||||
+ '</span>=<span class="attribute-value">"'
|
||||
+ this._unicodeToEntity(attr.nodeValue)
|
||||
+ '"</span>';
|
||||
}
|
||||
if (!node.hasChildNodes()) {
|
||||
str += "/>";
|
||||
}
|
||||
else {
|
||||
str += ">";
|
||||
var oldLine = this._lineCount;
|
||||
str += this._getInnerMarkup(node, indent + 2);
|
||||
if (oldLine == this._lineCount) {
|
||||
newline = "";
|
||||
padding = "";
|
||||
}
|
||||
else {
|
||||
newline = (this._lineCount == this._endTargetLine) ? "" : "\n";
|
||||
this._lineCount++;
|
||||
}
|
||||
str += newline + padding
|
||||
+ '</<span class="end-tag">' + node.nodeName + '</span>>';
|
||||
}
|
||||
break;
|
||||
case Node.TEXT_NODE: // Text
|
||||
var tmp = node.nodeValue;
|
||||
tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
|
||||
tmp = tmp.replace(/^ +/, "");
|
||||
tmp = tmp.replace(/ +$/, "");
|
||||
if (tmp.length != 0) {
|
||||
str += '<span class="text">' + this._unicodeToEntity(tmp) + '</span>';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (node == this._targetNode) {
|
||||
this._endTargetLine = this._lineCount;
|
||||
str += '</pre><pre>';
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
_unicodeToEntity(text) {
|
||||
const charTable = {
|
||||
'&': '&<span class="entity">amp;</span>',
|
||||
'<': '&<span class="entity">lt;</span>',
|
||||
'>': '&<span class="entity">gt;</span>',
|
||||
'"': '&<span class="entity">quot;</span>'
|
||||
};
|
||||
|
||||
function charTableLookup(letter) {
|
||||
return charTable[letter];
|
||||
}
|
||||
|
||||
function convertEntity(letter) {
|
||||
try {
|
||||
var unichar = this._entityConverter
|
||||
.ConvertToEntity(letter, entityVersion);
|
||||
var entity = unichar.substring(1); // extract '&'
|
||||
return '&<span class="entity">' + entity + '</span>';
|
||||
} catch (ex) {
|
||||
return letter;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._entityConverter) {
|
||||
try {
|
||||
this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"]
|
||||
.createInstance(Ci.nsIEntityConverter);
|
||||
} catch(e) { }
|
||||
}
|
||||
|
||||
const entityVersion = Ci.nsIEntityConverter.entityW3C;
|
||||
|
||||
var str = text;
|
||||
|
||||
// replace chars in our charTable
|
||||
str = str.replace(/[<>&"]/g, charTableLookup);
|
||||
|
||||
// replace chars > 0x7f via nsIEntityConverter
|
||||
str = str.replace(/[^\0-\u007f]/g, convertEntity);
|
||||
|
||||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the "Go to line" prompt for a user to hop to a particular line
|
||||
* of the source code they're viewing. This will keep prompting until the
|
||||
|
@ -16,10 +16,7 @@ function onLoadViewPartialSource() {
|
||||
.setAttribute("checked",
|
||||
Services.prefs.getBoolPref("view_source.syntax_highlight"));
|
||||
|
||||
if (window.arguments[3] == 'selection')
|
||||
viewSourceChrome.loadViewSourceFromSelection(window.arguments[2]);
|
||||
else
|
||||
viewSourceChrome.loadViewSourceFromFragment(window.arguments[2], window.arguments[3]);
|
||||
|
||||
let args = window.arguments;
|
||||
viewSourceChrome.loadViewSourceFromSelection(args.URI, args.drawSelection, args.baseURI);
|
||||
window.content.focus();
|
||||
}
|
||||
|
@ -44,11 +44,11 @@ let ViewSourceContent = {
|
||||
messages: [
|
||||
"ViewSource:LoadSource",
|
||||
"ViewSource:LoadSourceDeprecated",
|
||||
"ViewSource:LoadSourceWithSelection",
|
||||
"ViewSource:GoToLine",
|
||||
"ViewSource:ToggleWrapping",
|
||||
"ViewSource:ToggleSyntaxHighlighting",
|
||||
"ViewSource:SetCharacterSet",
|
||||
"ViewSource:ScheduleDrawSelection",
|
||||
],
|
||||
|
||||
/**
|
||||
@ -135,6 +135,9 @@ let ViewSourceContent = {
|
||||
this.viewSourceDeprecated(data.URL, objects.pageDescriptor, data.lineNumber,
|
||||
data.forcedCharSet);
|
||||
break;
|
||||
case "ViewSource:LoadSourceWithSelection":
|
||||
this.viewSourceWithSelection(data.URL, data.drawSelection, data.baseURI);
|
||||
break;
|
||||
case "ViewSource:GoToLine":
|
||||
this.goToLine(data.lineNumber);
|
||||
break;
|
||||
@ -147,9 +150,6 @@ let ViewSourceContent = {
|
||||
case "ViewSource:SetCharacterSet":
|
||||
this.setCharacterSet(data.charset, data.doPageLoad);
|
||||
break;
|
||||
case "ViewSource:ScheduleDrawSelection":
|
||||
this.scheduleDrawSelection();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -759,6 +759,28 @@ let ViewSourceContent = {
|
||||
sendAsyncMessage("ViewSource:UpdateStatus", { label });
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.needsDrawSelection = 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 referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURIWithOptions(uri, loadFlags,
|
||||
null, referrerPolicy, // referrer
|
||||
null, null, // postData, headers
|
||||
Services.io.newURI(baseURI, null, null));
|
||||
},
|
||||
|
||||
/**
|
||||
* nsISelectionListener
|
||||
*/
|
||||
@ -778,15 +800,6 @@ let ViewSourceContent = {
|
||||
this.updateStatusTask.arm();
|
||||
},
|
||||
|
||||
/**
|
||||
* Chrome has requested that we draw a selection once the content loads.
|
||||
* We set a flag, and wait for the load event, where drawSelection() will be
|
||||
* called to do the real work.
|
||||
*/
|
||||
scheduleDrawSelection() {
|
||||
this.needsDrawSelection = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Using special markers left in the serialized source, this helper makes the
|
||||
* underlying markup of the selected fragment to automatically appear as
|
||||
|
@ -305,7 +305,11 @@ ViewSourceChrome.prototype = {
|
||||
// We're using the modern API, which allows us to view the
|
||||
// source of documents from out of process browsers.
|
||||
let args = window.arguments[0];
|
||||
this.loadViewSource(args);
|
||||
|
||||
// viewPartialSource.js will take care of loading the content in partial mode.
|
||||
if (!args.partial) {
|
||||
this.loadViewSource(args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -323,12 +327,6 @@ ViewSourceChrome.prototype = {
|
||||
// arg[3] - Line number to go to.
|
||||
// arg[4] - Whether charset was forced by the user
|
||||
|
||||
if (aArguments[3] == "selection" ||
|
||||
aArguments[3] == "mathml") {
|
||||
// viewPartialSource.js will take care of loading the content.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aArguments[2]) {
|
||||
let pageDescriptor = aArguments[2];
|
||||
if (Cu.isCrossProcessWrapper(pageDescriptor)) {
|
||||
|
@ -95,33 +95,42 @@ var gViewSourceUtils = {
|
||||
* <browser>. This allows for non-window display methods, such as a tab from
|
||||
* Firefox.
|
||||
*
|
||||
* @param aSelection
|
||||
* A Selection object for the content of interest.
|
||||
* @param aViewSourceInBrowser
|
||||
* The browser to display the view source in.
|
||||
* The browser containing the page to view the source of.
|
||||
* @param aTarget
|
||||
* Set to the target node for MathML. Null for other types of elements.
|
||||
* @param aGetBrowserFn
|
||||
* If set, a function that will return a browser to open the source in.
|
||||
* If null, or this function returns null, opens the source in a new window.
|
||||
*/
|
||||
viewSourceFromSelectionInBrowser: function(aSelection, aViewSourceInBrowser) {
|
||||
let viewSourceBrowser = new ViewSourceBrowser(aViewSourceInBrowser);
|
||||
viewSourceBrowser.loadViewSourceFromSelection(aSelection);
|
||||
},
|
||||
viewPartialSourceInBrowser: function(aViewSourceInBrowser, aTarget, aGetBrowserFn) {
|
||||
let mm = aViewSourceInBrowser.messageManager;
|
||||
mm.addMessageListener("ViewSource:GetSelectionDone", function gotSelection(message) {
|
||||
mm.removeMessageListener("ViewSource:GetSelectionDone", gotSelection);
|
||||
|
||||
/**
|
||||
* Displays view source for a MathML fragment from some document in the
|
||||
* provided <browser>. This allows for non-window display methods, such as a
|
||||
* tab from Firefox.
|
||||
*
|
||||
* @param aNode
|
||||
* Some element within the fragment of interest.
|
||||
* @param aContext
|
||||
* A string denoting the type of fragment. Currently, "mathml" is the
|
||||
* only accepted value.
|
||||
* @param aViewSourceInBrowser
|
||||
* The browser to display the view source in.
|
||||
*/
|
||||
viewSourceFromFragmentInBrowser: function(aNode, aContext,
|
||||
aViewSourceInBrowser) {
|
||||
let viewSourceBrowser = new ViewSourceBrowser(aViewSourceInBrowser);
|
||||
viewSourceBrowser.loadViewSourceFromFragment(aNode, aContext);
|
||||
if (!message.data)
|
||||
return;
|
||||
|
||||
let browserToOpenIn = aGetBrowserFn ? aGetBrowserFn() : null;
|
||||
if (browserToOpenIn) {
|
||||
let viewSourceBrowser = new ViewSourceBrowser(browserToOpenIn);
|
||||
viewSourceBrowser.loadViewSourceFromSelection(message.data.uri, message.data.drawSelection,
|
||||
message.data.baseURI);
|
||||
}
|
||||
else {
|
||||
let docUrl = null;
|
||||
window.openDialog("chrome://global/content/viewPartialSource.xul",
|
||||
"_blank", "scrollbars,resizable,chrome,dialog=no",
|
||||
{
|
||||
URI: message.data.uri,
|
||||
drawSelection: message.data.drawSelection,
|
||||
baseURI: message.data.baseURI,
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mm.sendAsyncMessage("ViewSource:GetSelection", { }, { target: aTarget });
|
||||
},
|
||||
|
||||
// Opens the interval view source viewer
|
||||
|
@ -6,7 +6,6 @@ support-files = head.js
|
||||
[browser_bug699356.js]
|
||||
[browser_bug713810.js]
|
||||
[browser_contextmenu.js]
|
||||
skip-if = e10s # occasional timeouts with dead CPOW in console
|
||||
[browser_gotoline.js]
|
||||
[browser_viewsourceprefs.js]
|
||||
skip-if = e10s # Bug ?????? - obscure failure (Syntax highlighting off - Got true, expected false)
|
||||
|
@ -1,14 +1,12 @@
|
||||
const source = "http://example.com/browser/toolkit/components/viewsource/test/browser/file_bug464222.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
testSelection();
|
||||
}
|
||||
add_task(function *() {
|
||||
let viewSourceTab = yield* openDocumentSelect(source, "a");
|
||||
|
||||
function testSelection() {
|
||||
openDocumentSelect(source, "a", function(aWindow) {
|
||||
let aTags = aWindow.gBrowser.contentDocument.querySelectorAll("a[href]");
|
||||
is(aTags[0].href, "view-source:" + source, "Relative links broken?");
|
||||
closeViewSourceWindow(aWindow, finish);
|
||||
let href = yield ContentTask.spawn(viewSourceTab.linkedBrowser, { }, function* () {
|
||||
return content.document.querySelectorAll("a[href]")[0].href;
|
||||
});
|
||||
}
|
||||
|
||||
is(href, "view-source:" + source, "Relative links broken?");
|
||||
gBrowser.removeTab(viewSourceTab);
|
||||
});
|
||||
|
@ -2,27 +2,22 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
var source = '<html xmlns="http://www.w3.org/1999/xhtml"><body><p>This is a paragraph.</p></body></html>';
|
||||
const source = '<html xmlns="http://www.w3.org/1999/xhtml"><body><p>This is a paragraph.</p></body></html>';
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
testHTML();
|
||||
}
|
||||
|
||||
function testHTML() {
|
||||
openDocumentSelect("data:text/html," + source, "p", function(aWindow) {
|
||||
is(aWindow.gBrowser.contentDocument.body.textContent,
|
||||
"<p>This is a paragraph.</p>",
|
||||
"Correct source for text/html");
|
||||
closeViewSourceWindow(aWindow, testXHTML);
|
||||
add_task(function *() {
|
||||
let viewSourceTab = yield* openDocumentSelect("data:text/html," + source, "p");
|
||||
let text = yield ContentTask.spawn(viewSourceTab.linkedBrowser, { }, function* () {
|
||||
return content.document.body.textContent;
|
||||
});
|
||||
}
|
||||
is(text, "<p>This is a paragraph.</p>", "Correct source for text/html");
|
||||
gBrowser.removeTab(viewSourceTab);
|
||||
|
||||
function testXHTML() {
|
||||
openDocumentSelect("data:application/xhtml+xml," + source, "p", function(aWindow) {
|
||||
is(aWindow.gBrowser.contentDocument.body.textContent,
|
||||
'<p xmlns="http://www.w3.org/1999/xhtml">This is a paragraph.</p>',
|
||||
"Correct source for application/xhtml+xml");
|
||||
closeViewSourceWindow(aWindow, finish);
|
||||
viewSourceTab = yield* openDocumentSelect("data:application/xhtml+xml," + source, "p");
|
||||
text = yield ContentTask.spawn(viewSourceTab.linkedBrowser, { }, function* () {
|
||||
return content.document.body.textContent;
|
||||
});
|
||||
}
|
||||
is(text, '<p xmlns="http://www.w3.org/1999/xhtml">This is a paragraph.</p>',
|
||||
"Correct source for application/xhtml+xml");
|
||||
gBrowser.removeTab(viewSourceTab);
|
||||
});
|
||||
|
||||
|
@ -6,78 +6,85 @@ let source = "data:text/html,text<link%20href='http://example.com/'%20/>more%20t
|
||||
let gViewSourceWindow, gContextMenu, gCopyLinkMenuItem, gCopyEmailMenuItem;
|
||||
|
||||
let expectedData = [];
|
||||
let currentTest = 0;
|
||||
let partialTestRunning = false;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
openViewSourceWindow(source, onViewSourceWindowOpen);
|
||||
}
|
||||
add_task(function *() {
|
||||
let newWindow = yield loadViewSourceWindow(source);
|
||||
yield SimpleTest.promiseFocus(newWindow);
|
||||
|
||||
function onViewSourceWindowOpen(aWindow) {
|
||||
yield* onViewSourceWindowOpen(newWindow, false);
|
||||
|
||||
let contextMenu = gViewSourceWindow.document.getElementById("viewSourceContextMenu");
|
||||
|
||||
for (let test of expectedData) {
|
||||
yield* checkMenuItems(contextMenu, false, test[0], test[1], test[2], test[3]);
|
||||
}
|
||||
|
||||
yield new Promise(resolve => {
|
||||
closeViewSourceWindow(newWindow, resolve);
|
||||
});
|
||||
|
||||
expectedData = [];
|
||||
let newTab = yield openDocumentSelect(source, "body");
|
||||
yield* onViewSourceWindowOpen(window, true);
|
||||
|
||||
contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
|
||||
// Prepend view-source to this one as it opens in a tab.
|
||||
expectedData[0][3] = "view-source:" + expectedData[0][3];
|
||||
for (let test of expectedData) {
|
||||
yield* checkMenuItems(contextMenu, true, test[0], test[1], test[2], test[3]);
|
||||
}
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
function* onViewSourceWindowOpen(aWindow, aIsTab) {
|
||||
gViewSourceWindow = aWindow;
|
||||
|
||||
gContextMenu = aWindow.document.getElementById("viewSourceContextMenu");
|
||||
gCopyLinkMenuItem = aWindow.document.getElementById("context-copyLink");
|
||||
gCopyEmailMenuItem = aWindow.document.getElementById("context-copyEmail");
|
||||
gCopyLinkMenuItem = aWindow.document.getElementById(aIsTab ? "context-copylink" : "context-copyLink");
|
||||
gCopyEmailMenuItem = aWindow.document.getElementById(aIsTab ? "context-copyemail" : "context-copyEmail");
|
||||
|
||||
let aTags = aWindow.gBrowser.contentDocument.querySelectorAll("a[href]");
|
||||
is(aTags[0].href, "view-source:http://example.com/", "Link has correct href");
|
||||
is(aTags[1].href, "mailto:abc@def.ghi", "Link has correct href");
|
||||
let spanTag = aWindow.gBrowser.contentDocument.querySelector("span");
|
||||
let browser = aIsTab ? gBrowser.selectedBrowser : gViewSourceWindow.gBrowser;
|
||||
let items = yield ContentTask.spawn(browser, { }, function* (arg) {
|
||||
let tags = content.document.querySelectorAll("a[href]");
|
||||
return [tags[0].href, tags[1].href];
|
||||
});
|
||||
|
||||
expectedData.push([aTags[0], true, false, "http://example.com/"]);
|
||||
expectedData.push([aTags[1], false, true, "abc@def.ghi"]);
|
||||
expectedData.push([spanTag, false, false, null]);
|
||||
is(items[0], "view-source:http://example.com/", "Link has correct href");
|
||||
is(items[1], "mailto:abc@def.ghi", "Link has correct href");
|
||||
|
||||
waitForFocus(runNextTest, aWindow);
|
||||
expectedData.push(["a[href]", true, false, "http://example.com/"]);
|
||||
expectedData.push(["a[href^=mailto]", false, true, "abc@def.ghi"]);
|
||||
expectedData.push(["span", false, false, null]);
|
||||
}
|
||||
|
||||
function runNextTest() {
|
||||
if (currentTest == expectedData.length) {
|
||||
closeViewSourceWindow(gViewSourceWindow, function() {
|
||||
if (partialTestRunning) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
partialTestRunning = true;
|
||||
currentTest = 0;
|
||||
expectedData = [];
|
||||
openDocumentSelect(source, "body", onViewSourceWindowOpen);
|
||||
});
|
||||
return;
|
||||
}
|
||||
let test = expectedData[currentTest++];
|
||||
checkMenuItems(test[0], test[1], test[2], test[3]);
|
||||
}
|
||||
function checkMenuItems(contextMenu, isTab, selector, copyLinkExpected, copyEmailExpected, expectedClipboardContent) {
|
||||
|
||||
function checkMenuItems(popupNode, copyLinkExpected, copyEmailExpected, expectedClipboardContent) {
|
||||
popupNode.scrollIntoView();
|
||||
let browser = isTab ? gBrowser.selectedBrowser : gViewSourceWindow.gBrowser;
|
||||
yield ContentTask.spawn(browser, { selector: selector }, function* (arg) {
|
||||
content.document.querySelector(arg.selector).scrollIntoView();
|
||||
});
|
||||
|
||||
let cachedEvent = null;
|
||||
let mouseFn = function(event) {
|
||||
cachedEvent = event;
|
||||
};
|
||||
|
||||
gViewSourceWindow.gBrowser.contentWindow.addEventListener("mousedown", mouseFn, false);
|
||||
EventUtils.synthesizeMouseAtCenter(popupNode, { type: "contextmenu", button: 2 }, gViewSourceWindow.gBrowser.contentWindow);
|
||||
gViewSourceWindow.gBrowser.contentWindow.removeEventListener("mousedown", mouseFn, false);
|
||||
|
||||
gContextMenu.openPopup(popupNode, "after_start", 0, 0, false, false, cachedEvent);
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(selector,
|
||||
{ type: "contextmenu", button: 2}, browser);
|
||||
yield popupShownPromise;
|
||||
|
||||
is(gCopyLinkMenuItem.hidden, !copyLinkExpected, "Copy link menuitem is " + (copyLinkExpected ? "not hidden" : "hidden"));
|
||||
is(gCopyEmailMenuItem.hidden, !copyEmailExpected, "Copy email menuitem is " + (copyEmailExpected ? "not hidden" : "hidden"));
|
||||
|
||||
if (!copyLinkExpected && !copyEmailExpected) {
|
||||
runNextTest();
|
||||
return;
|
||||
if (copyLinkExpected || copyEmailExpected) {
|
||||
yield new Promise((resolve, reject) => {
|
||||
waitForClipboard(expectedClipboardContent, function() {
|
||||
if (copyLinkExpected)
|
||||
gCopyLinkMenuItem.click();
|
||||
else
|
||||
gCopyEmailMenuItem.click();
|
||||
}, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
waitForClipboard(expectedClipboardContent, function() {
|
||||
if (copyLinkExpected)
|
||||
gCopyLinkMenuItem.doCommand();
|
||||
else
|
||||
gCopyEmailMenuItem.doCommand();
|
||||
gContextMenu.hidePopup();
|
||||
}, runNextTest, runNextTest);
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
|
||||
contextMenu.hidePopup();
|
||||
yield popupHiddenPromise;
|
||||
}
|
||||
|
@ -40,17 +40,29 @@ function testViewSourceWindow(aURI, aTestCallback, aCloseCallback) {
|
||||
});
|
||||
}
|
||||
|
||||
function openViewPartialSourceWindow(aReference, aCallback) {
|
||||
let viewSourceWindow = openDialog("chrome://global/content/viewPartialSource.xul",
|
||||
null, null, null, null, aReference, "selection");
|
||||
viewSourceWindow.addEventListener("pageshow", function pageShowHandler(event) {
|
||||
// Wait for the inner window to load, not viewSourceWindow.
|
||||
if (/^view-source:/.test(event.target.location)) {
|
||||
info("View source window opened: " + event.target.location);
|
||||
viewSourceWindow.removeEventListener("pageshow", pageShowHandler, false);
|
||||
aCallback(viewSourceWindow);
|
||||
}
|
||||
}, false);
|
||||
/**
|
||||
* Opens a view source tab for a selection (View Selection Source) within the
|
||||
* currently selected browser in gBrowser.
|
||||
*
|
||||
* @param aCSSSelector - used to specify a node within the selection to
|
||||
* view the source of. It is expected that this node is
|
||||
* within an existing selection.
|
||||
* @returns the new tab which shows the source.
|
||||
*/
|
||||
function* openViewPartialSourceWindow(aCSSSelector) {
|
||||
var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(aCSSSelector,
|
||||
{ type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
|
||||
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null);
|
||||
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
EventUtils.synthesizeMouseAtCenter(document.getElementById("context-viewpartialsource-selection"), {});
|
||||
yield popupHiddenPromise;
|
||||
|
||||
return (yield newTabPromise);
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
@ -60,26 +72,38 @@ registerCleanupFunction(function() {
|
||||
windows.getNext().close();
|
||||
});
|
||||
|
||||
function openDocument(aURI, aCallback) {
|
||||
let tab = gBrowser.addTab(aURI);
|
||||
let browser = tab.linkedBrowser;
|
||||
browser.addEventListener("DOMContentLoaded", function pageLoadedListener() {
|
||||
browser.removeEventListener("DOMContentLoaded", pageLoadedListener, false);
|
||||
aCallback(tab);
|
||||
}, false);
|
||||
/**
|
||||
* Open a new document in a new tab, select part of it, and view the source of
|
||||
* that selection. The document is not closed afterwards.
|
||||
*
|
||||
* @param aURI - url to load
|
||||
* @param aCSSSelector - used to specify a node to select. All of this node's
|
||||
* children will be selected.
|
||||
* @returns the new tab which shows the source.
|
||||
*/
|
||||
function* openDocumentSelect(aURI, aCSSSelector) {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, aURI);
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
}
|
||||
|
||||
function openDocumentSelect(aURI, aCSSSelector, aCallback) {
|
||||
openDocument(aURI, function(aTab) {
|
||||
let element = aTab.linkedBrowser.contentDocument.querySelector(aCSSSelector);
|
||||
let selection = aTab.linkedBrowser.contentWindow.getSelection();
|
||||
selection.selectAllChildren(element);
|
||||
|
||||
openViewPartialSourceWindow(selection, aCallback);
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { selector: aCSSSelector }, function* (arg) {
|
||||
let element = content.document.querySelector(arg.selector);
|
||||
content.getSelection().selectAllChildren(element);
|
||||
});
|
||||
|
||||
let newtab = yield openViewPartialSourceWindow(aCSSSelector);
|
||||
|
||||
// Wait until the source has been loaded.
|
||||
yield new Promise(resolve => {
|
||||
let mm = newtab.linkedBrowser.messageManager;
|
||||
mm.addMessageListener("ViewSource:SourceLoaded", function selectionDrawn() {
|
||||
mm.removeMessageListener("ViewSource:SourceLoaded", selectionDrawn);
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
});
|
||||
|
||||
return newtab;
|
||||
}
|
||||
|
||||
function waitForPrefChange(pref) {
|
||||
|
@ -724,3 +724,362 @@ let AudioPlaybackListener = {
|
||||
},
|
||||
};
|
||||
AudioPlaybackListener.init();
|
||||
|
||||
let ViewSelectionSource = {
|
||||
init: function () {
|
||||
addMessageListener("ViewSource:GetSelection", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
if (message.name == "ViewSource:GetSelection") {
|
||||
let selectionDetails;
|
||||
try {
|
||||
selectionDetails = message.objects.target ? this.getMathMLSelection(message.objects.target)
|
||||
: this.getSelection();
|
||||
} finally {
|
||||
sendAsyncMessage("ViewSource:GetSelectionDone", selectionDetails);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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: function(ancestor, node) {
|
||||
var n = node;
|
||||
var p = n.parentNode;
|
||||
if (n == ancestor || !p)
|
||||
return null;
|
||||
var path = new Array();
|
||||
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: function () {
|
||||
// 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 { uri: (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 };
|
||||
},
|
||||
|
||||
/**
|
||||
* Reformat the source of a MathML node to highlight the node that was targetted.
|
||||
*
|
||||
* @param node
|
||||
* Some element within the fragment of interest.
|
||||
*/
|
||||
getMathMLSelection: function(node) {
|
||||
var Node = node.ownerDocument.defaultView.Node;
|
||||
this._lineCount = 0;
|
||||
this._startTargetLine = 0;
|
||||
this._endTargetLine = 0;
|
||||
this._targetNode = node;
|
||||
if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE)
|
||||
this._targetNode = this._targetNode.parentNode;
|
||||
|
||||
// walk up the tree to the top-level element (e.g., <math>, <svg>)
|
||||
var topTag = "math";
|
||||
var topNode = this._targetNode;
|
||||
while (topNode && topNode.localName != topTag) {
|
||||
topNode = topNode.parentNode;
|
||||
}
|
||||
if (!topNode)
|
||||
return;
|
||||
|
||||
// serialize
|
||||
const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css";
|
||||
const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
|
||||
|
||||
let bundle = Services.strings.createBundle(BUNDLE_URL);
|
||||
var title = bundle.GetStringFromName("viewMathMLSourceTitle");
|
||||
var wrapClass = this.wrapLongLines ? ' class="wrap"' : '';
|
||||
var source =
|
||||
'<!DOCTYPE html>'
|
||||
+ '<html>'
|
||||
+ '<head><title>' + title + '</title>'
|
||||
+ '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">'
|
||||
+ '<style type="text/css">'
|
||||
+ '#target { border: dashed 1px; background-color: lightyellow; }'
|
||||
+ '</style>'
|
||||
+ '</head>'
|
||||
+ '<body id="viewsource"' + wrapClass
|
||||
+ ' onload="document.title=\''+title+'\'; document.getElementById(\'target\').scrollIntoView(true)">'
|
||||
+ '<pre>'
|
||||
+ this.getOuterMarkup(topNode, 0)
|
||||
+ '</pre></body></html>'
|
||||
; // end
|
||||
|
||||
return { uri: "data:text/html;charset=utf-8," + encodeURIComponent(source),
|
||||
drawSelection: false, baseURI: node.ownerDocument.baseURI };
|
||||
},
|
||||
|
||||
get wrapLongLines() {
|
||||
return Services.prefs.getBoolPref("view_source.wrap_long_lines");
|
||||
},
|
||||
|
||||
getInnerMarkup: function(node, indent) {
|
||||
var str = '';
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
str += this.getOuterMarkup(node.childNodes.item(i), indent);
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
getOuterMarkup: function(node, indent) {
|
||||
var Node = node.ownerDocument.defaultView.Node;
|
||||
var newline = "";
|
||||
var padding = "";
|
||||
var str = "";
|
||||
if (node == this._targetNode) {
|
||||
this._startTargetLine = this._lineCount;
|
||||
str += '</pre><pre id="target">';
|
||||
}
|
||||
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE: // Element
|
||||
// to avoid the wide gap problem, '\n' is not emitted on the first
|
||||
// line and the lines before & after the <pre id="target">...</pre>
|
||||
if (this._lineCount > 0 &&
|
||||
this._lineCount != this._startTargetLine &&
|
||||
this._lineCount != this._endTargetLine) {
|
||||
newline = "\n";
|
||||
}
|
||||
this._lineCount++;
|
||||
for (var k = 0; k < indent; k++) {
|
||||
padding += " ";
|
||||
}
|
||||
str += newline + padding
|
||||
+ '<<span class="start-tag">' + node.nodeName + '</span>';
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
var attr = node.attributes.item(i);
|
||||
if (attr.nodeName.match(/^[-_]moz/)) {
|
||||
continue;
|
||||
}
|
||||
str += ' <span class="attribute-name">'
|
||||
+ attr.nodeName
|
||||
+ '</span>=<span class="attribute-value">"'
|
||||
+ this.unicodeToEntity(attr.nodeValue)
|
||||
+ '"</span>';
|
||||
}
|
||||
if (!node.hasChildNodes()) {
|
||||
str += "/>";
|
||||
}
|
||||
else {
|
||||
str += ">";
|
||||
var oldLine = this._lineCount;
|
||||
str += this.getInnerMarkup(node, indent + 2);
|
||||
if (oldLine == this._lineCount) {
|
||||
newline = "";
|
||||
padding = "";
|
||||
}
|
||||
else {
|
||||
newline = (this._lineCount == this._endTargetLine) ? "" : "\n";
|
||||
this._lineCount++;
|
||||
}
|
||||
str += newline + padding
|
||||
+ '</<span class="end-tag">' + node.nodeName + '</span>>';
|
||||
}
|
||||
break;
|
||||
case Node.TEXT_NODE: // Text
|
||||
var tmp = node.nodeValue;
|
||||
tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
|
||||
tmp = tmp.replace(/^ +/, "");
|
||||
tmp = tmp.replace(/ +$/, "");
|
||||
if (tmp.length != 0) {
|
||||
str += '<span class="text">' + this.unicodeToEntity(tmp) + '</span>';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (node == this._targetNode) {
|
||||
this._endTargetLine = this._lineCount;
|
||||
str += '</pre><pre>';
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
unicodeToEntity: function(text) {
|
||||
const charTable = {
|
||||
'&': '&<span class="entity">amp;</span>',
|
||||
'<': '&<span class="entity">lt;</span>',
|
||||
'>': '&<span class="entity">gt;</span>',
|
||||
'"': '&<span class="entity">quot;</span>'
|
||||
};
|
||||
|
||||
function charTableLookup(letter) {
|
||||
return charTable[letter];
|
||||
}
|
||||
|
||||
function convertEntity(letter) {
|
||||
try {
|
||||
var unichar = this._entityConverter
|
||||
.ConvertToEntity(letter, entityVersion);
|
||||
var entity = unichar.substring(1); // extract '&'
|
||||
return '&<span class="entity">' + entity + '</span>';
|
||||
} catch (ex) {
|
||||
return letter;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._entityConverter) {
|
||||
try {
|
||||
this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"]
|
||||
.createInstance(Ci.nsIEntityConverter);
|
||||
} catch(e) { }
|
||||
}
|
||||
|
||||
const entityVersion = Ci.nsIEntityConverter.entityW3C;
|
||||
|
||||
var str = text;
|
||||
|
||||
// replace chars in our charTable
|
||||
str = str.replace(/[<>&"]/g, charTableLookup);
|
||||
|
||||
// replace chars > 0x7f via nsIEntityConverter
|
||||
str = str.replace(/[^\0-\u007f]/g, convertEntity);
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
ViewSelectionSource.init();
|
||||
|
Loading…
Reference in New Issue
Block a user