Revision control

Copy as Markdown

Other Tools

/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
});
//******** define a js object to implement nsITreeView
function pageInfoTreeView(treeid, copycol)
{
/* copycol is the index number for the column that we want to add to
* the copy-n-paste buffer when the user hits accel-c.
*/
this.treeid = treeid;
this.copycol = copycol;
this.rows = 0;
this.tree = null;
this.data = [ ];
this.selection = null;
this.sortcol = -1;
this.sortdir = false;
}
pageInfoTreeView.prototype = {
get rowCount() { return this.rows; },
setTree: function(tree)
{
this.tree = tree;
},
getCellText: function(row, column)
{
// row can be null, but js arrays are 0-indexed.
return this.data[row][column.index] || "";
},
setCellValue: function(row, column, value)
{
},
setCellText: function(row, column, value)
{
this.data[row][column.index] = value;
},
addRow: function(row)
{
this.rows = this.data.push(row);
this.rowCountChanged(this.rows - 1, 1);
if (this.selection.count == 0 && this.rowCount && !gImageElement) {
this.selection.select(0);
}
},
addRows: function(rows)
{
for (let row of rows) {
this.addRow(row);
}
},
rowCountChanged: function(index, count)
{
this.tree.rowCountChanged(index, count);
},
invalidate: function()
{
this.tree.invalidate();
},
clear: function()
{
if (this.tree)
this.tree.rowCountChanged(0, -this.rows);
this.rows = 0;
this.data = [];
},
cycleHeader: function cycleHeader(col)
{
this.doSort(col, col.index);
},
doSort: function doSort(col, index, comparator)
{
var tree = document.getElementById(this.treeid);
if (!comparator) {
comparator = function comparator(a, b) {
return (a || "").toLowerCase().localeCompare((b || "").toLowerCase());
};
}
this.sortdir = gTreeUtils.sort(tree, this, this.data, index,
comparator, this.sortcol, this.sortdir);
Array.from(this.tree.columns).forEach(function(treecol) {
treecol.element.removeAttribute("sortActive");
treecol.element.removeAttribute("sortDirection");
});
col.element.setAttribute("sortActive", true);
col.element.setAttribute("sortDirection", this.sortdir ?
"ascending" : "descending");
this.sortcol = index;
},
getRowProperties: function(row) { return ""; },
getCellProperties: function(row, column) { return ""; },
getColumnProperties: function(column) { return ""; },
isContainer: function(index) { return false; },
isContainerOpen: function(index) { return false; },
isSeparator: function(index) { return false; },
isSorted: function() { return this.sortcol > -1 },
canDrop: function(index, orientation) { return false; },
drop: function(row, orientation) { return false; },
getParentIndex: function(index) { return -1; },
hasNextSibling: function(index, after) { return false; },
getLevel: function(index) { return 0; },
getImageSrc: function(row, column) { },
getProgressMode: function(row, column) { },
getCellValue: function(row, column) {
let col = (column != null) ? column : this.copycol;
return (row < 0 || col < 0) ? "" : (this.data[row][col] || "");
},
toggleOpenState: function(index) { },
selectionChanged: function() { },
cycleCell: function(row, column) { },
isEditable: function(row, column) { return false; },
isSelectable: function(row, column) { return false; },
};
// mmm, yummy. global variables.
var gDocInfo = null;
var gImageElement = null;
// column number to help using the data array
const COL_IMAGE_ADDRESS = 0;
const COL_IMAGE_TYPE = 1;
const COL_IMAGE_SIZE = 2;
const COL_IMAGE_ALT = 3;
const COL_IMAGE_COUNT = 4;
const COL_IMAGE_NODE = 5;
const COL_IMAGE_BG = 6;
const COL_IMAGE_SIZENUM = 7;
const COL_IMAGE_PERSIST = 8;
const COL_IMAGE_MIME = 9;
// column number to copy from, second argument to pageInfoTreeView's constructor
const COPYCOL_NONE = -1;
const COPYCOL_META_CONTENT = 1;
const COPYCOL_FORM_ACTION = 2;
const COPYCOL_FIELD_VALUE = 3;
const COPYCOL_LINK_ADDRESS = 1;
const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
// one nsITreeView for each tree in the window
var gMetaView = new pageInfoTreeView("metatree", COPYCOL_META_CONTENT);
var gFormView = new pageInfoTreeView("formtree", COPYCOL_FORM_ACTION);
var gFieldView = new pageInfoTreeView("formpreview", COPYCOL_FIELD_VALUE);
var gLinkView = new pageInfoTreeView("linktree", COPYCOL_LINK_ADDRESS);
var gImageView = new pageInfoTreeView("imagetree", COPYCOL_IMAGE);
gImageView.getCellProperties = function(row, col) {
var data = gImageView.data[row];
var item = gImageView.data[row][COL_IMAGE_NODE];
var properties = col.id == "image-address" ? "ltr" : "";
if (!checkProtocol(data) || item.HTMLEmbedElement ||
(item.HTMLObjectElement && !item.type.startsWith("image/")))
properties += " broken";
return properties;
};
gFormView.getCellProperties = function(row, col) {
return col.id == "form-action" ? "ltr" : "";
};
gLinkView.getCellProperties = function(row, col) {
return col.id == "link-address" ? "ltr" : "";
};
gImageView.cycleHeader = function(col)
{
var index = col.index;
var comparator;
switch (col.index) {
case COL_IMAGE_SIZE:
index = COL_IMAGE_SIZENUM;
case COL_IMAGE_COUNT:
comparator = function numComparator(a, b) { return a - b; };
break;
}
this.doSort(col, index, comparator);
};
var gImageHash = { };
// localized strings (will be filled in when the document is loaded)
// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
var gStrings = { };
var gBundle;
const DRAGSERVICE_CONTRACTID = "@mozilla.org/widget/dragservice;1";
const TRANSFERABLE_CONTRACTID = "@mozilla.org/widget/transferable;1";
const STRING_CONTRACTID = "@mozilla.org/supports-string;1";
var loadContextInfo = Services.loadContextInfo.fromLoadContext(
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext), false);
var diskStorage = Services.cache2.diskCacheStorage(loadContextInfo, false);
const nsICertificateDialogs = Ci.nsICertificateDialogs;
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
/* Overlays register functions here.
* These arrays are used to hold callbacks that Page Info will call at
* various stages. Use them by simply appending a function to them.
* For example, add a function to onLoadRegistry by invoking
* "onLoadRegistry.push(XXXLoadFunc);"
* The XXXLoadFunc should be unique to the overlay module, and will be
* invoked as "XXXLoadFunc();"
*/
// These functions are called to build the data displayed in the Page
// Info window.
var onLoadRegistry = [ ];
// These functions are called to remove old data still displayed in
// the window when the document whose information is displayed
// changes. For example, the list of images in the Media tab
// is cleared.
var onResetRegistry = [ ];
// These functions are called once when all the elements in all of the target
// document (and all of its subframes, if any) have been processed
var onFinished = [ ];
// These functions are called once when the Page Info window is closed.
var onUnloadRegistry = [ ];
/* Called when PageInfo window is loaded. Arguments are:
* window.arguments[0] - (optional) an object consisting of
* - doc: (optional) document to use for source. if not provided,
* the calling window's document will be used
* - initialTab: (optional) id of the inital tab to display
*/
function onLoadPageInfo()
{
gBundle = document.getElementById("pageinfobundle");
var strNames = ["unknown", "notSet", "mediaImg", "mediaBGImg",
"mediaBorderImg", "mediaListImg", "mediaCursor",
"mediaObject", "mediaEmbed", "mediaLink", "mediaInput",
"mediaVideo", "mediaAudio",
"formTitle", "formUntitled", "formDefaultTarget",
"formChecked", "formUnchecked", "formPassword", "linkAnchor",
"linkArea", "linkSubmission", "linkSubmit", "linkRel",
"linkStylesheet", "linkRev", "linkX", "linkScript",
"linkScriptInline", "yes"];
strNames.forEach(function(n) { gStrings[n] = gBundle.getString(n); });
var args = "arguments" in window &&
window.arguments.length >= 1 &&
window.arguments[0];
// init views
function initView(treeid, view)
{
document.getElementById(treeid).view = view;
}
initView("imagetree", gImageView);
initView("formtree", gFormView);
initView("formpreview", gFieldView);
initView("linktree", gLinkView);
initPermission();
/* Select the requested tab, if the name is specified */
loadTab(args);
Services.obs.notifyObservers(window, "page-info-dialog-loaded");
}
function loadPageInfo(frameOuterWindowID, imageElement, browser)
{
browser = browser || window.opener.gBrowser.selectedBrowser;
let mm = browser.messageManager;
gStrings["application/rss+xml"] = gBundle.getString("feedRss");
gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
gStrings["text/xml"] = gBundle.getString("feedXML");
gStrings["application/xml"] = gBundle.getString("feedXML");
gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
// Look for pageInfoListener in content.js.
// Sends message to listener with arguments.
mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
frameOuterWindowID: frameOuterWindowID},
{ imageElement });
let pageInfoData;
// Get initial pageInfoData needed to display the general, feeds, permission
// and security tabs.
mm.addMessageListener("PageInfo:data", function onmessage(message) {
mm.removeMessageListener("PageInfo:data", onmessage);
pageInfoData = message.data;
let docInfo = pageInfoData.docInfo;
let windowInfo = pageInfoData.windowInfo;
let uri = makeURI(docInfo.documentURIObject.spec);
let principal = docInfo.principal;
gDocInfo = docInfo;
gImageElement = pageInfoData.imageInfo;
var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
: "pageInfo.frame.title";
document.title = gBundle.getFormattedString(titleFormat,
[docInfo.location]);
document.getElementById("main-window").setAttribute("relatedUrl",
docInfo.location);
makeGeneralTab(pageInfoData.metaViewRows, docInfo);
initFeedTab(pageInfoData.feeds);
onLoadPermission(uri, principal);
securityOnLoad(uri, windowInfo);
});
// Get the media elements from content script to setup the media tab.
mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
// Page info window was closed.
if (window.closed) {
mm.removeMessageListener("PageInfo:mediaData", onmessage);
return;
}
// The page info media fetching has been completed.
if (message.data.isComplete) {
mm.removeMessageListener("PageInfo:mediaData", onmessage);
onFinished.forEach(function(func) { func(pageInfoData); });
return;
}
if (message.data.imageItems) {
for (let item of message.data.imageItems) {
addImage(item);
}
selectImage();
}
if (message.data.linkItems) {
gLinkView.addRows(message.data.linkItems);
}
if (message.data.formItems) {
gFormView.addRows(message.data.formItems);
}
});
/* Call registered overlay init functions */
onLoadRegistry.forEach(function(func) { func(); });
}
function resetPageInfo(args)
{
/* Reset Media tab */
// Remove the observer, only if there is at least 1 image.
if (gImageView.data.length != 0) {
Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
}
/* Reset tree views */
gMetaView.clear();
gFormView.clear();
gFieldView.clear();
gLinkView.clear();
gImageView.clear();
gImageHash = {};
/* Reset Feeds Tab */
var feedListbox = document.getElementById("feedListbox");
while (feedListbox.hasChildNodes())
feedListbox.lastChild.remove();
/* Call registered overlay reset functions */
onResetRegistry.forEach(function(func) { func(); });
/* Rebuild the data */
loadTab(args);
Services.obs.notifyObservers(window, "page-info-dialog-reset");
}
function onUnloadPageInfo()
{
// Remove the observer, only if there is at least 1 image.
if (gImageView.data.length != 0) {
Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
}
/* Call registered overlay unload functions */
onUnloadRegistry.forEach(function(func) { func(); });
}
function doHelpButton()
{
const helpTopics = {
"generalTab": "pageinfo_general",
"mediaTab": "pageinfo_media",
// "feedTab": "pageinfo_feed",
// "permTab": "pageinfo_permissions",
"formsTab": "pageinfo_forms",
"linksTab": "pageinfo_links",
"securityTab": "pageinfo_security"
};
var tabbox = document.getElementById("tabbox");
var helpdoc = helpTopics[tabbox.selectedTab.id] || "nav-page-info";
}
function showTab(id)
{
var tabbox = document.getElementById("tabbox");
var selectedTab = document.getElementById(id) ||
document.getElementById(id + "Tab") || // Firefox compatibility sillyness
document.getElementById("generalTab");
tabbox.selectedTab = selectedTab;
selectedTab.focus();
}
function loadTab(args)
{
// If the "View Image Info" context menu item was used, the related image
// element is provided as an argument. This can't be a background image.
let imageElement = args && args.imageElement;
let frameOuterWindowID = args && args.frameOuterWindowID;
let browser = args && args.browser;
/* Load the page info */
loadPageInfo(frameOuterWindowID, imageElement, browser);
/* Select the requested tab, if the name is specified */
var initialTab = (args && args.initialTab) || "generalTab";
showTab(initialTab);
}
function onClickMore()
{
showTab("securityTab");
}
function openCacheEntry(key, cb)
{
var checkCacheListener = {
onCacheEntryCheck: function(entry, appCache) {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable: function(entry, isNew, appCache, status) {
cb(entry);
}
};
diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "",
Ci.nsICacheStorage.OPEN_READONLY,
checkCacheListener);
}
function makeGeneralTab(metaViewRows, docInfo)
{
var title = (docInfo.title) ? docInfo.title : gBundle.getString("noPageTitle");
document.getElementById("titletext").value = title;
var url = docInfo.location.toString();
setItemValue("urltext", url);
var referrer = ("referrer" in docInfo && docInfo.referrer);
setItemValue("refertext", referrer);
var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
document.getElementById("modetext").value = gBundle.getString(mode);
// find out the mime type
var mimeType = docInfo.contentType;
setItemValue("typetext", mimeType);
// get the document characterset
var encoding = docInfo.characterSet;
document.getElementById("encodingtext").value = encoding;
var length = metaViewRows.length;
var metaGroup = document.getElementById("metaTags");
if (!length) {
metaGroup.collapsed = true;
}
else {
var metaTagsCaption = document.getElementById("metaTagsCaption");
if (length == 1)
metaTagsCaption.label = gBundle.getString("generalMetaTag");
else
metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
var metaTree = document.getElementById("metatree");
metaTree.view = gMetaView;
// Add the metaViewRows onto the general tab's meta info tree.
gMetaView.addRows(metaViewRows);
metaGroup.collapsed = false;
}
// get the date of last modification
var modifiedText = formatDate(docInfo.lastModified, gStrings.notSet);
document.getElementById("modifiedtext").value = modifiedText;
// get cache info
var cacheKey = url.replace(/#.*$/, "");
openCacheEntry(cacheKey, function(cacheEntry) {
var sizeText;
if (cacheEntry) {
var pageSize = cacheEntry.dataSize;
var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
}
setItemValue("sizetext", sizeText);
});
}
function ensureSelection(view)
{
// only select something if nothing is currently selected
// and if there's anything to select
if (view.selection && view.selection.count == 0 && view.rowCount)
view.selection.select(0);
}
function addImage(imageViewRow)
{
let [url, type, alt, elem, isBg] = imageViewRow;
if (!url)
return;
if (!gImageHash.hasOwnProperty(url))
gImageHash[url] = { };
if (!gImageHash[url].hasOwnProperty(type))
gImageHash[url][type] = { };
if (!gImageHash[url][type].hasOwnProperty(alt)) {
gImageHash[url][type][alt] = gImageView.data.length;
var row = [url, type, gStrings.unknown, alt, 1, elem, isBg, -1, null, null];
gImageView.addRow(row);
// Fill in cache data asynchronously
openCacheEntry(url, function(cacheEntry) {
if (cacheEntry) {
// Update the corresponding data entries from the cache.
var imageSize = cacheEntry.dataSize;
// If it is not -1 then replace with actual value, else keep as unknown.
if (imageSize && imageSize != -1) {
var kbSize = Math.round(imageSize / 1024 * 100) / 100;
row[2] = gBundle.getFormattedString("mediaFileSize",
[formatNumber(kbSize)]);
row[7] = imageSize;
}
row[8] = cacheEntry.persistent;
row[9] = getContentTypeFromHeaders(cacheEntry);
// Invalidate the row to trigger a repaint.
gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
}
});
// Add the observer, only once.
if (gImageView.data.length == 1) {
Services.obs.addObserver(imagePermissionObserver, "perm-changed");
}
}
else {
var i = gImageHash[url][type][alt];
gImageView.data[i][COL_IMAGE_COUNT]++;
// The same image can occur several times on the page at different sizes.
// If the "View Image Info" context menu item was used, ensure we select
// the correct element.
if (!gImageView.data[i][COL_IMAGE_BG] &&
gImageElement && url == gImageElement.currentSrc &&
gImageElement.width == elem.width &&
gImageElement.height == elem.height &&
gImageElement.imageText == elem.imageText) {
gImageView.data[i][COL_IMAGE_NODE] = elem;
}
}
}
//******** Form Stuff
function onFormSelect()
{
if (gFormView.selection.count == 1)
{
var formPreview = document.getElementById("formpreview");
gFieldView.clear();
formPreview.view = gFieldView;
var clickedRow = gFormView.selection.currentIndex;
// form-node;
var form = gFormView.data[clickedRow][3];
var ft = null;
if (form.name)
ft = gBundle.getFormattedString("formTitle", [form.name]);
setItemValue("formenctype", form.encoding, gStrings.default);
setItemValue("formtarget", form.target, gStrings.formDefaultTarget);
document.getElementById("formname").value = ft || gStrings.formUntitled;
gFieldView.addRows(form.formfields);
}
}
//******** Link Stuff
function onBeginLinkDrag(event,urlField,descField)
{
if (event.originalTarget.localName != "treechildren")
return;
var tree = event.target;
if (!("treeBoxObject" in tree))
tree = tree.parentNode;
var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
if (row == -1)
return;
// Adding URL flavor
var col = tree.columns[urlField];
var url = tree.view.getCellText(row, col);
col = tree.columns[descField];
var desc = tree.view.getCellText(row, col);
var dataTransfer = event.dataTransfer;
dataTransfer.setData("text/x-moz-url", url + "\n" + desc);
dataTransfer.setData("text/url-list", url);
dataTransfer.setData("text/plain", url);
}
//******** Image Stuff
function getSelectedRows(tree) {
var start = { };
var end = { };
var numRanges = tree.view.selection.getRangeCount();
var rowArray = [ ];
for (var t = 0; t < numRanges; t++) {
tree.view.selection.getRangeAt(t, start, end);
for (var v = start.value; v <= end.value; v++)
rowArray.push(v);
}
return rowArray;
}
function getSelectedRow(tree) {
var rows = getSelectedRows(tree);
return (rows.length == 1) ? rows[0] : -1;
}
function selectSaveFolder(aCallback) {
return selectSaveFolderTask(aCallback).catch(Cu.reportError);
}
async function selectSaveFolderTask(aCallback) {
let titleText = gBundle.getString("mediaSelectFolder");
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, titleText, Ci.nsIFilePicker.modeGetFolder);
fp.appendFilters(Ci.nsIFilePicker.filterAll);
try {
let initialDir = Services.prefs.getComplexValue("browser.download.dir",
Ci.nsIFile);
if (!initialDir) {
let downloadsDir = await Downloads.getSystemDownloadsDirectory();
initialDir = new FileUtils.File(downloadsDir);
}
fp.displayDirectory = initialDir;
} catch (ex) {
}
let result = await new Promise(resolve => fp.open(resolve));
if (result == Ci.nsIFilePicker.returnOK) {
aCallback(fp.file.QueryInterface(Ci.nsIFile));
} else {
aCallback(null);
}
}
function saveMedia()
{
var tree = document.getElementById("imagetree");
var rowArray = getSelectedRows(tree);
if (rowArray.length == 1) {
let row = rowArray[0];
let item = gImageView.data[row][COL_IMAGE_NODE];
let url = gImageView.data[row][COL_IMAGE_ADDRESS];
if (url) {
let titleKey = "SaveImageTitle";
if (item instanceof HTMLVideoElement)
titleKey = "SaveVideoTitle";
else if (item instanceof HTMLAudioElement)
titleKey = "SaveAudioTitle";
saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
null, gDocInfo.isContentWindowPrivate,
gDocument.nodePrincipal);
}
} else {
selectSaveFolder(function(aDirectory) {
if (aDirectory) {
var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
uniqueFile(aChosenData.file);
internalSave(aURIString, null, null, null, null, false,
"SaveImageTitle", aChosenData, aBaseURI, null, false,
null, gDocInfo.isContentWindowPrivate,
gDocument.nodePrincipal);
};
for (var i = 0; i < rowArray.length; i++) {
let v = rowArray[i];
let dir = aDirectory.clone();
let item = gImageView.data[v][COL_IMAGE_NODE];
let uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
let uri = makeURI(uriString);
try {
uri.QueryInterface(Ci.nsIURL);
dir.append(decodeURIComponent(uri.fileName));
} catch (ex) {
// data:/blob: uris
// Supply a dummy filename, otherwise Download Manager
// will try to delete the base directory on failure.
dir.append(gImageView.data[v][COL_IMAGE_TYPE]);
}
if (i == 0) {
saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
} else {
// This delay is a hack which prevents the download manager
// from opening many times. See bug 377339.
setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
makeURI(item.baseURI));
}
}
}
});
}
}
function onBlockImage(aChecked)
{
var uri = makeURI(document.getElementById("imageurltext").value);
if (aChecked)
Services.perms.add(uri, "image", Services.perms.DENY_ACTION);
else
Services.perms.remove(uri, "image");
}
function onImageSelect()
{
var previewBox = document.getElementById("mediaPreviewBox");
var mediaSaveBox = document.getElementById("mediaSaveBox");
var mediaSaveButton = document.getElementById("imagesaveasbutton");
var splitter = document.getElementById("mediaSplitter");
var tree = document.getElementById("imagetree");
var count = tree.view.selection.count;
if (count == 0)
{
previewBox.collapsed = true;
mediaSaveBox.collapsed = true;
mediaSaveButton.disabled = true;
splitter.collapsed = true;
tree.flex = 1;
}
else if (count > 1)
{
previewBox.collapsed = true;
mediaSaveBox.collapsed = false;
mediaSaveButton.disabled = false;
splitter.collapsed = true;
tree.flex = 1;
}
else
{
previewBox.collapsed = false;
mediaSaveBox.collapsed = true;
mediaSaveButton.disabled = false;
splitter.collapsed = false;
tree.flex = 0;
makePreview(tree.view.selection.currentIndex);
}
}
// Makes the media preview (image, video, etc) for the selected row on
// the media tab.
function makePreview(row)
{
var [url, type, sizeText, alt, count, item, isBG, imageSize, persistent, cachedType] = gImageView.data[row];
var isAudio = false;
setItemValue("imageurltext", url);
setItemValue("imagetext", item.imageText);
setItemValue("imagelongdesctext", item.longDesc);
// get cache info
var sourceText;
switch (persistent) {
case true:
sourceText = gBundle.getString("generalDiskCache");
break;
case false:
sourceText = gBundle.getString("generalMemoryCache");
break;
default:
sourceText = gBundle.getString("generalNotCached");
break;
}
setItemValue("imagesourcetext", sourceText);
// find out the file size
var sizeText;
if (imageSize && imageSize != -1) {
var kbSize = Math.round(imageSize / 1024 * 100) / 100;
sizeText = gBundle.getFormattedString("generalSize",
[formatNumber(kbSize),
formatNumber(imageSize)]);
}
else
sizeText = gBundle.getString("mediaUnknownNotCached");
setItemValue("imagesizetext", sizeText);
var mimeType = item.mimeType || cachedType;
var numFrames = item.numFrames;
var imageType;
if (mimeType) {
// We found the type, try to display it nicely
let imageMimeType = /^image\/(.*)/i.exec(mimeType);
if (imageMimeType) {
imageType = imageMimeType[1].toUpperCase();
if (numFrames > 1)
imageType = gBundle.getFormattedString("mediaAnimatedImageType",
[imageType, numFrames]);
else
imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
}
else {
// the MIME type doesn't begin with image/, display the raw type
imageType = mimeType;
}
}
else {
// We couldn't find the type, fall back to the value in the treeview
imageType = type;
}
setItemValue("imagetypetext", imageType);
var imageContainer = document.getElementById("theimagecontainer");
var oldImage = document.getElementById("thepreviewimage");
var isProtocolAllowed = checkProtocol(gImageView.data[row]);
var isImageType = mimeType && mimeType.startsWith("image/");
var newImage = new Image;
newImage.id = "thepreviewimage";
var physWidth = 0, physHeight = 0;
var width = 0, height = 0;
if ((item.HTMLLinkElement || item.HTMLInputElement ||
item.HTMLImageElement || item.SVGImageElement ||
(item.HTMLObjectElement && isImageType) ||
(item.HTMLEmbedElement && isImageType) ||
isBG) && isProtocolAllowed) {
// We need to wait for the image to finish loading before
// using width & height.
newImage.addEventListener("loadend", function() {
physWidth = newImage.width || 0;
physHeight = newImage.height || 0;
// "width" and "height" attributes must be set to newImage,
// even if there is no "width" or "height attribute in item;
// otherwise, the preview image cannot be displayed correctly.
// Since the image might have been loaded out-of-process, we expect
// the item to tell us its width / height dimensions. Failing that
// the item should tell us the natural dimensions of the image. Finally
// failing that, we'll assume that the image was never loaded in the
// other process (this can be true for favicons, for example), and so
// we'll assume that we can use the natural dimensions of the newImage
// we just created. If the natural dimensions of newImage are not known
// then the image is probably broken.
if (!isBG) {
newImage.width = ("width" in item && item.width) ||
newImage.naturalWidth;
newImage.height = ("height" in item && item.height) ||
newImage.naturalHeight;
}
else {
// The width and height of an HTML tag should not be used for its
// background image (for example, "table" can have "width" or "height"
// attributes).
newImage.width = item.naturalWidth || newImage.naturalWidth;
newImage.height = item.naturalHeight || newImage.naturalHeight;
}
if (item.SVGImageElement) {
newImage.width = item.SVGImageElementWidth;
newImage.height = item.SVGImageElementHeight;
}
width = newImage.width;
height = newImage.height;
document.getElementById("theimagecontainer").collapsed = false
document.getElementById("brokenimagecontainer").collapsed = true;
let imageSize = "";
if (url) {
if (width != physWidth || height != physHeight) {
imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
[formatNumber(physWidth),
formatNumber(physHeight),
formatNumber(width),
formatNumber(height)]);
} else {
imageSize = gBundle.getFormattedString("mediaDimensions",
[formatNumber(width),
formatNumber(height)]);
}
}
setItemValue("imagedimensiontext", imageSize);
}, {once: true});
newImage.setAttribute("src", url);
}
else {
// Handle the case where newImage is not used for width & height.
if (item.HTMLVideoElement && isProtocolAllowed) {
newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
newImage.id = "thepreviewimage";
newImage.src = url;
newImage.controls = true;
width = physWidth = item.videoWidth;
height = physHeight = item.videoHeight;
document.getElementById("theimagecontainer").collapsed = false
document.getElementById("brokenimagecontainer").collapsed = true;
}
else if (item.HTMLAudioElement && isProtocolAllowed) {
newImage = new Audio;
newImage.id = "thepreviewimage";
newImage.src = url;
newImage.controls = true;
newImage.preload = "metadata";
isAudio = true;
document.getElementById("theimagecontainer").collapsed = false
document.getElementById("brokenimagecontainer").collapsed = true;
}
else {
// fallback image for protocols not allowed (e.g., javascript:)
// or elements not [yet] handled (e.g., object, embed).
document.getElementById("brokenimagecontainer").collapsed = false;
document.getElementById("theimagecontainer").collapsed = true;
}
let imageSize = "";
if (url && !isAudio) {
imageSize = gBundle.getFormattedString("mediaDimensions",
[formatNumber(width),
formatNumber(height)]);
}
setItemValue("imagedimensiontext", imageSize);
}
makeBlockImage(url);
oldImage.remove();
imageContainer.appendChild(newImage);
}
function makeBlockImage(url)
{
var checkbox = document.getElementById("blockImage");
var imagePref = Services.prefs.getIntPref("permissions.default.image");
if (!(/^https?:/.test(url)) || imagePref == 2)
// We can't block the images from this host because either is is not
// for http(s) or we don't load images at all
checkbox.hidden = true;
else {
var uri = makeURI(url);
if (uri.host) {
checkbox.hidden = false;
checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
var perm = Services.perms.testPermission(uri, "image");
checkbox.checked = perm == Services.perms.DENY_ACTION;
}
else
checkbox.hidden = true;
}
}
var imagePermissionObserver = {
observe: function (aSubject, aTopic, aData)
{
if (document.getElementById("mediaPreviewBox").collapsed)
return;
if (aTopic == "perm-changed") {
var permission = aSubject.QueryInterface(Ci.nsIPermission);
if (permission.type == "image") {
var imageTree = document.getElementById("imagetree");
var row = imageTree.currentIndex;
var item = gImageView.data[row][COL_IMAGE_NODE];
var url = gImageView.data[row][COL_IMAGE_ADDRESS];
if (permission.matchesURI(makeURI(url), true))
makeBlockImage(url);
}
}
}
}
function getContentTypeFromHeaders(cacheEntryDescriptor)
{
if (!cacheEntryDescriptor)
return null;
let headers = cacheEntryDescriptor.getMetaDataElement("response-head");
let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/mi.exec(headers);
return type && type[1];
}
function setItemValue(id, value, defaultString = gStrings.notSet)
{
var item = document.getElementById(id);
if (value) {
item.disabled = false;
item.value = value;
}
else
{
item.value = defaultString;
item.disabled = true;
}
}
function formatNumber(number)
{
return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
}
function formatDate(datestr, unknown)
{
var date = new Date(datestr);
if (!date.valueOf())
return unknown;
const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
dateStyle: "full", timeStyle: "long"});
return dateTimeFormatter.format(date);
}
function getSelectedItems(linksMode)
{
// linksMode is a boolean that is used to determine
// whether the getSelectedItems() function needs to
// run with urlSecurityCheck() or not.
var elem = document.commandDispatcher.focusedElement;
var view = elem.view;
var selection = view.selection;
var text = [], tmp = '';
var min = {}, max = {};
var count = selection.getRangeCount();
for (var i = 0; i < count; i++) {
selection.getRangeAt(i, min, max);
for (var row = min.value; row <= max.value; row++) {
tmp = view.getCellValue(row, null);
if (tmp)
{
try {
if (linksMode)
urlSecurityCheck(tmp, gDocInfo.principal);
text.push(tmp);
}
catch (e) {
}
}
}
}
return text;
}
function doCopy(isLinkMode)
{
var text = getSelectedItems(isLinkMode);
Cc["@mozilla.org/widget/clipboardhelper;1"]
.getService(Ci.nsIClipboardHelper)
.copyString(text.join("\n"));
}
function doSelectAllMedia()
{
var tree = document.getElementById("imagetree");
if (tree)
tree.view.selection.selectAll();
}
function doSelectAll()
{
var elem = document.commandDispatcher.focusedElement;
if (elem && "treeBoxObject" in elem)
elem.view.selection.selectAll();
}
function selectImage() {
if (!gImageElement)
return;
var tree = document.getElementById("imagetree");
for (var i = 0; i < tree.view.rowCount; i++) {
// If the image row element is the image selected from
// the "View Image Info" context menu item.
let image = gImageView.data[i][COL_IMAGE_NODE];
if (!gImageView.data[i][COL_IMAGE_BG] &&
gImageElement.currentSrc == gImageView.data[i][COL_IMAGE_ADDRESS] &&
gImageElement.width == image.width &&
gImageElement.height == image.height &&
gImageElement.imageText == image.imageText) {
tree.view.selection.select(i);
tree.treeBoxObject.ensureRowIsVisible(i);
tree.focus();
return;
}
}
}
function checkProtocol(img)
{
var url = img[COL_IMAGE_ADDRESS];
return /^data:image\//i.test(url) ||
/^(https?|ftp|file|about|chrome|resource):/.test(url);
}
function onOpenIn(mode)
{
var linkList = getSelectedItems(true);
if (linkList.length)
openUILinkArrayIn(linkList, mode);
}