Merge f-t to m-c, a=merge

This commit is contained in:
Phil Ringnalda 2015-05-24 12:15:26 -07:00
commit c94954680c
56 changed files with 1571 additions and 1043 deletions

View File

@ -198,7 +198,7 @@ pref("app.update.url", "https://aus4.mozilla.org/update/3/%PRODUCT%/%VERSION%/%B
pref("app.update.idletime", 60);
// Whether or not we show a dialog box informing the user that the update was
// successfully applied. This is off in Firefox by default since we show a
// successfully applied. This is off in Firefox by default since we show a
// upgrade start page instead! Other apps may wish to show this UI, and supply
// a whatsNewURL field in their brand.properties that contains a link to a page
// which tells users what's new in this new update.
@ -225,10 +225,10 @@ pref("app.update.service.enabled", true);
pref("extensions.update.enabled", true);
pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
pref("extensions.update.interval", 86400); // Check for updates to Extensions and
pref("extensions.update.interval", 86400); // Check for updates to Extensions and
// Themes every day
// Non-symmetric (not shared by extensions) extension-specific [update] preferences
pref("extensions.dss.enabled", false); // Dynamic Skin Switching
pref("extensions.dss.enabled", false); // Dynamic Skin Switching
pref("extensions.dss.switchPending", false); // Non-dynamic switch pending after next
// restart.
@ -359,7 +359,7 @@ pref("browser.altClickSave", false);
pref("browser.download.debug", false);
// Number of milliseconds to wait for the http headers (and thus
// the Content-Disposition filename) before giving up and falling back to
// the Content-Disposition filename) before giving up and falling back to
// picking a filename without that info in hand so that the user sees some
// feedback from their action.
pref("browser.download.saveLinkAsFilenameTimeout", 4000);
@ -390,7 +390,7 @@ pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
// pointer to the default engine name
pref("browser.search.defaultenginename", "chrome://browser-region/locale/region.properties");
// Ordering of Search Engines in the Engine list.
// Ordering of Search Engines in the Engine list.
pref("browser.search.order.1", "chrome://browser-region/locale/region.properties");
pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");
pref("browser.search.order.3", "chrome://browser-region/locale/region.properties");
@ -462,7 +462,7 @@ pref("browser.tabs.drawInTitlebar", false);
pref("browser.tabs.drawInTitlebar", true);
#endif
// When tabs opened by links in other tabs via a combination of
// When tabs opened by links in other tabs via a combination of
// browser.link.open_newwindow being set to 3 and target="_blank" etc are
// closed:
// true return to the tab that opened this tab (its owner)
@ -476,7 +476,7 @@ pref("browser.ctrlTab.previews", false);
// be exported as HTML to the bookmarks.html file.
pref("browser.bookmarks.autoExportHTML", false);
// The maximum number of daily bookmark backups to
// The maximum number of daily bookmark backups to
// keep in {PROFILEDIR}/bookmarkbackups. Special values:
// -1: unlimited
// 0: no backups created (and deletes all existing backups)
@ -489,7 +489,7 @@ pref("javascript.options.showInConsole", true);
pref("general.warnOnAboutConfig", false);
#endif
// This is the pref to control the location bar, change this to true to
// This is the pref to control the location bar, change this to true to
// force this - this makes the origin of popup windows more obvious to avoid
// spoofing. We would rather not do it by default because it affects UE for web
// applications, but without it there isn't a really good way to prevent chrome
@ -917,7 +917,7 @@ pref("browser.audioFeeds.handler", "ask");
// At startup, if the handler service notices that the version number in the
// region.properties file is newer than the version number in the handler
// service datastore, it will add any new handlers it finds in the prefs (as
// seeded by this file) to its datastore.
// seeded by this file) to its datastore.
pref("gecko.handlerService.defaultHandlersVersion", "chrome://browser-region/locale/region.properties");
// The default set of web-based protocol handlers shown in the application
@ -1921,3 +1921,5 @@ pref("browser.pocket.site", "getpocket.com");
pref("browser.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4");
pref("browser.pocket.useLocaleList", true);
pref("browser.pocket.enabledLocales", "en-US de es-ES ja ja-JP-mac ru");
pref("view_source.tab", true);

View File

@ -986,6 +986,7 @@ var gBrowserInit = {
mm.loadFrameScript("chrome://browser/content/content.js", true);
mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);
window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
@ -2322,11 +2323,11 @@ function readFromClipboard()
* If aArgsOrDocument is an object, that object can take the
* following properties:
*
* browser:
* The browser containing the document that we would like to view the
* source of.
* URL:
* URL (required):
* A string URL for the page we'd like to view the source of.
* browser (optional):
* The browser containing the document that we would like to view the
* source of. This is required if outerWindowID is passed.
* outerWindowID (optional):
* The outerWindowID of the content window containing the document that
* we want to view the source of. You only need to provide this if you
@ -2359,7 +2360,18 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
args = aArgsOrDocument;
}
top.gViewSourceUtils.viewSource(args);
let inTab = Services.prefs.getBoolPref("view_source.tab");
if (inTab) {
let viewSourceURL = `view-source:${args.URL}`;
let tab = gBrowser.loadOneTab(viewSourceURL, {
relatedToCurrent: true,
inBackground: false
});
args.viewSourceBrowser = gBrowser.getBrowserForTab(tab);
top.gViewSourceUtils.viewSourceInBrowser(args);
} else {
top.gViewSourceUtils.viewSource(args);
}
}
/**
@ -5560,7 +5572,7 @@ function middleMousePaste(event) {
clipboard = stripUnsafeProtocolOnPaste(clipboard);
// if it's not the current tab, we don't need to do anything because the
// if it's not the current tab, we don't need to do anything because the
// browser doesn't exist.
let where = whereToOpenLink(event, true, false);
let lastLocationChange;
@ -6278,10 +6290,10 @@ function warnAboutClosingWindow() {
otherPBWindowExists = true;
if (win.toolbar.visible)
nonPopupPresent = true;
// If the current window is not in private browsing mode we don't need to
// look for other pb windows, we can leave the loop when finding the
// first non-popup window. If however the current window is in private
// browsing mode then we need at least one other pb and one non-popup
// If the current window is not in private browsing mode we don't need to
// look for other pb windows, we can leave the loop when finding the
// first non-popup window. If however the current window is in private
// browsing mode then we need at least one other pb and one non-popup
// window to break out early.
if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
break;
@ -7099,7 +7111,7 @@ var gIdentityHandler = {
dt.setData("text/html", htmlString);
dt.setDragImage(gProxyFavIcon, 16, 16);
},
handleEvent: function (event) {
switch (event.type) {
case "blur":

View File

@ -1005,11 +1005,28 @@ nsContextMenu.prototype = {
else
throw "not reached";
// 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);
let inTab = Services.prefs.getBoolPref("view_source.tab");
if (inTab) {
let tab = gBrowser.loadOneTab("about:blank", {
relatedToCurrent: true,
inBackground: false
});
let viewSourceBrowser = gBrowser.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);
}
},
// Open new "view source" window with the frame's URL.
@ -1153,7 +1170,7 @@ nsContextMenu.prototype = {
urlSecurityCheck(this.target.currentURI.spec, this.principal);
// Confirm since it's annoying if you hit this accidentally.
const kDesktopBackgroundURL =
const kDesktopBackgroundURL =
"chrome://browser/content/setDesktopBackground.xul";
#ifdef XP_MACOSX
// On Mac, the Set Desktop Background window is not modal.
@ -1196,7 +1213,7 @@ nsContextMenu.prototype = {
// file picker
function saveAsListener() {}
saveAsListener.prototype = {
extListener: null,
extListener: null,
onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
@ -1229,17 +1246,17 @@ nsContextMenu.prototype = {
return;
}
let extHelperAppSvc =
let extHelperAppSvc =
Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
getService(Ci.nsIExternalHelperAppService);
let channel = aRequest.QueryInterface(Ci.nsIChannel);
this.extListener =
extHelperAppSvc.doContent(channel.contentType, aRequest,
extHelperAppSvc.doContent(channel.contentType, aRequest,
null, true, window);
this.extListener.onStartRequest(aRequest, aContext);
},
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
@ -1272,12 +1289,12 @@ nsContextMenu.prototype = {
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
}
// if it we don't have the headers after a short time, the user
// if it we don't have the headers after a short time, the user
// won't have received any feedback from their click. that's bad. so
// we give up waiting for the filename.
// we give up waiting for the filename.
function timerCallback() {}
timerCallback.prototype = {
notify: function sLA_timer_notify(aTimer) {
@ -1319,8 +1336,8 @@ nsContextMenu.prototype = {
channel.forceAllowThirdPartyCookie = true;
}
// fallback to the old way if we don't see the headers quickly
var timeToWait =
// fallback to the old way if we don't see the headers quickly
var timeToWait =
gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(new timerCallback(), timeToWait,
@ -1499,7 +1516,7 @@ nsContextMenu.prototype = {
// Generate fully qualified URL for clicked-on link.
getLinkURL: function() {
var href = this.link.href;
var href = this.link.href;
if (href)
return href;

View File

@ -29,7 +29,7 @@ function test() {
function runTests1(aTab) {
let toolDefinition = {
id: toolId1,
isTargetSupported: function() true,
isTargetSupported: () => true,
visibilityswitch: "devtools.test-tool.enabled",
url: "about:blank",
label: "someLabel",
@ -90,7 +90,7 @@ function runTests1(aTab) {
function runTests2() {
let toolDefinition = {
id: toolId2,
isTargetSupported: function() true,
isTargetSupported: () => true,
visibilityswitch: "devtools.test-tool.enabled",
url: "about:blank",
label: "someLabel",
@ -229,11 +229,17 @@ DevToolPanel.prototype = {
return deferred.promise;
},
get target() this._toolbox.target,
get target() {
return this._toolbox.target;
},
get toolbox() this._toolbox,
get toolbox() {
return this._toolbox;
},
get isReady() this._isReady,
get isReady() {
return this._isReady;
},
_isReady: false,

View File

@ -13,7 +13,7 @@ function runTests(aTab) {
let toolDefinition = {
id: "testTool",
visibilityswitch: "devtools.testTool.enabled",
isTargetSupported: function() true,
isTargetSupported: () => true,
url: "about:blank",
label: "someLabel",
build: function(iframeWindow, toolbox) {

View File

@ -26,7 +26,7 @@ function testRegister(aToolbox)
id: "test-tool",
label: "Test Tool",
inMenu: true,
isTargetSupported: function() true,
isTargetSupported: () => true,
build: function() {}
});
}

View File

@ -23,7 +23,7 @@ add_task(function*() {
function registerNewTool() {
let toolDefinition = {
id: "test-tool",
isTargetSupported: function() true,
isTargetSupported: () => true,
visibilityswitch: "devtools.test-tool.enabled",
url: "about:blank",
label: "someLabel"

View File

@ -27,7 +27,7 @@ function test() {
visibilityswitch: "devtools.fakeTool4242.enabled",
url: toolURL,
label: "FAKE TOOL!!!",
isTargetSupported: function() true,
isTargetSupported: () => true,
build: function(iframeWindow, toolbox) {
let deferred = promise.defer();
executeSoon(() => {
@ -35,7 +35,7 @@ function test() {
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: function(){},
destroy: function() {},
panelDoc: iframeWindow.document,
});
});

View File

@ -22,7 +22,7 @@ function test() {
visibilityswitch: "devtools.testTool1072208.enabled",
url: toolURL,
label: "Test tool",
isTargetSupported: function() true,
isTargetSupported: () => true,
build: function(iframeWindow, toolbox) {
let deferred = promise.defer();
executeSoon(() => {
@ -30,7 +30,7 @@ function test() {
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: function(){},
destroy: function() {},
panelDoc: iframeWindow.document,
});
});

View File

@ -4,12 +4,12 @@
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is still waiting for a WebGL context to be created.");
function performChecks(target) {
return Task.spawn(function() {
return Task.spawn(function*() {
let toolIds = gDevTools.getToolDefinitionArray()
.filter(def => def.isTargetSupported(target))
.map(def => def.id);
@ -32,7 +32,7 @@ function performChecks(target) {
}
function test() {
Task.spawn(function() {
Task.spawn(function*() {
toggleAllTools(true);
let tab = yield addTab("about:blank");
let target = TargetFactory.forTab(tab);

View File

@ -4,7 +4,7 @@
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is still waiting for a WebGL context to be created.");
@ -40,7 +40,7 @@ const { DebuggerClient } =
*/
function runTools(target) {
return Task.spawn(function() {
return Task.spawn(function*() {
let toolIds = gDevTools.getToolDefinitionArray()
.filter(def => def.isTargetSupported(target))
.map(def => def.id);
@ -96,7 +96,7 @@ function getTarget(client) {
}
function test() {
Task.spawn(function() {
Task.spawn(function*() {
toggleAllTools(true);
yield addTab("about:blank");

View File

@ -23,20 +23,20 @@ function test() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM)
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(() => toolbox.selectTool(TOOL_ID_1))
// undock toolbox and check title
.then(function () toolbox.switchHost(Toolbox.HostType.WINDOW))
.then(() => toolbox.switchHost(Toolbox.HostType.WINDOW))
.then(checkTitle.bind(null, LABEL_1, URL_1, "toolbox undocked"))
// switch to different tool and check title
.then(function () toolbox.selectTool(TOOL_ID_2))
.then(() => toolbox.selectTool(TOOL_ID_2))
.then(checkTitle.bind(null, LABEL_2, URL_1, "tool changed"))
// navigate to different url and check title
.then(function () {
let deferred = promise.defer();
target.once("navigate", function () deferred.resolve());
target.once("navigate", () => deferred.resolve());
gBrowser.loadURI(URL_2);
return deferred.promise;
})
@ -55,12 +55,12 @@ function test() {
return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
})
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(() => toolbox.selectTool(TOOL_ID_1))
.then(checkTitle.bind(null, LABEL_1, URL_2,
"toolbox destroyed and recreated"))
// clean up
.then(function () toolbox.destroy())
.then(() => toolbox.destroy())
.then(function () {
toolbox = null;
gBrowser.removeCurrentTab();

View File

@ -181,4 +181,94 @@ const DOM = exports.DOM = {
hbox.appendChild(labelValue);
return hbox;
},
/**
* Builds a stack trace in an element.
*
* @param {Document} doc
* @param object params
* An options object with the following members:
* string type - String identifier for type of stack ("stack", "startStack" or "endStack")
* number frameIndex - The index of the topmost stack frame.
* array frames - Array of stack frames.
*/
buildStackTrace: function(doc, { type, frameIndex, frames }) {
let container = doc.createElement("vbox");
let labelName = doc.createElement("label");
labelName.className = "plain marker-details-labelname";
labelName.setAttribute("value", L10N.getStr(`timeline.markerDetail.${type}`));
container.appendChild(labelName);
let wasAsyncParent = false;
while (frameIndex > 0) {
let frame = frames[frameIndex];
let url = frame.source;
let displayName = frame.functionDisplayName;
let line = frame.line;
// If the previous frame had an async parent, then the async
// cause is in this frame and should be displayed.
if (wasAsyncParent) {
let asyncBox = doc.createElement("hbox");
let asyncLabel = doc.createElement("label");
asyncLabel.className = "devtools-monospace";
asyncLabel.setAttribute("value", L10N.getFormatStr("timeline.markerDetail.asyncStack",
frame.asyncCause));
asyncBox.appendChild(asyncLabel);
container.appendChild(asyncBox);
wasAsyncParent = false;
}
let hbox = doc.createElement("hbox");
if (displayName) {
let functionLabel = doc.createElement("label");
functionLabel.className = "devtools-monospace";
functionLabel.setAttribute("value", displayName);
hbox.appendChild(functionLabel);
}
if (url) {
let aNode = doc.createElement("a");
aNode.className = "waterfall-marker-location devtools-source-link";
aNode.href = url;
aNode.draggable = false;
aNode.setAttribute("title", url);
let urlNode = doc.createElement("label");
urlNode.className = "filename";
urlNode.setAttribute("value", WebConsoleUtils.Utils.abbreviateSourceURL(url));
let lineNode = doc.createElement("label");
lineNode.className = "line-number";
lineNode.setAttribute("value", `:${line}`);
aNode.appendChild(urlNode);
aNode.appendChild(lineNode);
hbox.appendChild(aNode);
// Clicking here will bubble up to the parent,
// which handles the view source.
aNode.setAttribute("data-action", JSON.stringify({
url, line, action: "view-source"
}));
}
if (!displayName && !url) {
let label = doc.createElement("label");
label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
hbox.appendChild(label);
}
container.appendChild(hbox);
if (frame.asyncParent) {
frameIndex = frame.asyncParent;
wasAsyncParent = true;
} else {
frameIndex = frame.parent;
}
}
return container;
}
};

View File

@ -29,10 +29,12 @@ loader.lazyRequireGetter(this, "MarkerUtils",
*/
function MarkerDetails(parent, splitter) {
EventEmitter.decorate(this);
this._onClick = this._onClick.bind(this);
this._document = parent.ownerDocument;
this._parent = parent;
this._splitter = splitter;
this._splitter.addEventListener("mouseup", () => this.emit("resize"));
this._parent.addEventListener("click", this._onClick);
}
MarkerDetails.prototype = {
@ -41,6 +43,7 @@ MarkerDetails.prototype = {
*/
destroy: function() {
this.empty();
this._parent.removeEventListener("click", this._onClick);
this._parent = null;
this._splitter = null;
},
@ -57,121 +60,66 @@ MarkerDetails.prototype = {
*
* @param object params
* An options object holding:
* toolbox - The toolbox.
* marker - The marker to display.
* frames - Array of stack frame information; see stack.js.
*/
render: function({toolbox: toolbox, marker: marker, frames: frames}) {
render: function({ marker, frames }) {
this.empty();
// UI for any marker
let title = MarkerUtils.DOM.buildTitle(this._document, marker);
let duration = MarkerUtils.DOM.buildDuration(this._document, marker);
let fields = MarkerUtils.DOM.buildFields(this._document, marker);
this._parent.appendChild(title);
this._parent.appendChild(duration);
fields.forEach(field => this._parent.appendChild(field));
let elements = [];
elements.push(MarkerUtils.DOM.buildTitle(this._document, marker));
elements.push(MarkerUtils.DOM.buildDuration(this._document, marker));
MarkerUtils.DOM.buildFields(this._document, marker).forEach(field => elements.push(field));
// Build a stack element -- and use the "startStack" label if
// we have both a star and endStack.
if (marker.stack) {
let property = "timeline.markerDetail.stack";
if (marker.endStack) {
property = "timeline.markerDetail.startStack";
}
this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: property,
frameIndex: marker.stack, frames: frames});
let type = marker.endStack ? "startStack" : "stack";
elements.push(MarkerUtils.DOM.buildStackTrace(this._document, {
frameIndex: marker.stack, frames, type
}));
}
if (marker.endStack) {
this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: "timeline.markerDetail.endStack",
frameIndex: marker.endStack, frames: frames});
}
elements.forEach(el => this._parent.appendChild(el));
},
/**
* Render a stack trace.
*
* @param object params
* An options object with the following members:
* object toolbox - The toolbox.
* nsIDOMNode parent - The parent node holding the view.
* string property - String identifier for label's name.
* integer frameIndex - The index of the topmost stack frame.
* array frames - Array of stack frames.
* Handles click in the marker details view. Based on the target,
* can handle different actions -- only supporting view source links
* for the moment.
*/
renderStackTrace: function({toolbox: toolbox, parent: parent,
property: property, frameIndex: frameIndex,
frames: frames}) {
let labelName = this._document.createElement("label");
labelName.className = "plain marker-details-labelname";
labelName.setAttribute("value", L10N.getStr(property));
parent.appendChild(labelName);
_onClick: function (e) {
let data = findActionFromEvent(e.target);
if (!data) {
return;
}
let wasAsyncParent = false;
while (frameIndex > 0) {
let frame = frames[frameIndex];
let url = frame.source;
let displayName = frame.functionDisplayName;
let line = frame.line;
// If the previous frame had an async parent, then the async
// cause is in this frame and should be displayed.
if (wasAsyncParent) {
let asyncBox = this._document.createElement("hbox");
let asyncLabel = this._document.createElement("label");
asyncLabel.className = "devtools-monospace";
asyncLabel.setAttribute("value", L10N.getFormatStr("timeline.markerDetail.asyncStack",
frame.asyncCause));
asyncBox.appendChild(asyncLabel);
parent.appendChild(asyncBox);
wasAsyncParent = false;
}
let hbox = this._document.createElement("hbox");
if (displayName) {
let functionLabel = this._document.createElement("label");
functionLabel.className = "devtools-monospace";
functionLabel.setAttribute("value", displayName);
hbox.appendChild(functionLabel);
}
if (url) {
let aNode = this._document.createElement("a");
aNode.className = "waterfall-marker-location theme-link devtools-monospace";
aNode.href = url;
aNode.draggable = false;
aNode.setAttribute("title", url);
let text = WebConsoleUtils.abbreviateSourceURL(url) + ":" + line;
let label = this._document.createElement("label");
label.setAttribute("value", text);
aNode.appendChild(label);
hbox.appendChild(aNode);
aNode.addEventListener("click", (event) => {
event.preventDefault();
this.emit("view-source", url, line);
});
}
if (!displayName && !url) {
let label = this._document.createElement("label");
label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
hbox.appendChild(label);
}
parent.appendChild(hbox);
if (frame.asyncParent) {
frameIndex = frame.asyncParent;
wasAsyncParent = true;
} else {
frameIndex = frame.parent;
}
if (data.action === "view-source") {
this.emit("view-source", data.url, data.line);
}
},
};
exports.MarkerDetails = MarkerDetails;
/**
* Take an element from an event `target`, and asend through
* the DOM, looking for an element with a `data-action` attribute. Return
* the parsed `data-action` value found, or null if none found before
* reaching the parent `container`.
*
* @param {Element} target
* @param {Element} container
* @return {?object}
*/
function findActionFromEvent (target, container) {
let el = target;
let action;
while (el !== container) {
if (action = el.getAttribute("data-action")) {
return JSON.parse(action);
}
el = el.parentNode;
}
return null;
}

View File

@ -3,6 +3,8 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const MARKER_DETAILS_WIDTH = 300;
/**
* Waterfall view containing the timeline markers, controlled by DetailsView.
*/
@ -24,6 +26,9 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
initialize: function () {
DetailsSubview.initialize.call(this);
// TODO bug 1167093 save the previously set width, and ensure minimum width
$("#waterfall-details").setAttribute("width", MARKER_DETAILS_WIDTH);
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));

View File

@ -123,6 +123,15 @@ exports.viewSourceInScratchpad = Task.async(function *(sourceURL, sourceLine) {
* @return {Promise}
*/
exports.viewSource = Task.async(function *(toolbox, sourceURL, sourceLine) {
// Attempt to access view source via a browser first, which may display it in
// a tab, if enabled.
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWin) {
return browserWin.BrowserViewSourceOfDocument({
URL: sourceURL,
lineNumber: sourceLine
});
}
let utils = toolbox.gViewSourceUtils;
utils.viewSource(sourceURL, null, toolbox.doc, sourceLine || 0);
});

View File

@ -111,7 +111,7 @@ function testAdvanceCharsFunction(doc) {
return aText.length > 0;
},
start: function(editor) {
for each (let ch in ":Test:") {
for (let ch of ":Test:") {
EventUtils.sendChar(ch);
}
},

View File

@ -44,7 +44,7 @@ function promiseTab(aURL) {
addTab(aURL, resolve));
}
registerCleanupFunction(function tearDown() {
registerCleanupFunction(function* tearDown() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
@ -126,7 +126,9 @@ function waitForValue(aOptions)
successFn(aOptions, lastValue);
}
else {
setTimeout(function() wait(validatorFn, successFn, failureFn), 100);
setTimeout(() => {
wait(validatorFn, successFn, failureFn);
}, 100);
}
}

View File

@ -7,7 +7,7 @@
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
@ -70,16 +70,16 @@ function* testInlineStyle(view, inspector) {
yield expandComputedViewPropertyByIndex(view, 0);
let onWindow = waitForWindow();
let onTab = waitForTab();
info("Clicking on the first rule-link in the computed-view");
clickLinkByIndex(view, 0);
let win = yield onWindow;
let tab = yield onTab;
let windowType = win.document.documentElement.getAttribute("windowtype");
is(windowType, "navigator:view-source", "View source window is open");
info("Closing window");
win.close();
let tabURI = tab.linkedBrowser.documentURI.spec;
ok(tabURI.startsWith("view-source:"), "View source tab is open");
info("Closing tab");
gBrowser.removeTab(tab);
}
function* testFirstInlineStyleSheet(view, toolbox) {

View File

@ -7,7 +7,7 @@
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
@ -70,16 +70,16 @@ add_task(function*() {
function* testInlineStyle(view, inspector) {
info("Testing inline style");
let onWindow = waitForWindow();
let onTab = waitForTab();
info("Clicking on the first link in the rule-view");
clickLinkByIndex(view, 0);
let win = yield onWindow;
let tab = yield onTab;
let windowType = win.document.documentElement.getAttribute("windowtype");
is(windowType, "navigator:view-source", "View source window is open");
info("Closing window");
win.close();
let tabURI = tab.linkedBrowser.documentURI.spec;
ok(tabURI.startsWith("view-source:"), "View source tab is open");
info("Closing tab");
gBrowser.removeTab(tab);
}
function* testFirstInlineStyleSheet(view, toolbox) {

View File

@ -491,6 +491,21 @@ function waitForWindow() {
return def.promise;
}
/**
* Listen for a new tab to open and return a promise that resolves when one
* does and completes the load event.
* @return a promise that resolves to the tab object
*/
let waitForTab = Task.async(function*() {
info("Waiting for a tab to open");
yield once(gBrowser.tabContainer, "TabOpen");
let tab = gBrowser.selectedTab;
let browser = tab.linkedBrowser;
yield once(browser, "load", true);
info("The tab load completed");
return tab;
});
/**
* @see SimpleTest.waitForClipboard
* @param {Function} setup Function to execute before checking for the

View File

@ -434,6 +434,15 @@ WebConsole.prototype = {
* The line number which should be highlighted.
*/
viewSource: function WC_viewSource(aSourceURL, aSourceLine) {
// Attempt to access view source via a browser first, which may display it in
// a tab, if enabled.
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWin) {
return browserWin.BrowserViewSourceOfDocument({
URL: aSourceURL,
lineNumber: aSourceLine
});
}
this.gViewSourceUtils.viewSource(aSourceURL, null, this.iframeWindow.document, aSourceLine || 0);
},

View File

@ -10,13 +10,9 @@ let getItemForAttachment;
let Sources;
let getItemInvoked = false;
function test() {
loadTab(TEST_URI).then(() => {
openConsole(null).then(testViewSource);
});
}
function testViewSource(hud) {
add_task(function*() {
yield loadTab(TEST_URI);
let hud = yield openConsole(null);
info("console opened");
let button = content.document.querySelector("button");
@ -25,58 +21,42 @@ function testViewSource(hud) {
expectUncaughtException();
EventUtils.sendMouseEvent({ type: "click" }, button, content);
openDebugger().then(({panelWin: { DebuggerView }}) => {
info("debugger opened");
Sources = DebuggerView.Sources;
openConsole().then((hud) => {
info("console opened again");
let { panelWin: { DebuggerView } } = yield openDebugger();
info("debugger opened");
Sources = DebuggerView.Sources;
hud = yield openConsole();
info("console opened again");
waitForMessages({
webconsole: hud,
messages: [{
text: "fooBazBaz is not defined",
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
}],
}).then(onMessage);
});
let [result] = yield waitForMessages({
webconsole: hud,
messages: [{
text: "fooBazBaz is not defined",
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
}],
});
function onMessage([result]) {
let msg = [...result.matched][0];
ok(msg, "error message");
let locationNode = msg.querySelector(".message-location");
ok(locationNode, "location node");
let msg = [...result.matched][0];
ok(msg, "error message");
let locationNode = msg.querySelector(".message-location");
ok(locationNode, "location node");
Services.ww.registerNotification(observer);
let onTabOpen = waitForTab();
getItemForAttachment = Sources.getItemForAttachment;
Sources.getItemForAttachment = () => {
getItemInvoked = true;
return false;
};
getItemForAttachment = Sources.getItemForAttachment;
Sources.getItemForAttachment = () => {
getItemInvoked = true;
return false;
};
EventUtils.sendMouseEvent({ type: "click" }, locationNode);
}
}
EventUtils.sendMouseEvent({ type: "click" }, locationNode);
let observer = {
observe: function(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened") {
return;
}
let tab = yield onTabOpen;
ok(true, "the view source tab was opened in response to clicking " +
"the location node");
gBrowser.removeTab(tab);
ok(true, "the view source window was opened in response to clicking " +
"the location node");
aSubject.close();
ok(getItemInvoked, "custom getItemForAttachment() was invoked");
Sources.getItemForAttachment = getItemForAttachment;
Sources = getItemForAttachment = null;
finishTest();
}
};
registerCleanupFunction(function() {
Services.ww.unregisterNotification(observer);
ok(getItemInvoked, "custom getItemForAttachment() was invoked");
Sources.getItemForAttachment = getItemForAttachment;
Sources = getItemForAttachment = null;
});

View File

@ -255,6 +255,21 @@ function waitForContextMenu(aPopup, aButton, aOnShown, aOnHidden)
return deferred.promise;
}
/**
* Listen for a new tab to open and return a promise that resolves when one
* does and completes the load event.
* @return a promise that resolves to the tab object
*/
let waitForTab = Task.async(function*() {
info("Waiting for a tab to open");
yield once(gBrowser.tabContainer, "TabOpen");
let tab = gBrowser.selectedTab;
let browser = tab.linkedBrowser;
yield once(browser, "load", true);
info("The tab load completed");
return tab;
});
/**
* Dump the output of all open Web Consoles - used only for debugging purposes.
*/

View File

@ -1136,10 +1136,10 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
%include ../shared/notification-icons.inc.css
.popup-notification-description[popupid="addon-progress"],
.popup-notification-description[popupid="addon-install-confirmation"] {
width: 27em;
max-width: 27em;
.popup-notification-body[popupid="addon-progress"],
.popup-notification-body[popupid="addon-install-confirmation"] {
width: 28em;
max-width: 28em;
}
.addon-install-confirmation-name {

View File

@ -3687,10 +3687,10 @@ notification[value="loop-sharing-notification"] .messageImage {
}
}
.popup-notification-description[popupid="addon-progress"],
.popup-notification-description[popupid="addon-install-confirmation"] {
width: 27em;
max-width: 27em;
.popup-notification-body[popupid="addon-progress"],
.popup-notification-body[popupid="addon-install-confirmation"] {
width: 28em;
max-width: 28em;
}
#addon-progress-notification-progresstext,

View File

@ -6,16 +6,17 @@
:root {
font: message-box;
%ifdef XP_MACOSX
--monospace-font-family: Menlo, monospace;
%elifdef XP_WIN
--monospace-font-family: Consolas, monospace;
%else
--monospace-font-family: monospace;
%endif
}
.devtools-monospace {
%ifdef XP_MACOSX
font-family: Menlo, monospace;
%elifdef XP_WIN
font-family: Consolas, monospace;
%else
font-family: monospace;
%endif
font-family: var(--monospace-font-family);
%if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
font-size: 80%;
%endif
@ -247,3 +248,34 @@
background-color: transparent;
border: none;
}
/* links to source code, like displaying `myfile.js:45` */
.devtools-source-link {
font-family: var(--monospace-font-family);
color: var(--theme-highlight-blue);
cursor: pointer;
white-space: nowrap;
display: flex;
text-decoration: none;
font-size: 11px;
width: 12em; /* probably should be changed for each tool */
}
.devtools-source-link:hover {
text-decoration: underline;
}
.devtools-source-link > .filename {
text-overflow: ellipsis;
text-align: end;
overflow: hidden;
margin: 2px 0px;
cursor: pointer;
}
.devtools-source-link > .line-number {
flex: none;
margin: 2px 0px;
cursor: pointer;
}

View File

@ -457,15 +457,6 @@
color: var(--theme-selection-color);
}
.waterfall-marker-location {
color: -moz-nativehyperlinktext;
}
.waterfall-marker-location:hover,
.waterfall-marker-location:focus {
text-decoration: underline;
}
#waterfall-details {
-moz-padding-start: 8px;
-moz-padding-end: 8px;

View File

@ -2153,10 +2153,10 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
%include ../shared/notification-icons.inc.css
.popup-notification-description[popupid="addon-progress"],
.popup-notification-description[popupid="addon-install-confirmation"] {
width: 27em;
max-width: 27em;
.popup-notification-body[popupid="addon-progress"],
.popup-notification-body[popupid="addon-install-confirmation"] {
width: 28em;
max-width: 28em;
}
.addon-install-confirmation-name {

View File

@ -40,45 +40,45 @@ span[id]:before {
font-weight: normal;
font-style: normal;
}
.start-tag {
.highlight .start-tag {
color: purple;
font-weight: bold;
}
.end-tag {
.highlight .end-tag {
color: purple;
font-weight: bold;
}
.comment {
.highlight .comment {
color: green;
font-style: italic;
}
.cdata {
.highlight .cdata {
color: #CC0066;
}
.doctype {
.highlight .doctype {
color: steelblue;
font-style: italic;
}
.pi {
.highlight .pi {
color: orchid;
font-style: italic;
}
.entity {
color:#FF4500;
.highlight .entity {
color: #FF4500;
font-weight: normal;
}
.text {
.highlight .text {
font-weight: normal;
}
.attribute-name {
.highlight .attribute-name {
color: black;
font-weight: bold;
}
.attribute-value {
.highlight .attribute-value {
color: blue;
font-weight: normal;
}
.markupdeclaration {
.highlight .markupdeclaration {
color: steelblue;
font-style: italic;
}
@ -88,9 +88,9 @@ span:not(.error), a:not(.error) {
span[id] {
unicode-bidi: -moz-isolate;
}
.error,
.error > :-moz-any(.start-tag, .end-tag, .comment, .cdata, .doctype, .pi,
.entity, .attribute-name, .attribute-value) {
.highlight .error,
.highlight .error > :-moz-any(.start-tag, .end-tag, .comment, .cdata, .doctype,
.pi, .entity, .attribute-name, .attribute-value) {
color: red;
font-weight: bold;
}

View File

@ -53,8 +53,6 @@ nsHtml5Highlighter::nsHtml5Highlighter(nsAHtml5TreeOpSink* aOpSink)
, mInlinesOpen(0)
, mInCharacters(false)
, mBuffer(nullptr)
, mSyntaxHighlight(Preferences::GetBool("view_source.syntax_highlight",
true))
, mOpSink(aOpSink)
, mCurrentRun(nullptr)
, mAmpersand(nullptr)
@ -713,9 +711,6 @@ nsHtml5Highlighter::AppendCharacters(const char16_t* aBuffer,
void
nsHtml5Highlighter::AddClass(const char16_t* aClass)
{
if (!mSyntaxHighlight) {
return;
}
mOpQueue.AppendElement()->InitAddClass(CurrentNode(), aClass);
}
@ -751,9 +746,6 @@ nsHtml5Highlighter::AddBase(const nsString& aValue)
void
nsHtml5Highlighter::AddErrorToCurrentNode(const char* aMsgId)
{
if (!mSyntaxHighlight) {
return;
}
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");
treeOp->Init(CurrentNode(), aMsgId);
@ -762,9 +754,6 @@ nsHtml5Highlighter::AddErrorToCurrentNode(const char* aMsgId)
void
nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId)
{
if (!mSyntaxHighlight) {
return;
}
NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");
@ -775,9 +764,6 @@ void
nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId,
nsIAtom* aName)
{
if (!mSyntaxHighlight) {
return;
}
NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");
@ -789,9 +775,6 @@ nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId,
nsIAtom* aName,
nsIAtom* aOther)
{
if (!mSyntaxHighlight) {
return;
}
NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");
@ -801,9 +784,6 @@ nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId,
void
nsHtml5Highlighter::AddErrorToCurrentAmpersand(const char* aMsgId)
{
if (!mSyntaxHighlight) {
return;
}
NS_PRECONDITION(mAmpersand, "Adding error to ampersand without one!");
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");
@ -813,9 +793,6 @@ nsHtml5Highlighter::AddErrorToCurrentAmpersand(const char* aMsgId)
void
nsHtml5Highlighter::AddErrorToCurrentSlash(const char* aMsgId)
{
if (!mSyntaxHighlight) {
return;
}
NS_PRECONDITION(mSlash, "Adding error to slash without one!");
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
NS_ASSERTION(treeOp, "Tree op allocation failed.");

View File

@ -313,11 +313,6 @@ class nsHtml5Highlighter
*/
nsHtml5UTF16Buffer* mBuffer;
/**
* Whether to highlight syntax visibly initially.
*/
bool mSyntaxHighlight;
/**
* The outgoing tree op queue.
*/

View File

@ -15,8 +15,14 @@ nsHtml5ViewSourceUtils::NewBodyAttributes()
nsString* id = new nsString(NS_LITERAL_STRING("viewsource"));
bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, id);
nsString* klass = new nsString();
if (mozilla::Preferences::GetBool("view_source.wrap_long_lines", true)) {
nsString* klass = new nsString(NS_LITERAL_STRING("wrap"));
klass->Append(NS_LITERAL_STRING("wrap "));
}
if (mozilla::Preferences::GetBool("view_source.syntax_highlight", true)) {
klass->Append(NS_LITERAL_STRING("highlight"));
}
if (!klass->IsEmpty()) {
bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_CLASS, klass);
}

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
<span id></span><span>&lt;<span class="start-tag">html</span>&gt;</span>
<span id></span><span>&lt;<span class="start-tag">head</span>&gt;</span>
<span id></span><span>&lt;<span class="start-tag">title</span>&gt;</span><span>Title</span><span>&lt;/<span class="end-tag">title</span>&gt;</span>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap"><pre id><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span id></span><span class="pi">&lt;?foo bar?&gt;</span>
<span id></span><span>&lt;<span class="start-tag">html</span>&gt;</span>
<span id></span><span>&lt;<span class="start-tag">head</span>&gt;</span>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
<span id></span>XX<span class="error">&amp;</span>XX
<span id></span>XX<span class="error">&amp;</span>nXX
<span id></span>XX<span class="error">&amp;</span>noXX

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap"><pre id><span class="error comment">&lt;!--&gt;</span> <span class="error comment">&lt;!X&gt;</span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="error comment">&lt;!--&gt;</span> <span class="error comment">&lt;!X&gt;</span>
<span id></span>
</pre>
<!-- View source CSS matches the <pre id> and <span id> elements and produces line numbers. -->

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span><span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span><span>
<span id></span></span><span>&lt;<span class="start-tag">body</span>&gt;</span><span>
<span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span>&lt;/<span class="end-tag">script</span>&gt;</span><span>X
<span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span>&lt;/<span class="end-tag">script</span> &gt;</span><span>X

View File

@ -1,2 +1,2 @@
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" style="-moz-tab-size: 4"><pre id="line1"><span></span><span class="doctype">&lt;!DOCTYPE html&gt;</span><span></span><span>&lt;<span class="start-tag">table</span>&gt;</span><span></span><span title="Start tag “input” seen in “table”." class="error">&lt;<span class="start-tag">input</span> <span class="attribute-name">type</span>=<a class="attribute-value">hidden</a>&gt;</span><span></span><span>&lt;/<span class="end-tag">table</span>&gt;</span><span>
<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="highlight" style="-moz-tab-size: 4"><pre id="line1"><span></span><span class="doctype">&lt;!DOCTYPE html&gt;</span><span></span><span>&lt;<span class="start-tag">table</span>&gt;</span><span></span><span title="Start tag “input” seen in “table”." class="error">&lt;<span class="start-tag">input</span> <span class="attribute-name">type</span>=<a class="attribute-value">hidden</a>&gt;</span><span></span><span>&lt;/<span class="end-tag">table</span>&gt;</span><span>
<span id="line2"></span></span></pre></body></html>

View File

@ -0,0 +1,627 @@
// -*- 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 { utils: Cu, interfaces: Ci, classes: Cc } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
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";
this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"];
/**
* ViewSourceBrowser manages the view source <browser> from the chrome side.
* It's companion frame script, viewSource-content.js, needs to be loaded as a
* frame script into the browser being managed.
*
* For a view source window using viewSource.xul, the script viewSource.js in
* the window extends an instance of this with more window specific functions.
* The page script takes care of loading the companion frame script.
*
* For a view source tab (or some other non-window case), an instance of this is
* created by viewSourceUtils.js to wrap the <browser>. The caller that manages
* the <browser> is responsible for ensuring the companion frame script has been
* loaded.
*/
this.ViewSourceBrowser = function ViewSourceBrowser(aBrowser) {
this._browser = aBrowser;
this.init();
}
ViewSourceBrowser.prototype = {
/**
* The <browser> that will be displaying the view source content.
*/
get browser() {
return this._browser;
},
/**
* Holds the value of the last line found via the "Go to line"
* command, to pre-populate the prompt the next time it is
* opened.
*/
lastLineFound: null,
/**
* These are the messages that ViewSourceBrowser will listen for
* from the frame script it injects. Any message names added here
* will automatically have ViewSourceBrowser listen for those messages,
* and remove the listeners on teardown.
*/
messages: [
"ViewSource:PromptAndGoToLine",
"ViewSource:GoToLine:Success",
"ViewSource:GoToLine:Failed",
],
/**
* This should be called as soon as the script loads. When this function
* executes, we can assume the DOM content has not yet loaded.
*/
init() {
this.messages.forEach((msgName) => {
this.mm.addMessageListener(msgName, this);
});
},
/**
* This should be called when the window is closing. This function should
* clean up event and message listeners.
*/
uninit() {
this.messages.forEach((msgName) => {
this.mm.removeMessageListener(msgName, this);
});
},
/**
* Anything added to the messages array will get handled here, and should
* get dispatched to a specific function for the message name.
*/
receiveMessage(message) {
let data = message.data;
switch(message.name) {
case "ViewSource:PromptAndGoToLine":
this.promptAndGoToLine();
break;
case "ViewSource:GoToLine:Success":
this.onGoToLineSuccess(data.lineNumber);
break;
case "ViewSource:GoToLine:Failed":
this.onGoToLineFailed();
break;
}
},
/**
* Getter for the message manager of the view source browser.
*/
get mm() {
return this.browser.messageManager;
},
/**
* Send a message to the view source browser.
*/
sendAsyncMessage(...args) {
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.
*/
get bundle() {
if (this._bundle) {
return this._bundle;
}
return this._bundle = Services.strings.createBundle(BUNDLE_URL);
},
/**
* Loads the source for a URL while applying some optional features if
* enabled.
*
* For the viewSource.xul window, this is called by onXULLoaded above.
* For view source in a specific browser, this is manually called after
* this object is constructed.
*
* This takes a single object argument containing:
*
* URL (required):
* A string URL for the page we'd like to view the source of.
* browser:
* The browser containing the document that we would like to view the
* source of. This argument is optional if outerWindowID is not passed.
* outerWindowID (optional):
* The outerWindowID of the content window containing the document that
* we want to view the source of. This is the only way of attempting to
* load the source out of the network cache.
* lineNumber (optional):
* The line number to focus on once the source is loaded.
*/
loadViewSource({ URL, browser, outerWindowID, lineNumber }) {
if (!URL) {
throw new Error("Must supply a URL when opening view source.");
}
if (browser) {
// If we're dealing with a remote browser, then the browser
// for view source needs to be remote as well.
this.updateBrowserRemoteness(browser.isRemoteBrowser);
} else {
if (outerWindowID) {
throw new Error("Must supply the browser if passing the outerWindowID");
}
}
this.sendAsyncMessage("ViewSource:LoadSource",
{ URL, outerWindowID, lineNumber });
},
/**
* Updates the "remote" attribute of the view source browser. This
* will remove the browser from the DOM, and then re-add it in the
* same place it was taken from.
*
* @param shouldBeRemote
* True if the browser should be made remote. If the browsers
* remoteness already matches this value, this function does
* nothing.
*/
updateBrowserRemoteness(shouldBeRemote) {
if (this.browser.isRemoteBrowser != shouldBeRemote) {
// In this base case, where we are handed a <browser> someone else is
// managing, we don't know for sure that it's safe to toggle remoteness.
// For view source in a window, this is overridden to actually do the
// flip if needed.
throw new Error("View source browser's remoteness mismatch");
}
},
/**
* 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
+ '&lt;<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 += "/&gt;";
}
else {
str += "&gt;";
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
+ '&lt;/<span class="end-tag">' + node.nodeName + '</span>&gt;';
}
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 = {
'&': '&amp;<span class="entity">amp;</span>',
'<': '&amp;<span class="entity">lt;</span>',
'>': '&amp;<span class="entity">gt;</span>',
'"': '&amp;<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 '&amp;<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
* user either cancels out of the prompt, or enters a valid line number.
*/
promptAndGoToLine() {
let input = { value: this.lastLineFound };
let window = Services.wm.getMostRecentWindow(null);
let ok = Services.prompt.prompt(
window,
this.bundle.GetStringFromName("goToLineTitle"),
this.bundle.GetStringFromName("goToLineText"),
input,
null,
{value:0});
if (!ok)
return;
let line = parseInt(input.value, 10);
if (!(line > 0)) {
Services.prompt.alert(window,
this.bundle.GetStringFromName("invalidInputTitle"),
this.bundle.GetStringFromName("invalidInputText"));
this.promptAndGoToLine();
} else {
this.goToLine(line);
}
},
/**
* Go to a particular line of the source code. This act is asynchronous.
*
* @param lineNumber
* The line number to try to go to to.
*/
goToLine(lineNumber) {
this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
},
/**
* Called when the frame script reports that a line was successfully gotten
* to.
*
* @param lineNumber
* The line number that we successfully got to.
*/
onGoToLineSuccess(lineNumber) {
// We'll pre-populate the "Go to line" prompt with this value the next
// time it comes up.
this.lastLineFound = lineNumber;
},
/**
* Called when the frame script reports that we failed to go to a particular
* line. This informs the user that their selection was likely out of range,
* and then reprompts the user to try again.
*/
onGoToLineFailed() {
let window = Services.wm.getMostRecentWindow(null);
Services.prompt.alert(window,
this.bundle.GetStringFromName("outOfRangeTitle"),
this.bundle.GetStringFromName("outOfRangeText"));
this.promptAndGoToLine();
},
};

View File

@ -6,479 +6,20 @@
Components.utils.import("resource://gre/modules/Services.jsm");
var gDebug = 0;
var gLineCount = 0;
var gStartTargetLine = 0;
var gEndTargetLine = 0;
var gTargetNode = null;
var gEntityConverter = null;
var gWrapLongLines = false;
const gViewSourceCSS = 'resource://gre-resources/viewsource.css';
const NS_XHTML = 'http://www.w3.org/1999/xhtml';
// 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';
function onLoadViewPartialSource()
{
function onLoadViewPartialSource() {
// check the view_source.wrap_long_lines pref
// and set the menuitem's checked attribute accordingly
gWrapLongLines = Services.prefs.getBoolPref("view_source.wrap_long_lines");
document.getElementById("menu_wrapLongLines").setAttribute("checked", gWrapLongLines);
let wrapLongLines = Services.prefs.getBoolPref("view_source.wrap_long_lines");
document.getElementById("menu_wrapLongLines")
.setAttribute("checked", wrapLongLines);
document.getElementById("menu_highlightSyntax")
.setAttribute("checked",
Services.prefs.getBoolPref("view_source.syntax_highlight"));
if (window.arguments[3] == 'selection')
viewPartialSourceForSelection(window.arguments[2]);
viewSourceChrome.loadViewSourceFromSelection(window.arguments[2]);
else
viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
gBrowser.droppedLinkHandler = function (event, url, name) {
viewSource(url)
event.preventDefault();
}
viewSourceChrome.loadViewSourceFromFragment(window.arguments[2], window.arguments[3]);
window.content.focus();
}
////////////////////////////////////////////////////////////////////////////////
// view-source of a selection with the special effect of remapping the selection
// to the underlying view-source output
function viewPartialSourceForSelection(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
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 = getPath(ancestorContainer, startContainer);
var endPath = 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();
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...)
var tmpNode;
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);
// the load is aynchronous and so we will wait until the view-source DOM is done
// before drawing the selection.
if (canDrawSelection) {
window.document.getElementById("content").addEventListener("load", drawSelection, true);
}
// 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;
ViewSourceChrome.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));
}
////////////////////////////////////////////////////////////////////////////////
// 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
function 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;
}
////////////////////////////////////////////////////////////////////////////////
// using special markers left in the serialized source, this helper makes the
// underlying markup of the selected fragment to automatically appear as selected
// on the inflated view-source DOM
function drawSelection()
{
gBrowser.contentDocument.title =
gViewSourceBundle.getString("viewSelectionSourceTitle");
// find the special selection markers that we added earlier, and
// draw the selection between the two...
var findService = null;
try {
// get the find service which stores the global find state
findService = Components.classes["@mozilla.org/find/find_service;1"]
.getService(Components.interfaces.nsIFindService);
} catch(e) { }
if (!findService)
return;
// cache the current global find state
var matchCase = findService.matchCase;
var entireWord = findService.entireWord;
var wrapFind = findService.wrapFind;
var findBackwards = findService.findBackwards;
var searchString = findService.searchString;
var replaceString = findService.replaceString;
// setup our find instance
var findInst = gBrowser.webBrowserFind;
findInst.matchCase = true;
findInst.entireWord = false;
findInst.wrapFind = true;
findInst.findBackwards = false;
// ...lookup the start mark
findInst.searchString = MARK_SELECTION_START;
var startLength = MARK_SELECTION_START.length;
findInst.findNext();
var selection = content.getSelection();
if (!selection.rangeCount)
return;
var range = selection.getRangeAt(0);
var startContainer = range.startContainer;
var startOffset = range.startOffset;
// ...lookup the end mark
findInst.searchString = MARK_SELECTION_END;
var endLength = MARK_SELECTION_END.length;
findInst.findNext();
var endContainer = selection.anchorNode;
var endOffset = selection.anchorOffset;
// reset the selection that find has left
selection.removeAllRanges();
// delete the special markers now...
endContainer.deleteData(endOffset, endLength);
startContainer.deleteData(startOffset, startLength);
if (startContainer == endContainer)
endOffset -= startLength; // has shrunk if on same text node...
range.setEnd(endContainer, endOffset);
// show the selection and scroll it into view
selection.addRange(range);
// the default behavior of the selection is to scroll at the end of
// the selection, whereas in this situation, it is more user-friendly
// to scroll at the beginning. So we override the default behavior here
try {
getSelectionController().scrollSelectionIntoView(
Ci.nsISelectionController.SELECTION_NORMAL,
Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
true);
}
catch(e) { }
// restore the current find state
findService.matchCase = matchCase;
findService.entireWord = entireWord;
findService.wrapFind = wrapFind;
findService.findBackwards = findBackwards;
findService.searchString = searchString;
findService.replaceString = replaceString;
findInst.matchCase = matchCase;
findInst.entireWord = entireWord;
findInst.wrapFind = wrapFind;
findInst.findBackwards = findBackwards;
findInst.searchString = searchString;
}
////////////////////////////////////////////////////////////////////////////////
// special handler for markups such as MathML where reformatting the output is
// helpful
function viewPartialSourceForFragment(node, context)
{
gTargetNode = node;
if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
gTargetNode = gTargetNode.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 = gTargetNode;
while (topNode && topNode.localName != topTag)
topNode = topNode.parentNode;
if (!topNode)
return;
// serialize
var title = gViewSourceBundle.getString("viewMathMLSourceTitle");
var wrapClass = gWrapLongLines ? ' class="wrap"' : '';
var source =
'<!DOCTYPE html>'
+ '<html>'
+ '<head><title>' + title + '</title>'
+ '<link rel="stylesheet" type="text/css" href="' + gViewSourceCSS + '">'
+ '<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>'
+ getOuterMarkup(topNode, 0)
+ '</pre></body></html>'
; // end
// display
gBrowser.loadURI("data:text/html;charset=utf-8," + encodeURIComponent(source));
}
////////////////////////////////////////////////////////////////////////////////
function getInnerMarkup(node, indent) {
var str = '';
for (var i = 0; i < node.childNodes.length; i++) {
str += getOuterMarkup(node.childNodes.item(i), indent);
}
return str;
}
////////////////////////////////////////////////////////////////////////////////
function getOuterMarkup(node, indent) {
var newline = '';
var padding = '';
var str = '';
if (node == gTargetNode) {
gStartTargetLine = gLineCount;
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 (gLineCount > 0 &&
gLineCount != gStartTargetLine &&
gLineCount != gEndTargetLine) {
newline = '\n';
}
gLineCount++;
if (gDebug) {
newline += gLineCount;
}
for (var k = 0; k < indent; k++) {
padding += ' ';
}
str += newline + padding
+ '&lt;<span class="start-tag">' + node.nodeName + '</span>';
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes.item(i);
if (!gDebug && attr.nodeName.match(/^[-_]moz/)) {
continue;
}
str += ' <span class="attribute-name">'
+ attr.nodeName
+ '</span>=<span class="attribute-value">"'
+ unicodeTOentity(attr.nodeValue)
+ '"</span>';
}
if (!node.hasChildNodes()) {
str += '/&gt;';
}
else {
str += '&gt;';
var oldLine = gLineCount;
str += getInnerMarkup(node, indent + 2);
if (oldLine == gLineCount) {
newline = '';
padding = '';
}
else {
newline = (gLineCount == gEndTargetLine) ? '' : '\n';
gLineCount++;
if (gDebug) {
newline += gLineCount;
}
}
str += newline + padding
+ '&lt;/<span class="end-tag">' + node.nodeName + '</span>&gt;';
}
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">' + unicodeTOentity(tmp) + '</span>';
}
break;
default:
break;
}
if (node == gTargetNode) {
gEndTargetLine = gLineCount;
str += '</pre><pre>';
}
return str;
}
////////////////////////////////////////////////////////////////////////////////
function unicodeTOentity(text)
{
const charTable = {
'&': '&amp;<span class="entity">amp;</span>',
'<': '&amp;<span class="entity">lt;</span>',
'>': '&amp;<span class="entity">gt;</span>',
'"': '&amp;<span class="entity">quot;</span>'
};
function charTableLookup(letter) {
return charTable[letter];
}
function convertEntity(letter) {
try {
var unichar = gEntityConverter.ConvertToEntity(letter, entityVersion);
var entity = unichar.substring(1); // extract '&'
return '&amp;<span class="entity">' + entity + '</span>';
} catch (ex) {
return letter;
}
}
if (!gEntityConverter) {
try {
gEntityConverter =
Components.classes["@mozilla.org/intl/entityconverter;1"]
.createInstance(Components.interfaces.nsIEntityConverter);
} catch(e) { }
}
const entityVersion = Components.interfaces.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;
}

View File

@ -4,8 +4,8 @@
# 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/.
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/viewSource.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/viewSource.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
@ -20,10 +20,10 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="onLoadViewPartialSource();"
contenttitlesetting="true"
title="&mainWindow.title;"
titlemodifier="&mainWindow.titlemodifier;"
title="&mainWindow.title;"
titlemodifier="&mainWindow.titlemodifier;"
titlepreface=""
titlemenuseparator ="&mainWindow.titlemodifierseparator;"
titlemenuseparator ="&mainWindow.titlemodifierseparator;"
windowtype="navigator:view-source"
width="500" height="300"
screenX="10" screenY="10"
@ -50,9 +50,9 @@
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
<command id="cmd_findPrevious"
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
<command id="cmd_goToLine" oncommand="ViewSourceChrome.promptAndGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="ViewSourceChrome.toggleSyntaxHighlighting();"/>
<command id="cmd_wrapLongLines" oncommand="ViewSourceChrome.toggleWrapping();"/>
<command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
<command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
<command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
<command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
<command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
@ -80,16 +80,16 @@
<menuitem id="context-copyLink"
label="&copyLinkCmd.label;"
accesskey="&copyLinkCmd.accesskey;"
oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
<menuitem id="context-copyEmail"
label="&copyEmailCmd.label;"
accesskey="&copyEmailCmd.accesskey;"
oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
<menuseparator/>
<menuitem id="cMenu_selectAll"/>
</menupopup>
<!-- Menu -->
<!-- Menu -->
<toolbox id="viewSource-toolbox">
<menubar id="viewSource-main-menubar">
@ -131,11 +131,11 @@
<menu id="menu_view" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
<menupopup id="viewmenu-popup">
<menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
<menupopup>
<menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
<menupopup>
<menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
key="key_textZoomEnlarge"/>
<menuitem id="menu_textReduce" command="cmd_textZoomReduce"
<menuitem id="menu_textReduce" command="cmd_textZoomReduce"
label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
key="key_textZoomReduce"/>
<menuseparator/>
@ -151,7 +151,7 @@
label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
</menupopup>
</menu>
</menubar>
</menubar>
</toolbox>
<vbox id="appcontent" flex="1">

View File

@ -12,8 +12,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
const NS_XHTML = "http://www.w3.org/1999/xhtml";
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";
let global = this;
/**
@ -39,8 +48,16 @@ let ViewSourceContent = {
"ViewSource:ToggleWrapping",
"ViewSource:ToggleSyntaxHighlighting",
"ViewSource:SetCharacterSet",
"ViewSource:ScheduleDrawSelection",
],
/**
* When showing selection source, chrome will construct a page fragment to
* show, and then instruct content to draw a selection after load. This is
* set true when there is a pending request to draw selection.
*/
needsDrawSelection: false,
/**
* ViewSourceContent is attached as an nsISelectionListener on pageshow,
* and removed on pagehide. When the initial about:blank is transitioned
@ -51,6 +68,17 @@ let ViewSourceContent = {
*/
selectionListenerAttached: false,
get isViewSource() {
let uri = content.document.documentURI;
return uri.startsWith("view-source:") ||
(uri.startsWith("data:") && uri.includes("MathML"));
},
get isAboutBlank() {
let uri = content.document.documentURI;
return uri == "about:blank";
},
/**
* This should be called as soon as this frame script has loaded.
*/
@ -93,6 +121,9 @@ let ViewSourceContent = {
* get dispatched to a specific function for the message name.
*/
receiveMessage(msg) {
if (!this.isViewSource && !this.isAboutBlank) {
return;
}
let data = msg.data;
let objects = msg.objects;
switch(msg.name) {
@ -116,6 +147,9 @@ let ViewSourceContent = {
case "ViewSource:SetCharacterSet":
this.setCharacterSet(data.charset, data.doPageLoad);
break;
case "ViewSource:ScheduleDrawSelection":
this.scheduleDrawSelection();
break;
}
},
@ -124,6 +158,9 @@ let ViewSourceContent = {
* a specific function for the event type.
*/
handleEvent(event) {
if (!this.isViewSource) {
return;
}
switch(event.type) {
case "pagehide":
this.onPageHide(event);
@ -161,6 +198,14 @@ let ViewSourceContent = {
.QueryInterface(Ci.nsISelectionController);
},
/**
* A shortcut to the nsIWebBrowserFind for the content.
*/
get webBrowserFind() {
return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebBrowserFind);
},
/**
* Called when the parent sends a message to view some source code.
*
@ -250,8 +295,13 @@ let ViewSourceContent = {
docShell.charset = forcedCharSet;
}
if (lineNumber) {
if (lineNumber && lineNumber > 0) {
let doneLoading = (event) => {
// Ignore possible initial load of about:blank
if (this.isAboutBlank ||
!content.document.body) {
return;
}
this.goToLine(lineNumber);
removeEventListener("pageshow", doneLoading);
};
@ -300,16 +350,28 @@ let ViewSourceContent = {
},
/**
* This handler is specifically for click events bubbling up from
* error page content, which can show up if the user attempts to
* view the source of an attack page.
* This handler is for click events from:
* * error page content, which can show up if the user attempts to view the
* source of an attack page.
* * in-page context menu actions
*/
onClick(event) {
let target = event.originalTarget;
// Check for content menu actions
if (target.id) {
this.contextMenuItems.forEach(itemSpec => {
if (itemSpec.id !== target.id) {
return;
}
itemSpec.handler.call(this, event);
event.stopPropagation();
});
}
// Don't trust synthetic events
if (!event.isTrusted || event.target.localName != "button")
return;
let target = event.originalTarget;
let errorDoc = target.ownerDocument;
if (/^about:blocked/.test(errorDoc.documentURI)) {
@ -341,12 +403,26 @@ let ViewSourceContent = {
* The pageshow event being handled.
*/
onPageShow(event) {
content.getSelection()
.QueryInterface(Ci.nsISelectionPrivate)
.addSelectionListener(this);
this.selectionListenerAttached = true;
let selection = content.getSelection();
if (selection) {
selection.QueryInterface(Ci.nsISelectionPrivate)
.addSelectionListener(this);
this.selectionListenerAttached = true;
}
content.focus();
// If we need to draw the selection, wait until an actual view source page
// has loaded, instead of about:blank.
if (this.needsDrawSelection &&
content.document.documentURI.startsWith("view-source:")) {
this.needsDrawSelection = false;
this.drawSelection();
}
if (content.document.body) {
this.injectContextMenu();
}
sendAsyncMessage("ViewSource:SourceLoaded");
},
@ -604,13 +680,12 @@ let ViewSourceContent = {
},
/**
* Called when the parent has changed the syntax highlighting pref.
* Toggles the "highlight" class on the document body, which sets whether
* or not syntax highlighting is displayed.
*/
toggleSyntaxHighlighting() {
// The parent process should have set the view_source.syntax_highlight
// pref to the desired value. The reload brings that setting into
// effect.
this.reload();
let body = content.document.body;
body.classList.toggle("highlight");
},
/**
@ -699,5 +774,184 @@ 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
* selected on the inflated view-source DOM.
*/
drawSelection() {
content.document.title =
this.bundle.GetStringFromName("viewSelectionSourceTitle");
// find the special selection markers that we added earlier, and
// draw the selection between the two...
var findService = null;
try {
// get the find service which stores the global find state
findService = Cc["@mozilla.org/find/find_service;1"]
.getService(Ci.nsIFindService);
} catch(e) { }
if (!findService)
return;
// cache the current global find state
var matchCase = findService.matchCase;
var entireWord = findService.entireWord;
var wrapFind = findService.wrapFind;
var findBackwards = findService.findBackwards;
var searchString = findService.searchString;
var replaceString = findService.replaceString;
// setup our find instance
var findInst = this.webBrowserFind;
findInst.matchCase = true;
findInst.entireWord = false;
findInst.wrapFind = true;
findInst.findBackwards = false;
// ...lookup the start mark
findInst.searchString = MARK_SELECTION_START;
var startLength = MARK_SELECTION_START.length;
findInst.findNext();
var selection = content.getSelection();
if (!selection.rangeCount)
return;
var range = selection.getRangeAt(0);
var startContainer = range.startContainer;
var startOffset = range.startOffset;
// ...lookup the end mark
findInst.searchString = MARK_SELECTION_END;
var endLength = MARK_SELECTION_END.length;
findInst.findNext();
var endContainer = selection.anchorNode;
var endOffset = selection.anchorOffset;
// reset the selection that find has left
selection.removeAllRanges();
// delete the special markers now...
endContainer.deleteData(endOffset, endLength);
startContainer.deleteData(startOffset, startLength);
if (startContainer == endContainer)
endOffset -= startLength; // has shrunk if on same text node...
range.setEnd(endContainer, endOffset);
// show the selection and scroll it into view
selection.addRange(range);
// the default behavior of the selection is to scroll at the end of
// the selection, whereas in this situation, it is more user-friendly
// to scroll at the beginning. So we override the default behavior here
try {
this.selectionController.scrollSelectionIntoView(
Ci.nsISelectionController.SELECTION_NORMAL,
Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
true);
}
catch(e) { }
// restore the current find state
findService.matchCase = matchCase;
findService.entireWord = entireWord;
findService.wrapFind = wrapFind;
findService.findBackwards = findBackwards;
findService.searchString = searchString;
findService.replaceString = replaceString;
findInst.matchCase = matchCase;
findInst.entireWord = entireWord;
findInst.wrapFind = wrapFind;
findInst.findBackwards = findBackwards;
findInst.searchString = searchString;
},
/**
* In-page context menu items that are injected after page load.
*/
contextMenuItems: [
{
id: "goToLine",
handler() {
sendAsyncMessage("ViewSource:PromptAndGoToLine");
}
},
{
id: "wrapLongLines",
get checked() {
return Services.prefs.getBoolPref("view_source.wrap_long_lines");
},
handler() {
this.toggleWrapping();
}
},
{
id: "highlightSyntax",
get checked() {
return Services.prefs.getBoolPref("view_source.syntax_highlight");
},
handler() {
this.toggleSyntaxHighlighting();
}
},
],
/**
* Add context menu items for view source specific actions.
*/
injectContextMenu() {
let doc = content.document;
let menu = doc.createElementNS(NS_XHTML, "menu");
menu.setAttribute("type", "context");
menu.setAttribute("id", "actions");
doc.body.appendChild(menu);
doc.body.setAttribute("contextmenu", "actions");
this.contextMenuItems.forEach(itemSpec => {
let item = doc.createElementNS(NS_XHTML, "menuitem");
item.setAttribute("id", itemSpec.id);
let labelName = `context_${itemSpec.id}_label`;
let label = this.bundle.GetStringFromName(labelName);
item.setAttribute("label", label);
if ("checked" in itemSpec) {
item.setAttribute("type", "checkbox");
}
menu.appendChild(item);
});
this.updateContextMenu();
},
/**
* Update state of checkbox-style context menu items.
*/
updateContextMenu() {
let doc = content.document;
this.contextMenuItems.forEach(itemSpec => {
if (!("checked" in itemSpec)) {
return;
}
let item = doc.getElementById(itemSpec.id);
if (itemSpec.checked) {
item.setAttribute("checked", true);
} else {
item.removeAttribute("checked");
}
});
},
};
ViewSourceContent.init();

View File

@ -7,6 +7,7 @@
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ViewSourceBrowser.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -31,15 +32,24 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
/**
* ViewSourceChrome is the primary interface for interacting with
* the view source browser. It initializes itself on script load.
* the view source browser from a self-contained window. It extends
* ViewSourceBrowser with additional things needed inside the special window.
*
* It initializes itself on script load.
*/
let ViewSourceChrome = {
function ViewSourceChrome() {
ViewSourceBrowser.call(this);
}
ViewSourceChrome.prototype = {
__proto__: ViewSourceBrowser.prototype,
/**
* Holds the value of the last line found via the "Go to line"
* command, to pre-populate the prompt the next time it is
* opened.
* The <browser> that will be displaying the view source content.
*/
lastLineFound: null,
get browser() {
return gBrowser;
},
/**
* The context menu, when opened from the content process, sends
@ -55,31 +65,22 @@ let ViewSourceChrome = {
* will automatically have ViewSourceChrome listen for those messages,
* and remove the listeners on teardown.
*/
messages: [
messages: ViewSourceBrowser.prototype.messages.concat([
"ViewSource:SourceLoaded",
"ViewSource:SourceUnloaded",
"ViewSource:Close",
"ViewSource:OpenURL",
"ViewSource:GoToLine:Success",
"ViewSource:GoToLine:Failed",
"ViewSource:UpdateStatus",
"ViewSource:ContextMenuOpening",
],
]),
/**
* This should be called as soon as the script loads. When this function
* executes, we can assume the DOM content has not yet loaded.
* This called via ViewSourceBrowser's constructor. This should be called as
* soon as the script loads. When this function executes, we can assume the
* DOM content has not yet loaded.
*/
init() {
// We use the window message manager so that if we switch remoteness of the
// browser (which we might do if we're attempting to load the document
// source out of the network cache), we automatically re-load the frame
// script.
let wMM = window.messageManager;
wMM.loadFrameScript("chrome://global/content/viewSource-content.js", true);
this.messages.forEach((msgName) => {
wMM.addMessageListener(msgName, this);
});
this.mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);
this.shouldWrap = Services.prefs.getBoolPref("view_source.wrap_long_lines");
this.shouldHighlight =
@ -89,6 +90,8 @@ let ViewSourceChrome = {
addEventListener("unload", this);
addEventListener("AppCommand", this, true);
addEventListener("MozSwipeGesture", this, true);
ViewSourceBrowser.prototype.init.call(this);
},
/**
@ -96,10 +99,7 @@ let ViewSourceChrome = {
* clean up event and message listeners.
*/
uninit() {
let wMM = window.messageManager;
this.messages.forEach((msgName) => {
wMM.removeMessageListener(msgName, this);
});
ViewSourceBrowser.prototype.uninit.call(this);
// "load" event listener is removed in its handler, to
// ensure we only fire it once.
@ -108,8 +108,9 @@ let ViewSourceChrome = {
removeEventListener("MozSwipeGesture", this, true);
gContextMenu.removeEventListener("popupshowing", this);
gContextMenu.removeEventListener("popuphidden", this);
Services.els.removeSystemEventListener(gBrowser, "dragover", this, true);
Services.els.removeSystemEventListener(gBrowser, "drop", this, true);
Services.els.removeSystemEventListener(this.browser, "dragover", this,
true);
Services.els.removeSystemEventListener(this.browser, "drop", this, true);
},
/**
@ -120,6 +121,17 @@ let ViewSourceChrome = {
let data = message.data;
switch(message.name) {
// Begin messages from super class
case "ViewSource:PromptAndGoToLine":
this.promptAndGoToLine();
break;
case "ViewSource:GoToLine:Success":
this.onGoToLineSuccess(data.lineNumber);
break;
case "ViewSource:GoToLine:Failed":
this.onGoToLineFailed();
break;
// End messages from super class
case "ViewSource:SourceLoaded":
this.onSourceLoaded();
break;
@ -132,18 +144,12 @@ let ViewSourceChrome = {
case "ViewSource:OpenURL":
this.openURL(data.URL);
break;
case "ViewSource:GoToLine:Failed":
this.onGoToLineFailed();
break;
case "ViewSource:GoToLine:Success":
this.onGoToLineSuccess(data.lineNumber);
break;
case "ViewSource:UpdateStatus":
this.updateStatus(data.label);
break;
case "ViewSource:ContextMenuOpening":
this.onContextMenuOpening(data.isLink, data.isEmail, data.href);
if (gBrowser.isRemoteBrowser) {
if (this.browser.isRemoteBrowser) {
this.openContextMenu(data.screenX, data.screenY);
}
break;
@ -188,35 +194,35 @@ let ViewSourceChrome = {
* has history enabled on it.
*/
get historyEnabled() {
return !gBrowser.hasAttribute("disablehistory");
return !this.browser.hasAttribute("disablehistory");
},
/**
* Getter for the message manager of the view source browser.
* Getter for the message manager used to communicate with the view source
* browser.
*
* In this window version of view source, we use the window message manager
* for loading scripts and listening for messages so that if we switch
* remoteness of the browser (which we might do if we're attempting to load
* the document source out of the network cache), we automatically re-load
* the frame script.
*/
get mm() {
return gBrowser.messageManager;
},
/**
* Getter for the nsIWebNavigation of the view source browser.
*/
get webNav() {
return gBrowser.webNavigation;
return window.messageManager;
},
/**
* Send the browser forward in its history.
*/
goForward() {
gBrowser.goForward();
this.browser.goForward();
},
/**
* Send the browser backward in its history.
*/
goBack() {
gBrowser.goBack();
this.browser.goBack();
},
/**
@ -267,8 +273,8 @@ let ViewSourceChrome = {
gContextMenu.addEventListener("popupshowing", this);
gContextMenu.addEventListener("popuphidden", this);
Services.els.addSystemEventListener(gBrowser, "dragover", this, true);
Services.els.addSystemEventListener(gBrowser, "drop", this, true);
Services.els.addSystemEventListener(this.browser, "dragover", this, true);
Services.els.addSystemEventListener(this.browser, "drop", this, true);
if (!this.historyEnabled) {
// Disable the BACK and FORWARD commands and hide the related menu items.
@ -279,12 +285,6 @@ let ViewSourceChrome = {
}
}
// This will only work with non-remote browsers. See bug 1158377.
gBrowser.droppedLinkHandler = function (event, url, name) {
ViewSourceChrome.loadURL(url);
event.preventDefault();
};
// We require the first argument to do any loading of source.
// otherwise, we're done.
if (!window.arguments[0]) {
@ -293,39 +293,20 @@ let ViewSourceChrome = {
if (typeof window.arguments[0] == "string") {
// We're using the deprecated API
return ViewSourceChrome._loadViewSourceDeprecated();
return this._loadViewSourceDeprecated(window.arguments);
}
// 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];
if (!args.URL) {
throw new Error("Must supply a URL when opening view source.");
}
if (args.browser) {
// If we're dealing with a remote browser, then the browser
// for view source needs to be remote as well.
this.updateBrowserRemoteness(args.browser.isRemoteBrowser);
} else {
if (args.outerWindowID) {
throw new Error("Must supply the browser if passing the outerWindowID");
}
}
this.mm.sendAsyncMessage("ViewSource:LoadSource", {
URL: args.URL,
outerWindowID: args.outerWindowID,
lineNumber: args.lineNumber,
});
this.loadViewSource(args);
},
/**
* This is the deprecated API for viewSource.xul, for old-timer consumers.
* This API might eventually go away.
*/
_loadViewSourceDeprecated() {
_loadViewSourceDeprecated(aArguments) {
Deprecated.warning("The arguments you're passing to viewSource.xul " +
"are using an out-of-date API.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
@ -336,34 +317,34 @@ let ViewSourceChrome = {
// arg[3] - Line number to go to.
// arg[4] - Whether charset was forced by the user
if (window.arguments[3] == "selection" ||
window.arguments[3] == "mathml") {
if (aArguments[3] == "selection" ||
aArguments[3] == "mathml") {
// viewPartialSource.js will take care of loading the content.
return;
}
if (window.arguments[2]) {
let pageDescriptor = window.arguments[2];
if (aArguments[2]) {
let pageDescriptor = aArguments[2];
if (Cu.isCrossProcessWrapper(pageDescriptor)) {
throw new Error("Cannot pass a CPOW as the page descriptor to viewSource.xul.");
}
}
if (gBrowser.isRemoteBrowser) {
if (this.browser.isRemoteBrowser) {
throw new Error("Deprecated view source API should not use a remote browser.");
}
let forcedCharSet;
if (window.arguments[4] && window.arguments[1].startsWith("charset=")) {
forcedCharSet = window.arguments[1].split("=")[1];
if (aArguments[4] && aArguments[1].startsWith("charset=")) {
forcedCharSet = aArguments[1].split("=")[1];
}
gBrowser.messageManager.sendAsyncMessage("ViewSource:LoadSourceDeprecated", {
URL: window.arguments[0],
lineNumber: window.arguments[3],
this.sendAsyncMessage("ViewSource:LoadSourceDeprecated", {
URL: aArguments[0],
lineNumber: aArguments[3],
forcedCharSet,
}, {
pageDescriptor: window.arguments[2],
pageDescriptor: aArguments[2],
});
},
@ -420,7 +401,7 @@ let ViewSourceChrome = {
this.updateCommands();
}
gBrowser.focus();
this.browser.focus();
},
/**
@ -446,13 +427,14 @@ let ViewSourceChrome = {
// If we don't have history enabled, we have to do a reload in order to
// show the character set change. See bug 136322.
this.mm.sendAsyncMessage("ViewSource:SetCharacterSet", {
this.sendAsyncMessage("ViewSource:SetCharacterSet", {
charset: charset,
doPageLoad: this.historyEnabled,
});
if (this.historyEnabled) {
gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
if (!this.historyEnabled) {
this.browser
.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
}
}
},
@ -583,7 +565,7 @@ let ViewSourceChrome = {
* A URL string to be opened in the view source browser.
*/
loadURL(URL) {
this.mm.sendAsyncMessage("ViewSource:LoadSource", { URL });
this.sendAsyncMessage("ViewSource:LoadSource", { URL });
},
/**
@ -618,47 +600,6 @@ let ViewSourceChrome = {
}
},
/**
* 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
* user either cancels out of the prompt, or enters a valid line number.
*/
promptAndGoToLine() {
let input = { value: this.lastLineFound };
let ok = Services.prompt.prompt(
window,
gViewSourceBundle.getString("goToLineTitle"),
gViewSourceBundle.getString("goToLineText"),
input,
null,
{value:0});
if (!ok)
return;
let line = parseInt(input.value, 10);
if (!(line > 0)) {
Services.prompt.alert(window,
gViewSourceBundle.getString("invalidInputTitle"),
gViewSourceBundle.getString("invalidInputText"));
this.promptAndGoToLine();
} else {
this.goToLine(line);
}
},
/**
* Go to a particular line of the source code. This act is asynchronous.
*
* @param lineNumber
* The line number to try to go to to.
*/
goToLine(lineNumber) {
this.mm.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
},
/**
* Called when the frame script reports that a line was successfully gotten
* to.
@ -667,31 +608,17 @@ let ViewSourceChrome = {
* The line number that we successfully got to.
*/
onGoToLineSuccess(lineNumber) {
// We'll pre-populate the "Go to line" prompt with this value the next
// time it comes up.
this.lastLineFound = lineNumber;
ViewSourceBrowser.prototype.onGoToLineSuccess.call(this, lineNumber);
document.getElementById("statusbar-line-col").label =
gViewSourceBundle.getFormattedString("statusBarLineCol", [lineNumber, 1]);
},
/**
* Called when the frame script reports that we failed to go to a particular
* line. This informs the user that their selection was likely out of range,
* and then reprompts the user to try again.
*/
onGoToLineFailed() {
Services.prompt.alert(window,
gViewSourceBundle.getString("outOfRangeTitle"),
gViewSourceBundle.getString("outOfRangeText"));
this.promptAndGoToLine();
},
/**
* Reloads the browser, bypassing the network cache.
*/
reload() {
gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
this.browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
},
/**
@ -710,7 +637,7 @@ let ViewSourceChrome = {
this.shouldWrap = !this.shouldWrap;
Services.prefs.setBoolPref("view_source.wrap_long_lines",
this.shouldWrap);
this.mm.sendAsyncMessage("ViewSource:ToggleWrapping");
this.sendAsyncMessage("ViewSource:ToggleWrapping");
},
/**
@ -721,11 +648,10 @@ let ViewSourceChrome = {
toggleSyntaxHighlighting() {
this.shouldHighlight = !this.shouldHighlight;
// We can't flip this value in the child, since prefs are read-only there.
// We flip it here, and then cause a reload in the child to make the change
// occur.
// We flip it here, and then toggle a class in the child.
Services.prefs.setBoolPref("view_source.syntax_highlight",
this.shouldHighlight);
this.mm.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
this.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
},
/**
@ -739,22 +665,22 @@ let ViewSourceChrome = {
* nothing.
*/
updateBrowserRemoteness(shouldBeRemote) {
if (gBrowser.isRemoteBrowser == shouldBeRemote) {
if (this.browser.isRemoteBrowser == shouldBeRemote) {
return;
}
let parentNode = gBrowser.parentNode;
let nextSibling = gBrowser.nextSibling;
let parentNode = this.browser.parentNode;
let nextSibling = this.browser.nextSibling;
gBrowser.remove();
this.browser.remove();
if (shouldBeRemote) {
gBrowser.setAttribute("remote", "true");
this.browser.setAttribute("remote", "true");
} else {
gBrowser.removeAttribute("remote");
this.browser.removeAttribute("remote");
}
// If nextSibling was null, this will put the browser at
// the end of the list.
parentNode.insertBefore(gBrowser, nextSibling);
parentNode.insertBefore(this.browser, nextSibling);
if (shouldBeRemote) {
// We're going to send a message down to the remote browser
@ -764,14 +690,14 @@ let ViewSourceChrome = {
// RemoteWebProgress, which is lazily loaded. We only need
// contentWindowAsCPOW for the printing support, and this
// should go away once bug 1146454 is fixed, since we can
// then just pass the outerWindowID of the gBrowser to
// then just pass the outerWindowID of the this.browser to
// PrintUtils.
gBrowser.webProgress;
this.browser.webProgress;
}
},
};
ViewSourceChrome.init();
let viewSourceChrome = new ViewSourceChrome();
/**
* PrintUtils uses this to make Print Preview work.
@ -820,7 +746,7 @@ function getBrowser() {
}
this.__defineGetter__("gPageLoader", function () {
var webnav = ViewSourceChrome.webNav;
var webnav = viewSourceChrome.webNav;
if (!webnav)
return null;
delete this.gPageLoader;
@ -843,48 +769,48 @@ function ViewSourceSavePage()
this.__defineGetter__("gLastLineFound", function () {
Deprecated.warning("gLastLineFound is deprecated - please use " +
"ViewSourceChrome.lastLineFound instead.",
"viewSourceChrome.lastLineFound instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
return ViewSourceChrome.lastLineFound;
return viewSourceChrome.lastLineFound;
});
function onLoadViewSource() {
Deprecated.warning("onLoadViewSource() is deprecated - please use " +
"ViewSourceChrome.onXULLoaded() instead.",
"viewSourceChrome.onXULLoaded() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.onXULLoaded();
viewSourceChrome.onXULLoaded();
}
function isHistoryEnabled() {
Deprecated.warning("isHistoryEnabled() is deprecated - please use " +
"ViewSourceChrome.historyEnabled instead.",
"viewSourceChrome.historyEnabled instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
return ViewSourceChrome.historyEnabled;
return viewSourceChrome.historyEnabled;
}
function ViewSourceClose() {
Deprecated.warning("ViewSourceClose() is deprecated - please use " +
"ViewSourceChrome.close() instead.",
"viewSourceChrome.close() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.close();
viewSourceChrome.close();
}
function ViewSourceReload() {
Deprecated.warning("ViewSourceReload() is deprecated - please use " +
"ViewSourceChrome.reload() instead.",
"viewSourceChrome.reload() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.reload();
viewSourceChrome.reload();
}
function getWebNavigation()
{
Deprecated.warning("getWebNavigation() is deprecated - please use " +
"ViewSourceChrome.webNav instead.",
"viewSourceChrome.webNav instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
// The original implementation returned null if anything threw during
// the getting of the webNavigation.
try {
return ViewSourceChrome.webNav;
return viewSourceChrome.webNav;
} catch (e) {
return null;
}
@ -892,44 +818,44 @@ function getWebNavigation()
function viewSource(url) {
Deprecated.warning("viewSource() is deprecated - please use " +
"ViewSourceChrome.loadURL() instead.",
"viewSourceChrome.loadURL() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.loadURL(url);
viewSourceChrome.loadURL(url);
}
function ViewSourceGoToLine()
{
Deprecated.warning("ViewSourceGoToLine() is deprecated - please use " +
"ViewSourceChrome.promptAndGoToLine() instead.",
"viewSourceChrome.promptAndGoToLine() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.promptAndGoToLine();
viewSourceChrome.promptAndGoToLine();
}
function goToLine(line)
{
Deprecated.warning("goToLine() is deprecated - please use " +
"ViewSourceChrome.goToLine() instead.",
"viewSourceChrome.goToLine() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.goToLine(line);
viewSourceChrome.goToLine(line);
}
function BrowserForward(aEvent) {
Deprecated.warning("BrowserForward() is deprecated - please use " +
"ViewSourceChrome.goForward() instead.",
"viewSourceChrome.goForward() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.goForward();
viewSourceChrome.goForward();
}
function BrowserBack(aEvent) {
Deprecated.warning("BrowserBack() is deprecated - please use " +
"ViewSourceChrome.goBack() instead.",
"viewSourceChrome.goBack() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.goBack();
viewSourceChrome.goBack();
}
function UpdateBackForwardCommands() {
Deprecated.warning("UpdateBackForwardCommands() is deprecated - please use " +
"ViewSourceChrome.updateCommands() instead.",
"viewSourceChrome.updateCommands() instead.",
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
ViewSourceChrome.updateCommands();
viewSourceChrome.updateCommands();
}

View File

@ -4,7 +4,7 @@
# 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/.
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/viewSource.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
@ -21,10 +21,10 @@
<window id="viewSource"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
contenttitlesetting="true"
title="&mainWindow.title;"
titlemodifier="&mainWindow.titlemodifier;"
title="&mainWindow.title;"
titlemodifier="&mainWindow.titlemodifier;"
titlepreface="&mainWindow.preface;"
titlemenuseparator ="&mainWindow.titlemodifierseparator;"
titlemenuseparator ="&mainWindow.titlemodifierseparator;"
windowtype="navigator:view-source"
width="640" height="480"
screenX="10" screenY="10"
@ -35,7 +35,7 @@
<script type="application/javascript" src="chrome://global/content/viewSource.js"/>
<script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<stringbundle id="viewSourceBundle" src="chrome://global/locale/viewSource.properties"/>
<command id="cmd_savePage" oncommand="ViewSourceSavePage();"/>
@ -54,16 +54,16 @@
<command id="cmd_findSelection"
oncommand="document.getElementById('FindToolbar').onFindSelectionCommand();"/>
#endif
<command id="cmd_reload" oncommand="ViewSourceChrome.reload();"/>
<command id="cmd_goToLine" oncommand="ViewSourceChrome.promptAndGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="ViewSourceChrome.toggleSyntaxHighlighting();"/>
<command id="cmd_wrapLongLines" oncommand="ViewSourceChrome.toggleWrapping();"/>
<command id="cmd_reload" oncommand="viewSourceChrome.reload();"/>
<command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
<command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
<command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
<command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
<command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
<command id="Browser:Back" oncommand="ViewSourceChrome.goBack()" observes="viewSourceNavigation"/>
<command id="Browser:Forward" oncommand="ViewSourceChrome.goForward()" observes="viewSourceNavigation"/>
<command id="Browser:Back" oncommand="viewSourceChrome.goBack()" observes="viewSourceNavigation"/>
<command id="Browser:Forward" oncommand="viewSourceChrome.goForward()" observes="viewSourceNavigation"/>
<broadcaster id="viewSourceNavigation"/>
@ -110,7 +110,7 @@
#endif
</keyset>
<tooltip id="aHTMLTooltip" page="true"/>
<menupopup id="viewSourceContextMenu">
@ -131,16 +131,16 @@
<menuitem id="context-copyLink"
label="&copyLinkCmd.label;"
accesskey="&copyLinkCmd.accesskey;"
oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
<menuitem id="context-copyEmail"
label="&copyEmailCmd.label;"
accesskey="&copyEmailCmd.accesskey;"
oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
<menuseparator/>
<menuitem id="cMenu_selectAll"/>
</menupopup>
<!-- Menu -->
<!-- Menu -->
<toolbox id="viewSource-toolbox">
<menubar id="viewSource-main-menubar">
@ -188,11 +188,11 @@
label="&reloadCmd.label;" key="key_reload"/>
<menuseparator />
<menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
<menupopup>
<menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
<menupopup>
<menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
key="key_textZoomEnlarge"/>
<menuitem id="menu_textReduce" command="cmd_textZoomReduce"
<menuitem id="menu_textReduce" command="cmd_textZoomReduce"
label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
key="key_textZoomReduce"/>
<menuseparator/>
@ -206,7 +206,7 @@
<menu id="charsetMenu"
label="&charsetMenu2.label;"
accesskey="&charsetMenu2.accesskey;"
oncommand="ViewSourceChrome.onSetCharacterSet(event);"
oncommand="viewSourceChrome.onSetCharacterSet(event);"
onpopupshowing="CharsetMenu.build(event.target);"
onpopupshown="CharsetMenu.update(event.target, content.document.characterSet);">
<menupopup/>
@ -218,7 +218,7 @@
label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
</menupopup>
</menu>
</menubar>
</menubar>
</toolbox>
<vbox id="appcontent" flex="1">
@ -226,7 +226,7 @@
<browser id="content" type="content-primary" name="content" src="about:blank" flex="1"
context="viewSourceContextMenu" showcaret="true" tooltip="aHTMLTooltip" />
<findbar id="FindToolbar" browserid="content"/>
</vbox>
</vbox>
<statusbar id="status-bar" class="chromeclass-status">
<statusbarpanel id="statusbar-line-col" label="" flex="1"/>

View File

@ -12,6 +12,10 @@
* getDefaultFileName, getNormalizedLeafName and getDefaultExtension
*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
"resource://gre/modules/ViewSourceBrowser.jsm");
var gViewSourceUtils = {
mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
@ -60,6 +64,72 @@ var gViewSourceUtils = {
}
},
/**
* Displays view source in the provided <browser>. This allows for non-window
* display methods, such as a tab from Firefox. The caller that manages
* the <browser> is responsible for ensuring the companion frame script,
* viewSource-content.js, has been loaded for the <browser>.
*
* @param aArgs
* An object with the following properties:
*
* URL (required):
* A string URL for the page we'd like to view the source of.
* viewSourceBrowser (required):
* The browser to display the view source in.
* browser (optional):
* The browser containing the document that we would like to view the
* source of. This is required if outerWindowID is passed.
* outerWindowID (optional):
* The outerWindowID of the content window containing the document that
* we want to view the source of. Pass this if you want to attempt to
* load the document source out of the network cache.
* lineNumber (optional):
* The line number to focus on once the source is loaded.
*/
viewSourceInBrowser: function(aArgs) {
let viewSourceBrowser = new ViewSourceBrowser(aArgs.viewSourceBrowser);
viewSourceBrowser.loadViewSource(aArgs);
},
/**
* Displays view source for a selection from some document in the provided
* <browser>. This allows for non-window display methods, such as a tab from
* Firefox. The caller that manages the <browser> is responsible for ensuring
* the companion frame script, viewSource-content.js, has been loaded for the
* <browser>.
*
* @param aSelection
* A Selection object for the content of interest.
* @param aViewSourceInBrowser
* The browser to display the view source in.
*/
viewSourceFromSelectionInBrowser: function(aSelection, aViewSourceInBrowser) {
let viewSourceBrowser = new ViewSourceBrowser(aViewSourceInBrowser);
viewSourceBrowser.loadViewSourceFromSelection(aSelection);
},
/**
* 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. The caller that manages the <browser> is responsible for
* ensuring the companion frame script, viewSource-content.js, has been loaded
* for the <browser>.
*
* @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);
},
// Opens the interval view source viewer
_openInInternalViewer: function(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber)
{
@ -114,7 +184,7 @@ var gViewSourceUtils = {
lineNumber: aLineNumber};
try {
var editor = this.getExternalViewSourceEditor();
var editor = this.getExternalViewSourceEditor();
if (!editor) {
this.handleCallBack(aCallBack, false, data);
return;
@ -129,7 +199,7 @@ var gViewSourceUtils = {
var path;
var contentType = aDocument ? aDocument.contentType : null;
if (uri.scheme == "file") {
if (uri.scheme == "file") {
// it's a local file; we can open it directly
path = uri.QueryInterface(Components.interfaces.nsIFileURL).file.path;
@ -140,7 +210,7 @@ var gViewSourceUtils = {
// set up the progress listener with what we know so far
this.viewSourceProgressListener.editor = editor;
this.viewSourceProgressListener.callBack = aCallBack;
this.viewSourceProgressListener.data = data;
this.viewSourceProgressListener.data = data;
if (!aPageDescriptor) {
// without a page descriptor, loadPage has no chance of working. download the file.
var file = this.getTemporaryFile(uri, aDocument, contentType);
@ -189,7 +259,7 @@ var gViewSourceUtils = {
var progress = webShell.QueryInterface(this.mnsIWebProgress);
progress.addProgressListener(this.viewSourceProgressListener,
this.mnsIWebProgress.NOTIFY_STATE_DOCUMENT);
var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
pageLoader.loadPage(aPageDescriptor, this.mnsIWebPageDescriptor.DISPLAY_AS_SOURCE);
}
}
@ -296,7 +366,7 @@ var gViewSourceUtils = {
// get a temporary filename using the attributes from the data object that
// openInExternalEditor gave us
this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
this.data.doc.contentType);
// we have to convert from the source charset.
@ -310,7 +380,7 @@ var gViewSourceUtils = {
// write the source to the file
coStream.writeString(webNavigation.document.body.textContent);
// clean up
coStream.close();
foStream.close();

View File

@ -9,4 +9,4 @@ toolkit.jar:
content/global/viewPartialSource.js (content/viewPartialSource.js)
* content/global/viewPartialSource.xul (content/viewPartialSource.xul)
content/global/viewSourceUtils.js (content/viewSourceUtils.js)
content/global/viewSource-content.js (content/viewSource-content.js)
content/global/viewSource-content.js (content/viewSource-content.js)

View File

@ -9,5 +9,9 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
JAR_MANIFESTS += ['jar.mn']
EXTRA_JS_MODULES += [
'ViewSourceBrowser.jsm',
]
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'View Source')

View File

@ -24,7 +24,7 @@ let checkViewSource = Task.async(function* (aWindow) {
is(statusPanel.getAttribute("label"), "", "Correct status bar text");
for (let i = 1; i <= 3; i++) {
aWindow.ViewSourceChrome.goToLine(i);
aWindow.viewSourceChrome.goToLine(i);
let result = yield ContentTask.spawn(aWindow.gBrowser, i, function*(i) {
let selection = content.getSelection();
return (selection.toString() == "line " + i);

View File

@ -53,21 +53,14 @@ let exercisePrefs = Task.async(function* (source, highlightable) {
yield checkStyle(win, "white-space", "pre");
// Check that the Syntax Highlighting menu item works.
let pageShowPromise = BrowserTestUtils.waitForEvent(win.gBrowser, "pageshow");
simulateClick(syntaxMenuItem);
yield pageShowPromise;
is(syntaxMenuItem.hasAttribute("checked"), false, "Syntax menu item unchecked");
is(SpecialPowers.getBoolPref("view_source.syntax_highlight"), false, "Syntax highlighting pref set");
yield checkHighlight(win, false);
pageShowPromise = BrowserTestUtils.waitForEvent(win.gBrowser, "pageshow");
simulateClick(syntaxMenuItem);
yield pageShowPromise;
is(syntaxMenuItem.hasAttribute("checked"), true, "Syntax menu item checked");
is(SpecialPowers.getBoolPref("view_source.syntax_highlight"), true, "Syntax highlighting pref set");
yield checkHighlight(win, highlightable);
yield BrowserTestUtils.closeWindow(win);
@ -127,7 +120,8 @@ let checkHighlight = Task.async(function* (win, expected) {
let highlighted = yield ContentTask.spawn(browser, {}, function* () {
let spans = content.document.getElementsByTagName("span");
return Array.some(spans, (span) => {
return span.className != "";
let style = content.getComputedStyle(span, null);
return style.getPropertyValue("color") !== "rgb(0, 0, 0)";
});
});
is(highlighted, expected, "Syntax highlighting " + (expected ? "on" : "off"));

View File

@ -475,7 +475,7 @@
<xul:image class="popup-notification-icon"
xbl:inherits="popupid,src=icon"/>
</xul:vbox>
<xul:vbox>
<xul:vbox class="popup-notification-body" xbl:inherits="popupid">
<xul:hbox align="start">
<xul:vbox flex="1">
<xul:label class="popup-notification-origin header"

View File

@ -978,7 +978,6 @@ TabActor.prototype = {
});
this._extraActors = null;
this._styleSheetActors.clear();
this._exited = true;
},
@ -1325,6 +1324,10 @@ TabActor.prototype = {
this._popContext();
// Shut down actors that belong to this tab's pool.
for (let sheetActor of this._styleSheetActors.values()) {
this._tabPool.removeActor(sheetActor);
}
this._styleSheetActors.clear();
this.conn.removeActorPool(this._tabPool);
this._tabPool = null;
if (this._tabActorPool) {
@ -1642,12 +1645,6 @@ TabActor.prototype = {
threadActor.global = window;
}
for (let sheetActor of this._styleSheetActors.values()) {
this._tabPool.removeActor(sheetActor);
}
this._styleSheetActors.clear();
// Refresh the debuggee list when a new window object appears (top window or
// iframe).
if (threadActor.attached) {

View File

@ -11,3 +11,7 @@ outOfRangeText = The specified line was not found.
statusBarLineCol = Line %1$S, Col %2$S
viewSelectionSourceTitle = DOM Source of Selection
viewMathMLSourceTitle = DOM Source of MathML
context_goToLine_label = Go to Line…
context_wrapLongLines_label = Wrap Long Lines
context_highlightSyntax_label = Syntax Highlighting

View File

@ -63,8 +63,8 @@ notification[type="critical"] {
/* Popup notification */
.popup-notification-description {
max-width: 24em;
.popup-notification-body {
max-width: 25em;
}
.popup-notification-origin:not([value]),

View File

@ -101,8 +101,8 @@ notification[type="info"]:not([value="translation"]) .close-icon:not(:hover) {
/* Popup notification */
.popup-notification-description {
max-width: 24em;
.popup-notification-body {
max-width: 25em;
}
.popup-notification-origin:not([value]),

View File

@ -58,8 +58,8 @@ notification[type="critical"] {
/* Popup notification */
.popup-notification-description {
max-width: 24em;
.popup-notification-body {
max-width: 25em;
}
.popup-notification-origin:not([value]),