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