michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Cu = Components.utils; michael@0: Cu.import("resource://gre/modules/LoadContextInfo.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: //******** define a js object to implement nsITreeView michael@0: function pageInfoTreeView(treeid, copycol) michael@0: { michael@0: // copycol is the index number for the column that we want to add to michael@0: // the copy-n-paste buffer when the user hits accel-c michael@0: this.treeid = treeid; michael@0: this.copycol = copycol; michael@0: this.rows = 0; michael@0: this.tree = null; michael@0: this.data = [ ]; michael@0: this.selection = null; michael@0: this.sortcol = -1; michael@0: this.sortdir = false; michael@0: } michael@0: michael@0: pageInfoTreeView.prototype = { michael@0: set rowCount(c) { throw "rowCount is a readonly property"; }, michael@0: get rowCount() { return this.rows; }, michael@0: michael@0: setTree: function(tree) michael@0: { michael@0: this.tree = tree; michael@0: }, michael@0: michael@0: getCellText: function(row, column) michael@0: { michael@0: // row can be null, but js arrays are 0-indexed. michael@0: // colidx cannot be null, but can be larger than the number michael@0: // of columns in the array. In this case it's the fault of michael@0: // whoever typoed while calling this function. michael@0: return this.data[row][column.index] || ""; michael@0: }, michael@0: michael@0: setCellValue: function(row, column, value) michael@0: { michael@0: }, michael@0: michael@0: setCellText: function(row, column, value) michael@0: { michael@0: this.data[row][column.index] = value; michael@0: }, michael@0: michael@0: addRow: function(row) michael@0: { michael@0: this.rows = this.data.push(row); michael@0: this.rowCountChanged(this.rows - 1, 1); michael@0: if (this.selection.count == 0 && this.rowCount && !gImageElement) michael@0: this.selection.select(0); michael@0: }, michael@0: michael@0: rowCountChanged: function(index, count) michael@0: { michael@0: this.tree.rowCountChanged(index, count); michael@0: }, michael@0: michael@0: invalidate: function() michael@0: { michael@0: this.tree.invalidate(); michael@0: }, michael@0: michael@0: clear: function() michael@0: { michael@0: if (this.tree) michael@0: this.tree.rowCountChanged(0, -this.rows); michael@0: this.rows = 0; michael@0: this.data = [ ]; michael@0: }, michael@0: michael@0: handleCopy: function(row) michael@0: { michael@0: return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || ""); michael@0: }, michael@0: michael@0: performActionOnRow: function(action, row) michael@0: { michael@0: if (action == "copy") { michael@0: var data = this.handleCopy(row) michael@0: this.tree.treeBody.parentNode.setAttribute("copybuffer", data); michael@0: } michael@0: }, michael@0: michael@0: onPageMediaSort : function(columnname) michael@0: { michael@0: var tree = document.getElementById(this.treeid); michael@0: var treecol = tree.columns.getNamedColumn(columnname); michael@0: michael@0: this.sortdir = michael@0: gTreeUtils.sort( michael@0: tree, michael@0: this, michael@0: this.data, michael@0: treecol.index, michael@0: function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }, michael@0: this.sortcol, michael@0: this.sortdir michael@0: ); michael@0: michael@0: this.sortcol = treecol.index; michael@0: }, michael@0: michael@0: getRowProperties: function(row) { return ""; }, michael@0: getCellProperties: function(row, column) { return ""; }, michael@0: getColumnProperties: function(column) { return ""; }, michael@0: isContainer: function(index) { return false; }, michael@0: isContainerOpen: function(index) { return false; }, michael@0: isSeparator: function(index) { return false; }, michael@0: isSorted: function() { }, michael@0: canDrop: function(index, orientation) { return false; }, michael@0: drop: function(row, orientation) { return false; }, michael@0: getParentIndex: function(index) { return 0; }, michael@0: hasNextSibling: function(index, after) { return false; }, michael@0: getLevel: function(index) { return 0; }, michael@0: getImageSrc: function(row, column) { }, michael@0: getProgressMode: function(row, column) { }, michael@0: getCellValue: function(row, column) { }, michael@0: toggleOpenState: function(index) { }, michael@0: cycleHeader: function(col) { }, michael@0: selectionChanged: function() { }, michael@0: cycleCell: function(row, column) { }, michael@0: isEditable: function(row, column) { return false; }, michael@0: isSelectable: function(row, column) { return false; }, michael@0: performAction: function(action) { }, michael@0: performActionOnCell: function(action, row, column) { } michael@0: }; michael@0: michael@0: // mmm, yummy. global variables. michael@0: var gWindow = null; michael@0: var gDocument = null; michael@0: var gImageElement = null; michael@0: michael@0: // column number to help using the data array michael@0: const COL_IMAGE_ADDRESS = 0; michael@0: const COL_IMAGE_TYPE = 1; michael@0: const COL_IMAGE_SIZE = 2; michael@0: const COL_IMAGE_ALT = 3; michael@0: const COL_IMAGE_COUNT = 4; michael@0: const COL_IMAGE_NODE = 5; michael@0: const COL_IMAGE_BG = 6; michael@0: michael@0: // column number to copy from, second argument to pageInfoTreeView's constructor michael@0: const COPYCOL_NONE = -1; michael@0: const COPYCOL_META_CONTENT = 1; michael@0: const COPYCOL_IMAGE = COL_IMAGE_ADDRESS; michael@0: michael@0: // one nsITreeView for each tree in the window michael@0: var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT); michael@0: var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE); michael@0: michael@0: gImageView.getCellProperties = function(row, col) { michael@0: var data = gImageView.data[row]; michael@0: var item = gImageView.data[row][COL_IMAGE_NODE]; michael@0: var props = ""; michael@0: if (!checkProtocol(data) || michael@0: item instanceof HTMLEmbedElement || michael@0: (item instanceof HTMLObjectElement && !item.type.startsWith("image/"))) michael@0: props += "broken"; michael@0: michael@0: if (col.element.id == "image-address") michael@0: props += " ltr"; michael@0: michael@0: return props; michael@0: }; michael@0: michael@0: gImageView.getCellText = function(row, column) { michael@0: var value = this.data[row][column.index]; michael@0: if (column.index == COL_IMAGE_SIZE) { michael@0: if (value == -1) { michael@0: return gStrings.unknown; michael@0: } else { michael@0: var kbSize = Number(Math.round(value / 1024 * 100) / 100); michael@0: return gBundle.getFormattedString("mediaFileSize", [kbSize]); michael@0: } michael@0: } michael@0: return value || ""; michael@0: }; michael@0: michael@0: gImageView.onPageMediaSort = function(columnname) { michael@0: var tree = document.getElementById(this.treeid); michael@0: var treecol = tree.columns.getNamedColumn(columnname); michael@0: michael@0: var comparator; michael@0: if (treecol.index == COL_IMAGE_SIZE || treecol.index == COL_IMAGE_COUNT) { michael@0: comparator = function numComparator(a, b) { return a - b; }; michael@0: } else { michael@0: comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }; michael@0: } michael@0: michael@0: this.sortdir = michael@0: gTreeUtils.sort( michael@0: tree, michael@0: this, michael@0: this.data, michael@0: treecol.index, michael@0: comparator, michael@0: this.sortcol, michael@0: this.sortdir michael@0: ); michael@0: michael@0: this.sortcol = treecol.index; michael@0: }; michael@0: michael@0: var gImageHash = { }; michael@0: michael@0: // localized strings (will be filled in when the document is loaded) michael@0: // this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop michael@0: var gStrings = { }; michael@0: var gBundle; michael@0: michael@0: const PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1"; michael@0: const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1"; michael@0: const ATOM_CONTRACTID = "@mozilla.org/atom-service;1"; michael@0: michael@0: // a number of services I'll need later michael@0: // the cache services michael@0: const nsICacheStorageService = Components.interfaces.nsICacheStorageService; michael@0: const nsICacheStorage = Components.interfaces.nsICacheStorage; michael@0: const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService); michael@0: michael@0: var loadContextInfo = LoadContextInfo.fromLoadContext( michael@0: window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIWebNavigation) michael@0: .QueryInterface(Components.interfaces.nsILoadContext), false); michael@0: var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false); michael@0: michael@0: const nsICookiePermission = Components.interfaces.nsICookiePermission; michael@0: const nsIPermissionManager = Components.interfaces.nsIPermissionManager; michael@0: michael@0: const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs; michael@0: const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1" michael@0: michael@0: // clipboard helper michael@0: try { michael@0: const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper); michael@0: } michael@0: catch(e) { michael@0: // do nothing, later code will handle the error michael@0: } michael@0: michael@0: // Interface for image loading content michael@0: const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent; michael@0: michael@0: // namespaces, don't need all of these yet... michael@0: const XLinkNS = "http://www.w3.org/1999/xlink"; michael@0: const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: const XMLNS = "http://www.w3.org/XML/1998/namespace"; michael@0: const XHTMLNS = "http://www.w3.org/1999/xhtml"; michael@0: const XHTML2NS = "http://www.w3.org/2002/06/xhtml2" michael@0: michael@0: const XHTMLNSre = "^http\:\/\/www\.w3\.org\/1999\/xhtml$"; michael@0: const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$"; michael@0: const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, ""); michael@0: michael@0: /* Overlays register functions here. michael@0: * These arrays are used to hold callbacks that Page Info will call at michael@0: * various stages. Use them by simply appending a function to them. michael@0: * For example, add a function to onLoadRegistry by invoking michael@0: * "onLoadRegistry.push(XXXLoadFunc);" michael@0: * The XXXLoadFunc should be unique to the overlay module, and will be michael@0: * invoked as "XXXLoadFunc();" michael@0: */ michael@0: michael@0: // These functions are called to build the data displayed in the Page michael@0: // Info window. The global variables gDocument and gWindow are set. michael@0: var onLoadRegistry = [ ]; michael@0: michael@0: // These functions are called to remove old data still displayed in michael@0: // the window when the document whose information is displayed michael@0: // changes. For example, at this time, the list of images of the Media michael@0: // tab is cleared. michael@0: var onResetRegistry = [ ]; michael@0: michael@0: // These are called once for each subframe of the target document and michael@0: // the target document itself. The frame is passed as an argument. michael@0: var onProcessFrame = [ ]; michael@0: michael@0: // These functions are called once for each element (in all subframes, if any) michael@0: // in the target document. The element is passed as an argument. michael@0: var onProcessElement = [ ]; michael@0: michael@0: // These functions are called once when all the elements in all of the target michael@0: // document (and all of its subframes, if any) have been processed michael@0: var onFinished = [ ]; michael@0: michael@0: // These functions are called once when the Page Info window is closed. michael@0: var onUnloadRegistry = [ ]; michael@0: michael@0: // These functions are called once when an image preview is shown. michael@0: var onImagePreviewShown = [ ]; michael@0: michael@0: /* Called when PageInfo window is loaded. Arguments are: michael@0: * window.arguments[0] - (optional) an object consisting of michael@0: * - doc: (optional) document to use for source. if not provided, michael@0: * the calling window's document will be used michael@0: * - initialTab: (optional) id of the inital tab to display michael@0: */ michael@0: function onLoadPageInfo() michael@0: { michael@0: gBundle = document.getElementById("pageinfobundle"); michael@0: gStrings.unknown = gBundle.getString("unknown"); michael@0: gStrings.notSet = gBundle.getString("notset"); michael@0: gStrings.mediaImg = gBundle.getString("mediaImg"); michael@0: gStrings.mediaBGImg = gBundle.getString("mediaBGImg"); michael@0: gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg"); michael@0: gStrings.mediaListImg = gBundle.getString("mediaListImg"); michael@0: gStrings.mediaCursor = gBundle.getString("mediaCursor"); michael@0: gStrings.mediaObject = gBundle.getString("mediaObject"); michael@0: gStrings.mediaEmbed = gBundle.getString("mediaEmbed"); michael@0: gStrings.mediaLink = gBundle.getString("mediaLink"); michael@0: gStrings.mediaInput = gBundle.getString("mediaInput"); michael@0: gStrings.mediaVideo = gBundle.getString("mediaVideo"); michael@0: gStrings.mediaAudio = gBundle.getString("mediaAudio"); michael@0: michael@0: var args = "arguments" in window && michael@0: window.arguments.length >= 1 && michael@0: window.arguments[0]; michael@0: michael@0: if (!args || !args.doc) { michael@0: gWindow = window.opener.content; michael@0: gDocument = gWindow.document; michael@0: } michael@0: michael@0: // init media view michael@0: var imageTree = document.getElementById("imagetree"); michael@0: imageTree.view = gImageView; michael@0: michael@0: /* Select the requested tab, if the name is specified */ michael@0: loadTab(args); michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .notifyObservers(window, "page-info-dialog-loaded", null); michael@0: } michael@0: michael@0: function loadPageInfo() michael@0: { michael@0: var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title" michael@0: : "pageInfo.page.title"; michael@0: document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]); michael@0: michael@0: document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location); michael@0: michael@0: // do the easy stuff first michael@0: makeGeneralTab(); michael@0: michael@0: // and then the hard stuff michael@0: makeTabs(gDocument, gWindow); michael@0: michael@0: initFeedTab(); michael@0: onLoadPermission(); michael@0: michael@0: /* Call registered overlay init functions */ michael@0: onLoadRegistry.forEach(function(func) { func(); }); michael@0: } michael@0: michael@0: function resetPageInfo(args) michael@0: { michael@0: /* Reset Meta tags part */ michael@0: gMetaView.clear(); michael@0: michael@0: /* Reset Media tab */ michael@0: var mediaTab = document.getElementById("mediaTab"); michael@0: if (!mediaTab.hidden) { michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .removeObserver(imagePermissionObserver, "perm-changed"); michael@0: mediaTab.hidden = true; michael@0: } michael@0: gImageView.clear(); michael@0: gImageHash = {}; michael@0: michael@0: /* Reset Feeds Tab */ michael@0: var feedListbox = document.getElementById("feedListbox"); michael@0: while (feedListbox.firstChild) michael@0: feedListbox.removeChild(feedListbox.firstChild); michael@0: michael@0: /* Call registered overlay reset functions */ michael@0: onResetRegistry.forEach(function(func) { func(); }); michael@0: michael@0: /* Rebuild the data */ michael@0: loadTab(args); michael@0: } michael@0: michael@0: function onUnloadPageInfo() michael@0: { michael@0: // Remove the observer, only if there is at least 1 image. michael@0: if (!document.getElementById("mediaTab").hidden) { michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .removeObserver(imagePermissionObserver, "perm-changed"); michael@0: } michael@0: michael@0: /* Call registered overlay unload functions */ michael@0: onUnloadRegistry.forEach(function(func) { func(); }); michael@0: } michael@0: michael@0: function doHelpButton() michael@0: { michael@0: const helpTopics = { michael@0: "generalPanel": "pageinfo_general", michael@0: "mediaPanel": "pageinfo_media", michael@0: "feedPanel": "pageinfo_feed", michael@0: "permPanel": "pageinfo_permissions", michael@0: "securityPanel": "pageinfo_security" michael@0: }; michael@0: michael@0: var deck = document.getElementById("mainDeck"); michael@0: var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general"; michael@0: openHelpLink(helpdoc); michael@0: } michael@0: michael@0: function showTab(id) michael@0: { michael@0: var deck = document.getElementById("mainDeck"); michael@0: var pagel = document.getElementById(id + "Panel"); michael@0: deck.selectedPanel = pagel; michael@0: } michael@0: michael@0: function loadTab(args) michael@0: { michael@0: if (args && args.doc) { michael@0: gDocument = args.doc; michael@0: gWindow = gDocument.defaultView; michael@0: } michael@0: michael@0: gImageElement = args && args.imageElement; michael@0: michael@0: /* Load the page info */ michael@0: loadPageInfo(); michael@0: michael@0: var initialTab = (args && args.initialTab) || "generalTab"; michael@0: var radioGroup = document.getElementById("viewGroup"); michael@0: initialTab = document.getElementById(initialTab) || document.getElementById("generalTab"); michael@0: radioGroup.selectedItem = initialTab; michael@0: radioGroup.selectedItem.doCommand(); michael@0: radioGroup.focus(); michael@0: } michael@0: michael@0: function onClickMore() michael@0: { michael@0: var radioGrp = document.getElementById("viewGroup"); michael@0: var radioElt = document.getElementById("securityTab"); michael@0: radioGrp.selectedItem = radioElt; michael@0: showTab('security'); michael@0: } michael@0: michael@0: function toggleGroupbox(id) michael@0: { michael@0: var elt = document.getElementById(id); michael@0: if (elt.hasAttribute("closed")) { michael@0: elt.removeAttribute("closed"); michael@0: if (elt.flexWhenOpened) michael@0: elt.flex = elt.flexWhenOpened; michael@0: } michael@0: else { michael@0: elt.setAttribute("closed", "true"); michael@0: if (elt.flex) { michael@0: elt.flexWhenOpened = elt.flex; michael@0: elt.flex = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: function openCacheEntry(key, cb) michael@0: { michael@0: var checkCacheListener = { michael@0: onCacheEntryCheck: function(entry, appCache) { michael@0: return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED; michael@0: }, michael@0: onCacheEntryAvailable: function(entry, isNew, appCache, status) { michael@0: cb(entry); michael@0: } michael@0: }; michael@0: diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener); michael@0: } michael@0: michael@0: function makeGeneralTab() michael@0: { michael@0: var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle"); michael@0: document.getElementById("titletext").value = title; michael@0: michael@0: var url = gDocument.location.toString(); michael@0: setItemValue("urltext", url); michael@0: michael@0: var referrer = ("referrer" in gDocument && gDocument.referrer); michael@0: setItemValue("refertext", referrer); michael@0: michael@0: var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode"; michael@0: document.getElementById("modetext").value = gBundle.getString(mode); michael@0: michael@0: // find out the mime type michael@0: var mimeType = gDocument.contentType; michael@0: setItemValue("typetext", mimeType); michael@0: michael@0: // get the document characterset michael@0: var encoding = gDocument.characterSet; michael@0: document.getElementById("encodingtext").value = encoding; michael@0: michael@0: // get the meta tags michael@0: var metaNodes = gDocument.getElementsByTagName("meta"); michael@0: var length = metaNodes.length; michael@0: michael@0: var metaGroup = document.getElementById("metaTags"); michael@0: if (!length) michael@0: metaGroup.collapsed = true; michael@0: else { michael@0: var metaTagsCaption = document.getElementById("metaTagsCaption"); michael@0: if (length == 1) michael@0: metaTagsCaption.label = gBundle.getString("generalMetaTag"); michael@0: else michael@0: metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]); michael@0: var metaTree = document.getElementById("metatree"); michael@0: metaTree.treeBoxObject.view = gMetaView; michael@0: michael@0: for (var i = 0; i < length; i++) michael@0: gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]); michael@0: michael@0: metaGroup.collapsed = false; michael@0: } michael@0: michael@0: // get the date of last modification michael@0: var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet); michael@0: document.getElementById("modifiedtext").value = modifiedText; michael@0: michael@0: // get cache info michael@0: var cacheKey = url.replace(/#.*$/, ""); michael@0: openCacheEntry(cacheKey, function(cacheEntry) { michael@0: var sizeText; michael@0: if (cacheEntry) { michael@0: var pageSize = cacheEntry.dataSize; michael@0: var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100); michael@0: sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]); michael@0: } michael@0: setItemValue("sizetext", sizeText); michael@0: }); michael@0: michael@0: securityOnLoad(); michael@0: } michael@0: michael@0: //******** Generic Build-a-tab michael@0: // Assumes the views are empty. Only called once to build the tabs, and michael@0: // does so by farming the task off to another thread via setTimeout(). michael@0: // The actual work is done with a TreeWalker that calls doGrab() once for michael@0: // each element node in the document. michael@0: michael@0: var gFrameList = [ ]; michael@0: michael@0: function makeTabs(aDocument, aWindow) michael@0: { michael@0: goThroughFrames(aDocument, aWindow); michael@0: processFrames(); michael@0: } michael@0: michael@0: function goThroughFrames(aDocument, aWindow) michael@0: { michael@0: gFrameList.push(aDocument); michael@0: if (aWindow && aWindow.frames.length > 0) { michael@0: var num = aWindow.frames.length; michael@0: for (var i = 0; i < num; i++) michael@0: goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames michael@0: } michael@0: } michael@0: michael@0: function processFrames() michael@0: { michael@0: if (gFrameList.length) { michael@0: var doc = gFrameList[0]; michael@0: onProcessFrame.forEach(function(func) { func(doc); }); michael@0: var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll); michael@0: gFrameList.shift(); michael@0: setTimeout(doGrab, 10, iterator); michael@0: onFinished.push(selectImage); michael@0: } michael@0: else michael@0: onFinished.forEach(function(func) { func(); }); michael@0: } michael@0: michael@0: function doGrab(iterator) michael@0: { michael@0: for (var i = 0; i < 500; ++i) michael@0: if (!iterator.nextNode()) { michael@0: processFrames(); michael@0: return; michael@0: } michael@0: michael@0: setTimeout(doGrab, 10, iterator); michael@0: } michael@0: michael@0: function addImage(url, type, alt, elem, isBg) michael@0: { michael@0: if (!url) michael@0: return; michael@0: michael@0: if (!gImageHash.hasOwnProperty(url)) michael@0: gImageHash[url] = { }; michael@0: if (!gImageHash[url].hasOwnProperty(type)) michael@0: gImageHash[url][type] = { }; michael@0: if (!gImageHash[url][type].hasOwnProperty(alt)) { michael@0: gImageHash[url][type][alt] = gImageView.data.length; michael@0: var row = [url, type, -1, alt, 1, elem, isBg]; michael@0: gImageView.addRow(row); michael@0: michael@0: // Fill in cache data asynchronously michael@0: openCacheEntry(url, function(cacheEntry) { michael@0: // The data at row[2] corresponds to the data size. michael@0: if (cacheEntry) { michael@0: row[2] = cacheEntry.dataSize; michael@0: // Invalidate the row to trigger a repaint. michael@0: gImageView.tree.invalidateRow(gImageView.data.indexOf(row)); michael@0: } michael@0: }); michael@0: michael@0: // Add the observer, only once. michael@0: if (gImageView.data.length == 1) { michael@0: document.getElementById("mediaTab").hidden = false; michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .addObserver(imagePermissionObserver, "perm-changed", false); michael@0: } michael@0: } michael@0: else { michael@0: var i = gImageHash[url][type][alt]; michael@0: gImageView.data[i][COL_IMAGE_COUNT]++; michael@0: if (elem == gImageElement) michael@0: gImageView.data[i][COL_IMAGE_NODE] = elem; michael@0: } michael@0: } michael@0: michael@0: function grabAll(elem) michael@0: { michael@0: // check for images defined in CSS (e.g. background, borders), any node may have multiple michael@0: var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, ""); michael@0: michael@0: if (computedStyle) { michael@0: var addImgFunc = function (label, val) { michael@0: if (val.primitiveType == CSSPrimitiveValue.CSS_URI) { michael@0: addImage(val.getStringValue(), label, gStrings.notSet, elem, true); michael@0: } michael@0: else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) { michael@0: // This is for -moz-image-rect. michael@0: // TODO: Reimplement once bug 714757 is fixed michael@0: var strVal = val.getStringValue(); michael@0: if (strVal.search(/^.*url\(\"?/) > -1) { michael@0: url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,""); michael@0: addImage(url, label, gStrings.notSet, elem, true); michael@0: } michael@0: } michael@0: else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) { michael@0: // recursively resolve multiple nested CSS value lists michael@0: for (var i = 0; i < val.length; i++) michael@0: addImgFunc(label, val.item(i)); michael@0: } michael@0: }; michael@0: michael@0: addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image")); michael@0: addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source")); michael@0: addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image")); michael@0: addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor")); michael@0: } michael@0: michael@0: // one swi^H^H^Hif-else to rule them all michael@0: if (elem instanceof HTMLImageElement) michael@0: addImage(elem.src, gStrings.mediaImg, michael@0: (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false); michael@0: else if (elem instanceof SVGImageElement) { michael@0: try { michael@0: // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI michael@0: // or the URI formed from the baseURI and the URL is not a valid URI michael@0: var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal); michael@0: addImage(href, gStrings.mediaImg, "", elem, false); michael@0: } catch (e) { } michael@0: } michael@0: else if (elem instanceof HTMLVideoElement) { michael@0: addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false); michael@0: } michael@0: else if (elem instanceof HTMLAudioElement) { michael@0: addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false); michael@0: } michael@0: else if (elem instanceof HTMLLinkElement) { michael@0: if (elem.rel && /\bicon\b/i.test(elem.rel)) michael@0: addImage(elem.href, gStrings.mediaLink, "", elem, false); michael@0: } michael@0: else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) { michael@0: if (elem.type.toLowerCase() == "image") michael@0: addImage(elem.src, gStrings.mediaInput, michael@0: (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false); michael@0: } michael@0: else if (elem instanceof HTMLObjectElement) michael@0: addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false); michael@0: else if (elem instanceof HTMLEmbedElement) michael@0: addImage(elem.src, gStrings.mediaEmbed, "", elem, false); michael@0: michael@0: onProcessElement.forEach(function(func) { func(elem); }); michael@0: michael@0: return NodeFilter.FILTER_ACCEPT; michael@0: } michael@0: michael@0: //******** Link Stuff michael@0: function openURL(target) michael@0: { michael@0: var url = target.parentNode.childNodes[2].value; michael@0: window.open(url, "_blank", "chrome"); michael@0: } michael@0: michael@0: function onBeginLinkDrag(event,urlField,descField) michael@0: { michael@0: if (event.originalTarget.localName != "treechildren") michael@0: return; michael@0: michael@0: var tree = event.target; michael@0: if (!("treeBoxObject" in tree)) michael@0: tree = tree.parentNode; michael@0: michael@0: var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY); michael@0: if (row == -1) michael@0: return; michael@0: michael@0: // Adding URL flavor michael@0: var col = tree.columns[urlField]; michael@0: var url = tree.view.getCellText(row, col); michael@0: col = tree.columns[descField]; michael@0: var desc = tree.view.getCellText(row, col); michael@0: michael@0: var dt = event.dataTransfer; michael@0: dt.setData("text/x-moz-url", url + "\n" + desc); michael@0: dt.setData("text/url-list", url); michael@0: dt.setData("text/plain", url); michael@0: } michael@0: michael@0: //******** Image Stuff michael@0: function getSelectedRows(tree) michael@0: { michael@0: var start = { }; michael@0: var end = { }; michael@0: var numRanges = tree.view.selection.getRangeCount(); michael@0: michael@0: var rowArray = [ ]; michael@0: for (var t = 0; t < numRanges; t++) { michael@0: tree.view.selection.getRangeAt(t, start, end); michael@0: for (var v = start.value; v <= end.value; v++) michael@0: rowArray.push(v); michael@0: } michael@0: michael@0: return rowArray; michael@0: } michael@0: michael@0: function getSelectedRow(tree) michael@0: { michael@0: var rows = getSelectedRows(tree); michael@0: return (rows.length == 1) ? rows[0] : -1; michael@0: } michael@0: michael@0: function selectSaveFolder(aCallback) michael@0: { michael@0: const nsILocalFile = Components.interfaces.nsILocalFile; michael@0: const nsIFilePicker = Components.interfaces.nsIFilePicker; michael@0: let titleText = gBundle.getString("mediaSelectFolder"); michael@0: let fp = Components.classes["@mozilla.org/filepicker;1"]. michael@0: createInstance(nsIFilePicker); michael@0: let fpCallback = function fpCallback_done(aResult) { michael@0: if (aResult == nsIFilePicker.returnOK) { michael@0: aCallback(fp.file.QueryInterface(nsILocalFile)); michael@0: } else { michael@0: aCallback(null); michael@0: } michael@0: }; michael@0: michael@0: fp.init(window, titleText, nsIFilePicker.modeGetFolder); michael@0: fp.appendFilters(nsIFilePicker.filterAll); michael@0: try { michael@0: let prefs = Components.classes[PREFERENCES_CONTRACTID]. michael@0: getService(Components.interfaces.nsIPrefBranch); michael@0: let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile); michael@0: if (initialDir) { michael@0: fp.displayDirectory = initialDir; michael@0: } michael@0: } catch (ex) { michael@0: } michael@0: fp.open(fpCallback); michael@0: } michael@0: michael@0: function saveMedia() michael@0: { michael@0: var tree = document.getElementById("imagetree"); michael@0: var rowArray = getSelectedRows(tree); michael@0: if (rowArray.length == 1) { michael@0: var row = rowArray[0]; michael@0: var item = gImageView.data[row][COL_IMAGE_NODE]; michael@0: var url = gImageView.data[row][COL_IMAGE_ADDRESS]; michael@0: michael@0: if (url) { michael@0: var titleKey = "SaveImageTitle"; michael@0: michael@0: if (item instanceof HTMLVideoElement) michael@0: titleKey = "SaveVideoTitle"; michael@0: else if (item instanceof HTMLAudioElement) michael@0: titleKey = "SaveAudioTitle"; michael@0: michael@0: saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), gDocument); michael@0: } michael@0: } else { michael@0: selectSaveFolder(function(aDirectory) { michael@0: if (aDirectory) { michael@0: var saveAnImage = function(aURIString, aChosenData, aBaseURI) { michael@0: internalSave(aURIString, null, null, null, null, false, "SaveImageTitle", michael@0: aChosenData, aBaseURI, gDocument); michael@0: }; michael@0: michael@0: for (var i = 0; i < rowArray.length; i++) { michael@0: var v = rowArray[i]; michael@0: var dir = aDirectory.clone(); michael@0: var item = gImageView.data[v][COL_IMAGE_NODE]; michael@0: var uriString = gImageView.data[v][COL_IMAGE_ADDRESS]; michael@0: var uri = makeURI(uriString); michael@0: michael@0: try { michael@0: uri.QueryInterface(Components.interfaces.nsIURL); michael@0: dir.append(decodeURIComponent(uri.fileName)); michael@0: } catch(ex) { michael@0: /* data: uris */ michael@0: } michael@0: michael@0: if (i == 0) { michael@0: saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI)); michael@0: } else { michael@0: // This delay is a hack which prevents the download manager michael@0: // from opening many times. See bug 377339. michael@0: setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri), michael@0: makeURI(item.baseURI)); michael@0: } michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: function onBlockImage() michael@0: { michael@0: var permissionManager = Components.classes[PERMISSION_CONTRACTID] michael@0: .getService(nsIPermissionManager); michael@0: michael@0: var checkbox = document.getElementById("blockImage"); michael@0: var uri = makeURI(document.getElementById("imageurltext").value); michael@0: if (checkbox.checked) michael@0: permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION); michael@0: else michael@0: permissionManager.remove(uri.host, "image"); michael@0: } michael@0: michael@0: function onImageSelect() michael@0: { michael@0: var previewBox = document.getElementById("mediaPreviewBox"); michael@0: var mediaSaveBox = document.getElementById("mediaSaveBox"); michael@0: var splitter = document.getElementById("mediaSplitter"); michael@0: var tree = document.getElementById("imagetree"); michael@0: var count = tree.view.selection.count; michael@0: if (count == 0) { michael@0: previewBox.collapsed = true; michael@0: mediaSaveBox.collapsed = true; michael@0: splitter.collapsed = true; michael@0: tree.flex = 1; michael@0: } michael@0: else if (count > 1) { michael@0: splitter.collapsed = true; michael@0: previewBox.collapsed = true; michael@0: mediaSaveBox.collapsed = false; michael@0: tree.flex = 1; michael@0: } michael@0: else { michael@0: mediaSaveBox.collapsed = true; michael@0: splitter.collapsed = false; michael@0: previewBox.collapsed = false; michael@0: tree.flex = 0; michael@0: makePreview(getSelectedRows(tree)[0]); michael@0: } michael@0: } michael@0: michael@0: function makePreview(row) michael@0: { michael@0: var imageTree = document.getElementById("imagetree"); michael@0: var item = gImageView.data[row][COL_IMAGE_NODE]; michael@0: var url = gImageView.data[row][COL_IMAGE_ADDRESS]; michael@0: var isBG = gImageView.data[row][COL_IMAGE_BG]; michael@0: var isAudio = false; michael@0: michael@0: setItemValue("imageurltext", url); michael@0: michael@0: var imageText; michael@0: if (!isBG && michael@0: !(item instanceof SVGImageElement) && michael@0: !(gDocument instanceof ImageDocument)) { michael@0: imageText = item.title || item.alt; michael@0: michael@0: if (!imageText && !(item instanceof HTMLImageElement)) michael@0: imageText = getValueText(item); michael@0: } michael@0: setItemValue("imagetext", imageText); michael@0: michael@0: setItemValue("imagelongdesctext", item.longDesc); michael@0: michael@0: // get cache info michael@0: var cacheKey = url.replace(/#.*$/, ""); michael@0: openCacheEntry(cacheKey, function(cacheEntry) { michael@0: // find out the file size michael@0: var sizeText; michael@0: if (cacheEntry) { michael@0: var imageSize = cacheEntry.dataSize; michael@0: var kbSize = Math.round(imageSize / 1024 * 100) / 100; michael@0: sizeText = gBundle.getFormattedString("generalSize", michael@0: [formatNumber(kbSize), formatNumber(imageSize)]); michael@0: } michael@0: else michael@0: sizeText = gBundle.getString("mediaUnknownNotCached"); michael@0: setItemValue("imagesizetext", sizeText); michael@0: michael@0: var mimeType; michael@0: var numFrames = 1; michael@0: if (item instanceof HTMLObjectElement || michael@0: item instanceof HTMLEmbedElement || michael@0: item instanceof HTMLLinkElement) michael@0: mimeType = item.type; michael@0: michael@0: if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) { michael@0: var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST); michael@0: if (imageRequest) { michael@0: mimeType = imageRequest.mimeType; michael@0: var image = imageRequest.image; michael@0: if (image) michael@0: numFrames = image.numFrames; michael@0: } michael@0: } michael@0: michael@0: if (!mimeType) michael@0: mimeType = getContentTypeFromHeaders(cacheEntry); michael@0: michael@0: // if we have a data url, get the MIME type from the url michael@0: if (!mimeType && url.startsWith("data:")) { michael@0: let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url); michael@0: if (dataMimeType) michael@0: mimeType = dataMimeType[1].toLowerCase(); michael@0: } michael@0: michael@0: var imageType; michael@0: if (mimeType) { michael@0: // We found the type, try to display it nicely michael@0: let imageMimeType = /^image\/(.*)/i.exec(mimeType); michael@0: if (imageMimeType) { michael@0: imageType = imageMimeType[1].toUpperCase(); michael@0: if (numFrames > 1) michael@0: imageType = gBundle.getFormattedString("mediaAnimatedImageType", michael@0: [imageType, numFrames]); michael@0: else michael@0: imageType = gBundle.getFormattedString("mediaImageType", [imageType]); michael@0: } michael@0: else { michael@0: // the MIME type doesn't begin with image/, display the raw type michael@0: imageType = mimeType; michael@0: } michael@0: } michael@0: else { michael@0: // We couldn't find the type, fall back to the value in the treeview michael@0: imageType = gImageView.data[row][COL_IMAGE_TYPE]; michael@0: } michael@0: setItemValue("imagetypetext", imageType); michael@0: michael@0: var imageContainer = document.getElementById("theimagecontainer"); michael@0: var oldImage = document.getElementById("thepreviewimage"); michael@0: michael@0: var isProtocolAllowed = checkProtocol(gImageView.data[row]); michael@0: michael@0: var newImage = new Image; michael@0: newImage.id = "thepreviewimage"; michael@0: var physWidth = 0, physHeight = 0; michael@0: var width = 0, height = 0; michael@0: michael@0: if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement || michael@0: item instanceof HTMLImageElement || michael@0: item instanceof SVGImageElement || michael@0: (item instanceof HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed) { michael@0: newImage.setAttribute("src", url); michael@0: physWidth = newImage.width || 0; michael@0: physHeight = newImage.height || 0; michael@0: michael@0: // "width" and "height" attributes must be set to newImage, michael@0: // even if there is no "width" or "height attribute in item; michael@0: // otherwise, the preview image cannot be displayed correctly. michael@0: if (!isBG) { michael@0: newImage.width = ("width" in item && item.width) || newImage.naturalWidth; michael@0: newImage.height = ("height" in item && item.height) || newImage.naturalHeight; michael@0: } michael@0: else { michael@0: // the Width and Height of an HTML tag should not be used for its background image michael@0: // (for example, "table" can have "width" or "height" attributes) michael@0: newImage.width = newImage.naturalWidth; michael@0: newImage.height = newImage.naturalHeight; michael@0: } michael@0: michael@0: if (item instanceof SVGImageElement) { michael@0: newImage.width = item.width.baseVal.value; michael@0: newImage.height = item.height.baseVal.value; michael@0: } michael@0: michael@0: width = newImage.width; michael@0: height = newImage.height; michael@0: michael@0: document.getElementById("theimagecontainer").collapsed = false michael@0: document.getElementById("brokenimagecontainer").collapsed = true; michael@0: } michael@0: else if (item instanceof HTMLVideoElement && isProtocolAllowed) { michael@0: newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video"); michael@0: newImage.id = "thepreviewimage"; michael@0: newImage.src = url; michael@0: newImage.controls = true; michael@0: width = physWidth = item.videoWidth; michael@0: height = physHeight = item.videoHeight; michael@0: michael@0: document.getElementById("theimagecontainer").collapsed = false; michael@0: document.getElementById("brokenimagecontainer").collapsed = true; michael@0: } michael@0: else if (item instanceof HTMLAudioElement && isProtocolAllowed) { michael@0: newImage = new Audio; michael@0: newImage.id = "thepreviewimage"; michael@0: newImage.src = url; michael@0: newImage.controls = true; michael@0: isAudio = true; michael@0: michael@0: document.getElementById("theimagecontainer").collapsed = false; michael@0: document.getElementById("brokenimagecontainer").collapsed = true; michael@0: } michael@0: else { michael@0: // fallback image for protocols not allowed (e.g., javascript:) michael@0: // or elements not [yet] handled (e.g., object, embed). michael@0: document.getElementById("brokenimagecontainer").collapsed = false; michael@0: document.getElementById("theimagecontainer").collapsed = true; michael@0: } michael@0: michael@0: var imageSize = ""; michael@0: if (url && !isAudio) { michael@0: if (width != physWidth || height != physHeight) { michael@0: imageSize = gBundle.getFormattedString("mediaDimensionsScaled", michael@0: [formatNumber(physWidth), michael@0: formatNumber(physHeight), michael@0: formatNumber(width), michael@0: formatNumber(height)]); michael@0: } michael@0: else { michael@0: imageSize = gBundle.getFormattedString("mediaDimensions", michael@0: [formatNumber(width), michael@0: formatNumber(height)]); michael@0: } michael@0: } michael@0: setItemValue("imagedimensiontext", imageSize); michael@0: michael@0: makeBlockImage(url); michael@0: michael@0: imageContainer.removeChild(oldImage); michael@0: imageContainer.appendChild(newImage); michael@0: michael@0: onImagePreviewShown.forEach(function(func) { func(); }); michael@0: }); michael@0: } michael@0: michael@0: function makeBlockImage(url) michael@0: { michael@0: var permissionManager = Components.classes[PERMISSION_CONTRACTID] michael@0: .getService(nsIPermissionManager); michael@0: var prefs = Components.classes[PREFERENCES_CONTRACTID] michael@0: .getService(Components.interfaces.nsIPrefBranch); michael@0: michael@0: var checkbox = document.getElementById("blockImage"); michael@0: var imagePref = prefs.getIntPref("permissions.default.image"); michael@0: if (!(/^https?:/.test(url)) || imagePref == 2) michael@0: // We can't block the images from this host because either is is not michael@0: // for http(s) or we don't load images at all michael@0: checkbox.hidden = true; michael@0: else { michael@0: var uri = makeURI(url); michael@0: if (uri.host) { michael@0: checkbox.hidden = false; michael@0: checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]); michael@0: var perm = permissionManager.testPermission(uri, "image"); michael@0: checkbox.checked = perm == nsIPermissionManager.DENY_ACTION; michael@0: } michael@0: else michael@0: checkbox.hidden = true; michael@0: } michael@0: } michael@0: michael@0: var imagePermissionObserver = { michael@0: observe: function (aSubject, aTopic, aData) michael@0: { michael@0: if (document.getElementById("mediaPreviewBox").collapsed) michael@0: return; michael@0: michael@0: if (aTopic == "perm-changed") { michael@0: var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission); michael@0: if (permission.type == "image") { michael@0: var imageTree = document.getElementById("imagetree"); michael@0: var row = getSelectedRow(imageTree); michael@0: var item = gImageView.data[row][COL_IMAGE_NODE]; michael@0: var url = gImageView.data[row][COL_IMAGE_ADDRESS]; michael@0: if (makeURI(url).host == permission.host) michael@0: makeBlockImage(url); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: function getContentTypeFromHeaders(cacheEntryDescriptor) michael@0: { michael@0: if (!cacheEntryDescriptor) michael@0: return null; michael@0: michael@0: return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi michael@0: .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1]; michael@0: } michael@0: michael@0: //******** Other Misc Stuff michael@0: // Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html michael@0: // parse a node to extract the contents of the node michael@0: function getValueText(node) michael@0: { michael@0: var valueText = ""; michael@0: michael@0: // form input elements don't generally contain information that is useful to our callers, so return nothing michael@0: if (node instanceof HTMLInputElement || michael@0: node instanceof HTMLSelectElement || michael@0: node instanceof HTMLTextAreaElement) michael@0: return valueText; michael@0: michael@0: // otherwise recurse for each child michael@0: var length = node.childNodes.length; michael@0: for (var i = 0; i < length; i++) { michael@0: var childNode = node.childNodes[i]; michael@0: var nodeType = childNode.nodeType; michael@0: michael@0: // text nodes are where the goods are michael@0: if (nodeType == Node.TEXT_NODE) michael@0: valueText += " " + childNode.nodeValue; michael@0: // and elements can have more text inside them michael@0: else if (nodeType == Node.ELEMENT_NODE) { michael@0: // images are special, we want to capture the alt text as if the image weren't there michael@0: if (childNode instanceof HTMLImageElement) michael@0: valueText += " " + getAltText(childNode); michael@0: else michael@0: valueText += " " + getValueText(childNode); michael@0: } michael@0: } michael@0: michael@0: return stripWS(valueText); michael@0: } michael@0: michael@0: // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html michael@0: // traverse the tree in search of an img or area element and grab its alt tag michael@0: function getAltText(node) michael@0: { michael@0: var altText = ""; michael@0: michael@0: if (node.alt) michael@0: return node.alt; michael@0: var length = node.childNodes.length; michael@0: for (var i = 0; i < length; i++) michael@0: if ((altText = getAltText(node.childNodes[i]) != undefined)) // stupid js warning... michael@0: return altText; michael@0: return ""; michael@0: } michael@0: michael@0: // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html michael@0: // strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space michael@0: function stripWS(text) michael@0: { michael@0: var middleRE = /\s+/g; michael@0: var endRE = /(^\s+)|(\s+$)/g; michael@0: michael@0: text = text.replace(middleRE, " "); michael@0: return text.replace(endRE, ""); michael@0: } michael@0: michael@0: function setItemValue(id, value) michael@0: { michael@0: var item = document.getElementById(id); michael@0: if (value) { michael@0: item.parentNode.collapsed = false; michael@0: item.value = value; michael@0: } michael@0: else michael@0: item.parentNode.collapsed = true; michael@0: } michael@0: michael@0: function formatNumber(number) michael@0: { michael@0: return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString() michael@0: } michael@0: michael@0: function formatDate(datestr, unknown) michael@0: { michael@0: // scriptable date formatter, for pretty printing dates michael@0: var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"] michael@0: .getService(Components.interfaces.nsIScriptableDateFormat); michael@0: michael@0: var date = new Date(datestr); michael@0: if (!date.valueOf()) michael@0: return unknown; michael@0: michael@0: return dateService.FormatDateTime("", dateService.dateFormatLong, michael@0: dateService.timeFormatSeconds, michael@0: date.getFullYear(), date.getMonth()+1, date.getDate(), michael@0: date.getHours(), date.getMinutes(), date.getSeconds()); michael@0: } michael@0: michael@0: function doCopy() michael@0: { michael@0: if (!gClipboardHelper) michael@0: return; michael@0: michael@0: var elem = document.commandDispatcher.focusedElement; michael@0: michael@0: if (elem && "treeBoxObject" in elem) { michael@0: var view = elem.view; michael@0: var selection = view.selection; michael@0: var text = [], tmp = ''; michael@0: var min = {}, max = {}; michael@0: michael@0: var count = selection.getRangeCount(); michael@0: michael@0: for (var i = 0; i < count; i++) { michael@0: selection.getRangeAt(i, min, max); michael@0: michael@0: for (var row = min.value; row <= max.value; row++) { michael@0: view.performActionOnRow("copy", row); michael@0: michael@0: tmp = elem.getAttribute("copybuffer"); michael@0: if (tmp) michael@0: text.push(tmp); michael@0: elem.removeAttribute("copybuffer"); michael@0: } michael@0: } michael@0: gClipboardHelper.copyString(text.join("\n"), document); michael@0: } michael@0: } michael@0: michael@0: function doSelectAll() michael@0: { michael@0: var elem = document.commandDispatcher.focusedElement; michael@0: michael@0: if (elem && "treeBoxObject" in elem) michael@0: elem.view.selection.selectAll(); michael@0: } michael@0: michael@0: function selectImage() michael@0: { michael@0: if (!gImageElement) michael@0: return; michael@0: michael@0: var tree = document.getElementById("imagetree"); michael@0: for (var i = 0; i < tree.view.rowCount; i++) { michael@0: if (gImageElement == gImageView.data[i][COL_IMAGE_NODE] && michael@0: !gImageView.data[i][COL_IMAGE_BG]) { michael@0: tree.view.selection.select(i); michael@0: tree.treeBoxObject.ensureRowIsVisible(i); michael@0: tree.focus(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: function checkProtocol(img) michael@0: { michael@0: var url = img[COL_IMAGE_ADDRESS]; michael@0: return /^data:image\//i.test(url) || michael@0: /^(https?|ftp|file|about|chrome|resource):/.test(url); michael@0: }