Mypal68/browser/base/content/pageinfo/pageInfo.js
2024-02-16 13:22:56 +02:00

1225 lines
36 KiB
JavaScript

/* 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/. */
/* import-globals-from ../../../../toolkit/content/globalOverlay.js */
/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
/* import-globals-from ../../../../toolkit/content/treeUtils.js */
/* import-globals-from ../utilityOverlay.js */
/* import-globals-from permissions.js */
/* import-globals-from security.js */
XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
});
// 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 = {
set rowCount(c) {
throw new Error("rowCount is a readonly property");
},
get rowCount() {
return this.rows;
},
setTree(tree) {
this.tree = tree;
},
getCellText(row, column) {
// row can be null, but js arrays are 0-indexed.
// colidx cannot be null, but can be larger than the number
// of columns in the array. In this case it's the fault of
// whoever typoed while calling this function.
return this.data[row][column.index] || "";
},
setCellValue(row, column, value) {},
setCellText(row, column, value) {
this.data[row][column.index] = value;
},
addRow(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(rows) {
for (let row of rows) {
this.addRow(row);
}
},
rowCountChanged(index, count) {
this.tree.rowCountChanged(index, count);
},
invalidate() {
this.tree.invalidate();
},
clear() {
if (this.tree) {
this.tree.rowCountChanged(0, -this.rows);
}
this.rows = 0;
this.data = [];
},
onPageMediaSort(columnname) {
var tree = document.getElementById(this.treeid);
var treecol = tree.columns.getNamedColumn(columnname);
this.sortdir = gTreeUtils.sort(
tree,
this,
this.data,
treecol.index,
function textComparator(a, b) {
return (a || "").toLowerCase().localeCompare((b || "").toLowerCase());
},
this.sortcol,
this.sortdir
);
for (let col of tree.columns) {
col.element.removeAttribute("sortActive");
col.element.removeAttribute("sortDirection");
}
treecol.element.setAttribute("sortActive", "true");
treecol.element.setAttribute(
"sortDirection",
this.sortdir ? "ascending" : "descending"
);
this.sortcol = treecol.index;
},
getRowProperties(row) {
return "";
},
getCellProperties(row, column) {
return "";
},
getColumnProperties(column) {
return "";
},
isContainer(index) {
return false;
},
isContainerOpen(index) {
return false;
},
isSeparator(index) {
return false;
},
isSorted() {
return this.sortcol > -1;
},
canDrop(index, orientation) {
return false;
},
drop(row, orientation) {
return false;
},
getParentIndex(index) {
return 0;
},
hasNextSibling(index, after) {
return false;
},
getLevel(index) {
return 0;
},
getImageSrc(row, column) {},
getCellValue(row, column) {},
toggleOpenState(index) {},
cycleHeader(col) {},
selectionChanged() {},
cycleCell(row, column) {},
isEditable(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;
// column number to copy from, second argument to pageInfoTreeView's constructor
const COPYCOL_NONE = -1;
const COPYCOL_META_CONTENT = 1;
const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
// one nsITreeView for each tree in the window
var gMetaView = new pageInfoTreeView("metatree", COPYCOL_META_CONTENT);
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 props = "";
if (
!checkProtocol(data) ||
item instanceof HTMLEmbedElement ||
(item instanceof HTMLObjectElement && !item.type.startsWith("image/"))
) {
props += "broken";
}
if (col.element.id == "image-address") {
props += " ltr";
}
return props;
};
gImageView.onPageMediaSort = function(columnname) {
var tree = document.getElementById(this.treeid);
var treecol = tree.columns.getNamedColumn(columnname);
var comparator;
var index = treecol.index;
if (index == COL_IMAGE_SIZE || index == COL_IMAGE_COUNT) {
comparator = function numComparator(a, b) {
return a - b;
};
} else {
comparator = function textComparator(a, b) {
return (a || "").toLowerCase().localeCompare((b || "").toLowerCase());
};
}
this.sortdir = gTreeUtils.sort(
tree,
this,
this.data,
index,
comparator,
this.sortcol,
this.sortdir
);
for (let col of tree.columns) {
col.element.removeAttribute("sortActive");
col.element.removeAttribute("sortDirection");
}
treecol.element.setAttribute("sortActive", "true");
treecol.element.setAttribute(
"sortDirection",
this.sortdir ? "ascending" : "descending"
);
this.sortcol = index;
};
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 PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1";
const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";
// a number of services I'll need later
// the cache services
const nsICacheStorageService = Ci.nsICacheStorageService;
const nsICacheStorage = Ci.nsICacheStorage;
const cacheService = Cc[
"@mozilla.org/netwerk/cache-storage-service;1"
].getService(nsICacheStorageService);
var loadContextInfo = Services.loadContextInfo.fromLoadContext(
window.docShell.QueryInterface(Ci.nsILoadContext),
false
);
var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
const nsICookiePermission = Ci.nsICookiePermission;
const nsIPermissionManager = Ci.nsIPermissionManager;
const nsICertificateDialogs = Ci.nsICertificateDialogs;
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1";
// namespaces, don't need all of these yet...
const XLinkNS = "http://www.w3.org/1999/xlink";
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XMLNS = "http://www.w3.org/XML/1998/namespace";
const XHTMLNS = "http://www.w3.org/1999/xhtml";
const XHTML2NS = "http://www.w3.org/2002/06/xhtml2";
const XHTMLNSre = "^http://www.w3.org/1999/xhtml$";
const XHTML2NSre = "^http://www.w3.org/2002/06/xhtml2$";
const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
/* 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, at this time, the list of images of 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
*/
async function onLoadPageInfo() {
gStrings.unknown = await document.l10n.formatValue("image-size-unknown");
gStrings.notSet = await document.l10n.formatValue("not-set-alternative-text");
gStrings.mediaImg = await document.l10n.formatValue("media-img");
gStrings.mediaBGImg = await document.l10n.formatValue("media-bg-img");
gStrings.mediaBorderImg = await document.l10n.formatValue("media-border-img");
gStrings.mediaListImg = await document.l10n.formatValue("media-list-img");
gStrings.mediaCursor = await document.l10n.formatValue("media-cursor");
gStrings.mediaObject = await document.l10n.formatValue("media-object");
gStrings.mediaEmbed = await document.l10n.formatValue("media-embed");
gStrings.mediaLink = await document.l10n.formatValue("media-link");
gStrings.mediaInput = await document.l10n.formatValue("media-input");
gStrings.mediaVideo = await document.l10n.formatValue("media-video");
gStrings.mediaAudio = await document.l10n.formatValue("media-audio");
var args =
"arguments" in window &&
window.arguments.length >= 1 &&
window.arguments[0];
// init media view
var imageTree = document.getElementById("imagetree");
imageTree.view = gImageView;
/* 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;
let imageInfo = imageElement;
// Look for pageInfoListener in content.js. Sends message to listener with arguments.
mm.sendAsyncMessage("PageInfo:getData", {
strings: gStrings,
frameOuterWindowID,
});
let pageInfoData;
// Get initial pageInfoData needed to display the general, permission and security tabs.
mm.addMessageListener("PageInfo:data", async function onmessage(message) {
mm.removeMessageListener("PageInfo:data", onmessage);
pageInfoData = message.data;
let docInfo = pageInfoData.docInfo;
let windowInfo = pageInfoData.windowInfo;
let uri = Services.io.newURI(docInfo.documentURIObject.spec);
let principal = docInfo.principal;
gDocInfo = docInfo;
gImageElement = imageInfo;
var titleFormat = windowInfo.isTopWindow
? "page-info-page"
: "page-info-frame";
document.l10n.setAttributes(document.documentElement, titleFormat, {
website: docInfo.location,
});
document
.getElementById("main-window")
.setAttribute("relatedUrl", docInfo.location);
await makeGeneralTab(pageInfoData.metaViewRows, docInfo);
if (
uri.spec.startsWith("about:neterror") ||
uri.spec.startsWith("about:certerror")
) {
uri = browser.currentURI;
principal = Services.scriptSecurityManager.createCodebasePrincipal(
uri,
browser.contentPrincipal.originAttributes
);
}
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;
}
for (let item of message.data.mediaItems) {
addImage(item);
}
selectImage();
});
/* Call registered overlay init functions */
onLoadRegistry.forEach(function(func) {
func();
});
}
function resetPageInfo(args) {
/* Reset Meta tags part */
gMetaView.clear();
/* Reset Media tab */
var mediaTab = document.getElementById("mediaTab");
if (!mediaTab.hidden) {
Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
mediaTab.hidden = true;
}
gImageView.clear();
gImageHash = {};
/* Call registered overlay reset functions */
onResetRegistry.forEach(function(func) {
func();
});
/* Rebuild the data */
loadTab(args);
}
function onUnloadPageInfo() {
// Remove the observer, only if there is at least 1 image.
if (!document.getElementById("mediaTab").hidden) {
Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
}
/* Call registered overlay unload functions */
onUnloadRegistry.forEach(function(func) {
func();
});
}
function doHelpButton() {
const helpTopics = {
generalPanel: "pageinfo_general",
mediaPanel: "pageinfo_media",
permPanel: "pageinfo_permissions",
securityPanel: "pageinfo_security",
};
var deck = document.getElementById("mainDeck");
var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
openHelpLink(helpdoc);
}
function showTab(id) {
var deck = document.getElementById("mainDeck");
var pagel = document.getElementById(id + "Panel");
deck.selectedPanel = pagel;
}
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);
var initialTab = (args && args.initialTab) || "generalTab";
var radioGroup = document.getElementById("viewGroup");
initialTab =
document.getElementById(initialTab) ||
document.getElementById("generalTab");
radioGroup.selectedItem = initialTab;
radioGroup.selectedItem.doCommand();
radioGroup.focus();
}
function openCacheEntry(key, cb) {
var checkCacheListener = {
onCacheEntryCheck(entry, appCache) {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable(entry, isNew, appCache, status) {
cb(entry);
},
};
diskStorage.asyncOpenURI(
Services.io.newURI(key),
"",
nsICacheStorage.OPEN_READONLY,
checkCacheListener
);
}
async function makeGeneralTab(metaViewRows, docInfo) {
// Sets Title in the General Tab, set to "Untitled Page" if no title found
if (docInfo.title) {
document.getElementById("titletext").value = docInfo.title;
} else {
document.l10n.setAttributes(
document.getElementById("titletext"),
"no-page-title"
);
}
var url = docInfo.location;
setItemValue("urltext", url);
var referrer = "referrer" in docInfo && docInfo.referrer;
setItemValue("refertext", referrer);
var mode =
"compatMode" in docInfo && docInfo.compatMode == "BackCompat"
? "general-quirks-mode"
: "general-strict-mode";
document.l10n.setAttributes(document.getElementById("modetext"), 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;
let length = metaViewRows.length;
var metaGroup = document.getElementById("metaTags");
if (!length) {
metaGroup.style.visibility = "hidden";
} else {
document.l10n.setAttributes(
document.getElementById("metaTagsCaption"),
"general-meta-tags",
{ tags: length }
);
var metaTree = document.getElementById("metatree");
metaTree.view = gMetaView;
// Add the metaViewRows onto the general tab's meta info tree.
gMetaView.addRows(metaViewRows);
metaGroup.style.removeProperty("visibility");
}
var modifiedText = formatDate(
docInfo.lastModified,
await document.l10n.formatValue("not-set-date")
);
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);
document.l10n.setAttributes(
document.getElementById("sizetext"),
"properties-general-size",
{ kb: kbSize, bytes: formatNumber(pageSize) }
);
} else {
setItemValue("sizetext", sizeText);
}
});
}
async 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];
gImageView.addRow(row);
// Fill in cache data asynchronously
openCacheEntry(url, function(cacheEntry) {
// The data at row[2] corresponds to the data size.
if (cacheEntry) {
let value = cacheEntry.dataSize;
// If value is not -1 then replace with actual value, else keep as "unknown"
if (value != -1) {
let kbSize = Number(Math.round((value / 1024) * 100) / 100);
document.l10n
.formatValue("media-file-size", { size: kbSize })
.then(function(response) {
row[2] = response;
// Invalidate the row to trigger a repaint.
gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
});
}
}
});
// Add the observer, only once.
if (gImageView.data.length == 1) {
document.getElementById("mediaTab").hidden = false;
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;
}
}
}
// Link Stuff
function openURL(target) {
var url = target.parentNode.childNodes[2].value;
window.open(url, "_blank", "chrome");
}
function onBeginLinkDrag(event, urlField, descField) {
if (event.originalTarget.localName != "treechildren") {
return;
}
var tree = event.target;
if (tree.localName != "tree") {
tree = tree.parentNode;
}
var row = tree.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 dt = event.dataTransfer;
dt.setData("text/x-moz-url", url + "\n" + desc);
dt.setData("text/url-list", url);
dt.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;
}
async function selectSaveFolder(aCallback) {
const nsIFile = Ci.nsIFile;
const nsIFilePicker = Ci.nsIFilePicker;
let titleText = await document.l10n.formatValue("media-select-folder");
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == nsIFilePicker.returnOK) {
aCallback(fp.file.QueryInterface(nsIFile));
} else {
aCallback(null);
}
};
fp.init(window, titleText, nsIFilePicker.modeGetFolder);
fp.appendFilters(nsIFilePicker.filterAll);
try {
let initialDir = Services.prefs.getComplexValue(
"browser.download.dir",
nsIFile
);
if (initialDir) {
fp.displayDirectory = initialDir;
}
} catch (ex) {}
fp.open(fpCallback);
}
function saveMedia() {
var tree = document.getElementById("imagetree");
var rowArray = getSelectedRows(tree);
let ReferrerInfo = Components.Constructor(
"@mozilla.org/referrer-info;1",
"nsIReferrerInfo",
"init"
);
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) {
var titleKey = "SaveImageTitle";
if (item instanceof HTMLVideoElement) {
titleKey = "SaveVideoTitle";
} else if (item instanceof HTMLAudioElement) {
titleKey = "SaveAudioTitle";
}
// Bug 1565216 to evaluate passing referrer as item.baseURL
let referrerInfo = new ReferrerInfo(
Ci.nsIReferrerInfo.EMPTY,
true,
Services.io.newURI(item.baseURI)
);
saveURL(
url,
null,
titleKey,
false,
false,
referrerInfo,
null,
gDocInfo.isContentWindowPrivate,
gDocInfo.principal
);
}
} else {
selectSaveFolder(function(aDirectory) {
if (aDirectory) {
var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
uniqueFile(aChosenData.file);
let referrerInfo = new ReferrerInfo(
Ci.nsIReferrerInfo.EMPTY,
true,
aBaseURI
);
internalSave(
aURIString,
null,
null,
null,
null,
false,
"SaveImageTitle",
aChosenData,
referrerInfo,
null,
false,
null,
gDocInfo.isContentWindowPrivate,
gDocInfo.principal
);
};
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 = Services.io.newURI(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),
Services.io.newURI(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),
Services.io.newURI(item.baseURI)
);
}
}
}
});
}
}
function onBlockImage() {
var permissionManager = Cc[PERMISSION_CONTRACTID].getService(
nsIPermissionManager
);
var checkbox = document.getElementById("blockImage");
var uri = Services.io.newURI(document.getElementById("imageurltext").value);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(
uri,
gDocInfo.principal.originAttributes
);
if (checkbox.checked) {
permissionManager.addFromPrincipal(
principal,
"image",
nsIPermissionManager.DENY_ACTION
);
} else {
permissionManager.removeFromPrincipal(principal, "image");
}
}
function onImageSelect() {
var previewBox = document.getElementById("mediaPreviewBox");
var mediaSaveBox = document.getElementById("mediaSaveBox");
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;
splitter.collapsed = true;
tree.flex = 1;
} else if (count > 1) {
splitter.collapsed = true;
previewBox.collapsed = true;
mediaSaveBox.collapsed = false;
tree.flex = 1;
} else {
mediaSaveBox.collapsed = true;
splitter.collapsed = false;
previewBox.collapsed = false;
tree.flex = 0;
makePreview(getSelectedRows(tree)[0]);
}
}
// Makes the media preview (image, video, etc) for the selected row on the media tab.
function makePreview(row) {
var item = gImageView.data[row][COL_IMAGE_NODE];
var url = gImageView.data[row][COL_IMAGE_ADDRESS];
var isBG = gImageView.data[row][COL_IMAGE_BG];
var isAudio = false;
setItemValue("imageurltext", url);
setItemValue("imagetext", item.imageText);
setItemValue("imagelongdesctext", item.longDesc);
// get cache info
var cacheKey = url.replace(/#.*$/, "");
openCacheEntry(cacheKey, function(cacheEntry) {
// find out the file size
if (cacheEntry) {
let imageSize = cacheEntry.dataSize;
var kbSize = Math.round((imageSize / 1024) * 100) / 100;
document.l10n.setAttributes(
document.getElementById("imagesizetext"),
"properties-general-size",
{ kb: formatNumber(kbSize), bytes: formatNumber(imageSize) }
);
} else {
document.l10n.setAttributes(
document.getElementById("imagesizetext"),
"media-unknown-not-cached"
);
}
var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry);
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) {
document.l10n.setAttributes(
document.getElementById("imagetypetext"),
"media-animated-image-type",
{ type: imageType, frames: numFrames }
);
} else {
document.l10n.setAttributes(
document.getElementById("imagetypetext"),
"media-image-type",
{ type: imageType }
);
}
} else {
// the MIME type doesn't begin with image/, display the raw type
setItemValue("imagetypetext", mimeType);
}
} else {
// We couldn't find the type, fall back to the value in the treeview
setItemValue("imagetypetext", gImageView.data[row][COL_IMAGE_TYPE]);
}
var imageContainer = document.getElementById("theimagecontainer");
var oldImage = document.getElementById("thepreviewimage");
var isProtocolAllowed = checkProtocol(gImageView.data[row]);
var newImage = new Image();
newImage.id = "thepreviewimage";
var physWidth = 0,
physHeight = 0;
var width = 0,
height = 0;
let triggeringPrinStr = E10SUtils.serializePrincipal(gDocInfo.principal);
if (
(item.HTMLLinkElement ||
item.HTMLInputElement ||
item.HTMLImageElement ||
item.SVGImageElement ||
(item.HTMLObjectElement && mimeType && mimeType.startsWith("image/")) ||
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;
if (url) {
if (width != physWidth || height != physHeight) {
document.l10n.setAttributes(
document.getElementById("imagedimensiontext"),
"media-dimensions-scaled",
{
dimx: formatNumber(physWidth),
dimy: formatNumber(physHeight),
scaledx: formatNumber(width),
scaledy: formatNumber(height),
}
);
} else {
document.l10n.setAttributes(
document.getElementById("imagedimensiontext"),
"media-dimensions",
{ dimx: formatNumber(width), dimy: formatNumber(height) }
);
}
}
},
{ once: true }
);
newImage.setAttribute("triggeringprincipal", triggeringPrinStr);
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.setAttribute("triggeringprincipal", triggeringPrinStr);
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.setAttribute("triggeringprincipal", triggeringPrinStr);
newImage.src = url;
newImage.controls = true;
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;
}
if (url && !isAudio) {
document.l10n.setAttributes(
document.getElementById("imagedimensiontext"),
"media-dimensions",
{ dimx: formatNumber(width), dimy: formatNumber(height) }
);
}
}
makeBlockImage(url);
imageContainer.removeChild(oldImage);
imageContainer.appendChild(newImage);
});
}
function makeBlockImage(url) {
var permissionManager = Cc[PERMISSION_CONTRACTID].getService(
nsIPermissionManager
);
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 = Services.io.newURI(url);
if (uri.host) {
checkbox.hidden = false;
document.l10n.setAttributes(checkbox, "media-block-image", {
website: uri.host,
});
let principal = Services.scriptSecurityManager.createCodebasePrincipal(
uri,
gDocInfo.principal.originAttributes
);
let perm = permissionManager.testPermissionFromPrincipal(
principal,
"image"
);
checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
} else {
checkbox.hidden = true;
}
}
}
var imagePermissionObserver = {
observe(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 = getSelectedRow(imageTree);
var url = gImageView.data[row][COL_IMAGE_ADDRESS];
if (permission.matchesURI(Services.io.newURI(url), true)) {
makeBlockImage(url);
}
}
}
},
};
function getContentTypeFromHeaders(cacheEntryDescriptor) {
if (!cacheEntryDescriptor) {
return null;
}
let headers = cacheEntryDescriptor.getMetaDataElement("response-head");
let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/im.exec(headers);
return type && type[1];
}
function setItemValue(id, value) {
var item = document.getElementById(id);
if (value) {
item.parentNode.collapsed = false;
item.value = value;
} else {
item.parentNode.collapsed = 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: "long",
timeStyle: "long",
});
return dateTimeFormatter.format(date);
}
function doSelectAllMedia() {
var tree = document.getElementById("imagetree");
if (tree) {
tree.view.selection.selectAll();
}
}
function doSelectAll() {
var elem = document.commandDispatcher.focusedElement;
if (elem && elem.localName == "tree") {
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.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)
);
}