gecko/browser/base/content/pageinfo/pageInfo.js
Ehsan Akhgari a95b6edeea Bug 722872 - Part 1: Add nsITransferable::Init(nsILoadContext*), enforce that it's called in debug builds, and add nsIDOMDocument* arguments to nsIClipboardHelper methods; r=roc
This patch does the following:

* It adds nsITransferable::Init(nsILoadContext*).  The load context
  might be null, which means that the transferable is non-private, but
  if it's non-null, we extract the boolean value for the privacy mode
  and store it in the transferable.
* It adds checks in debug builds to make sure that Init is always
  called, in form of fatal assertions.
* It adds nsIDOMDocument* agruments to nsIClipboardHelper methods which
  represent the document that the string is coming from.
  nsIClipboardHelper implementation internally gets the nsILoadContext
  from that and passes it on to the transferable upon creation.  The
  reason that I did this was that nsIClipboardHelper is supposed to be a
  high-level helper, and in most of its call sites, we have easy access
  to a document object.
* It modifies all of the call sites of the above interfaces according to
  this change.
* It adds a GetLoadContext helper to nsIDocument to help with changing
  the call sites.
2012-04-16 22:14:01 -04:00

1281 lines
40 KiB
JavaScript

# -*- Mode: Java; 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/.
//******** 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 "rowCount is a readonly property"; },
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.
// 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: 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);
},
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 = [ ];
},
handleCopy: function(row)
{
return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
},
performActionOnRow: function(action, row)
{
if (action == "copy") {
var data = this.handleCopy(row)
this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
}
},
onPageMediaSort : function(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
);
this.sortcol = treecol.index;
},
getRowProperties: function(row, prop) { },
getCellProperties: function(row, column, prop) { },
getColumnProperties: function(column, prop) { },
isContainer: function(index) { return false; },
isContainerOpen: function(index) { return false; },
isSeparator: function(index) { return false; },
isSorted: function() { },
canDrop: function(index, orientation) { return false; },
drop: function(row, orientation) { return false; },
getParentIndex: function(index) { return 0; },
hasNextSibling: function(index, after) { return false; },
getLevel: function(index) { return 0; },
getImageSrc: function(row, column) { },
getProgressMode: function(row, column) { },
getCellValue: function(row, column) { },
toggleOpenState: function(index) { },
cycleHeader: function(col) { },
selectionChanged: function() { },
cycleCell: function(row, column) { },
isEditable: function(row, column) { return false; },
isSelectable: function(row, column) { return false; },
performAction: function(action) { },
performActionOnCell: function(action, row, column) { }
};
// mmm, yummy. global variables.
var gWindow = null;
var gDocument = 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);
var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
.getService(Components.interfaces.nsIAtomService);
gImageView._ltrAtom = atomSvc.getAtom("ltr");
gImageView._brokenAtom = atomSvc.getAtom("broken");
gImageView.getCellProperties = function(row, col, props) {
var data = gImageView.data[row];
var item = gImageView.data[row][COL_IMAGE_NODE];
if (!checkProtocol(data) ||
item instanceof HTMLEmbedElement ||
(item instanceof HTMLObjectElement && !/^image\//.test(item.type)))
props.AppendElement(this._brokenAtom);
if (col.element.id == "image-address")
props.AppendElement(this._ltrAtom);
};
gImageView.getCellText = function(row, column) {
var value = this.data[row][column.index];
if (column.index == COL_IMAGE_SIZE) {
if (value == -1) {
return gStrings.unknown;
} else {
var kbSize = Number(Math.round(value / 1024 * 100) / 100);
return gBundle.getFormattedString("mediaFileSize", [kbSize]);
}
}
return value || "";
};
gImageView.onPageMediaSort = function(columnname) {
var tree = document.getElementById(this.treeid);
var treecol = tree.columns.getNamedColumn(columnname);
var comparator;
if (treecol.index == COL_IMAGE_SIZE) {
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,
treecol.index,
comparator,
this.sortcol,
this.sortdir
);
this.sortcol = treecol.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";
const ATOM_CONTRACTID = "@mozilla.org/atom-service;1";
// a number of services I'll need later
// the cache services
const nsICacheService = Components.interfaces.nsICacheService;
const ACCESS_READ = Components.interfaces.nsICache.ACCESS_READ;
const cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService);
var httpCacheSession = cacheService.createSession("HTTP", 0, true);
httpCacheSession.doomEntriesIfExpired = false;
var ftpCacheSession = cacheService.createSession("FTP", 0, true);
ftpCacheSession.doomEntriesIfExpired = false;
const nsICookiePermission = Components.interfaces.nsICookiePermission;
const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
// clipboard helper
try {
const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
}
catch(e) {
// do nothing, later code will handle the error
}
// Interface for image loading content
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
// 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. The global variables gDocument and gWindow are set.
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 are called once for each subframe of the target document and
// the target document itself. The frame is passed as an argument.
var onProcessFrame = [ ];
// These functions are called once for each element (in all subframes, if any)
// in the target document. The element is passed as an argument.
var onProcessElement = [ ];
// 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");
gStrings.unknown = gBundle.getString("unknown");
gStrings.notSet = gBundle.getString("notset");
gStrings.mediaImg = gBundle.getString("mediaImg");
gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
gStrings.mediaListImg = gBundle.getString("mediaListImg");
gStrings.mediaCursor = gBundle.getString("mediaCursor");
gStrings.mediaObject = gBundle.getString("mediaObject");
gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
gStrings.mediaLink = gBundle.getString("mediaLink");
gStrings.mediaInput = gBundle.getString("mediaInput");
#ifdef MOZ_MEDIA
gStrings.mediaVideo = gBundle.getString("mediaVideo");
gStrings.mediaAudio = gBundle.getString("mediaAudio");
#endif
var args = "arguments" in window &&
window.arguments.length >= 1 &&
window.arguments[0];
if (!args || !args.doc) {
gWindow = window.opener.content;
gDocument = gWindow.document;
}
// init media view
var imageTree = document.getElementById("imagetree");
imageTree.view = gImageView;
/* Select the requested tab, if the name is specified */
loadTab(args);
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(window, "page-info-dialog-loaded", null);
}
function loadPageInfo()
{
var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title"
: "pageInfo.page.title";
document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]);
document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location);
// do the easy stuff first
makeGeneralTab();
// and then the hard stuff
makeTabs(gDocument, gWindow);
initFeedTab();
onLoadPermission();
/* 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) {
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.removeObserver(imagePermissionObserver, "perm-changed");
mediaTab.hidden = true;
}
gImageView.clear();
gImageHash = {};
/* Reset Feeds Tab */
var feedListbox = document.getElementById("feedListbox");
while (feedListbox.firstChild)
feedListbox.removeChild(feedListbox.firstChild);
/* 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) {
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.removeObserver(imagePermissionObserver, "perm-changed");
}
/* Call registered overlay unload functions */
onUnloadRegistry.forEach(function(func) { func(); });
}
function doHelpButton()
{
const helpTopics = {
"generalPanel": "pageinfo_general",
"mediaPanel": "pageinfo_media",
"feedPanel": "pageinfo_feed",
"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 (args && args.doc) {
gDocument = args.doc;
gWindow = gDocument.defaultView;
}
gImageElement = args && args.imageElement;
/* Load the page info */
loadPageInfo();
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 onClickMore()
{
var radioGrp = document.getElementById("viewGroup");
var radioElt = document.getElementById("securityTab");
radioGrp.selectedItem = radioElt;
showTab('security');
}
function toggleGroupbox(id)
{
var elt = document.getElementById(id);
if (elt.hasAttribute("closed")) {
elt.removeAttribute("closed");
if (elt.flexWhenOpened)
elt.flex = elt.flexWhenOpened;
}
else {
elt.setAttribute("closed", "true");
if (elt.flex) {
elt.flexWhenOpened = elt.flex;
elt.flex = 0;
}
}
}
function makeGeneralTab()
{
var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
document.getElementById("titletext").value = title;
var url = gDocument.location.toString();
setItemValue("urltext", url);
var referrer = ("referrer" in gDocument && gDocument.referrer);
setItemValue("refertext", referrer);
var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
document.getElementById("modetext").value = gBundle.getString(mode);
// find out the mime type
var mimeType = gDocument.contentType;
setItemValue("typetext", mimeType);
// get the document characterset
var encoding = gDocument.characterSet;
document.getElementById("encodingtext").value = encoding;
// get the meta tags
var metaNodes = gDocument.getElementsByTagName("meta");
var length = metaNodes.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.treeBoxObject.view = gMetaView;
for (var i = 0; i < length; i++)
gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]);
metaGroup.collapsed = false;
}
// get the date of last modification
var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet);
document.getElementById("modifiedtext").value = modifiedText;
// get cache info
var cacheKey = url.replace(/#.*$/, "");
try {
var cacheEntryDescriptor = httpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false);
}
catch(ex) {
try {
cacheEntryDescriptor = ftpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false);
}
catch(ex2) { }
}
var sizeText;
if (cacheEntryDescriptor) {
var pageSize = cacheEntryDescriptor.dataSize;
var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
}
setItemValue("sizetext", sizeText);
securityOnLoad();
}
//******** Generic Build-a-tab
// Assumes the views are empty. Only called once to build the tabs, and
// does so by farming the task off to another thread via setTimeout().
// The actual work is done with a TreeWalker that calls doGrab() once for
// each element node in the document.
var gFrameList = [ ];
function makeTabs(aDocument, aWindow)
{
goThroughFrames(aDocument, aWindow);
processFrames();
}
function goThroughFrames(aDocument, aWindow)
{
gFrameList.push(aDocument);
if (aWindow && aWindow.frames.length > 0) {
var num = aWindow.frames.length;
for (var i = 0; i < num; i++)
goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames
}
}
function processFrames()
{
if (gFrameList.length) {
var doc = gFrameList[0];
onProcessFrame.forEach(function(func) { func(doc); });
var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll, true);
gFrameList.shift();
setTimeout(doGrab, 10, iterator);
onFinished.push(selectImage);
}
else
onFinished.forEach(function(func) { func(); });
}
function doGrab(iterator)
{
for (var i = 0; i < 500; ++i)
if (!iterator.nextNode()) {
processFrames();
return;
}
setTimeout(doGrab, 10, iterator);
}
function addImage(url, type, alt, elem, isBg)
{
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;
try {
// open for READ, in non-blocking mode
var cacheEntryDescriptor = httpCacheSession.openCacheEntry(url, ACCESS_READ, false);
}
catch(ex) {
try {
// open for READ, in non-blocking mode
cacheEntryDescriptor = ftpCacheSession.openCacheEntry(url, ACCESS_READ, false);
}
catch(ex2) { }
}
var dataSize = (cacheEntryDescriptor) ? cacheEntryDescriptor.dataSize : -1;
gImageView.addRow([url, type, dataSize, alt, 1, elem, isBg]);
// Add the observer, only once.
if (gImageView.data.length == 1) {
document.getElementById("mediaTab").hidden = false;
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.addObserver(imagePermissionObserver, "perm-changed", false);
}
}
else {
var i = gImageHash[url][type][alt];
gImageView.data[i][COL_IMAGE_COUNT]++;
if (elem == gImageElement)
gImageView.data[i][COL_IMAGE_NODE] = elem;
}
}
function grabAll(elem)
{
// check for images defined in CSS (e.g. background, borders), any node may have multiple
var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
if (computedStyle) {
var addImgFunc = function (label, val) {
if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
}
else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
// This is for -moz-image-rect.
// TODO: Reimplement once bug 714757 is fixed
var strVal = val.getStringValue();
if (strVal.search(/^.*url\(\"?/) > -1) {
url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
addImage(url, label, gStrings.notSet, elem, true);
}
}
else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
// recursively resolve multiple nested CSS value lists
for (var i = 0; i < val.length; i++)
addImgFunc(label, val.item(i));
}
};
addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
}
// one swi^H^H^Hif-else to rule them all
if (elem instanceof HTMLImageElement)
addImage(elem.src, gStrings.mediaImg,
(elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
else if (elem instanceof SVGImageElement) {
try {
// Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
// or the URI formed from the baseURI and the URL is not a valid URI
var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
addImage(href, gStrings.mediaImg, "", elem, false);
} catch (e) { }
}
#ifdef MOZ_MEDIA
else if (elem instanceof HTMLVideoElement) {
addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false);
}
else if (elem instanceof HTMLAudioElement) {
addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false);
}
#endif
else if (elem instanceof HTMLLinkElement) {
if (elem.rel && /\bicon\b/i.test(elem.rel))
addImage(elem.href, gStrings.mediaLink, "", elem, false);
}
else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) {
if (elem.type.toLowerCase() == "image")
addImage(elem.src, gStrings.mediaInput,
(elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
}
else if (elem instanceof HTMLObjectElement)
addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false);
else if (elem instanceof HTMLEmbedElement)
addImage(elem.src, gStrings.mediaEmbed, "", elem, false);
onProcessElement.forEach(function(func) { func(elem); });
return NodeFilter.FILTER_ACCEPT;
}
//******** 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 (!("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 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 getSelectedImage(tree)
{
if (!gImageView.rowCount)
return null;
// Only works if only one item is selected
var clickedRow = tree.view.selection.currentIndex;
if (clickedRow == -1)
return null;
// image-node
return gImageView.data[clickedRow][COL_IMAGE_NODE];
}
function selectSaveFolder()
{
const nsILocalFile = Components.interfaces.nsILocalFile;
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
var titleText = gBundle.getString("mediaSelectFolder");
fp.init(window, titleText, nsIFilePicker.modeGetFolder);
try {
var prefs = Components.classes[PREFERENCES_CONTRACTID]
.getService(Components.interfaces.nsIPrefBranch);
var initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
if (initialDir)
fp.displayDirectory = initialDir;
}
catch (ex) { }
fp.appendFilters(nsIFilePicker.filterAll);
var ret = fp.show();
if (ret == nsIFilePicker.returnOK)
return fp.file.QueryInterface(nsILocalFile);
return null;
}
function saveMedia()
{
var tree = document.getElementById("imagetree");
var count = tree.view.selection.count;
if (count == 1) {
var item = getSelectedImage(tree);
var url = gImageView.data[tree.currentIndex][COL_IMAGE_ADDRESS];
if (url) {
var titleKey = "SaveImageTitle";
if (item instanceof HTMLVideoElement)
titleKey = "SaveVideoTitle";
else if (item instanceof HTMLAudioElement)
titleKey = "SaveAudioTitle";
saveURL(url, null, titleKey, false, false, makeURI(item.baseURI));
}
}
else {
var odir = selectSaveFolder();
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);
}
var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
aChosenData, aBaseURI);
}
for (var i = 0; i < rowArray.length; i++) {
var v = rowArray[i];
var dir = odir.clone();
var item = gImageView.data[v][COL_IMAGE_NODE];
var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
var uri = makeURI(uriString);
try {
uri.QueryInterface(Components.interfaces.nsIURL);
dir.append(decodeURIComponent(uri.fileName));
}
catch(ex) { /* data: uris */ }
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()
{
var permissionManager = Components.classes[PERMISSION_CONTRACTID]
.getService(nsIPermissionManager);
var checkbox = document.getElementById("blockImage");
var uri = makeURI(document.getElementById("imageurltext").value);
if (checkbox.checked)
permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
else
permissionManager.remove(uri.host, "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(tree.view.selection.currentIndex);
}
}
function makePreview(row)
{
var imageTree = document.getElementById("imagetree");
var item = getSelectedImage(imageTree);
var url = gImageView.data[row][COL_IMAGE_ADDRESS];
var isBG = gImageView.data[row][COL_IMAGE_BG];
var isAudio = false;
setItemValue("imageurltext", url);
var imageText;
if (!isBG &&
!(item instanceof SVGImageElement) &&
!(gDocument instanceof ImageDocument)) {
imageText = item.title || item.alt;
if (!imageText && !(item instanceof HTMLImageElement))
imageText = getValueText(item);
}
setItemValue("imagetext", imageText);
setItemValue("imagelongdesctext", item.longDesc);
// get cache info
var cacheKey = url.replace(/#.*$/, "");
try {
// open for READ, in non-blocking mode
var cacheEntryDescriptor = httpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false);
}
catch(ex) {
try {
// open for READ, in non-blocking mode
cacheEntryDescriptor = ftpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false);
}
catch(ex2) { }
}
// find out the file size
var sizeText;
if (cacheEntryDescriptor) {
var imageSize = cacheEntryDescriptor.dataSize;
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;
var numFrames = 1;
if (item instanceof HTMLObjectElement ||
item instanceof HTMLEmbedElement ||
item instanceof HTMLLinkElement)
mimeType = item.type;
if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) {
var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
if (imageRequest) {
mimeType = imageRequest.mimeType;
var image = imageRequest.image;
if (image)
numFrames = image.numFrames;
}
}
if (!mimeType)
mimeType = getContentTypeFromHeaders(cacheEntryDescriptor);
// if we have a data url, get the MIME type from the url
if (!mimeType && /^data:/.test(url)) {
let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
if (dataMimeType)
mimeType = dataMimeType[1].toLowerCase();
}
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 = gImageView.data[row][COL_IMAGE_TYPE];
}
setItemValue("imagetypetext", imageType);
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;
if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement ||
item instanceof HTMLImageElement ||
item instanceof SVGImageElement ||
(item instanceof HTMLObjectElement && /^image\//.test(mimeType)) || isBG) && isProtocolAllowed) {
newImage.setAttribute("src", url);
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.
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 = newImage.naturalWidth;
newImage.height = newImage.naturalHeight;
}
if (item instanceof SVGImageElement) {
newImage.width = item.width.baseVal.value;
newImage.height = item.height.baseVal.value;
}
width = newImage.width;
height = newImage.height;
document.getElementById("theimagecontainer").collapsed = false
document.getElementById("brokenimagecontainer").collapsed = true;
}
#ifdef MOZ_MEDIA
else if (item instanceof HTMLVideoElement && isProtocolAllowed) {
newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
newImage.id = "thepreviewimage";
newImage.mozLoadFrom(item);
newImage.controls = true;
width = physWidth = item.videoWidth;
height = physHeight = item.videoHeight;
document.getElementById("theimagecontainer").collapsed = false;
document.getElementById("brokenimagecontainer").collapsed = true;
}
else if (item instanceof HTMLAudioElement && isProtocolAllowed) {
newImage = new Audio;
newImage.id = "thepreviewimage";
newImage.src = url;
newImage.controls = true;
isAudio = true;
document.getElementById("theimagecontainer").collapsed = false;
document.getElementById("brokenimagecontainer").collapsed = true;
}
#endif
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;
}
var imageSize = "";
if (url && !isAudio) {
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);
makeBlockImage(url);
imageContainer.removeChild(oldImage);
imageContainer.appendChild(newImage);
}
function makeBlockImage(url)
{
var permissionManager = Components.classes[PERMISSION_CONTRACTID]
.getService(nsIPermissionManager);
var prefs = Components.classes[PREFERENCES_CONTRACTID]
.getService(Components.interfaces.nsIPrefBranch);
var checkbox = document.getElementById("blockImage");
var imagePref = 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 = permissionManager.testPermission(uri, "image");
checkbox.checked = perm == nsIPermissionManager.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(Components.interfaces.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 (makeURI(url).host == permission.host)
makeBlockImage(url);
}
}
}
}
function getContentTypeFromHeaders(cacheEntryDescriptor)
{
if (!cacheEntryDescriptor)
return null;
return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
.exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
}
//******** Other Misc Stuff
// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
// parse a node to extract the contents of the node
function getValueText(node)
{
var valueText = "";
// form input elements don't generally contain information that is useful to our callers, so return nothing
if (node instanceof HTMLInputElement ||
node instanceof HTMLSelectElement ||
node instanceof HTMLTextAreaElement)
return valueText;
// otherwise recurse for each child
var length = node.childNodes.length;
for (var i = 0; i < length; i++) {
var childNode = node.childNodes[i];
var nodeType = childNode.nodeType;
// text nodes are where the goods are
if (nodeType == Node.TEXT_NODE)
valueText += " " + childNode.nodeValue;
// and elements can have more text inside them
else if (nodeType == Node.ELEMENT_NODE) {
// images are special, we want to capture the alt text as if the image weren't there
if (childNode instanceof HTMLImageElement)
valueText += " " + getAltText(childNode);
else
valueText += " " + getValueText(childNode);
}
}
return stripWS(valueText);
}
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
// traverse the tree in search of an img or area element and grab its alt tag
function getAltText(node)
{
var altText = "";
if (node.alt)
return node.alt;
var length = node.childNodes.length;
for (var i = 0; i < length; i++)
if ((altText = getAltText(node.childNodes[i]) != undefined)) // stupid js warning...
return altText;
return "";
}
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
// strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space
function stripWS(text)
{
var middleRE = /\s+/g;
var endRE = /(^\s+)|(\s+$)/g;
text = text.replace(middleRE, " ");
return text.replace(endRE, "");
}
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)
{
// scriptable date formatter, for pretty printing dates
var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
.getService(Components.interfaces.nsIScriptableDateFormat);
var date = new Date(datestr);
if (!date.valueOf())
return unknown;
return dateService.FormatDateTime("", dateService.dateFormatLong,
dateService.timeFormatSeconds,
date.getFullYear(), date.getMonth()+1, date.getDate(),
date.getHours(), date.getMinutes(), date.getSeconds());
}
function doCopy()
{
if (!gClipboardHelper)
return;
var elem = document.commandDispatcher.focusedElement;
if (elem && "treeBoxObject" in elem) {
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++) {
view.performActionOnRow("copy", row);
tmp = elem.getAttribute("copybuffer");
if (tmp)
text.push(tmp);
elem.removeAttribute("copybuffer");
}
}
gClipboardHelper.copyString(text.join("\n"), document);
}
}
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 (gImageElement == gImageView.data[i][COL_IMAGE_NODE] &&
!gImageView.data[i][COL_IMAGE_BG]) {
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);
}