diff options
Diffstat (limited to 'application/basilisk/base/content/pageinfo')
8 files changed, 2418 insertions, 0 deletions
diff --git a/application/basilisk/base/content/pageinfo/feeds.js b/application/basilisk/base/content/pageinfo/feeds.js new file mode 100644 index 000000000..c9731b4ef --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/feeds.xml b/application/basilisk/base/content/pageinfo/feeds.xml new file mode 100644 index 000000000..782c05a73 --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/pageInfo.css b/application/basilisk/base/content/pageinfo/pageInfo.css new file mode 100644 index 000000000..622b56bb5 --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/pageInfo.js b/application/basilisk/base/content/pageinfo/pageInfo.js new file mode 100644 index 000000000..7a6d0a063 --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/pageInfo.xml b/application/basilisk/base/content/pageinfo/pageInfo.xml new file mode 100644 index 000000000..62c699bf2 --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/pageInfo.xul b/application/basilisk/base/content/pageinfo/pageInfo.xul new file mode 100644 index 000000000..8352a8aa7 --- /dev/null +++ b/application/basilisk/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="©.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="©.label;" command="cmd_copy" accesskey="©.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/application/basilisk/base/content/pageinfo/permissions.js b/application/basilisk/base/content/pageinfo/permissions.js new file mode 100644 index 000000000..0e6b9cba1 --- /dev/null +++ b/application/basilisk/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/application/basilisk/base/content/pageinfo/security.js b/application/basilisk/base/content/pageinfo/security.js new file mode 100644 index 000000000..5295a8fe6 --- /dev/null +++ b/application/basilisk/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; +} |