1275 lines
36 KiB
JavaScript
1275 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 */
|
|
|
|
ChromeUtils.defineESModuleGetters(this, {
|
|
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
|
|
});
|
|
|
|
// 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() {},
|
|
|
|
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() {
|
|
return "";
|
|
},
|
|
getCellProperties() {
|
|
return "";
|
|
},
|
|
getColumnProperties() {
|
|
return "";
|
|
},
|
|
isContainer() {
|
|
return false;
|
|
},
|
|
isContainerOpen() {
|
|
return false;
|
|
},
|
|
isSeparator() {
|
|
return false;
|
|
},
|
|
isSorted() {
|
|
return this.sortcol > -1;
|
|
},
|
|
canDrop() {
|
|
return false;
|
|
},
|
|
drop() {
|
|
return false;
|
|
},
|
|
getParentIndex() {
|
|
return 0;
|
|
},
|
|
hasNextSibling() {
|
|
return false;
|
|
},
|
|
getLevel() {
|
|
return 0;
|
|
},
|
|
getImageSrc() {},
|
|
getCellValue(row, column) {
|
|
let col = column != null ? column : this.copycol;
|
|
return row < 0 || col < 0 ? "" : this.data[row][col] || "";
|
|
},
|
|
toggleOpenState() {},
|
|
cycleHeader() {},
|
|
selectionChanged() {},
|
|
cycleCell() {},
|
|
isEditable() {
|
|
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_RAWSIZE = 7;
|
|
|
|
// 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) ||
|
|
HTMLEmbedElement.isInstance(item) ||
|
|
(HTMLObjectElement.isInstance(item) && !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;
|
|
};
|
|
|
|
// COL_IMAGE_SIZE contains the localized string, compare raw numbers.
|
|
if (index == COL_IMAGE_SIZE) {
|
|
index = COL_IMAGE_RAWSIZE;
|
|
}
|
|
} 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)
|
|
const MEDIA_STRINGS = {};
|
|
let SIZE_UNKNOWN = "";
|
|
let ALT_NOT_SET = "";
|
|
|
|
// 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 diskStorage = null;
|
|
|
|
const nsICookiePermission = Ci.nsICookiePermission;
|
|
|
|
const nsICertificateDialogs = Ci.nsICertificateDialogs;
|
|
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1";
|
|
|
|
// clipboard helper
|
|
function getClipboardHelper() {
|
|
try {
|
|
return Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
|
Ci.nsIClipboardHelper
|
|
);
|
|
} catch (e) {
|
|
// do nothing, later code will handle the error
|
|
return null;
|
|
}
|
|
}
|
|
const gClipboardHelper = getClipboardHelper();
|
|
|
|
/* 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
|
|
*/
|
|
window.addEventListener(
|
|
"load",
|
|
async function onLoadPageInfo() {
|
|
[
|
|
SIZE_UNKNOWN,
|
|
ALT_NOT_SET,
|
|
MEDIA_STRINGS.img,
|
|
MEDIA_STRINGS["bg-img"],
|
|
MEDIA_STRINGS["border-img"],
|
|
MEDIA_STRINGS["list-img"],
|
|
MEDIA_STRINGS.cursor,
|
|
MEDIA_STRINGS.object,
|
|
MEDIA_STRINGS.embed,
|
|
MEDIA_STRINGS.link,
|
|
MEDIA_STRINGS.input,
|
|
MEDIA_STRINGS.video,
|
|
MEDIA_STRINGS.audio,
|
|
] = await document.l10n.formatValues([
|
|
"image-size-unknown",
|
|
"not-set-alternative-text",
|
|
"media-img",
|
|
"media-bg-img",
|
|
"media-border-img",
|
|
"media-list-img",
|
|
"media-cursor",
|
|
"media-object",
|
|
"media-embed",
|
|
"media-link",
|
|
"media-input",
|
|
"media-video",
|
|
"media-audio",
|
|
]);
|
|
|
|
const args =
|
|
"arguments" in window &&
|
|
window.arguments.length >= 1 &&
|
|
window.arguments[0];
|
|
|
|
// Init media view
|
|
let imageTree = document.getElementById("imagetree");
|
|
imageTree.view = gImageView;
|
|
|
|
imageTree.controllers.appendController(treeController);
|
|
|
|
document
|
|
.getElementById("metatree")
|
|
.controllers.appendController(treeController);
|
|
|
|
document
|
|
.querySelector("#metatree > treecols")
|
|
.addEventListener("click", event => {
|
|
let id = event.target.id;
|
|
switch (id) {
|
|
case "meta-name":
|
|
case "meta-content":
|
|
gMetaView.onPageMediaSort(id);
|
|
break;
|
|
}
|
|
});
|
|
|
|
document
|
|
.querySelector("#imagetree > treecols")
|
|
.addEventListener("click", event => {
|
|
let id = event.target.id;
|
|
switch (id) {
|
|
case "image-address":
|
|
case "image-type":
|
|
case "image-size":
|
|
case "image-alt":
|
|
case "image-count":
|
|
gImageView.onPageMediaSort(id);
|
|
break;
|
|
}
|
|
});
|
|
|
|
let imagetree = document.getElementById("imagetree");
|
|
imagetree.addEventListener("select", onImageSelect);
|
|
imagetree.addEventListener("dragstart", event =>
|
|
onBeginLinkDrag(event, "image-address", "image-alt")
|
|
);
|
|
|
|
document.addEventListener("command", event => {
|
|
switch (event.target.id) {
|
|
// == pageInfoCommandSet ==
|
|
case "cmd_close":
|
|
window.close();
|
|
break;
|
|
case "cmd_help":
|
|
doHelpButton();
|
|
break;
|
|
// == topBar ==
|
|
case "generalTab":
|
|
case "mediaTab":
|
|
case "permTab":
|
|
case "securityTab":
|
|
showTab(event.target.id.slice(0, -3));
|
|
break;
|
|
// == imageSaveBox ==
|
|
case "selectallbutton":
|
|
doSelectAllMedia();
|
|
break;
|
|
case "imagesaveasbutton":
|
|
case "mediasaveasbutton":
|
|
saveMedia();
|
|
break;
|
|
// == securityPanel ==
|
|
case "security-view-cert":
|
|
security.viewCert();
|
|
break;
|
|
case "security-clear-sitedata":
|
|
security.clearSiteData();
|
|
break;
|
|
case "security-view-password":
|
|
security.viewPasswords();
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Select the requested tab, if the name is specified
|
|
await loadTab(args);
|
|
|
|
// Emit init event for tests
|
|
window.dispatchEvent(new Event("page-info-init"));
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
async function loadPageInfo(browsingContext, imageElement, browser) {
|
|
browser = browser || window.opener.gBrowser.selectedBrowser;
|
|
browsingContext = browsingContext || browser.browsingContext;
|
|
|
|
let actor = browsingContext.currentWindowGlobal.getActor("PageInfo");
|
|
|
|
let result = await actor.sendQuery("PageInfo:getData");
|
|
await onNonMediaPageInfoLoad(browser, result, imageElement);
|
|
|
|
// Here, we are walking the frame tree via BrowsingContexts to collect all of the
|
|
// media information for each frame
|
|
let contextsToVisit = [browsingContext];
|
|
while (contextsToVisit.length) {
|
|
let currContext = contextsToVisit.pop();
|
|
let global = currContext.currentWindowGlobal;
|
|
|
|
if (!global) {
|
|
continue;
|
|
}
|
|
|
|
let subframeActor = global.getActor("PageInfo");
|
|
let mediaResult = await subframeActor.sendQuery("PageInfo:getMediaData");
|
|
for (let item of mediaResult.mediaItems) {
|
|
addImage(item);
|
|
}
|
|
selectImage();
|
|
contextsToVisit.push(...currContext.children);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* onNonMediaPageInfoLoad is responsible for populating the page info
|
|
* UI other than the media tab. This includes general, permissions, and security.
|
|
*/
|
|
async function onNonMediaPageInfoLoad(browser, pageInfoData, imageInfo) {
|
|
const { docInfo, windowInfo } = pageInfoData;
|
|
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.spec.startsWith("about:httpsonlyerror")
|
|
) {
|
|
uri = browser.currentURI;
|
|
principal = Services.scriptSecurityManager.createContentPrincipal(
|
|
uri,
|
|
browser.contentPrincipal.originAttributes
|
|
);
|
|
}
|
|
onLoadPermission(uri, principal);
|
|
securityOnLoad(uri, windowInfo);
|
|
}
|
|
|
|
function resetPageInfo(args) {
|
|
/* Reset Meta tags part */
|
|
gMetaView.clear();
|
|
|
|
/* Reset Media tab */
|
|
var mediaTab = document.getElementById("mediaTab");
|
|
if (!mediaTab.hidden) {
|
|
mediaTab.hidden = true;
|
|
}
|
|
gImageView.clear();
|
|
gImageHash = {};
|
|
|
|
/* Rebuild the data */
|
|
loadTab(args);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
async 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?.imageElement;
|
|
let browsingContext = args?.browsingContext;
|
|
let browser = args?.browser;
|
|
|
|
// Check if diskStorage has not be created yet if it has not been, get
|
|
// partitionKey from content process and create diskStorage with said partitionKey
|
|
if (!diskStorage) {
|
|
let oaWithPartitionKey = await getOaWithPartitionKey(
|
|
browsingContext,
|
|
browser
|
|
);
|
|
let loadContextInfo = Services.loadContextInfo.custom(
|
|
false,
|
|
oaWithPartitionKey
|
|
);
|
|
diskStorage = cacheService.diskCacheStorage(loadContextInfo);
|
|
}
|
|
|
|
/* Load the page info */
|
|
await loadPageInfo(browsingContext, imageElement, browser);
|
|
|
|
var initialTab = args?.initialTab || "generalTab";
|
|
var radioGroup = document.getElementById("viewGroup");
|
|
initialTab =
|
|
document.getElementById(initialTab) ||
|
|
document.getElementById("generalTab");
|
|
radioGroup.selectedItem = initialTab;
|
|
radioGroup.selectedItem.doCommand();
|
|
radioGroup.focus({ focusVisible: false });
|
|
}
|
|
|
|
function openCacheEntry(key, cb) {
|
|
var checkCacheListener = {
|
|
onCacheEntryCheck() {
|
|
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
|
|
},
|
|
onCacheEntryAvailable(entry) {
|
|
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
|
|
setItemValue("typetext", docInfo.contentType);
|
|
|
|
// 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 }
|
|
);
|
|
|
|
document.getElementById("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) {
|
|
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", null);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function addImage({ url, type, alt, altNotProvided, element, isBg }) {
|
|
if (!url) {
|
|
return;
|
|
}
|
|
|
|
if (altNotProvided) {
|
|
alt = ALT_NOT_SET;
|
|
}
|
|
|
|
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,
|
|
MEDIA_STRINGS[type],
|
|
SIZE_UNKNOWN,
|
|
alt,
|
|
1,
|
|
element,
|
|
isBg,
|
|
-1,
|
|
];
|
|
gImageView.addRow(row);
|
|
|
|
// Fill in cache data asynchronously
|
|
openCacheEntry(url, function (cacheEntry) {
|
|
if (cacheEntry) {
|
|
let value = cacheEntry.dataSize;
|
|
// If value is not -1 then replace with actual value, else keep as "unknown"
|
|
if (value != -1) {
|
|
row[COL_IMAGE_RAWSIZE] = value;
|
|
let kbSize = Number(Math.round((value / 1024) * 100) / 100);
|
|
document.l10n
|
|
.formatValue("media-file-size", { size: kbSize })
|
|
.then(function (response) {
|
|
row[COL_IMAGE_SIZE] = response;
|
|
// Invalidate the row to trigger a repaint.
|
|
gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
if (gImageView.data.length == 1) {
|
|
document.getElementById("mediaTab").hidden = false;
|
|
}
|
|
} 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 == element.width &&
|
|
gImageElement.height == element.height &&
|
|
gImageElement.imageText == element.imageText
|
|
) {
|
|
gImageView.data[i][COL_IMAGE_NODE] = element;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Link Stuff
|
|
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, nsIFilePicker } = Ci;
|
|
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.browsingContext, 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 (HTMLVideoElement.isInstance(item)) {
|
|
titleKey = "SaveVideoTitle";
|
|
} else if (HTMLAudioElement.isInstance(item)) {
|
|
titleKey = "SaveAudioTitle";
|
|
}
|
|
|
|
// Bug 1565216 to evaluate passing referrer as item.baseURL
|
|
let referrerInfo = new ReferrerInfo(
|
|
Ci.nsIReferrerInfo.EMPTY,
|
|
true,
|
|
Services.io.newURI(item.baseURI)
|
|
);
|
|
let cookieJarSettings = E10SUtils.deserializeCookieJarSettings(
|
|
gDocInfo.cookieJarSettings
|
|
);
|
|
internalSave(
|
|
url,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
item.mimeType,
|
|
false,
|
|
titleKey,
|
|
null,
|
|
referrerInfo,
|
|
cookieJarSettings,
|
|
null,
|
|
false,
|
|
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
|
|
);
|
|
let cookieJarSettings = E10SUtils.deserializeCookieJarSettings(
|
|
gDocInfo.cookieJarSettings
|
|
);
|
|
internalSave(
|
|
aURIString,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
false,
|
|
"SaveImageTitle",
|
|
aChosenData,
|
|
referrerInfo,
|
|
cookieJarSettings,
|
|
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 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.setAttribute("flex", "1");
|
|
} else if (count > 1) {
|
|
splitter.collapsed = true;
|
|
previewBox.collapsed = true;
|
|
mediaSaveBox.collapsed = false;
|
|
tree.setAttribute("flex", "1");
|
|
} else {
|
|
mediaSaveBox.collapsed = true;
|
|
splitter.collapsed = false;
|
|
previewBox.collapsed = false;
|
|
tree.setAttribute("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;
|
|
|
|
let element = document.getElementById("imagetypetext");
|
|
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(element, "media-animated-image-type", {
|
|
type: imageType,
|
|
frames: numFrames,
|
|
});
|
|
} else {
|
|
document.l10n.setAttributes(element, "media-image-type", {
|
|
type: imageType,
|
|
});
|
|
}
|
|
} else {
|
|
// the MIME type doesn't begin with image/, display the raw type
|
|
element.setAttribute("value", mimeType);
|
|
element.removeAttribute("data-l10n-id");
|
|
}
|
|
} else {
|
|
// We couldn't find the type, fall back to the value in the treeview
|
|
element.setAttribute("value", gImageView.data[row][COL_IMAGE_TYPE]);
|
|
element.removeAttribute("data-l10n-id");
|
|
}
|
|
|
|
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
|
|
) {
|
|
function loadOrErrorListener() {
|
|
newImage.removeEventListener("load", loadOrErrorListener);
|
|
newImage.removeEventListener("error", loadOrErrorListener);
|
|
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) }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We need to wait for the image to finish loading before using width & height
|
|
newImage.addEventListener("load", loadOrErrorListener);
|
|
newImage.addEventListener("error", loadOrErrorListener);
|
|
|
|
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.createElement("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) }
|
|
);
|
|
}
|
|
}
|
|
|
|
imageContainer.removeChild(oldImage);
|
|
imageContainer.appendChild(newImage);
|
|
});
|
|
}
|
|
|
|
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);
|
|
item.closest("tr").hidden = !value;
|
|
if (value) {
|
|
item.value = value;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
let treeController = {
|
|
supportsCommand(command) {
|
|
return command == "cmd_copy" || command == "cmd_selectAll";
|
|
},
|
|
|
|
isCommandEnabled() {
|
|
return true; // not worth checking for this
|
|
},
|
|
|
|
doCommand(command) {
|
|
switch (command) {
|
|
case "cmd_copy":
|
|
doCopy();
|
|
break;
|
|
case "cmd_selectAll":
|
|
document.activeElement.view.selection.selectAll();
|
|
break;
|
|
}
|
|
},
|
|
};
|
|
|
|
function doCopy() {
|
|
if (!gClipboardHelper) {
|
|
return;
|
|
}
|
|
|
|
var elem = document.commandDispatcher.focusedElement;
|
|
|
|
if (elem && elem.localName == "tree") {
|
|
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) {
|
|
text.push(tmp);
|
|
}
|
|
}
|
|
}
|
|
gClipboardHelper.copyString(text.join("\n"));
|
|
}
|
|
}
|
|
|
|
function doSelectAllMedia() {
|
|
var tree = document.getElementById("imagetree");
|
|
|
|
if (tree) {
|
|
tree.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?|file|about|chrome|resource):/.test(url)
|
|
);
|
|
}
|
|
|
|
async function getOaWithPartitionKey(browsingContext, browser) {
|
|
browser = browser || window.opener.gBrowser.selectedBrowser;
|
|
browsingContext = browsingContext || browser.browsingContext;
|
|
|
|
let actor = browsingContext.currentWindowGlobal.getActor("PageInfo");
|
|
let partitionKeyFromChild = await actor.sendQuery("PageInfo:getPartitionKey");
|
|
|
|
let oa = browser.contentPrincipal.originAttributes;
|
|
oa.partitionKey = partitionKeyFromChild.partitionKey;
|
|
|
|
return oa;
|
|
}
|