summaryrefslogtreecommitdiffstats
path: root/browser/base/content/pageinfo
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/pageinfo')
-rw-r--r--browser/base/content/pageinfo/pageInfo.css89
-rw-r--r--browser/base/content/pageinfo/pageInfo.js1172
-rw-r--r--browser/base/content/pageinfo/pageInfo.xhtml411
-rw-r--r--browser/base/content/pageinfo/permissions.js240
-rw-r--r--browser/base/content/pageinfo/security.js426
5 files changed, 2338 insertions, 0 deletions
diff --git a/browser/base/content/pageinfo/pageInfo.css b/browser/base/content/pageinfo/pageInfo.css
new file mode 100644
index 0000000000..d3c023a4c0
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.css
@@ -0,0 +1,89 @@
+/* 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/. */
+
+:root {
+ min-width: 24em;
+ min-height: 24em;
+}
+
+#mainDeck {
+ padding: 10px;
+ min-height: 0;
+}
+
+#mediaPreviewBox,
+#imagecontainerbox,
+#thepreviewimage,
+#mainDeck > vbox {
+ min-width: 0;
+ min-height: 0;
+}
+
+#viewGroup > radio > .radio-label-box {
+ flex-direction: column;
+ align-items: center;
+}
+
+/* Hide the radio button for the section headers */
+#viewGroup > radio > .radio-check {
+ display: none;
+}
+
+#thepreviewimage {
+ margin: 1em auto;
+ flex: none;
+ display: block;
+}
+
+table {
+ border-spacing: 0;
+}
+
+.tableSeparator {
+ height: 6px;
+}
+
+th, td {
+ padding: 0;
+}
+
+th {
+ font: inherit;
+ text-align: start;
+ padding-inline-end: .5em;
+}
+
+/*
+ Make the first column shrink to its min-content, except for #securityTable
+ which has full sentences in its first column.
+*/
+table:not(#securityTable) th {
+ width: 0;
+}
+
+th > label,
+td > input,
+.table-split-column {
+ width: 100%;
+ margin-block: 1px 4px;
+}
+
+.table-split-column {
+ display: flex;
+ align-items: center;
+}
+
+.table-split-column > label,
+.table-split-column > input {
+ flex: 1 auto;
+}
+
+.table-split-column > button {
+ flex-shrink: 0;
+}
+
+#hostText {
+ flex: 1;
+ margin-top: 1px; /* same margin as adjacent label */
+}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
new file mode 100644
index 0000000000..e3339afdda
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -0,0 +1,1172 @@
+/* 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",
+});
+
+// Inherit color scheme overrides from parent window. This is to inherit the
+// color scheme of dark themed PBM windows.
+{
+ let openerColorSchemeOverride =
+ window.opener?.browsingContext?.top.prefersColorSchemeOverride;
+ if (
+ openerColorSchemeOverride &&
+ window.browsingContext == window.browsingContext.top
+ ) {
+ window.browsingContext.prefersColorSchemeOverride =
+ openerColorSchemeOverride;
+ }
+}
+
+// define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol) {
+ // copycol is the index number for the column that we want to add to
+ // the copy-n-paste buffer when the user hits accel-c
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ set rowCount(c) {
+ throw new Error("rowCount is a readonly property");
+ },
+ get rowCount() {
+ return this.rows;
+ },
+
+ setTree(tree) {
+ this.tree = tree;
+ },
+
+ getCellText(row, column) {
+ // row can be null, but js arrays are 0-indexed.
+ // colidx cannot be null, but can be larger than the number
+ // of columns in the array. In this case it's the fault of
+ // whoever typoed while calling this function.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue(row, column, value) {},
+
+ setCellText(row, column, value) {
+ this.data[row][column.index] = value;
+ },
+
+ addRow(row) {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement) {
+ this.selection.select(0);
+ }
+ },
+
+ addRows(rows) {
+ for (let row of rows) {
+ this.addRow(row);
+ }
+ },
+
+ rowCountChanged(index, count) {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate() {
+ this.tree.invalidate();
+ },
+
+ clear() {
+ if (this.tree) {
+ this.tree.rowCountChanged(0, -this.rows);
+ }
+ this.rows = 0;
+ this.data = [];
+ },
+
+ onPageMediaSort(columnname) {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ this.sortdir = gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ function textComparator(a, b) {
+ return (a || "").toLowerCase().localeCompare((b || "").toLowerCase());
+ },
+ this.sortcol,
+ this.sortdir
+ );
+
+ for (let col of tree.columns) {
+ col.element.removeAttribute("sortActive");
+ col.element.removeAttribute("sortDirection");
+ }
+ treecol.element.setAttribute("sortActive", "true");
+ treecol.element.setAttribute(
+ "sortDirection",
+ this.sortdir ? "ascending" : "descending"
+ );
+
+ this.sortcol = treecol.index;
+ },
+
+ getRowProperties(row) {
+ return "";
+ },
+ getCellProperties(row, column) {
+ return "";
+ },
+ getColumnProperties(column) {
+ return "";
+ },
+ isContainer(index) {
+ return false;
+ },
+ isContainerOpen(index) {
+ return false;
+ },
+ isSeparator(index) {
+ return false;
+ },
+ isSorted() {
+ return this.sortcol > -1;
+ },
+ canDrop(index, orientation) {
+ return false;
+ },
+ drop(row, orientation) {
+ return false;
+ },
+ getParentIndex(index) {
+ return 0;
+ },
+ hasNextSibling(index, after) {
+ return false;
+ },
+ getLevel(index) {
+ return 0;
+ },
+ getImageSrc(row, column) {},
+ getCellValue(row, column) {
+ let col = column != null ? column : this.copycol;
+ return row < 0 || col < 0 ? "" : this.data[row][col] || "";
+ },
+ toggleOpenState(index) {},
+ cycleHeader(col) {},
+ selectionChanged() {},
+ cycleCell(row, column) {},
+ isEditable(row, column) {
+ return false;
+ },
+};
+
+// mmm, yummy. global variables.
+var gDocInfo = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView("metatree", COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView("imagetree", COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function (row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var props = "";
+ if (
+ !checkProtocol(data) ||
+ 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;
+ };
+ } 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 loadContextInfo = Services.loadContextInfo.fromLoadContext(
+ window.docShell.QueryInterface(Ci.nsILoadContext),
+ false
+);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo);
+
+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
+ */
+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);
+
+ // Select the requested tab, if the name is specified
+ await loadTab(args);
+
+ // Emit init event for tests
+ window.dispatchEvent(new Event("page-info-init"));
+}
+
+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;
+
+ /* 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(entry) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable(entry, isNew, status) {
+ cb(entry);
+ },
+ };
+ diskStorage.asyncOpenURI(
+ Services.io.newURI(key),
+ "",
+ nsICacheStorage.OPEN_READONLY,
+ checkCacheListener
+ );
+}
+
+async function makeGeneralTab(metaViewRows, docInfo) {
+ // Sets Title in the General Tab, set to "Untitled Page" if no title found
+ if (docInfo.title) {
+ document.getElementById("titletext").value = docInfo.title;
+ } else {
+ document.l10n.setAttributes(
+ document.getElementById("titletext"),
+ "no-page-title"
+ );
+ }
+
+ var url = docInfo.location;
+ setItemValue("urltext", url);
+
+ var referrer = "referrer" in docInfo && docInfo.referrer;
+ setItemValue("refertext", referrer);
+
+ var mode =
+ "compatMode" in docInfo && docInfo.compatMode == "BackCompat"
+ ? "general-quirks-mode"
+ : "general-strict-mode";
+ document.l10n.setAttributes(document.getElementById("modetext"), mode);
+
+ // find out the mime type
+ 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];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function (cacheEntry) {
+ // The data at row[2] corresponds to the data size.
+ if (cacheEntry) {
+ let value = cacheEntry.dataSize;
+ // If value is not -1 then replace with actual value, else keep as "unknown"
+ if (value != -1) {
+ let kbSize = Number(Math.round((value / 1024) * 100) / 100);
+ document.l10n
+ .formatValue("media-file-size", { size: kbSize })
+ .then(function (response) {
+ row[2] = response;
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ });
+ }
+ }
+ });
+
+ 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, 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
+ );
+ saveURL(
+ url,
+ null,
+ null,
+ titleKey,
+ false,
+ false,
+ referrerInfo,
+ cookieJarSettings,
+ 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(command) {
+ 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)
+ );
+}
diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml
new file mode 100644
index 0000000000..5e4c893b22
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xhtml
@@ -0,0 +1,411 @@
+<?xml version="1.0"?>
+# 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/.
+
+<?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ data-l10n-id="page-info-window"
+ data-l10n-attrs="style"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ align="stretch"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode">
+
+ <linkset>
+ <html:link rel="localization" href="browser/pageInfo.ftl"/>
+ </linkset>
+ #ifdef XP_MACOSX
+ #include ../macWindow.inc.xhtml
+ #else
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://browser/content/utilityOverlay.js"/>
+ #endif
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://browser/content/pageinfo/pageInfo.js"/>
+ <script src="chrome://browser/content/pageinfo/permissions.js"/>
+ <script src="chrome://browser/content/pageinfo/security.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
+ <stringbundle id="browserBundle" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <commandset id="pageInfoCommandSet">
+ <command id="cmd_close" oncommand="window.close();"/>
+ <command id="cmd_help" oncommand="doHelpButton();"/>
+ </commandset>
+
+ <keyset id="pageInfoKeySet">
+ <key data-l10n-id="close-dialog" data-l10n-attrs="key" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+#ifdef XP_MACOSX
+ <key key="." modifiers="meta" command="cmd_close"/>
+#else
+ <key keycode="VK_F1" command="cmd_help"/>
+#endif
+ <key data-l10n-id="copy" data-l10n-attrs="key" modifiers="accel" command="cmd_copy"/>
+ <key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="accel" command="cmd_selectAll"/>
+ <key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="alt" command="cmd_selectAll"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" data-l10n-id="menu-select-all" command="cmd_selectAll"/>
+ <menuitem id="menu_copy" data-l10n-id="menu-copy" command="cmd_copy"/>
+ </menupopup>
+
+ <vbox id="topBar">
+ <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
+ <radio id="generalTab" data-l10n-id="general-tab"
+ oncommand="showTab('general');"/>
+ <radio id="mediaTab" data-l10n-id="media-tab"
+ oncommand="showTab('media');" hidden="true"/>
+ <radio id="permTab" data-l10n-id="perm-tab"
+ oncommand="showTab('perm');"/>
+ <radio id="securityTab" data-l10n-id="security-tab"
+ oncommand="showTab('security');"/>
+ </radiogroup>
+ </vbox>
+
+ <deck id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <table id="generalTable" xmlns="http://www.w3.org/1999/xhtml">
+ <tr id="generalTitle">
+ <th>
+ <xul:label control="titletext" data-l10n-id="general-title"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="titletext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="generalURLRow">
+ <th>
+ <xul:label control="urltext" data-l10n-id="general-url"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="urltext"/>
+ </td>
+ </tr>
+ <tr class="tableSeparator"/>
+ <tr id="generalTypeRow">
+ <th>
+ <xul:label control="typetext" data-l10n-id="general-type"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="typetext"/>
+ </td>
+ </tr>
+ <tr id="generalModeRow">
+ <th>
+ <xul:label control="modetext" data-l10n-id="general-mode"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="modetext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="generalEncodingRow">
+ <th>
+ <xul:label control="encodingtext" data-l10n-id="general-encoding"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="encodingtext"/>
+ </td>
+ </tr>
+ <tr id="generalSizeRow">
+ <th>
+ <xul:label control="sizetext" data-l10n-id="general-size"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="sizetext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="generalReferrerRow">
+ <th>
+ <xul:label control="refertext" data-l10n-id="general-referrer"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="refertext"/>
+ </td>
+ </tr>
+ <tr class="tableSeparator"/>
+ <tr id="generalModifiedRow">
+ <th>
+ <xul:label control="modifiedtext" data-l10n-id="general-modified"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="modifiedtext"/>
+ </td>
+ </tr>
+ </table>
+ <separator class="thin"/>
+ <vbox id="metaTags" flex="1">
+ <label control="metatree" id="metaTagsCaption" class="header"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" data-l10n-id="general-meta-name"
+ persist="width" style="flex: 1 auto;"
+ onclick="gMetaView.onPageMediaSort('meta-name');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" data-l10n-id="general-meta-content"
+ persist="width" style="flex: 4 4 auto"
+ onclick="gMetaView.onPageMediaSort('meta-content');"/>
+ </treecols>
+ <treechildren id="metatreechildren" flex="1"/>
+ </tree>
+ </vbox>
+ <hbox pack="end">
+ <button command="cmd_help" data-l10n-id="help-button" class="help-button"/>
+ </hbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event, 'image-address', 'image-alt')">
+ <treecols>
+ <treecol primary="true" persist="width" style="flex: 10 10 auto"
+ width="10" id="image-address" data-l10n-id="media-address"
+ onclick="gImageView.onPageMediaSort('image-address');"/>
+ <splitter class="tree-splitter"/>
+ <treecol persist="hidden width" style="flex: 2 2 auto"
+ width="2" id="image-type" data-l10n-id="media-type"
+ onclick="gImageView.onPageMediaSort('image-type');"/>
+ <splitter class="tree-splitter"/>
+ <treecol hidden="true" persist="hidden width" style="flex: 2 2 auto"
+ width="2" id="image-size" data-l10n-id="media-size" value="size"
+ onclick="gImageView.onPageMediaSort('image-size');"/>
+ <splitter class="tree-splitter"/>
+ <treecol hidden="true" persist="hidden width" style="flex: 4 4 auto"
+ width="4" id="image-alt" data-l10n-id="media-alt-header"
+ onclick="gImageView.onPageMediaSort('image-alt');"/>
+ <splitter class="tree-splitter"/>
+ <treecol hidden="true" persist="hidden width" style="flex: 1 1 auto"
+ width="1" id="image-count" data-l10n-id="media-count"
+ onclick="gImageView.onPageMediaSort('image-count');"/>
+ </treecols>
+ <treechildren id="imagetreechildren" flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter" resizebefore="sibling" resizeafter="none" />
+ <vbox flex="1" id="mediaPreviewBox" collapsed="true">
+ <table id="mediaTable" xmlns="http://www.w3.org/1999/xhtml">
+ <tr id="mediaLocationRow">
+ <th>
+ <xul:label control="imageurltext" data-l10n-id="media-location"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="imageurltext"/>
+ </td>
+ </tr>
+ <tr id="mediaTypeRow">
+ <th>
+ <xul:label control="imagetypetext" data-l10n-id="general-type"/>
+ </th>
+ <td>
+ <input id="imagetypetext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="mediaSizeRow">
+ <th>
+ <xul:label control="imagesizetext" data-l10n-id="general-size"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="imagesizetext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="mediaDimensionRow">
+ <th>
+ <xul:label control="imagedimensiontext" data-l10n-id="media-dimension"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="imagedimensiontext" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <tr id="mediaTextRow">
+ <th>
+ <xul:label control="imagetext" data-l10n-id="media-text"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="imagetext"/>
+ </td>
+ </tr>
+ <tr id="mediaLongdescRow">
+ <th>
+ <xul:label control="imagelongdesctext" data-l10n-id="media-long-desc"/>
+ </th>
+ <td>
+ <input readonly="readonly" id="imagelongdesctext"/>
+ </td>
+ </tr>
+ </table>
+ <hbox id="imageSaveBox" align="end">
+ <spacer id="imageSaveBoxSpacer" flex="1"/>
+ <button data-l10n-id="media-select-all"
+ id="selectallbutton"
+ oncommand="doSelectAllMedia();"/>
+ <button data-l10n-id="media-save-as"
+ id="imagesaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox id="imagecontainerbox" flex="1">
+ <hbox id="theimagecontainer">
+ <image id="thepreviewimage"/>
+ </hbox>
+ <hbox id="brokenimagecontainer" pack="center" collapsed="true">
+ <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox id="mediaSaveBox" collapsed="true">
+ <spacer id="mediaSaveBoxSpacer" flex="1"/>
+ <button data-l10n-id="media-save-image-as"
+ id="mediasaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <hbox pack="end">
+ <button command="cmd_help" data-l10n-id="help-button" class="help-button"/>
+ </hbox>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox id="permHostBox">
+ <label data-l10n-id="permissions-for" control="hostText" />
+ <html:input id="hostText" class="header" readonly="readonly"/>
+ </hbox>
+
+ <vbox id="permList" flex="1"/>
+ <hbox pack="end">
+ <button command="cmd_help" data-l10n-id="help-button" class="help-button"/>
+ </hbox>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox>
+ <label class="header" data-l10n-id="security-view-identity"/>
+ <table xmlns="http://www.w3.org/1999/xhtml">
+ <!-- Domain -->
+ <tr>
+ <th>
+ <xul:label data-l10n-id="security-view-identity-domain"
+ control="security-identity-domain-value"/>
+ </th>
+ <td>
+ <input id="security-identity-domain-value" readonly="readonly"/>
+ </td>
+ </tr>
+ <!-- Owner -->
+ <tr>
+ <th>
+ <xul:label id="security-identity-owner-label"
+ class="fieldLabel"
+ data-l10n-id="security-view-identity-owner"
+ control="security-identity-owner-value"/>
+ </th>
+ <td>
+ <input id="security-identity-owner-value" readonly="readonly" data-l10n-attrs="value"/>
+ </td>
+ </tr>
+ <!-- Verifier -->
+ <tr>
+ <th>
+ <xul:label data-l10n-id="security-view-identity-verifier"
+ control="security-identity-verifier-value"/>
+ </th>
+ <td>
+ <div class="table-split-column">
+ <input id="security-identity-verifier-value" readonly="readonly"
+ data-l10n-attrs="value"/>
+ <xul:button id="security-view-cert" data-l10n-id="security-view"
+ collapsed="true"
+ oncommand="security.viewCert();"/>
+ </div>
+ </td>
+ </tr>
+ <!-- Certificate Validity -->
+ <tr id="security-identity-validity-row">
+ <th>
+ <xul:label data-l10n-id="security-view-identity-validity"
+ control="security-identity-validity-value"/>
+ </th>
+ <td>
+ <input id="security-identity-validity-value" readonly="readonly"/>
+ </td>
+ </tr>
+ </table>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox>
+ <label class="header" data-l10n-id="security-view-privacy"/>
+ <table id="securityTable" xmlns="http://www.w3.org/1999/xhtml">
+ <!-- History -->
+ <tr>
+ <th>
+ <xul:label control="security-privacy-history-value" data-l10n-id="security-view-privacy-history-value"/>
+ </th>
+ <td>
+ <xul:label id="security-privacy-history-value"
+ data-l10n-id="security-view-unknown"/>
+ </td>
+ </tr>
+ <!-- Site Data & Cookies -->
+ <tr id="security-privacy-sitedata-row">
+ <th>
+ <xul:label control="security-privacy-sitedata-value" data-l10n-id="security-view-privacy-sitedata-value"/>
+ </th>
+ <td>
+ <div class="table-split-column">
+ <xul:label id="security-privacy-sitedata-value" data-l10n-id="security-view-unknown"/>
+ <xul:button id="security-clear-sitedata"
+ disabled="true"
+ data-l10n-id="security-view-privacy-clearsitedata"
+ oncommand="security.clearSiteData();"/>
+ </div>
+ </td>
+ </tr>
+ <!-- Passwords -->
+ <tr>
+ <th>
+ <xul:label control="security-privacy-passwords-value" data-l10n-id="security-view-privacy-passwords-value"/>
+ </th>
+ <td>
+ <div class="table-split-column">
+ <xul:label id="security-privacy-passwords-value"
+ data-l10n-id="security-view-unknown"/>
+ <xul:button id="security-view-password"
+ data-l10n-id="security-view-privacy-viewpasswords"
+ oncommand="security.viewPasswords();"/>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox>
+ <label class="header" data-l10n-id="security-view-technical"/>
+ <label id="security-technical-shortform"/>
+ <description id="security-technical-longform1"/>
+ <description id="security-technical-longform2"/>
+ <description id="security-technical-certificate-transparency"/>
+ </groupbox>
+
+ <hbox pack="end">
+ <button command="cmd_help" data-l10n-id="help-button" class="help-button"/>
+ </hbox>
+ </vbox>
+ <!-- Others added by overlay -->
+ </deck>
+
+</window>
diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js
new file mode 100644
index 0000000000..7834e27c98
--- /dev/null
+++ b/browser/base/content/pageinfo/permissions.js
@@ -0,0 +1,240 @@
+/* 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 pageInfo.js */
+
+const { SitePermissions } = ChromeUtils.importESModule(
+ "resource:///modules/SitePermissions.sys.mjs"
+);
+
+var gPermPrincipal;
+
+// List of ids of permissions to hide.
+const EXCLUDE_PERMS = ["open-protocol-handler"];
+
+// Array of permissionIDs sorted alphabetically by label.
+let gPermissions = SitePermissions.listPermissions()
+ .filter(permissionID => {
+ if (!SitePermissions.getPermissionLabel(permissionID)) {
+ return false;
+ }
+ return !EXCLUDE_PERMS.includes(permissionID);
+ })
+ .sort((a, b) => {
+ let firstLabel = SitePermissions.getPermissionLabel(a);
+ let secondLabel = SitePermissions.getPermissionLabel(b);
+ return firstLabel.localeCompare(secondLabel);
+ });
+
+var permissionObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (
+ permission.matches(gPermPrincipal, true) &&
+ gPermissions.includes(permission.type)
+ ) {
+ initRow(permission.type);
+ }
+ }
+ },
+};
+
+function getExcludedPermissions() {
+ return EXCLUDE_PERMS;
+}
+
+function onLoadPermission(uri, principal) {
+ var permTab = document.getElementById("permTab");
+ if (SitePermissions.isSupportedPrincipal(principal)) {
+ gPermPrincipal = principal;
+ var hostText = document.getElementById("hostText");
+ hostText.value = uri.displayPrePath;
+
+ for (var i of gPermissions) {
+ initRow(i);
+ }
+ Services.obs.addObserver(permissionObserver, "perm-changed");
+ window.addEventListener("unload", onUnloadPermission);
+ permTab.hidden = false;
+ } else {
+ permTab.hidden = true;
+ }
+}
+
+function onUnloadPermission() {
+ Services.obs.removeObserver(permissionObserver, "perm-changed");
+}
+
+function initRow(aPartId) {
+ createRow(aPartId);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var { state, scope } = SitePermissions.getForPrincipal(
+ gPermPrincipal,
+ aPartId
+ );
+ let defaultState = SitePermissions.getDefault(aPartId);
+
+ // Since cookies preferences have many different possible configuration states
+ // we don't consider any permission except "no permission" to be default.
+ if (aPartId == "cookie") {
+ state = Services.perms.testPermissionFromPrincipal(
+ gPermPrincipal,
+ "cookie"
+ );
+
+ if (state == SitePermissions.UNKNOWN) {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ // Don't select any item in the radio group, as we can't
+ // confidently say that all cookies on the site will be allowed.
+ let radioGroup = document.getElementById("cookieRadioGroup");
+ radioGroup.selectedItem = null;
+ } else {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+
+ setRadioState(aPartId, state);
+
+ checkbox.disabled = Services.prefs.prefIsLocked(
+ "network.cookie.cookieBehavior"
+ );
+
+ return;
+ }
+
+ if (state != defaultState) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ } else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ }
+
+ if (
+ [SitePermissions.SCOPE_POLICY, SitePermissions.SCOPE_GLOBAL].includes(scope)
+ ) {
+ checkbox.setAttribute("disabled", "true");
+ command.setAttribute("disabled", "true");
+ }
+
+ setRadioState(aPartId, state);
+
+ switch (aPartId) {
+ case "install":
+ checkbox.disabled = !Services.policies.isAllowed("xpinstall");
+ break;
+ case "popup":
+ checkbox.disabled = Services.prefs.prefIsLocked(
+ "dom.disable_open_during_load"
+ );
+ break;
+ case "autoplay-media":
+ checkbox.disabled = Services.prefs.prefIsLocked("media.autoplay.default");
+ break;
+ case "geo":
+ case "desktop-notification":
+ case "camera":
+ case "microphone":
+ case "xr":
+ checkbox.disabled = Services.prefs.prefIsLocked(
+ "permissions.default." + aPartId
+ );
+ break;
+ }
+}
+
+function createRow(aPartId) {
+ let rowId = "perm-" + aPartId + "-row";
+ if (document.getElementById(rowId)) {
+ return;
+ }
+
+ let commandId = "cmd_" + aPartId + "Toggle";
+ let labelId = "perm-" + aPartId + "-label";
+ let radiogroupId = aPartId + "RadioGroup";
+
+ let command = document.createXULElement("command");
+ command.setAttribute("id", commandId);
+ command.setAttribute("oncommand", "onRadioClick('" + aPartId + "');");
+ document.getElementById("pageInfoCommandSet").appendChild(command);
+
+ let row = document.createXULElement("vbox");
+ row.setAttribute("id", rowId);
+ row.setAttribute("class", "permission");
+
+ let label = document.createXULElement("label");
+ label.setAttribute("id", labelId);
+ label.setAttribute("control", radiogroupId);
+ label.setAttribute("value", SitePermissions.getPermissionLabel(aPartId));
+ label.setAttribute("class", "permissionLabel");
+ row.appendChild(label);
+
+ let controls = document.createXULElement("hbox");
+ controls.setAttribute("role", "group");
+ controls.setAttribute("aria-labelledby", labelId);
+
+ let checkbox = document.createXULElement("checkbox");
+ checkbox.setAttribute("id", aPartId + "Def");
+ checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
+ checkbox.setAttribute("native", true);
+ document.l10n.setAttributes(checkbox, "permissions-use-default");
+ controls.appendChild(checkbox);
+
+ let spacer = document.createXULElement("spacer");
+ spacer.setAttribute("flex", "1");
+ controls.appendChild(spacer);
+
+ let radiogroup = document.createXULElement("radiogroup");
+ radiogroup.setAttribute("id", radiogroupId);
+ radiogroup.setAttribute("orient", "horizontal");
+ for (let state of SitePermissions.getAvailableStates(aPartId)) {
+ let radio = document.createXULElement("radio");
+ radio.setAttribute("id", aPartId + "#" + state);
+ radio.setAttribute(
+ "label",
+ SitePermissions.getMultichoiceStateLabel(aPartId, state)
+ );
+ radio.setAttribute("command", commandId);
+ radiogroup.appendChild(radio);
+ }
+ controls.appendChild(radiogroup);
+
+ row.appendChild(controls);
+
+ document.getElementById("permList").appendChild(row);
+}
+
+function onCheckboxClick(aPartId) {
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var checkbox = document.getElementById(aPartId + "Def");
+ if (checkbox.checked) {
+ SitePermissions.removeFromPrincipal(gPermPrincipal, aPartId);
+ command.setAttribute("disabled", "true");
+ } else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onRadioClick(aPartId) {
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ let permission;
+ if (radioGroup.selectedItem) {
+ permission = parseInt(radioGroup.selectedItem.id.split("#")[1]);
+ } else {
+ permission = SitePermissions.getDefault(aPartId);
+ }
+ SitePermissions.setForPrincipal(gPermPrincipal, aPartId, permission);
+}
+
+function setRadioState(aPartId, aValue) {
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ if (radio) {
+ radio.radioGroup.selectedItem = radio;
+ }
+}
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
new file mode 100644
index 0000000000..e4d52f889f
--- /dev/null
+++ b/browser/base/content/pageinfo/security.js
@@ -0,0 +1,426 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+const { SiteDataManager } = ChromeUtils.import(
+ "resource:///modules/SiteDataManager.jsm"
+);
+const { DownloadUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadUtils.sys.mjs"
+);
+
+/* import-globals-from pageInfo.js */
+
+ChromeUtils.defineESModuleGetters(this, {
+ LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
+ PluralForm: "resource://gre/modules/PluralForm.sys.mjs",
+});
+
+var security = {
+ async init(uri, windowInfo) {
+ this.uri = uri;
+ this.windowInfo = windowInfo;
+ this.securityInfo = await this._getSecurityInfo();
+ },
+
+ viewCert() {
+ let certChain = this.securityInfo.certChain;
+ let certs = certChain.map(elem =>
+ encodeURIComponent(elem.getBase64DERString())
+ );
+ let certsStringURL = certs.map(elem => `cert=${elem}`);
+ certsStringURL = certsStringURL.join("&");
+ let url = `about:certificate?${certsStringURL}`;
+ let win = BrowserWindowTracker.getTopWindow();
+ win.switchToTabHavingURI(url, true, {});
+ },
+
+ async _getSecurityInfo() {
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (!this.windowInfo.isTopWindow) {
+ return null;
+ }
+
+ var ui = security._getSecurityUI();
+ if (!ui) {
+ return null;
+ }
+
+ var isBroken = ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+ var isMixed =
+ ui.state &
+ (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ var isEV = ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+
+ let retval = {
+ cAName: "",
+ encryptionAlgorithm: "",
+ encryptionStrength: 0,
+ version: "",
+ isBroken,
+ isMixed,
+ isEV,
+ cert: null,
+ certificateTransparency: null,
+ };
+
+ // Only show certificate info for secure contexts. This prevents us from
+ // showing certificate data for http origins when using a proxy.
+ // https://searchfox.org/mozilla-central/rev/9c72508fcf2bba709a5b5b9eae9da35e0c707baa/security/manager/ssl/nsSecureBrowserUI.cpp#62-64
+ if (!ui.isSecureContext) {
+ return retval;
+ }
+
+ let secInfo = ui.secInfo;
+ if (!secInfo) {
+ return retval;
+ }
+
+ let cert = secInfo.serverCert;
+ let issuerName = null;
+ if (cert) {
+ issuerName = cert.issuerOrganization || cert.issuerName;
+ }
+
+ let certChainArray = [];
+ if (secInfo.succeededCertChain.length) {
+ certChainArray = secInfo.succeededCertChain;
+ } else {
+ certChainArray = secInfo.failedCertChain;
+ }
+
+ retval = {
+ cAName: issuerName,
+ encryptionAlgorithm: undefined,
+ encryptionStrength: undefined,
+ version: undefined,
+ isBroken,
+ isMixed,
+ isEV,
+ cert,
+ certChain: certChainArray,
+ certificateTransparency: undefined,
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = secInfo.cipherName;
+ retval.encryptionStrength = secInfo.secretKeyLength;
+ version = secInfo.protocolVersion;
+ } catch (e) {}
+
+ switch (version) {
+ case Ci.nsITransportSecurityInfo.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case Ci.nsITransportSecurityInfo.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2";
+ break;
+ case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3";
+ break;
+ }
+
+ // Select the status text to display for Certificate Transparency.
+ // Since we do not yet enforce the CT Policy on secure connections,
+ // we must not complain on policy discompliance (it might be viewed
+ // as a security issue by the user).
+ switch (secInfo.certificateTransparencyStatus) {
+ case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+ case Ci.nsITransportSecurityInfo
+ .CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
+ case Ci.nsITransportSecurityInfo
+ .CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
+ retval.certificateTransparency = null;
+ break;
+ case Ci.nsITransportSecurityInfo
+ .CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
+ retval.certificateTransparency = "Compliant";
+ break;
+ }
+
+ return retval;
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI() {
+ if (window.opener.gBrowser) {
+ return window.opener.gBrowser.securityUI;
+ }
+ return null;
+ },
+
+ async _updateSiteDataInfo() {
+ // Save site data info for deleting.
+ this.siteData = await SiteDataManager.getSite(this.uri.host);
+
+ let clearSiteDataButton = document.getElementById(
+ "security-clear-sitedata"
+ );
+ let siteDataLabel = document.getElementById(
+ "security-privacy-sitedata-value"
+ );
+
+ if (!this.siteData) {
+ document.l10n.setAttributes(siteDataLabel, "security-site-data-no");
+ clearSiteDataButton.setAttribute("disabled", "true");
+ return;
+ }
+
+ let { usage } = this.siteData;
+ if (usage > 0) {
+ let size = DownloadUtils.convertByteUnits(usage);
+ if (this.siteData.cookies.length) {
+ document.l10n.setAttributes(
+ siteDataLabel,
+ "security-site-data-cookies",
+ { value: size[0], unit: size[1] }
+ );
+ } else {
+ document.l10n.setAttributes(siteDataLabel, "security-site-data-only", {
+ value: size[0],
+ unit: size[1],
+ });
+ }
+ } else {
+ // We're storing cookies, else getSite would have returned null.
+ document.l10n.setAttributes(
+ siteDataLabel,
+ "security-site-data-cookies-only"
+ );
+ }
+
+ clearSiteDataButton.removeAttribute("disabled");
+ },
+
+ /**
+ * Clear Site Data and Cookies
+ */
+ clearSiteData() {
+ if (this.siteData) {
+ let { baseDomain } = this.siteData;
+ if (SiteDataManager.promptSiteDataRemoval(window, [baseDomain])) {
+ SiteDataManager.remove(baseDomain).then(() =>
+ this._updateSiteDataInfo()
+ );
+ }
+ }
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords() {
+ LoginHelper.openPasswordManager(window, {
+ filterString: this.windowInfo.hostName,
+ entryPoint: "pageinfo",
+ });
+ },
+};
+
+async function securityOnLoad(uri, windowInfo) {
+ await security.init(uri, windowInfo);
+
+ let info = security.securityInfo;
+ if (
+ !info ||
+ (uri.scheme === "about" && !uri.spec.startsWith("about:certerror"))
+ ) {
+ document.getElementById("securityTab").hidden = true;
+ return;
+ }
+ document.getElementById("securityTab").hidden = false;
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", windowInfo.hostName);
+
+ var validity;
+ if (info.cert && !info.isBroken) {
+ validity = info.cert.validity.notAfterLocalDay;
+
+ // Try to pull out meaningful values. Technically these fields are optional
+ // so we'll employ fallbacks where appropriate. The EV spec states that Org
+ // fields must be specified for subject and issuer so that case is simpler.
+ if (info.isEV) {
+ setText("security-identity-owner-value", info.cert.organization);
+ setText("security-identity-verifier-value", info.cAName);
+ } else {
+ // Technically, a non-EV cert might specify an owner in the O field or not,
+ // depending on the CA's issuing policies. However we don't have any programmatic
+ // way to tell those apart, and no policy way to establish which organization
+ // vetting standards are good enough (that's what EV is for) so we default to
+ // treating these certs as domain-validated only.
+ document.l10n.setAttributes(
+ document.getElementById("security-identity-owner-value"),
+ "page-info-security-no-owner"
+ );
+ setText(
+ "security-identity-verifier-value",
+ info.cAName || info.cert.issuerCommonName || info.cert.issuerName
+ );
+ }
+ } else {
+ // We don't have valid identity credentials.
+ document.l10n.setAttributes(
+ document.getElementById("security-identity-owner-value"),
+ "page-info-security-no-owner"
+ );
+ document.l10n.setAttributes(
+ document.getElementById("security-identity-verifier-value"),
+ "page-info-not-specified"
+ );
+ }
+
+ if (validity) {
+ setText("security-identity-validity-value", validity);
+ } else {
+ document.getElementById("security-identity-validity-row").hidden = true;
+ }
+
+ /* Manage the View Cert button*/
+ var viewCert = document.getElementById("security-view-cert");
+ if (info.cert) {
+ viewCert.collapsed = false;
+ } else {
+ viewCert.collapsed = true;
+ }
+
+ /* Set Privacy & History section text */
+
+ // Only show quota usage data for websites, not internal sites.
+ if (uri.scheme == "http" || uri.scheme == "https") {
+ SiteDataManager.updateSites().then(() => security._updateSiteDataInfo());
+ } else {
+ document.getElementById("security-privacy-sitedata-row").hidden = true;
+ }
+
+ if (realmHasPasswords(uri)) {
+ document.l10n.setAttributes(
+ document.getElementById("security-privacy-passwords-value"),
+ "saved-passwords-yes"
+ );
+ } else {
+ document.l10n.setAttributes(
+ document.getElementById("security-privacy-passwords-value"),
+ "saved-passwords-no"
+ );
+ }
+
+ document.l10n.setAttributes(
+ document.getElementById("security-privacy-history-value"),
+ "security-visits-number",
+ { visits: previousVisitCount(windowInfo.hostName) }
+ );
+
+ /* Set the Technical Detail section messages */
+ const pkiBundle = document.getElementById("pkiBundle");
+ var hdr;
+ var msg1;
+ var msg2;
+
+ if (info.isBroken) {
+ if (info.isMixed) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ msg1 = pkiBundle.getString("pageInfo_MixedContent2");
+ } else {
+ hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption", [
+ info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version,
+ ]);
+ msg1 = pkiBundle.getString("pageInfo_WeakCipher");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ } else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString(
+ "pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm, info.encryptionStrength + "", info.version]
+ );
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
+ } else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (windowInfo.hostName != null) {
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [
+ windowInfo.hostName,
+ ]);
+ } else {
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ setText("security-technical-shortform", hdr);
+ setText("security-technical-longform1", msg1);
+ setText("security-technical-longform2", msg2);
+
+ const ctStatus = document.getElementById(
+ "security-technical-certificate-transparency"
+ );
+ if (info.certificateTransparency) {
+ ctStatus.hidden = false;
+ ctStatus.value = pkiBundle.getString(
+ "pageInfo_CertificateTransparency_" + info.certificateTransparency
+ );
+ } else {
+ ctStatus.hidden = true;
+ }
+}
+
+function setText(id, value) {
+ var element = document.getElementById(id);
+ if (!element) {
+ return;
+ }
+ if (element.localName == "input" || element.localName == "label") {
+ element.value = value;
+ } else {
+ element.textContent = value;
+ }
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(uri) {
+ return Services.logins.countLogins(uri.prePath, "", "") > 0;
+}
+
+/**
+ * Return the number of previous visits recorded for host before today.
+ *
+ * @param host - the domain name to look for in history
+ */
+function previousVisitCount(host, endTimeReference) {
+ if (!host) {
+ return 0;
+ }
+
+ var historyService = Cc[
+ "@mozilla.org/browser/nav-history-service;1"
+ ].getService(Ci.nsINavHistoryService);
+
+ var options = historyService.getNewQueryOptions();
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ // Search for visits to this host before today
+ var query = historyService.getNewQuery();
+ query.endTimeReference = query.TIME_RELATIVE_TODAY;
+ query.endTime = 0;
+ query.domain = host;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+ var cc = result.root.childCount;
+ result.root.containerOpen = false;
+ return cc;
+}