/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* import-globals-from ../../../../toolkit/content/globalOverlay.js */ /* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */ /* import-globals-from ../../../../toolkit/content/treeUtils.js */ /* import-globals-from ../utilityOverlay.js */ /* import-globals-from permissions.js */ /* import-globals-from security.js */ XPCOMUtils.defineLazyModuleGetters(this, { E10SUtils: "resource://gre/modules/E10SUtils.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 new Error("rowCount is a readonly property"); }, get rowCount() { return this.rows; }, setTree(tree) { this.tree = tree; }, getCellText(row, column) { // row can be null, but js arrays are 0-indexed. // colidx cannot be null, but can be larger than the number // of columns in the array. In this case it's the fault of // whoever typoed while calling this function. return this.data[row][column.index] || ""; }, setCellValue(row, column, value) {}, setCellText(row, column, value) { this.data[row][column.index] = value; }, addRow(row) { this.rows = this.data.push(row); this.rowCountChanged(this.rows - 1, 1); if (this.selection.count == 0 && this.rowCount && !gImageElement) { this.selection.select(0); } }, addRows(rows) { for (let row of rows) { this.addRow(row); } }, rowCountChanged(index, count) { this.tree.rowCountChanged(index, count); }, invalidate() { this.tree.invalidate(); }, clear() { if (this.tree) { this.tree.rowCountChanged(0, -this.rows); } this.rows = 0; this.data = []; }, onPageMediaSort(columnname) { var tree = document.getElementById(this.treeid); var treecol = tree.columns.getNamedColumn(columnname); this.sortdir = gTreeUtils.sort( tree, this, this.data, treecol.index, function textComparator(a, b) { return (a || "").toLowerCase().localeCompare((b || "").toLowerCase()); }, this.sortcol, this.sortdir ); for (let col of tree.columns) { col.element.removeAttribute("sortActive"); col.element.removeAttribute("sortDirection"); } treecol.element.setAttribute("sortActive", "true"); treecol.element.setAttribute( "sortDirection", this.sortdir ? "ascending" : "descending" ); this.sortcol = treecol.index; }, getRowProperties(row) { return ""; }, getCellProperties(row, column) { return ""; }, getColumnProperties(column) { return ""; }, isContainer(index) { return false; }, isContainerOpen(index) { return false; }, isSeparator(index) { return false; }, isSorted() { return this.sortcol > -1; }, canDrop(index, orientation) { return false; }, drop(row, orientation) { return false; }, getParentIndex(index) { return 0; }, hasNextSibling(index, after) { return false; }, getLevel(index) { return 0; }, getImageSrc(row, column) {}, getCellValue(row, column) {}, toggleOpenState(index) {}, cycleHeader(col) {}, selectionChanged() {}, cycleCell(row, column) {}, isEditable(row, column) { return false; }, }; // mmm, yummy. global variables. var gDocInfo = null; var gImageElement = null; // column number to help using the data array const COL_IMAGE_ADDRESS = 0; const COL_IMAGE_TYPE = 1; const COL_IMAGE_SIZE = 2; const COL_IMAGE_ALT = 3; const COL_IMAGE_COUNT = 4; const COL_IMAGE_NODE = 5; const COL_IMAGE_BG = 6; // column number to copy from, second argument to pageInfoTreeView's constructor const COPYCOL_NONE = -1; const COPYCOL_META_CONTENT = 1; const COPYCOL_IMAGE = COL_IMAGE_ADDRESS; // one nsITreeView for each tree in the window var gMetaView = new pageInfoTreeView("metatree", COPYCOL_META_CONTENT); var gImageView = new pageInfoTreeView("imagetree", COPYCOL_IMAGE); gImageView.getCellProperties = function(row, col) { var data = gImageView.data[row]; var item = gImageView.data[row][COL_IMAGE_NODE]; var props = ""; if ( !checkProtocol(data) || item instanceof HTMLEmbedElement || (item instanceof HTMLObjectElement && !item.type.startsWith("image/")) ) { props += "broken"; } if (col.element.id == "image-address") { props += " ltr"; } return props; }; gImageView.onPageMediaSort = function(columnname) { var tree = document.getElementById(this.treeid); var treecol = tree.columns.getNamedColumn(columnname); var comparator; var index = treecol.index; if (index == COL_IMAGE_SIZE || index == COL_IMAGE_COUNT) { comparator = function numComparator(a, b) { return a - b; }; } else { comparator = function textComparator(a, b) { return (a || "").toLowerCase().localeCompare((b || "").toLowerCase()); }; } this.sortdir = gTreeUtils.sort( tree, this, this.data, index, comparator, this.sortcol, this.sortdir ); for (let col of tree.columns) { col.element.removeAttribute("sortActive"); col.element.removeAttribute("sortDirection"); } treecol.element.setAttribute("sortActive", "true"); treecol.element.setAttribute( "sortDirection", this.sortdir ? "ascending" : "descending" ); this.sortcol = index; }; var gImageHash = {}; // localized strings (will be filled in when the document is loaded) // 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"; // a number of services I'll need later // the cache services const nsICacheStorageService = Ci.nsICacheStorageService; const nsICacheStorage = Ci.nsICacheStorage; const cacheService = Cc[ "@mozilla.org/netwerk/cache-storage-service;1" ].getService(nsICacheStorageService); var loadContextInfo = Services.loadContextInfo.fromLoadContext( window.docShell.QueryInterface(Ci.nsILoadContext), false ); var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false); const nsICookiePermission = Ci.nsICookiePermission; const nsIPermissionManager = Ci.nsIPermissionManager; const nsICertificateDialogs = Ci.nsICertificateDialogs; const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"; // 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 */ async function onLoadPageInfo() { gStrings.unknown = await document.l10n.formatValue("image-size-unknown"); gStrings.notSet = await document.l10n.formatValue("not-set-alternative-text"); gStrings.mediaImg = await document.l10n.formatValue("media-img"); gStrings.mediaBGImg = await document.l10n.formatValue("media-bg-img"); gStrings.mediaBorderImg = await document.l10n.formatValue("media-border-img"); gStrings.mediaListImg = await document.l10n.formatValue("media-list-img"); gStrings.mediaCursor = await document.l10n.formatValue("media-cursor"); gStrings.mediaObject = await document.l10n.formatValue("media-object"); gStrings.mediaEmbed = await document.l10n.formatValue("media-embed"); gStrings.mediaLink = await document.l10n.formatValue("media-link"); gStrings.mediaInput = await document.l10n.formatValue("media-input"); gStrings.mediaVideo = await document.l10n.formatValue("media-video"); gStrings.mediaAudio = await document.l10n.formatValue("media-audio"); 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); Services.obs.notifyObservers(window, "page-info-dialog-loaded"); } function loadPageInfo(frameOuterWindowID, imageElement, browser) { browser = browser || window.opener.gBrowser.selectedBrowser; let mm = browser.messageManager; let imageInfo = imageElement; // Look for pageInfoListener in content.js. Sends message to listener with arguments. mm.sendAsyncMessage("PageInfo:getData", { strings: gStrings, frameOuterWindowID, }); let pageInfoData; // Get initial pageInfoData needed to display the general, permission and security tabs. mm.addMessageListener("PageInfo:data", async function onmessage(message) { mm.removeMessageListener("PageInfo:data", onmessage); pageInfoData = message.data; let docInfo = pageInfoData.docInfo; let windowInfo = pageInfoData.windowInfo; let uri = Services.io.newURI(docInfo.documentURIObject.spec); let principal = docInfo.principal; gDocInfo = docInfo; gImageElement = imageInfo; var titleFormat = windowInfo.isTopWindow ? "page-info-page" : "page-info-frame"; document.l10n.setAttributes(document.documentElement, titleFormat, { website: docInfo.location, }); document .getElementById("main-window") .setAttribute("relatedUrl", docInfo.location); await makeGeneralTab(pageInfoData.metaViewRows, docInfo); if ( uri.spec.startsWith("about:neterror") || uri.spec.startsWith("about:certerror") ) { uri = browser.currentURI; principal = Services.scriptSecurityManager.createCodebasePrincipal( uri, browser.contentPrincipal.originAttributes ); } 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) { Services.obs.removeObserver(imagePermissionObserver, "perm-changed"); mediaTab.hidden = true; } gImageView.clear(); gImageHash = {}; /* 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) { Services.obs.removeObserver(imagePermissionObserver, "perm-changed"); } /* Call registered overlay unload functions */ onUnloadRegistry.forEach(function(func) { func(); }); } function doHelpButton() { const helpTopics = { generalPanel: "pageinfo_general", mediaPanel: "pageinfo_media", permPanel: "pageinfo_permissions", securityPanel: "pageinfo_security", }; var deck = document.getElementById("mainDeck"); var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general"; openHelpLink(helpdoc); } function showTab(id) { var deck = document.getElementById("mainDeck"); var pagel = document.getElementById(id + "Panel"); deck.selectedPanel = pagel; } 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 openCacheEntry(key, cb) { var checkCacheListener = { onCacheEntryCheck(entry, appCache) { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; }, onCacheEntryAvailable(entry, isNew, appCache, status) { cb(entry); }, }; diskStorage.asyncOpenURI( Services.io.newURI(key), "", nsICacheStorage.OPEN_READONLY, checkCacheListener ); } async function makeGeneralTab(metaViewRows, docInfo) { // Sets Title in the General Tab, set to "Untitled Page" if no title found if (docInfo.title) { document.getElementById("titletext").value = docInfo.title; } else { document.l10n.setAttributes( document.getElementById("titletext"), "no-page-title" ); } var url = docInfo.location; setItemValue("urltext", url); var referrer = "referrer" in docInfo && docInfo.referrer; setItemValue("refertext", referrer); var mode = "compatMode" in docInfo && docInfo.compatMode == "BackCompat" ? "general-quirks-mode" : "general-strict-mode"; document.l10n.setAttributes(document.getElementById("modetext"), mode); // find out the mime type 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.style.visibility = "hidden"; } else { document.l10n.setAttributes( document.getElementById("metaTagsCaption"), "general-meta-tags", { tags: length } ); var metaTree = document.getElementById("metatree"); metaTree.view = gMetaView; // Add the metaViewRows onto the general tab's meta info tree. gMetaView.addRows(metaViewRows); metaGroup.style.removeProperty("visibility"); } var modifiedText = formatDate( docInfo.lastModified, await document.l10n.formatValue("not-set-date") ); document.getElementById("modifiedtext").value = modifiedText; // get cache info var cacheKey = url.replace(/#.*$/, ""); openCacheEntry(cacheKey, function(cacheEntry) { var sizeText; if (cacheEntry) { var pageSize = cacheEntry.dataSize; var kbSize = formatNumber(Math.round((pageSize / 1024) * 100) / 100); document.l10n.setAttributes( document.getElementById("sizetext"), "properties-general-size", { kb: kbSize, bytes: formatNumber(pageSize) } ); } else { setItemValue("sizetext", sizeText); } }); } async function addImage(imageViewRow) { let [url, type, alt, elem, isBg] = imageViewRow; if (!url) { return; } if (!gImageHash.hasOwnProperty(url)) { gImageHash[url] = {}; } if (!gImageHash[url].hasOwnProperty(type)) { gImageHash[url][type] = {}; } if (!gImageHash[url][type].hasOwnProperty(alt)) { gImageHash[url][type][alt] = gImageView.data.length; var row = [url, type, gStrings.unknown, alt, 1, elem, isBg]; gImageView.addRow(row); // Fill in cache data asynchronously openCacheEntry(url, function(cacheEntry) { // The data at row[2] corresponds to the data size. if (cacheEntry) { let value = cacheEntry.dataSize; // If value is not -1 then replace with actual value, else keep as "unknown" if (value != -1) { let kbSize = Number(Math.round((value / 1024) * 100) / 100); document.l10n .formatValue("media-file-size", { size: kbSize }) .then(function(response) { row[2] = response; // Invalidate the row to trigger a repaint. gImageView.tree.invalidateRow(gImageView.data.indexOf(row)); }); } } }); // Add the observer, only once. if (gImageView.data.length == 1) { document.getElementById("mediaTab").hidden = false; Services.obs.addObserver(imagePermissionObserver, "perm-changed"); } } else { var i = gImageHash[url][type][alt]; gImageView.data[i][COL_IMAGE_COUNT]++; // The same image can occur several times on the page at different sizes. // If the "View Image Info" context menu item was used, ensure we select // the correct element. if ( !gImageView.data[i][COL_IMAGE_BG] && gImageElement && url == gImageElement.currentSrc && gImageElement.width == elem.width && gImageElement.height == elem.height && gImageElement.imageText == elem.imageText ) { gImageView.data[i][COL_IMAGE_NODE] = elem; } } } // 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 (tree.localName != "tree") { tree = tree.parentNode; } var row = tree.getRowAt(event.clientX, event.clientY); if (row == -1) { return; } // Adding URL flavor var col = tree.columns[urlField]; var url = tree.view.getCellText(row, col); col = tree.columns[descField]; var desc = tree.view.getCellText(row, col); var dt = event.dataTransfer; dt.setData("text/x-moz-url", url + "\n" + desc); dt.setData("text/url-list", url); dt.setData("text/plain", url); } // Image Stuff function getSelectedRows(tree) { var start = {}; var end = {}; var numRanges = tree.view.selection.getRangeCount(); var rowArray = []; for (var t = 0; t < numRanges; t++) { tree.view.selection.getRangeAt(t, start, end); for (var v = start.value; v <= end.value; v++) { rowArray.push(v); } } return rowArray; } function getSelectedRow(tree) { var rows = getSelectedRows(tree); return rows.length == 1 ? rows[0] : -1; } async function selectSaveFolder(aCallback) { const nsIFile = Ci.nsIFile; const nsIFilePicker = Ci.nsIFilePicker; let titleText = await document.l10n.formatValue("media-select-folder"); let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); let fpCallback = function fpCallback_done(aResult) { if (aResult == nsIFilePicker.returnOK) { aCallback(fp.file.QueryInterface(nsIFile)); } else { aCallback(null); } }; fp.init(window, titleText, nsIFilePicker.modeGetFolder); fp.appendFilters(nsIFilePicker.filterAll); try { let initialDir = Services.prefs.getComplexValue( "browser.download.dir", nsIFile ); if (initialDir) { fp.displayDirectory = initialDir; } } catch (ex) {} fp.open(fpCallback); } function saveMedia() { var tree = document.getElementById("imagetree"); var rowArray = getSelectedRows(tree); let ReferrerInfo = Components.Constructor( "@mozilla.org/referrer-info;1", "nsIReferrerInfo", "init" ); if (rowArray.length == 1) { let row = rowArray[0]; let item = gImageView.data[row][COL_IMAGE_NODE]; let url = gImageView.data[row][COL_IMAGE_ADDRESS]; if (url) { var titleKey = "SaveImageTitle"; if (item instanceof HTMLVideoElement) { titleKey = "SaveVideoTitle"; } else if (item instanceof HTMLAudioElement) { titleKey = "SaveAudioTitle"; } // Bug 1565216 to evaluate passing referrer as item.baseURL let referrerInfo = new ReferrerInfo( Ci.nsIReferrerInfo.EMPTY, true, Services.io.newURI(item.baseURI) ); saveURL( url, null, titleKey, false, false, referrerInfo, null, gDocInfo.isContentWindowPrivate, gDocInfo.principal ); } } else { selectSaveFolder(function(aDirectory) { if (aDirectory) { var saveAnImage = function(aURIString, aChosenData, aBaseURI) { uniqueFile(aChosenData.file); let referrerInfo = new ReferrerInfo( Ci.nsIReferrerInfo.EMPTY, true, aBaseURI ); internalSave( aURIString, null, null, null, null, false, "SaveImageTitle", aChosenData, referrerInfo, null, false, null, gDocInfo.isContentWindowPrivate, gDocInfo.principal ); }; for (var i = 0; i < rowArray.length; i++) { let v = rowArray[i]; let dir = aDirectory.clone(); let item = gImageView.data[v][COL_IMAGE_NODE]; let uriString = gImageView.data[v][COL_IMAGE_ADDRESS]; let uri = Services.io.newURI(uriString); try { uri.QueryInterface(Ci.nsIURL); dir.append(decodeURIComponent(uri.fileName)); } catch (ex) { // data:/blob: uris // Supply a dummy filename, otherwise Download Manager // will try to delete the base directory on failure. dir.append(gImageView.data[v][COL_IMAGE_TYPE]); } if (i == 0) { saveAnImage( uriString, new AutoChosen(dir, uri), Services.io.newURI(item.baseURI) ); } else { // This delay is a hack which prevents the download manager // from opening many times. See bug 377339. setTimeout( saveAnImage, 200, uriString, new AutoChosen(dir, uri), Services.io.newURI(item.baseURI) ); } } } }); } } function onBlockImage() { var permissionManager = Cc[PERMISSION_CONTRACTID].getService( nsIPermissionManager ); var checkbox = document.getElementById("blockImage"); var uri = Services.io.newURI(document.getElementById("imageurltext").value); let principal = Services.scriptSecurityManager.createCodebasePrincipal( uri, gDocInfo.principal.originAttributes ); if (checkbox.checked) { permissionManager.addFromPrincipal( principal, "image", nsIPermissionManager.DENY_ACTION ); } else { permissionManager.removeFromPrincipal(principal, "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 if (cacheEntry) { let imageSize = cacheEntry.dataSize; var kbSize = Math.round((imageSize / 1024) * 100) / 100; document.l10n.setAttributes( document.getElementById("imagesizetext"), "properties-general-size", { kb: formatNumber(kbSize), bytes: formatNumber(imageSize) } ); } else { document.l10n.setAttributes( document.getElementById("imagesizetext"), "media-unknown-not-cached" ); } var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry); var numFrames = item.numFrames; var imageType; if (mimeType) { // We found the type, try to display it nicely let imageMimeType = /^image\/(.*)/i.exec(mimeType); if (imageMimeType) { imageType = imageMimeType[1].toUpperCase(); if (numFrames > 1) { document.l10n.setAttributes( document.getElementById("imagetypetext"), "media-animated-image-type", { type: imageType, frames: numFrames } ); } else { document.l10n.setAttributes( document.getElementById("imagetypetext"), "media-image-type", { type: imageType } ); } } else { // the MIME type doesn't begin with image/, display the raw type setItemValue("imagetypetext", mimeType); } } else { // We couldn't find the type, fall back to the value in the treeview setItemValue("imagetypetext", gImageView.data[row][COL_IMAGE_TYPE]); } var imageContainer = document.getElementById("theimagecontainer"); var oldImage = document.getElementById("thepreviewimage"); var isProtocolAllowed = checkProtocol(gImageView.data[row]); var newImage = new Image(); newImage.id = "thepreviewimage"; var physWidth = 0, physHeight = 0; var width = 0, height = 0; let triggeringPrinStr = E10SUtils.serializePrincipal(gDocInfo.principal); if ( (item.HTMLLinkElement || item.HTMLInputElement || item.HTMLImageElement || item.SVGImageElement || (item.HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed ) { // We need to wait for the image to finish loading before using width & height newImage.addEventListener( "loadend", function() { physWidth = newImage.width || 0; physHeight = newImage.height || 0; // "width" and "height" attributes must be set to newImage, // even if there is no "width" or "height attribute in item; // otherwise, the preview image cannot be displayed correctly. // Since the image might have been loaded out-of-process, we expect // the item to tell us its width / height dimensions. Failing that // the item should tell us the natural dimensions of the image. Finally // failing that, we'll assume that the image was never loaded in the // other process (this can be true for favicons, for example), and so // we'll assume that we can use the natural dimensions of the newImage // we just created. If the natural dimensions of newImage are not known // then the image is probably broken. if (!isBG) { newImage.width = ("width" in item && item.width) || newImage.naturalWidth; newImage.height = ("height" in item && item.height) || newImage.naturalHeight; } else { // the Width and Height of an HTML tag should not be used for its background image // (for example, "table" can have "width" or "height" attributes) newImage.width = item.naturalWidth || newImage.naturalWidth; newImage.height = item.naturalHeight || newImage.naturalHeight; } if (item.SVGImageElement) { newImage.width = item.SVGImageElementWidth; newImage.height = item.SVGImageElementHeight; } width = newImage.width; height = newImage.height; document.getElementById("theimagecontainer").collapsed = false; document.getElementById("brokenimagecontainer").collapsed = true; if (url) { if (width != physWidth || height != physHeight) { document.l10n.setAttributes( document.getElementById("imagedimensiontext"), "media-dimensions-scaled", { dimx: formatNumber(physWidth), dimy: formatNumber(physHeight), scaledx: formatNumber(width), scaledy: formatNumber(height), } ); } else { document.l10n.setAttributes( document.getElementById("imagedimensiontext"), "media-dimensions", { dimx: formatNumber(width), dimy: formatNumber(height) } ); } } }, { once: true } ); newImage.setAttribute("triggeringprincipal", triggeringPrinStr); newImage.setAttribute("src", url); } else { // Handle the case where newImage is not used for width & height if (item.HTMLVideoElement && isProtocolAllowed) { newImage = document.createElementNS( "http://www.w3.org/1999/xhtml", "video" ); newImage.id = "thepreviewimage"; newImage.setAttribute("triggeringprincipal", triggeringPrinStr); newImage.src = url; newImage.controls = true; width = physWidth = item.videoWidth; height = physHeight = item.videoHeight; document.getElementById("theimagecontainer").collapsed = false; document.getElementById("brokenimagecontainer").collapsed = true; } else if (item.HTMLAudioElement && isProtocolAllowed) { newImage = new Audio(); newImage.id = "thepreviewimage"; newImage.setAttribute("triggeringprincipal", triggeringPrinStr); newImage.src = url; newImage.controls = true; isAudio = true; document.getElementById("theimagecontainer").collapsed = false; document.getElementById("brokenimagecontainer").collapsed = true; } else { // fallback image for protocols not allowed (e.g., javascript:) // or elements not [yet] handled (e.g., object, embed). document.getElementById("brokenimagecontainer").collapsed = false; document.getElementById("theimagecontainer").collapsed = true; } if (url && !isAudio) { document.l10n.setAttributes( document.getElementById("imagedimensiontext"), "media-dimensions", { dimx: formatNumber(width), dimy: formatNumber(height) } ); } } makeBlockImage(url); imageContainer.removeChild(oldImage); imageContainer.appendChild(newImage); }); } function makeBlockImage(url) { var permissionManager = Cc[PERMISSION_CONTRACTID].getService( nsIPermissionManager ); var checkbox = document.getElementById("blockImage"); var imagePref = Services.prefs.getIntPref("permissions.default.image"); if (!/^https?:/.test(url) || imagePref == 2) { // We can't block the images from this host because either is is not // for http(s) or we don't load images at all checkbox.hidden = true; } else { var uri = Services.io.newURI(url); if (uri.host) { checkbox.hidden = false; document.l10n.setAttributes(checkbox, "media-block-image", { website: uri.host, }); let principal = Services.scriptSecurityManager.createCodebasePrincipal( uri, gDocInfo.principal.originAttributes ); let perm = permissionManager.testPermissionFromPrincipal( principal, "image" ); checkbox.checked = perm == nsIPermissionManager.DENY_ACTION; } else { checkbox.hidden = true; } } } var imagePermissionObserver = { observe(aSubject, aTopic, aData) { if (document.getElementById("mediaPreviewBox").collapsed) { return; } if (aTopic == "perm-changed") { var permission = aSubject.QueryInterface(Ci.nsIPermission); if (permission.type == "image") { var imageTree = document.getElementById("imagetree"); var row = getSelectedRow(imageTree); var url = gImageView.data[row][COL_IMAGE_ADDRESS]; if (permission.matchesURI(Services.io.newURI(url), true)) { makeBlockImage(url); } } } }, }; function getContentTypeFromHeaders(cacheEntryDescriptor) { if (!cacheEntryDescriptor) { return null; } let headers = cacheEntryDescriptor.getMetaDataElement("response-head"); let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/im.exec(headers); return type && type[1]; } function setItemValue(id, value) { var item = document.getElementById(id); 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 dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, { dateStyle: "long", timeStyle: "long", }); return dateTimeFormatter.format(date); } function doSelectAllMedia() { var tree = document.getElementById("imagetree"); if (tree) { tree.view.selection.selectAll(); } } function doSelectAll() { var elem = document.commandDispatcher.focusedElement; if (elem && elem.localName == "tree") { 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.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) ); }