summaryrefslogtreecommitdiffstats
path: root/browser/base/content/pageinfo
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /browser/base/content/pageinfo
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'browser/base/content/pageinfo')
-rw-r--r--browser/base/content/pageinfo/feeds.js32
-rw-r--r--browser/base/content/pageinfo/feeds.xml40
-rw-r--r--browser/base/content/pageinfo/pageInfo.css26
-rw-r--r--browser/base/content/pageinfo/pageInfo.js1140
-rw-r--r--browser/base/content/pageinfo/pageInfo.xml20
-rw-r--r--browser/base/content/pageinfo/pageInfo.xul438
-rw-r--r--browser/base/content/pageinfo/permissions.js334
-rw-r--r--browser/base/content/pageinfo/security.js388
8 files changed, 2418 insertions, 0 deletions
diff --git a/browser/base/content/pageinfo/feeds.js b/browser/base/content/pageinfo/feeds.js
new file mode 100644
index 000000000..c9731b4ef
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.js
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+
+function initFeedTab(feeds)
+{
+ for (let feed of feeds) {
+ let [name, type, url] = feed;
+ addRow(name, type, url);
+ }
+
+ var feedListbox = document.getElementById("feedListbox");
+ document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
+}
+
+function onSubscribeFeed()
+{
+ var listbox = document.getElementById("feedListbox");
+ openUILinkIn(listbox.selectedItem.getAttribute("feedURL"), "current",
+ { ignoreAlt: true });
+}
+
+function addRow(name, type, url)
+{
+ var item = document.createElement("richlistitem");
+ item.setAttribute("feed", "true");
+ item.setAttribute("name", name);
+ item.setAttribute("type", type);
+ item.setAttribute("feedURL", url);
+ document.getElementById("feedListbox").appendChild(item);
+}
diff --git a/browser/base/content/pageinfo/feeds.xml b/browser/base/content/pageinfo/feeds.xml
new file mode 100644
index 000000000..782c05a73
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.xml
@@ -0,0 +1,40 @@
+<?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/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox flex="1">
+ <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
+ class="feedTitle"/>
+ <xul:label xbl:inherits="value=type"/>
+ </xul:hbox>
+ <xul:vbox>
+ <xul:vbox align="start">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL" class="text-link" flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox flex="1" class="feed-subscribe">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
+ oncommand="onSubscribeFeed()"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.css b/browser/base/content/pageinfo/pageInfo.css
new file mode 100644
index 000000000..622b56bb5
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.css
@@ -0,0 +1,26 @@
+/* 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/. */
+
+
+#viewGroup > radio {
+ -moz-binding: url("chrome://browser/content/pageinfo/pageInfo.xml#viewbutton");
+}
+
+richlistitem[feed] {
+ -moz-binding: url("chrome://browser/content/pageinfo/feeds.xml#feed");
+}
+
+richlistitem[feed]:not([selected="true"]) .feed-subscribe {
+ display: none;
+}
+
+groupbox[closed="true"] > .groupbox-body {
+ visibility: collapse;
+}
+
+#thepreviewimage {
+ display: block;
+/* This following entry can be removed when Bug 522850 is fixed. */
+ min-width: 1px;
+}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
new file mode 100644
index 000000000..7a6d0a063
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -0,0 +1,1140 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol)
+{
+ // copycol is the index number for the column that we want to add to
+ // the copy-n-paste buffer when the user hits accel-c
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [ ];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ set rowCount(c) { throw "rowCount is a readonly property"; },
+ get rowCount() { return this.rows; },
+
+ setTree: function(tree)
+ {
+ this.tree = tree;
+ },
+
+ getCellText: function(row, column)
+ {
+ // row can be null, but js arrays are 0-indexed.
+ // colidx cannot be null, but can be larger than the number
+ // of columns in the array. In this case it's the fault of
+ // whoever typoed while calling this function.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue: function(row, column, value)
+ {
+ },
+
+ setCellText: function(row, column, value)
+ {
+ this.data[row][column.index] = value;
+ },
+
+ addRow: function(row)
+ {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement) {
+ this.selection.select(0);
+ }
+ },
+
+ 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 = [ ];
+ },
+
+ handleCopy: function(row)
+ {
+ return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
+ },
+
+ performActionOnRow: function(action, row)
+ {
+ if (action == "copy") {
+ var data = this.handleCopy(row)
+ this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
+ }
+ },
+
+ onPageMediaSort : function(columnname)
+ {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ function textComparator(a, b) { return (a || "").toLowerCase().localeCompare((b || "").toLowerCase()); },
+ this.sortcol,
+ this.sortdir
+ );
+
+ Array.forEach(tree.columns, function(col) {
+ 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: 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 0; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, column) { },
+ getProgressMode: function(row, column) { },
+ getCellValue: function(row, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(col) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(row, column) { return false; },
+ isSelectable: function(row, column) { return false; },
+ performAction: function(action) { },
+ performActionOnCell: function(action, row, column) { }
+};
+
+// mmm, yummy. global variables.
+var gDocInfo = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function(row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var props = "";
+ if (!checkProtocol(data) ||
+ item instanceof HTMLEmbedElement ||
+ (item instanceof HTMLObjectElement && !item.type.startsWith("image/")))
+ props += "broken";
+
+ if (col.element.id == "image-address")
+ props += " ltr";
+
+ return props;
+};
+
+gImageView.getCellText = function(row, column) {
+ var value = this.data[row][column.index];
+ if (column.index == COL_IMAGE_SIZE) {
+ if (value == -1) {
+ return gStrings.unknown;
+ }
+ var kbSize = Number(Math.round(value / 1024 * 100) / 100);
+ return gBundle.getFormattedString("mediaFileSize", [kbSize]);
+ }
+ return value || "";
+};
+
+gImageView.onPageMediaSort = function(columnname) {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ var comparator;
+ 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
+ );
+
+ Array.forEach(tree.columns, function(col) {
+ col.element.removeAttribute("sortActive");
+ col.element.removeAttribute("sortDirection");
+ });
+ treecol.element.setAttribute("sortActive", "true");
+ treecol.element.setAttribute("sortDirection", this.sortdir ?
+ "ascending" : "descending");
+
+ this.sortcol = index;
+};
+
+var gImageHash = { };
+
+// localized strings (will be filled in when the document is loaded)
+// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
+var gStrings = { };
+var gBundle;
+
+const PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1";
+const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";
+const ATOM_CONTRACTID = "@mozilla.org/atom-service;1";
+
+// a number of services I'll need later
+// the cache services
+const nsICacheStorageService = Components.interfaces.nsICacheStorageService;
+const nsICacheStorage = Components.interfaces.nsICacheStorage;
+const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
+
+var loadContextInfo = LoadContextInfo.fromLoadContext(
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext), false);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
+
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
+
+// clipboard helper
+function getClipboardHelper() {
+ try {
+ return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+ } catch (e) {
+ // do nothing, later code will handle the error
+ return null;
+ }
+}
+const gClipboardHelper = getClipboardHelper();
+
+// Interface for image loading content
+const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
+
+// namespaces, don't need all of these yet...
+const XLinkNS = "http://www.w3.org/1999/xlink";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+const XHTML2NS = "http://www.w3.org/2002/06/xhtml2"
+
+const XHTMLNSre = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
+const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$";
+const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
+
+/* Overlays register functions here.
+ * These arrays are used to hold callbacks that Page Info will call at
+ * various stages. Use them by simply appending a function to them.
+ * For example, add a function to onLoadRegistry by invoking
+ * "onLoadRegistry.push(XXXLoadFunc);"
+ * The XXXLoadFunc should be unique to the overlay module, and will be
+ * invoked as "XXXLoadFunc();"
+ */
+
+// These functions are called to build the data displayed in the Page Info window.
+var onLoadRegistry = [ ];
+
+// These functions are called to remove old data still displayed in
+// the window when the document whose information is displayed
+// changes. For example, at this time, the list of images of the Media
+// tab is cleared.
+var onResetRegistry = [ ];
+
+// These functions are called once when all the elements in all of the target
+// document (and all of its subframes, if any) have been processed
+var onFinished = [ ];
+
+// These functions are called once when the Page Info window is closed.
+var onUnloadRegistry = [ ];
+
+/* Called when PageInfo window is loaded. Arguments are:
+ * window.arguments[0] - (optional) an object consisting of
+ * - doc: (optional) document to use for source. if not provided,
+ * the calling window's document will be used
+ * - initialTab: (optional) id of the inital tab to display
+ */
+function onLoadPageInfo()
+{
+ gBundle = document.getElementById("pageinfobundle");
+ gStrings.unknown = gBundle.getString("unknown");
+ gStrings.notSet = gBundle.getString("notset");
+ gStrings.mediaImg = gBundle.getString("mediaImg");
+ gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
+ gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
+ gStrings.mediaListImg = gBundle.getString("mediaListImg");
+ gStrings.mediaCursor = gBundle.getString("mediaCursor");
+ gStrings.mediaObject = gBundle.getString("mediaObject");
+ gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
+ gStrings.mediaLink = gBundle.getString("mediaLink");
+ gStrings.mediaInput = gBundle.getString("mediaInput");
+ gStrings.mediaVideo = gBundle.getString("mediaVideo");
+ gStrings.mediaAudio = gBundle.getString("mediaAudio");
+
+ var args = "arguments" in window &&
+ window.arguments.length >= 1 &&
+ window.arguments[0];
+
+ // init media view
+ var imageTree = document.getElementById("imagetree");
+ imageTree.view = gImageView;
+
+ /* Select the requested tab, if the name is specified */
+ loadTab(args);
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "page-info-dialog-loaded", null);
+}
+
+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,
+ docInfo.documentURIObject.originCharset);
+ 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;
+ }
+
+ for (let item of message.data.mediaItems) {
+ addImage(item);
+ }
+
+ selectImage();
+ });
+
+ /* Call registered overlay init functions */
+ onLoadRegistry.forEach(function(func) { func(); });
+}
+
+function resetPageInfo(args)
+{
+ /* Reset Meta tags part */
+ gMetaView.clear();
+
+ /* Reset Media tab */
+ var mediaTab = document.getElementById("mediaTab");
+ if (!mediaTab.hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ mediaTab.hidden = true;
+ }
+ gImageView.clear();
+ gImageHash = {};
+
+ /* Reset Feeds Tab */
+ var feedListbox = document.getElementById("feedListbox");
+ while (feedListbox.firstChild)
+ feedListbox.removeChild(feedListbox.firstChild);
+
+ /* Call registered overlay reset functions */
+ onResetRegistry.forEach(function(func) { func(); });
+
+ /* Rebuild the data */
+ loadTab(args);
+}
+
+function onUnloadPageInfo()
+{
+ // Remove the observer, only if there is at least 1 image.
+ if (!document.getElementById("mediaTab").hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Call registered overlay unload functions */
+ onUnloadRegistry.forEach(function(func) { func(); });
+}
+
+function doHelpButton()
+{
+ const helpTopics = {
+ "generalPanel": "pageinfo_general",
+ "mediaPanel": "pageinfo_media",
+ "feedPanel": "pageinfo_feed",
+ "permPanel": "pageinfo_permissions",
+ "securityPanel": "pageinfo_security"
+ };
+
+ var deck = document.getElementById("mainDeck");
+ var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
+ openHelpLink(helpdoc);
+}
+
+function showTab(id)
+{
+ var deck = document.getElementById("mainDeck");
+ var pagel = document.getElementById(id + "Panel");
+ deck.selectedPanel = pagel;
+}
+
+function loadTab(args)
+{
+ // If the "View Image Info" context menu item was used, the related image
+ // element is provided as an argument. This can't be a background image.
+ let imageElement = args && args.imageElement;
+ let frameOuterWindowID = args && args.frameOuterWindowID;
+ let browser = args && args.browser;
+
+ /* Load the page info */
+ loadPageInfo(frameOuterWindowID, imageElement, browser);
+
+ var initialTab = (args && args.initialTab) || "generalTab";
+ var radioGroup = document.getElementById("viewGroup");
+ initialTab = document.getElementById(initialTab) || document.getElementById("generalTab");
+ radioGroup.selectedItem = initialTab;
+ radioGroup.selectedItem.doCommand();
+ radioGroup.focus();
+}
+
+function toggleGroupbox(id)
+{
+ var elt = document.getElementById(id);
+ if (elt.hasAttribute("closed")) {
+ elt.removeAttribute("closed");
+ if (elt.flexWhenOpened)
+ elt.flex = elt.flexWhenOpened;
+ }
+ else {
+ elt.setAttribute("closed", "true");
+ if (elt.flex) {
+ elt.flexWhenOpened = elt.flex;
+ elt.flex = 0;
+ }
+ }
+}
+
+function openCacheEntry(key, cb)
+{
+ var checkCacheListener = {
+ onCacheEntryCheck: function(entry, appCache) {
+ return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+ cb(entry);
+ }
+ };
+ diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", 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;
+ 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;
+
+ let 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 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, -1, alt, 1, elem, isBg];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function(cacheEntry) {
+ // The data at row[2] corresponds to the data size.
+ if (cacheEntry) {
+ row[2] = cacheEntry.dataSize;
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ }
+ });
+
+ // Add the observer, only once.
+ if (gImageView.data.length == 1) {
+ document.getElementById("mediaTab").hidden = false;
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .addObserver(imagePermissionObserver, "perm-changed", false);
+ }
+ }
+ else {
+ var i = gImageHash[url][type][alt];
+ gImageView.data[i][COL_IMAGE_COUNT]++;
+ // The same image can occur several times on the page at different sizes.
+ // If the "View Image Info" context menu item was used, ensure we select
+ // the correct element.
+ if (!gImageView.data[i][COL_IMAGE_BG] &&
+ gImageElement && url == gImageElement.currentSrc &&
+ gImageElement.width == elem.width &&
+ gImageElement.height == elem.height &&
+ gImageElement.imageText == elem.imageText) {
+ gImageView.data[i][COL_IMAGE_NODE] = elem;
+ }
+ }
+}
+
+// Link Stuff
+function openURL(target)
+{
+ var url = target.parentNode.childNodes[2].value;
+ window.open(url, "_blank", "chrome");
+}
+
+function onBeginLinkDrag(event, urlField, descField)
+{
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var tree = event.target;
+ if (!("treeBoxObject" in tree))
+ tree = tree.parentNode;
+
+ var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1)
+ return;
+
+ // Adding URL flavor
+ var col = tree.columns[urlField];
+ var url = tree.view.getCellText(row, col);
+ col = tree.columns[descField];
+ var desc = tree.view.getCellText(row, col);
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", url + "\n" + desc);
+ dt.setData("text/url-list", url);
+ dt.setData("text/plain", url);
+}
+
+// Image Stuff
+function 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)
+{
+ const nsILocalFile = Components.interfaces.nsILocalFile;
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ let titleText = gBundle.getString("mediaSelectFolder");
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ aCallback(fp.file.QueryInterface(nsILocalFile));
+ } else {
+ aCallback(null);
+ }
+ };
+
+ fp.init(window, titleText, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ try {
+ let prefs = Components.classes[PREFERENCES_CONTRACTID].
+ getService(Components.interfaces.nsIPrefBranch);
+ let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
+ if (initialDir) {
+ fp.displayDirectory = initialDir;
+ }
+ } catch (ex) {
+ }
+ fp.open(fpCallback);
+}
+
+function saveMedia()
+{
+ var tree = document.getElementById("imagetree");
+ var rowArray = getSelectedRows(tree);
+ if (rowArray.length == 1) {
+ var row = rowArray[0];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+
+ if (url) {
+ var titleKey = "SaveImageTitle";
+
+ if (item instanceof HTMLVideoElement)
+ titleKey = "SaveVideoTitle";
+ else if (item instanceof HTMLAudioElement)
+ titleKey = "SaveAudioTitle";
+
+ saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
+ null, gDocInfo.isContentWindowPrivate);
+ }
+ } 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);
+ };
+
+ for (var i = 0; i < rowArray.length; i++) {
+ var v = rowArray[i];
+ var dir = aDirectory.clone();
+ var item = gImageView.data[v][COL_IMAGE_NODE];
+ var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
+ var uri = makeURI(uriString);
+
+ try {
+ uri.QueryInterface(Components.interfaces.nsIURL);
+ dir.append(decodeURIComponent(uri.fileName));
+ } catch (ex) {
+ // data:/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()
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById("blockImage");
+ var uri = makeURI(document.getElementById("imageurltext").value);
+ if (checkbox.checked)
+ permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
+ else
+ permissionManager.remove(uri, "image");
+}
+
+function onImageSelect()
+{
+ var previewBox = document.getElementById("mediaPreviewBox");
+ var mediaSaveBox = document.getElementById("mediaSaveBox");
+ var splitter = document.getElementById("mediaSplitter");
+ var tree = document.getElementById("imagetree");
+ var count = tree.view.selection.count;
+ if (count == 0) {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else if (count > 1) {
+ splitter.collapsed = true;
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = false;
+ tree.flex = 1;
+ }
+ else {
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = false;
+ previewBox.collapsed = false;
+ tree.flex = 0;
+ makePreview(getSelectedRows(tree)[0]);
+ }
+}
+
+// Makes the media preview (image, video, etc) for the selected row on the media tab.
+function makePreview(row)
+{
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ var isBG = gImageView.data[row][COL_IMAGE_BG];
+ var isAudio = false;
+
+ setItemValue("imageurltext", url);
+ setItemValue("imagetext", item.imageText);
+ setItemValue("imagelongdesctext", item.longDesc);
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ // find out the file size
+ var sizeText;
+ if (cacheEntry) {
+ let imageSize = cacheEntry.dataSize;
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ sizeText = gBundle.getFormattedString("generalSize",
+ [formatNumber(kbSize), formatNumber(imageSize)]);
+ }
+ else
+ sizeText = gBundle.getString("mediaUnknownNotCached");
+ setItemValue("imagesizetext", sizeText);
+
+ var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry);
+ var numFrames = item.numFrames;
+
+ var imageType;
+ if (mimeType) {
+ // We found the type, try to display it nicely
+ let imageMimeType = /^image\/(.*)/i.exec(mimeType);
+ if (imageMimeType) {
+ imageType = imageMimeType[1].toUpperCase();
+ if (numFrames > 1)
+ imageType = gBundle.getFormattedString("mediaAnimatedImageType",
+ [imageType, numFrames]);
+ else
+ imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+ }
+ else {
+ // the MIME type doesn't begin with image/, display the raw type
+ imageType = mimeType;
+ }
+ }
+ else {
+ // We couldn't find the type, fall back to the value in the treeview
+ imageType = gImageView.data[row][COL_IMAGE_TYPE];
+ }
+ setItemValue("imagetypetext", imageType);
+
+ var imageContainer = document.getElementById("theimagecontainer");
+ var oldImage = document.getElementById("thepreviewimage");
+
+ var isProtocolAllowed = checkProtocol(gImageView.data[row]);
+
+ var newImage = new Image;
+ newImage.id = "thepreviewimage";
+ var physWidth = 0, physHeight = 0;
+ var width = 0, height = 0;
+
+ if ((item.HTMLLinkElement || item.HTMLInputElement ||
+ item.HTMLImageElement || item.SVGImageElement ||
+ (item.HTMLObjectElement && mimeType && mimeType.startsWith("image/")) ||
+ isBG) && isProtocolAllowed) {
+ newImage.setAttribute("src", url);
+ physWidth = newImage.width || 0;
+ physHeight = newImage.height || 0;
+
+ // "width" and "height" attributes must be set to newImage,
+ // even if there is no "width" or "height attribute in item;
+ // otherwise, the preview image cannot be displayed correctly.
+ // 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;
+ }
+ else 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;
+ 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) {
+ if (width != physWidth || height != physHeight) {
+ imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
+ [formatNumber(physWidth),
+ formatNumber(physHeight),
+ formatNumber(width),
+ formatNumber(height)]);
+ }
+ else {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ }
+ setItemValue("imagedimensiontext", imageSize);
+
+ makeBlockImage(url);
+
+ imageContainer.removeChild(oldImage);
+ imageContainer.appendChild(newImage);
+ });
+}
+
+function makeBlockImage(url)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ var prefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var checkbox = document.getElementById("blockImage");
+ var imagePref = prefs.getIntPref("permissions.default.image");
+ if (!(/^https?:/.test(url)) || imagePref == 2)
+ // We can't block the images from this host because either is is not
+ // for http(s) or we don't load images at all
+ checkbox.hidden = true;
+ else {
+ var uri = makeURI(url);
+ if (uri.host) {
+ checkbox.hidden = false;
+ checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+ var perm = permissionManager.testPermission(uri, "image");
+ checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
+ }
+ else
+ checkbox.hidden = true;
+ }
+}
+
+var imagePermissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (document.getElementById("mediaPreviewBox").collapsed)
+ return;
+
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.type == "image") {
+ var imageTree = document.getElementById("imagetree");
+ var row = getSelectedRow(imageTree);
+ 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)
+{
+ var item = document.getElementById(id);
+ if (value) {
+ item.parentNode.collapsed = false;
+ item.value = value;
+ }
+ else
+ item.parentNode.collapsed = true;
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function formatDate(datestr, unknown)
+{
+ var date = new Date(datestr);
+ if (!date.valueOf())
+ return unknown;
+
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+function doCopy()
+{
+ if (!gClipboardHelper)
+ return;
+
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem) {
+ var view = elem.view;
+ var selection = view.selection;
+ var text = [], tmp = '';
+ var min = {}, max = {};
+
+ var count = selection.getRangeCount();
+
+ for (var i = 0; i < count; i++) {
+ selection.getRangeAt(i, min, max);
+
+ for (var row = min.value; row <= max.value; row++) {
+ view.performActionOnRow("copy", row);
+
+ tmp = elem.getAttribute("copybuffer");
+ if (tmp)
+ text.push(tmp);
+ elem.removeAttribute("copybuffer");
+ }
+ }
+ gClipboardHelper.copyString(text.join("\n"));
+ }
+}
+
+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);
+}
diff --git a/browser/base/content/pageinfo/pageInfo.xml b/browser/base/content/pageinfo/pageInfo.xml
new file mode 100644
index 000000000..62c699bf2
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xml
@@ -0,0 +1,20 @@
+<?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/. -->
+
+
+<bindings id="pageInfoBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- based on preferences.xml paneButton -->
+ <binding id="viewbutton" extends="chrome://global/content/bindings/radio.xml#radio" role="xullistitem">
+ <content>
+ <xul:image class="viewButtonIcon" xbl:inherits="src"/>
+ <xul:label class="viewButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.xul b/browser/base/content/pageinfo/pageInfo.xul
new file mode 100644
index 000000000..8352a8aa7
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -0,0 +1,438 @@
+<?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"?>
+
+<!DOCTYPE window [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ onunload="onUnloadPageInfo()"
+ align="stretch"
+ screenX="10" screenY="10"
+ width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
+ <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();"/>
+ <command id="cmd_copy" oncommand="doCopy();"/>
+ <command id="cmd_selectall" oncommand="doSelectAll();"/>
+
+ <!-- permissions tab -->
+ <command id="cmd_pluginsDef" oncommand="onCheckboxClick('plugins');"/>
+ <command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
+ </commandset>
+
+ <keyset id="pageInfoKeySet">
+ <key key="&closeWindow.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 key="&copy.key;" modifiers="accel" command="cmd_copy"/>
+ <key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
+ <key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="&copy.label;" command="cmd_copy" accesskey="&copy.accesskey;"/>
+ </menupopup>
+
+ <windowdragbox id="topBar" class="viewGroupWrapper">
+ <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
+ <radio id="generalTab" label="&generalTab;" accesskey="&generalTab.accesskey;"
+ oncommand="showTab('general');"/>
+ <radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
+ oncommand="showTab('media');" hidden="true"/>
+ <radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
+ oncommand="showTab('feed');" hidden="true"/>
+ <radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
+ oncommand="showTab('perm');"/>
+ <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+ oncommand="showTab('security');"/>
+ <!-- Others added by overlay -->
+ </radiogroup>
+ </windowdragbox>
+
+ <deck id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <grid id="generalGrid">
+ <columns>
+ <column/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="generalRows">
+ <row id="generalTitle">
+ <label control="titletext" value="&generalTitle;"/>
+ <separator/>
+ <textbox readonly="true" id="titletext"/>
+ </row>
+ <row id="generalURLRow">
+ <label control="urltext" value="&generalURL;"/>
+ <separator/>
+ <textbox readonly="true" id="urltext"/>
+ </row>
+ <row id="generalSeparatorRow1">
+ <separator class="thin"/>
+ </row>
+ <row id="generalTypeRow">
+ <label control="typetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="typetext"/>
+ </row>
+ <row id="generalModeRow">
+ <label control="modetext" value="&generalMode;"/>
+ <separator/>
+ <textbox readonly="true" crop="end" id="modetext"/>
+ </row>
+ <row id="generalEncodingRow">
+ <label control="encodingtext" value="&generalEncoding2;"/>
+ <separator/>
+ <textbox readonly="true" id="encodingtext"/>
+ </row>
+ <row id="generalSizeRow">
+ <label control="sizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="sizetext"/>
+ </row>
+ <row id="generalReferrerRow">
+ <label control="refertext" value="&generalReferrer;"/>
+ <separator/>
+ <textbox readonly="true" id="refertext"/>
+ </row>
+ <row id="generalSeparatorRow2">
+ <separator class="thin"/>
+ </row>
+ <row id="generalModifiedRow">
+ <label control="modifiedtext" value="&generalModified;"/>
+ <separator/>
+ <textbox readonly="true" id="modifiedtext"/>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <groupbox id="metaTags" flex="1" class="collapsable treebox">
+ <caption id="metaTagsCaption" onclick="toggleGroupbox('metaTags');"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" label="&generalMetaName;"
+ persist="width" flex="1"
+ onclick="gMetaView.onPageMediaSort('meta-name');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" label="&generalMetaContent;"
+ persist="width" flex="4"
+ onclick="gMetaView.onPageMediaSort('meta-content');"/>
+ </treecols>
+ <treechildren id="metatreechildren" flex="1"/>
+ </tree>
+ </groupbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="10"
+ width="10" id="image-address" label="&mediaAddress;"
+ onclick="gImageView.onPageMediaSort('image-address');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="image-type" label="&mediaType;"
+ onclick="gImageView.onPageMediaSort('image-type');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
+ width="2" id="image-size" label="&mediaSize;" value="size"
+ onclick="gImageView.onPageMediaSort('image-size');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
+ width="4" id="image-alt" label="&mediaAltHeader;"
+ onclick="gImageView.onPageMediaSort('image-alt');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
+ width="1" id="image-count" label="&mediaCount;"
+ onclick="gImageView.onPageMediaSort('image-count');"/>
+ </treecols>
+ <treechildren id="imagetreechildren" flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter"/>
+ <vbox flex="1" id="mediaPreviewBox" collapsed="true">
+ <grid id="mediaGrid">
+ <columns>
+ <column id="mediaLabelColumn"/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="mediaRows">
+ <row id="mediaLocationRow">
+ <label control="imageurltext" value="&mediaLocation;"/>
+ <separator/>
+ <textbox readonly="true" id="imageurltext"/>
+ </row>
+ <row id="mediaTypeRow">
+ <label control="imagetypetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetypetext"/>
+ </row>
+ <row id="mediaSizeRow">
+ <label control="imagesizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesizetext"/>
+ </row>
+ <row id="mediaDimensionRow">
+ <label control="imagedimensiontext" value="&mediaDimension;"/>
+ <separator/>
+ <textbox readonly="true" id="imagedimensiontext"/>
+ </row>
+ <row id="mediaTextRow">
+ <label control="imagetext" value="&mediaText;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetext"/>
+ </row>
+ <row id="mediaLongdescRow">
+ <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+ <separator/>
+ <textbox readonly="true" id="imagelongdesctext"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox id="imageSaveBox" align="end">
+ <vbox id="blockImageBox">
+ <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
+ accesskey="&mediaBlockImage.accesskey;"/>
+ <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+ </vbox>
+ <spacer id="imageSaveBoxSpacer" flex="1"/>
+ <button label="&selectall.label;" accesskey="&selectall.accesskey;"
+ id="selectallbutton"
+ oncommand="doSelectAllMedia();"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+ icon="save" id="imagesaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center">
+ <hbox id="theimagecontainer" pack="center">
+ <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 label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+ icon="save" id="mediasaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Feeds -->
+ <vbox id="feedPanel">
+ <richlistbox id="feedListbox" flex="1"/>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox id="permHostBox">
+ <label value="&permissionsFor;" control="hostText" />
+ <textbox id="hostText" class="header" readonly="true"
+ crop="end" flex="1"/>
+ </hbox>
+
+ <vbox id="permList" flex="1">
+ <hbox id="perm-indexedDB-extras">
+ <spacer flex="1"/>
+ <vbox id="permIndexedDBStatusBox" pack="center">
+ <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
+ </vbox>
+ <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
+ accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
+ </hbox>
+ <vbox class="permission" id="perm-plugins-row">
+ <label class="permissionLabel" id="permPluginsLabel"
+ value="&permPlugins;" control="pluginsRadioGroup"/>
+ <hbox id="permPluginTemplate" role="group" aria-labelledby="permPluginsLabel" align="baseline">
+ <label class="permPluginTemplateLabel"/>
+ <spacer flex="1"/>
+ <radiogroup class="permPluginTemplateRadioGroup" orient="horizontal" command="cmd_pluginsToggle">
+ <radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
+ <radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
+ <radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
+ <radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox id="security-identity-groupbox" flex="1">
+ <caption id="security-identity" label="&securityView.identity.header;"/>
+ <grid id="security-identity-grid" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-identity-rows">
+ <!-- Domain -->
+ <row id="security-identity-domain-row">
+ <label id="security-identity-domain-label"
+ class="fieldLabel"
+ value="&securityView.identity.domain;"
+ control="security-identity-domain-value"/>
+ <textbox id="security-identity-domain-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Owner -->
+ <row id="security-identity-owner-row">
+ <label id="security-identity-owner-label"
+ class="fieldLabel"
+ value="&securityView.identity.owner;"
+ control="security-identity-owner-value"/>
+ <textbox id="security-identity-owner-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Verifier -->
+ <row id="security-identity-verifier-row">
+ <label id="security-identity-verifier-label"
+ class="fieldLabel"
+ value="&securityView.identity.verifier;"
+ control="security-identity-verifier-value"/>
+ <textbox id="security-identity-verifier-value"
+ class="fieldValue" readonly="true" />
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <!-- Cert button -->
+ <hbox id="security-view-cert-box" pack="end">
+ <button id="security-view-cert" label="&securityView.certView;"
+ accesskey="&securityView.accesskey;"
+ oncommand="security.viewCert();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox id="security-privacy-groupbox" flex="1">
+ <caption id="security-privacy" label="&securityView.privacy.header;" />
+ <grid id="security-privacy-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-privacy-rows">
+ <!-- History -->
+ <row id="security-privacy-history-row">
+ <label id="security-privacy-history-label"
+ control="security-privacy-history-value"
+ class="fieldLabel">&securityView.privacy.history;</label>
+ <textbox id="security-privacy-history-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ readonly="true"/>
+ </row>
+ <!-- Cookies -->
+ <row id="security-privacy-cookies-row">
+ <label id="security-privacy-cookies-label"
+ control="security-privacy-cookies-value"
+ class="fieldLabel">&securityView.privacy.cookies;</label>
+ <hbox id="security-privacy-cookies-box" align="center">
+ <textbox id="security-privacy-cookies-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-cookies"
+ label="&securityView.privacy.viewCookies;"
+ accesskey="&securityView.privacy.viewCookies.accessKey;"
+ oncommand="security.viewCookies();"/>
+ </hbox>
+ </row>
+ <!-- Passwords -->
+ <row id="security-privacy-passwords-row">
+ <label id="security-privacy-passwords-label"
+ control="security-privacy-passwords-value"
+ class="fieldLabel">&securityView.privacy.passwords;</label>
+ <hbox id="security-privacy-passwords-box" align="center">
+ <textbox id="security-privacy-passwords-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-password"
+ label="&securityView.privacy.viewPasswords;"
+ accesskey="&securityView.privacy.viewPasswords.accessKey;"
+ oncommand="security.viewPasswords();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox id="security-technical-groupbox" flex="1">
+ <caption id="security-technical" label="&securityView.technical.header;" />
+ <vbox id="security-technical-box" flex="1">
+ <label id="security-technical-shortform" class="fieldValue"/>
+ <description id="security-technical-longform1" class="fieldLabel"/>
+ <description id="security-technical-longform2" class="fieldLabel"/>
+ <description id="security-technical-certificate-transparency" class="fieldLabel"/>
+ </vbox>
+ </groupbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+ <!-- Others added by overlay -->
+ </deck>
+
+#ifdef XP_MACOSX
+#include ../browserMountPoints.inc
+#endif
+
+</window>
diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js
new file mode 100644
index 000000000..0e6b9cba1
--- /dev/null
+++ b/browser/base/content/pageinfo/permissions.js
@@ -0,0 +1,334 @@
+/* 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/. */
+
+Components.utils.import("resource:///modules/SitePermissions.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+
+const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService;
+
+var gPermURI;
+var gPermPrincipal;
+var gUsageRequest;
+
+// Array of permissionIDs sorted alphabetically by label.
+var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
+ let firstLabel = SitePermissions.getPermissionLabel(a);
+ let secondLabel = SitePermissions.getPermissionLabel(b);
+ return firstLabel.localeCompare(secondLabel);
+});
+gPermissions.push("plugins");
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.matchesURI(gPermURI, true)) {
+ if (gPermissions.indexOf(permission.type) > -1)
+ initRow(permission.type);
+ else if (permission.type.startsWith("plugin"))
+ setPluginsRadioState();
+ }
+ }
+ }
+};
+
+function onLoadPermission(uri, principal)
+{
+ var permTab = document.getElementById("permTab");
+ if (SitePermissions.isSupportedURI(uri)) {
+ gPermURI = uri;
+ gPermPrincipal = principal;
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermURI.prePath;
+
+ for (var i of gPermissions)
+ initRow(i);
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(permissionObserver, "perm-changed", false);
+ onUnloadRegistry.push(onUnloadPermission);
+ permTab.hidden = false;
+ }
+ else
+ permTab.hidden = true;
+}
+
+function onUnloadPermission()
+{
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(permissionObserver, "perm-changed");
+
+ if (gUsageRequest) {
+ gUsageRequest.cancel();
+ gUsageRequest = null;
+ }
+}
+
+function initRow(aPartId)
+{
+ if (aPartId == "plugins") {
+ initPluginsRow();
+ return;
+ }
+
+ createRow(aPartId);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var perm = SitePermissions.get(gPermURI, aPartId);
+
+ if (perm) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+ else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ perm = SitePermissions.getDefault(aPartId);
+ }
+ setRadioState(aPartId, perm);
+
+ if (aPartId == "indexedDB") {
+ initIndexedDBRow();
+ }
+}
+
+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.createElement("command");
+ command.setAttribute("id", commandId);
+ command.setAttribute("oncommand", "onRadioClick('" + aPartId + "');");
+ document.getElementById("pageInfoCommandSet").appendChild(command);
+
+ let row = document.createElement("vbox");
+ row.setAttribute("id", rowId);
+ row.setAttribute("class", "permission");
+
+ let label = document.createElement("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.createElement("hbox");
+ controls.setAttribute("role", "group");
+ controls.setAttribute("aria-labelledby", labelId);
+
+ let checkbox = document.createElement("checkbox");
+ checkbox.setAttribute("id", aPartId + "Def");
+ checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
+ checkbox.setAttribute("label", gBundle.getString("permissions.useDefault"));
+ controls.appendChild(checkbox);
+
+ let spacer = document.createElement("spacer");
+ spacer.setAttribute("flex", "1");
+ controls.appendChild(spacer);
+
+ let radiogroup = document.createElement("radiogroup");
+ radiogroup.setAttribute("id", radiogroupId);
+ radiogroup.setAttribute("orient", "horizontal");
+ for (let state of SitePermissions.getAvailableStates(aPartId)) {
+ let radio = document.createElement("radio");
+ radio.setAttribute("id", aPartId + "#" + state);
+ radio.setAttribute("label", SitePermissions.getStateLabel(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.remove(gPermURI, aPartId);
+ command.setAttribute("disabled", "true");
+ var perm = SitePermissions.getDefault(aPartId);
+ setRadioState(aPartId, perm);
+ }
+ else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onPluginRadioClick(aEvent) {
+ onRadioClick(aEvent.originalTarget.getAttribute("id").split('#')[0]);
+}
+
+function onRadioClick(aPartId)
+{
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ var id = radioGroup.selectedItem.id;
+ var permission = id.split('#')[1];
+ SitePermissions.set(gPermURI, aPartId, permission);
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ if (radio) {
+ radio.radioGroup.selectedItem = radio;
+ }
+}
+
+function initIndexedDBRow()
+{
+ let row = document.getElementById("perm-indexedDB-row");
+ let extras = document.getElementById("perm-indexedDB-extras");
+
+ row.appendChild(extras);
+
+ var quotaManagerService =
+ Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+ .getService(nsIQuotaManagerService);
+ gUsageRequest =
+ quotaManagerService.getUsageForPrincipal(gPermPrincipal,
+ onIndexedDBUsageCallback);
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value = "";
+ status.setAttribute("hidden", "true");
+ button.setAttribute("hidden", "true");
+}
+
+function onIndexedDBClear()
+{
+ Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+ .getService(nsIQuotaManagerService)
+ .clearStoragesForPrincipal(gPermPrincipal);
+
+ Components.classes["@mozilla.org/serviceworkers/manager;1"]
+ .getService(Components.interfaces.nsIServiceWorkerManager)
+ .removeAndPropagate(gPermURI.host);
+
+ SitePermissions.remove(gPermURI, "indexedDB");
+ initIndexedDBRow();
+}
+
+function onIndexedDBUsageCallback(request)
+{
+ let uri = request.principal.URI;
+ if (!uri.equals(gPermURI)) {
+ throw new Error("Callback received for bad URI: " + uri);
+ }
+
+ let usage = request.result.usage;
+ if (usage) {
+ if (!("DownloadUtils" in window)) {
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+ }
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value =
+ gBundle.getFormattedString("indexedDBUsage",
+ DownloadUtils.convertByteUnits(usage));
+ status.removeAttribute("hidden");
+ button.removeAttribute("hidden");
+ }
+}
+
+function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
+ let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
+ permPluginTemplate.setAttribute("permString", aPermissionString);
+ let attrs = [
+ [ ".permPluginTemplateLabel", "value", aPluginName ],
+ [ ".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup" ],
+ [ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
+ [ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
+ [ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
+ [ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
+ ];
+
+ for (let attr of attrs) {
+ permPluginTemplate.querySelector(attr[0]).setAttribute(attr[1], attr[2]);
+ }
+
+ return permPluginTemplate;
+}
+
+function clearPluginPermissionTemplate() {
+ let permPluginTemplate = document.getElementById("permPluginTemplate");
+ permPluginTemplate.hidden = true;
+ permPluginTemplate.removeAttribute("permString");
+ document.querySelector(".permPluginTemplateLabel").removeAttribute("value");
+ document.querySelector(".permPluginTemplateRadioGroup").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAsk").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAllow").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioBlock").removeAttribute("id");
+}
+
+function initPluginsRow() {
+ let vulnerableLabel = document.getElementById("browserBundle").getString("pluginActivateVulnerable.label");
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+ let permissionMap = new Map();
+
+ for (let plugin of pluginHost.getPluginTags()) {
+ if (plugin.disabled) {
+ continue;
+ }
+ for (let mimeType of plugin.getMimeTypes()) {
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ let name = BrowserUtils.makeNicePluginName(plugin.name);
+ if (permString.startsWith("plugin-vulnerable:")) {
+ name += " \u2014 " + vulnerableLabel;
+ }
+ permissionMap.set(permString, name);
+ }
+ }
+ }
+
+ let entries = Array.from(permissionMap, item => ({ name: item[1], permission: item[0] }));
+
+ entries.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+
+ let permissionEntries = entries.map(p => fillInPluginPermissionTemplate(p.name, p.permission));
+
+ let permPluginsRow = document.getElementById("perm-plugins-row");
+ clearPluginPermissionTemplate();
+ if (permissionEntries.length < 1) {
+ permPluginsRow.hidden = true;
+ return;
+ }
+
+ for (let permissionEntry of permissionEntries) {
+ permPluginsRow.appendChild(permissionEntry);
+ }
+
+ setPluginsRadioState();
+}
+
+function setPluginsRadioState() {
+ let box = document.getElementById("perm-plugins-row");
+ for (let permissionEntry of box.childNodes) {
+ if (permissionEntry.hasAttribute("permString")) {
+ let permString = permissionEntry.getAttribute("permString");
+ let permission = SitePermissions.get(gPermURI, permString);
+ setRadioState(permString, permission);
+ }
+ }
+}
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
new file mode 100644
index 000000000..5295a8fe6
--- /dev/null
+++ b/browser/base/content/pageinfo/security.js
@@ -0,0 +1,388 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+var security = {
+ init: function(uri, windowInfo) {
+ this.uri = uri;
+ this.windowInfo = windowInfo;
+ },
+
+ // Display the server certificate (static)
+ viewCert : function () {
+ var cert = security._cert;
+ viewCertHelper(window, cert);
+ },
+
+ _getSecurityInfo : function() {
+ const nsISSLStatusProvider = Components.interfaces.nsISSLStatusProvider;
+ const nsISSLStatus = Components.interfaces.nsISSLStatus;
+
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (!this.windowInfo.isTopWindow)
+ return null;
+
+ var hostName = this.windowInfo.hostName;
+
+ var ui = security._getSecurityUI();
+ if (!ui)
+ return null;
+
+ var isBroken =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_BROKEN);
+ var isMixed =
+ (ui.state & (Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ var isInsecure =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_INSECURE);
+ var isEV =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
+ ui.QueryInterface(nsISSLStatusProvider);
+ var status = ui.SSLStatus;
+
+ if (!isInsecure && status) {
+ status.QueryInterface(nsISSLStatus);
+ var cert = status.serverCert;
+ var issuerName =
+ this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
+
+ var retval = {
+ hostName : hostName,
+ cAName : issuerName,
+ encryptionAlgorithm : undefined,
+ encryptionStrength : undefined,
+ version: undefined,
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : cert,
+ certificateTransparency : undefined
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ version = status.protocolVersion;
+ }
+ catch (e) {
+ }
+
+ switch (version) {
+ case nsISSLStatus.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case nsISSLStatus.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2"
+ break;
+ case nsISSLStatus.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3"
+ break;
+ }
+
+ // Select status text to display for Certificate Transparency.
+ switch (status.certificateTransparencyStatus) {
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+ // CT compliance checks were not performed,
+ // do not display any status text.
+ retval.certificateTransparency = null;
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NONE:
+ retval.certificateTransparency = "None";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_OK:
+ retval.certificateTransparency = "OK";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG:
+ retval.certificateTransparency = "UnknownLog";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_INVALID:
+ retval.certificateTransparency = "Invalid";
+ break;
+ }
+
+ return retval;
+ }
+ return {
+ hostName : hostName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ version: "",
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : null,
+ certificateTransparency : null
+ };
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI : function() {
+ if (window.opener.gBrowser)
+ return window.opener.gBrowser.securityUI;
+ return null;
+ },
+
+ // Interface for mapping a certificate issuer organization to
+ // the value to be displayed.
+ // Bug 82017 - this implementation should be moved to pipnss C++ code
+ mapIssuerOrganization: function(name) {
+ if (!name) return null;
+
+ if (name == "RSA Data Security, Inc.") return "Verisign, Inc.";
+
+ // No mapping required
+ return name;
+ },
+
+ /**
+ * Open the cookie manager window
+ */
+ viewCookies : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Browser:Cookies");
+ var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"].
+ getService(Components.interfaces.nsIEffectiveTLDService);
+
+ var eTLD;
+ try {
+ eTLD = eTLDService.getBaseDomain(this.uri);
+ }
+ catch (e) {
+ // getBaseDomain will fail if the host is an IP address or is empty
+ eTLD = this.uri.asciiHost;
+ }
+
+ if (win) {
+ win.gCookiesWindow.setFilter(eTLD);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {filterString : eTLD});
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords : function() {
+ LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
+ },
+
+ _cert : null
+};
+
+function securityOnLoad(uri, windowInfo) {
+ security.init(uri, windowInfo);
+
+ var info = security._getSecurityInfo();
+ if (!info) {
+ document.getElementById("securityTab").hidden = true;
+ return;
+ }
+ document.getElementById("securityTab").hidden = false;
+
+ const pageInfoBundle = document.getElementById("pageinfobundle");
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", info.hostName);
+
+ var owner, verifier;
+ if (info.cert && !info.isBroken) {
+ // 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) {
+ owner = info.cert.organization;
+ verifier = security.mapIssuerOrganization(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.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = security.mapIssuerOrganization(info.cAName ||
+ info.cert.issuerCommonName ||
+ info.cert.issuerName);
+ }
+ }
+ else {
+ // We don't have valid identity credentials.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = pageInfoBundle.getString("notset");
+ }
+
+ setText("security-identity-owner-value", owner);
+ setText("security-identity-verifier-value", verifier);
+
+ /* Manage the View Cert button*/
+ var viewCert = document.getElementById("security-view-cert");
+ if (info.cert) {
+ security._cert = info.cert;
+ viewCert.collapsed = false;
+ }
+ else
+ viewCert.collapsed = true;
+
+ /* Set Privacy & History section text */
+ var yesStr = pageInfoBundle.getString("yes");
+ var noStr = pageInfoBundle.getString("no");
+
+ setText("security-privacy-cookies-value",
+ hostHasCookies(uri) ? yesStr : noStr);
+ setText("security-privacy-passwords-value",
+ realmHasPasswords(uri) ? yesStr : noStr);
+
+ var visitCount = previousVisitCount(info.hostName);
+ if (visitCount > 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
+ }
+ else if (visitCount == 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getString("securityOneVisit"));
+ }
+ else {
+ setText("security-privacy-history-value", noStr);
+ }
+
+ /* 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");
+ security._cert = info.cert;
+ }
+ else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (info.hostName != null)
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.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 == "textbox" || element.localName == "label")
+ element.value = value;
+ else {
+ if (element.hasChildNodes())
+ element.removeChild(element.firstChild);
+ var textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+ }
+}
+
+function viewCertHelper(parent, cert)
+{
+ if (!cert)
+ return;
+
+ var cd = Components.classes[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+/**
+ * Return true iff we have cookies for uri
+ */
+function hostHasCookies(uri) {
+ var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager2);
+
+ return cookieManager.countCookiesFromHost(uri.asciiHost) > 0;
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(uri) {
+ var passwordManager = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ return passwordManager.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 false;
+
+ var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Components.interfaces.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;
+}