browser/base/content/pageinfo/pageInfo.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 const Cu = Components.utils;
     6 Cu.import("resource://gre/modules/LoadContextInfo.jsm");
     7 Cu.import("resource://gre/modules/Services.jsm");
     9 //******** define a js object to implement nsITreeView
    10 function pageInfoTreeView(treeid, copycol)
    11 {
    12   // copycol is the index number for the column that we want to add to
    13   // the copy-n-paste buffer when the user hits accel-c
    14   this.treeid = treeid;
    15   this.copycol = copycol;
    16   this.rows = 0;
    17   this.tree = null;
    18   this.data = [ ];
    19   this.selection = null;
    20   this.sortcol = -1;
    21   this.sortdir = false;
    22 }
    24 pageInfoTreeView.prototype = {
    25   set rowCount(c) { throw "rowCount is a readonly property"; },
    26   get rowCount() { return this.rows; },
    28   setTree: function(tree)
    29   {
    30     this.tree = tree;
    31   },
    33   getCellText: function(row, column)
    34   {
    35     // row can be null, but js arrays are 0-indexed.
    36     // colidx cannot be null, but can be larger than the number
    37     // of columns in the array. In this case it's the fault of
    38     // whoever typoed while calling this function.
    39     return this.data[row][column.index] || "";
    40   },
    42   setCellValue: function(row, column, value)
    43   {
    44   },
    46   setCellText: function(row, column, value)
    47   {
    48     this.data[row][column.index] = value;
    49   },
    51   addRow: function(row)
    52   {
    53     this.rows = this.data.push(row);
    54     this.rowCountChanged(this.rows - 1, 1);
    55     if (this.selection.count == 0 && this.rowCount && !gImageElement)
    56       this.selection.select(0);
    57   },
    59   rowCountChanged: function(index, count)
    60   {
    61     this.tree.rowCountChanged(index, count);
    62   },
    64   invalidate: function()
    65   {
    66     this.tree.invalidate();
    67   },
    69   clear: function()
    70   {
    71     if (this.tree)
    72       this.tree.rowCountChanged(0, -this.rows);
    73     this.rows = 0;
    74     this.data = [ ];
    75   },
    77   handleCopy: function(row)
    78   {
    79     return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
    80   },
    82   performActionOnRow: function(action, row)
    83   {
    84     if (action == "copy") {
    85       var data = this.handleCopy(row)
    86       this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
    87     }
    88   },
    90   onPageMediaSort : function(columnname)
    91   {
    92     var tree = document.getElementById(this.treeid);
    93     var treecol = tree.columns.getNamedColumn(columnname);
    95     this.sortdir =
    96       gTreeUtils.sort(
    97         tree,
    98         this,
    99         this.data,
   100         treecol.index,
   101         function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); },
   102         this.sortcol,
   103         this.sortdir
   104       );
   106     this.sortcol = treecol.index;
   107   },
   109   getRowProperties: function(row) { return ""; },
   110   getCellProperties: function(row, column) { return ""; },
   111   getColumnProperties: function(column) { return ""; },
   112   isContainer: function(index) { return false; },
   113   isContainerOpen: function(index) { return false; },
   114   isSeparator: function(index) { return false; },
   115   isSorted: function() { },
   116   canDrop: function(index, orientation) { return false; },
   117   drop: function(row, orientation) { return false; },
   118   getParentIndex: function(index) { return 0; },
   119   hasNextSibling: function(index, after) { return false; },
   120   getLevel: function(index) { return 0; },
   121   getImageSrc: function(row, column) { },
   122   getProgressMode: function(row, column) { },
   123   getCellValue: function(row, column) { },
   124   toggleOpenState: function(index) { },
   125   cycleHeader: function(col) { },
   126   selectionChanged: function() { },
   127   cycleCell: function(row, column) { },
   128   isEditable: function(row, column) { return false; },
   129   isSelectable: function(row, column) { return false; },
   130   performAction: function(action) { },
   131   performActionOnCell: function(action, row, column) { }
   132 };
   134 // mmm, yummy. global variables.
   135 var gWindow = null;
   136 var gDocument = null;
   137 var gImageElement = null;
   139 // column number to help using the data array
   140 const COL_IMAGE_ADDRESS = 0;
   141 const COL_IMAGE_TYPE    = 1;
   142 const COL_IMAGE_SIZE    = 2;
   143 const COL_IMAGE_ALT     = 3;
   144 const COL_IMAGE_COUNT   = 4;
   145 const COL_IMAGE_NODE    = 5;
   146 const COL_IMAGE_BG      = 6;
   148 // column number to copy from, second argument to pageInfoTreeView's constructor
   149 const COPYCOL_NONE = -1;
   150 const COPYCOL_META_CONTENT = 1;
   151 const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
   153 // one nsITreeView for each tree in the window
   154 var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
   155 var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
   157 gImageView.getCellProperties = function(row, col) {
   158   var data = gImageView.data[row];
   159   var item = gImageView.data[row][COL_IMAGE_NODE];
   160   var props = "";
   161   if (!checkProtocol(data) ||
   162       item instanceof HTMLEmbedElement ||
   163       (item instanceof HTMLObjectElement && !item.type.startsWith("image/")))
   164     props += "broken";
   166   if (col.element.id == "image-address")
   167     props += " ltr";
   169   return props;
   170 };
   172 gImageView.getCellText = function(row, column) {
   173   var value = this.data[row][column.index];
   174   if (column.index == COL_IMAGE_SIZE) {
   175     if (value == -1) {
   176       return gStrings.unknown;
   177     } else {
   178       var kbSize = Number(Math.round(value / 1024 * 100) / 100);
   179       return gBundle.getFormattedString("mediaFileSize", [kbSize]);
   180     }
   181   }
   182   return value || "";
   183 };
   185 gImageView.onPageMediaSort = function(columnname) {
   186   var tree = document.getElementById(this.treeid);
   187   var treecol = tree.columns.getNamedColumn(columnname);
   189   var comparator;
   190   if (treecol.index == COL_IMAGE_SIZE || treecol.index == COL_IMAGE_COUNT) {
   191     comparator = function numComparator(a, b) { return a - b; };
   192   } else {
   193     comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); };
   194   }
   196   this.sortdir =
   197     gTreeUtils.sort(
   198       tree,
   199       this,
   200       this.data,
   201       treecol.index,
   202       comparator,
   203       this.sortcol,
   204       this.sortdir
   205     );
   207   this.sortcol = treecol.index;
   208 };
   210 var gImageHash = { };
   212 // localized strings (will be filled in when the document is loaded)
   213 // this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
   214 var gStrings = { };
   215 var gBundle;
   217 const PERMISSION_CONTRACTID     = "@mozilla.org/permissionmanager;1";
   218 const PREFERENCES_CONTRACTID    = "@mozilla.org/preferences-service;1";
   219 const ATOM_CONTRACTID           = "@mozilla.org/atom-service;1";
   221 // a number of services I'll need later
   222 // the cache services
   223 const nsICacheStorageService = Components.interfaces.nsICacheStorageService;
   224 const nsICacheStorage = Components.interfaces.nsICacheStorage;
   225 const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
   227 var loadContextInfo = LoadContextInfo.fromLoadContext(
   228   window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
   229         .getInterface(Components.interfaces.nsIWebNavigation)
   230         .QueryInterface(Components.interfaces.nsILoadContext), false);
   231 var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
   233 const nsICookiePermission  = Components.interfaces.nsICookiePermission;
   234 const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
   236 const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
   237 const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
   239 // clipboard helper
   240 try {
   241   const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
   242 }
   243 catch(e) {
   244   // do nothing, later code will handle the error
   245 }
   247 // Interface for image loading content
   248 const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
   250 // namespaces, don't need all of these yet...
   251 const XLinkNS  = "http://www.w3.org/1999/xlink";
   252 const XULNS    = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   253 const XMLNS    = "http://www.w3.org/XML/1998/namespace";
   254 const XHTMLNS  = "http://www.w3.org/1999/xhtml";
   255 const XHTML2NS = "http://www.w3.org/2002/06/xhtml2"
   257 const XHTMLNSre  = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
   258 const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$";
   259 const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
   261 /* Overlays register functions here.
   262  * These arrays are used to hold callbacks that Page Info will call at
   263  * various stages. Use them by simply appending a function to them.
   264  * For example, add a function to onLoadRegistry by invoking
   265  *   "onLoadRegistry.push(XXXLoadFunc);"
   266  * The XXXLoadFunc should be unique to the overlay module, and will be
   267  * invoked as "XXXLoadFunc();"
   268  */
   270 // These functions are called to build the data displayed in the Page
   271 // Info window. The global variables gDocument and gWindow are set.
   272 var onLoadRegistry = [ ];
   274 // These functions are called to remove old data still displayed in
   275 // the window when the document whose information is displayed
   276 // changes. For example, at this time, the list of images of the Media
   277 // tab is cleared.
   278 var onResetRegistry = [ ];
   280 // These are called once for each subframe of the target document and
   281 // the target document itself. The frame is passed as an argument.
   282 var onProcessFrame = [ ];
   284 // These functions are called once for each element (in all subframes, if any)
   285 // in the target document. The element is passed as an argument.
   286 var onProcessElement = [ ];
   288 // These functions are called once when all the elements in all of the target
   289 // document (and all of its subframes, if any) have been processed
   290 var onFinished = [ ];
   292 // These functions are called once when the Page Info window is closed.
   293 var onUnloadRegistry = [ ];
   295 // These functions are called once when an image preview is shown.
   296 var onImagePreviewShown = [ ];
   298 /* Called when PageInfo window is loaded.  Arguments are:
   299  *  window.arguments[0] - (optional) an object consisting of
   300  *                         - doc: (optional) document to use for source. if not provided,
   301  *                                the calling window's document will be used
   302  *                         - initialTab: (optional) id of the inital tab to display
   303  */
   304 function onLoadPageInfo()
   305 {
   306   gBundle = document.getElementById("pageinfobundle");
   307   gStrings.unknown = gBundle.getString("unknown");
   308   gStrings.notSet = gBundle.getString("notset");
   309   gStrings.mediaImg = gBundle.getString("mediaImg");
   310   gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
   311   gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
   312   gStrings.mediaListImg = gBundle.getString("mediaListImg");
   313   gStrings.mediaCursor = gBundle.getString("mediaCursor");
   314   gStrings.mediaObject = gBundle.getString("mediaObject");
   315   gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
   316   gStrings.mediaLink = gBundle.getString("mediaLink");
   317   gStrings.mediaInput = gBundle.getString("mediaInput");
   318   gStrings.mediaVideo = gBundle.getString("mediaVideo");
   319   gStrings.mediaAudio = gBundle.getString("mediaAudio");
   321   var args = "arguments" in window &&
   322              window.arguments.length >= 1 &&
   323              window.arguments[0];
   325   if (!args || !args.doc) {
   326     gWindow = window.opener.content;
   327     gDocument = gWindow.document;
   328   }
   330   // init media view
   331   var imageTree = document.getElementById("imagetree");
   332   imageTree.view = gImageView;
   334   /* Select the requested tab, if the name is specified */
   335   loadTab(args);
   336   Components.classes["@mozilla.org/observer-service;1"]
   337             .getService(Components.interfaces.nsIObserverService)
   338             .notifyObservers(window, "page-info-dialog-loaded", null);
   339 }
   341 function loadPageInfo()
   342 {
   343   var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title"
   344                                            : "pageInfo.page.title";
   345   document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]);
   347   document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location);
   349   // do the easy stuff first
   350   makeGeneralTab();
   352   // and then the hard stuff
   353   makeTabs(gDocument, gWindow);
   355   initFeedTab();
   356   onLoadPermission();
   358   /* Call registered overlay init functions */
   359   onLoadRegistry.forEach(function(func) { func(); });
   360 }
   362 function resetPageInfo(args)
   363 {
   364   /* Reset Meta tags part */
   365   gMetaView.clear();
   367   /* Reset Media tab */
   368   var mediaTab = document.getElementById("mediaTab");
   369   if (!mediaTab.hidden) {
   370     Components.classes["@mozilla.org/observer-service;1"]
   371               .getService(Components.interfaces.nsIObserverService)
   372               .removeObserver(imagePermissionObserver, "perm-changed");
   373     mediaTab.hidden = true;
   374   }
   375   gImageView.clear();
   376   gImageHash = {};
   378   /* Reset Feeds Tab */
   379   var feedListbox = document.getElementById("feedListbox");
   380   while (feedListbox.firstChild)
   381     feedListbox.removeChild(feedListbox.firstChild);
   383   /* Call registered overlay reset functions */
   384   onResetRegistry.forEach(function(func) { func(); });
   386   /* Rebuild the data */
   387   loadTab(args);
   388 }
   390 function onUnloadPageInfo()
   391 {
   392   // Remove the observer, only if there is at least 1 image.
   393   if (!document.getElementById("mediaTab").hidden) {
   394     Components.classes["@mozilla.org/observer-service;1"]
   395               .getService(Components.interfaces.nsIObserverService)
   396               .removeObserver(imagePermissionObserver, "perm-changed");
   397   }
   399   /* Call registered overlay unload functions */
   400   onUnloadRegistry.forEach(function(func) { func(); });
   401 }
   403 function doHelpButton()
   404 {
   405   const helpTopics = {
   406     "generalPanel":  "pageinfo_general",
   407     "mediaPanel":    "pageinfo_media",
   408     "feedPanel":     "pageinfo_feed",
   409     "permPanel":     "pageinfo_permissions",
   410     "securityPanel": "pageinfo_security"
   411   };
   413   var deck  = document.getElementById("mainDeck");
   414   var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
   415   openHelpLink(helpdoc);
   416 }
   418 function showTab(id)
   419 {
   420   var deck  = document.getElementById("mainDeck");
   421   var pagel = document.getElementById(id + "Panel");
   422   deck.selectedPanel = pagel;
   423 }
   425 function loadTab(args)
   426 {
   427   if (args && args.doc) {
   428     gDocument = args.doc;
   429     gWindow = gDocument.defaultView;
   430   }
   432   gImageElement = args && args.imageElement;
   434   /* Load the page info */
   435   loadPageInfo();
   437   var initialTab = (args && args.initialTab) || "generalTab";
   438   var radioGroup = document.getElementById("viewGroup");
   439   initialTab = document.getElementById(initialTab) || document.getElementById("generalTab");
   440   radioGroup.selectedItem = initialTab;
   441   radioGroup.selectedItem.doCommand();
   442   radioGroup.focus();
   443 }
   445 function onClickMore()
   446 {
   447   var radioGrp = document.getElementById("viewGroup");
   448   var radioElt = document.getElementById("securityTab");
   449   radioGrp.selectedItem = radioElt;
   450   showTab('security');
   451 }
   453 function toggleGroupbox(id)
   454 {
   455   var elt = document.getElementById(id);
   456   if (elt.hasAttribute("closed")) {
   457     elt.removeAttribute("closed");
   458     if (elt.flexWhenOpened)
   459       elt.flex = elt.flexWhenOpened;
   460   }
   461   else {
   462     elt.setAttribute("closed", "true");
   463     if (elt.flex) {
   464       elt.flexWhenOpened = elt.flex;
   465       elt.flex = 0;
   466     }
   467   }
   468 }
   470 function openCacheEntry(key, cb)
   471 {
   472   var checkCacheListener = {
   473     onCacheEntryCheck: function(entry, appCache) {
   474       return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
   475     },
   476     onCacheEntryAvailable: function(entry, isNew, appCache, status) {
   477       cb(entry);
   478     }
   479   };
   480   diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
   481 }
   483 function makeGeneralTab()
   484 {
   485   var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
   486   document.getElementById("titletext").value = title;
   488   var url = gDocument.location.toString();
   489   setItemValue("urltext", url);
   491   var referrer = ("referrer" in gDocument && gDocument.referrer);
   492   setItemValue("refertext", referrer);
   494   var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
   495   document.getElementById("modetext").value = gBundle.getString(mode);
   497   // find out the mime type
   498   var mimeType = gDocument.contentType;
   499   setItemValue("typetext", mimeType);
   501   // get the document characterset
   502   var encoding = gDocument.characterSet;
   503   document.getElementById("encodingtext").value = encoding;
   505   // get the meta tags
   506   var metaNodes = gDocument.getElementsByTagName("meta");
   507   var length = metaNodes.length;
   509   var metaGroup = document.getElementById("metaTags");
   510   if (!length)
   511     metaGroup.collapsed = true;
   512   else {
   513     var metaTagsCaption = document.getElementById("metaTagsCaption");
   514     if (length == 1)
   515       metaTagsCaption.label = gBundle.getString("generalMetaTag");
   516     else
   517       metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
   518     var metaTree = document.getElementById("metatree");
   519     metaTree.treeBoxObject.view = gMetaView;
   521     for (var i = 0; i < length; i++)
   522       gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]);
   524     metaGroup.collapsed = false;
   525   }
   527   // get the date of last modification
   528   var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet);
   529   document.getElementById("modifiedtext").value = modifiedText;
   531   // get cache info
   532   var cacheKey = url.replace(/#.*$/, "");
   533   openCacheEntry(cacheKey, function(cacheEntry) {
   534     var sizeText;
   535     if (cacheEntry) {
   536       var pageSize = cacheEntry.dataSize;
   537       var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
   538       sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
   539     }
   540     setItemValue("sizetext", sizeText);
   541   });
   543   securityOnLoad();
   544 }
   546 //******** Generic Build-a-tab
   547 // Assumes the views are empty. Only called once to build the tabs, and
   548 // does so by farming the task off to another thread via setTimeout().
   549 // The actual work is done with a TreeWalker that calls doGrab() once for
   550 // each element node in the document.
   552 var gFrameList = [ ];
   554 function makeTabs(aDocument, aWindow)
   555 {
   556   goThroughFrames(aDocument, aWindow);
   557   processFrames();
   558 }
   560 function goThroughFrames(aDocument, aWindow)
   561 {
   562   gFrameList.push(aDocument);
   563   if (aWindow && aWindow.frames.length > 0) {
   564     var num = aWindow.frames.length;
   565     for (var i = 0; i < num; i++)
   566       goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]);  // recurse through the frames
   567   }
   568 }
   570 function processFrames()
   571 {
   572   if (gFrameList.length) {
   573     var doc = gFrameList[0];
   574     onProcessFrame.forEach(function(func) { func(doc); });
   575     var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll);
   576     gFrameList.shift();
   577     setTimeout(doGrab, 10, iterator);
   578     onFinished.push(selectImage);
   579   }
   580   else
   581     onFinished.forEach(function(func) { func(); });
   582 }
   584 function doGrab(iterator)
   585 {
   586   for (var i = 0; i < 500; ++i)
   587     if (!iterator.nextNode()) {
   588       processFrames();
   589       return;
   590     }
   592   setTimeout(doGrab, 10, iterator);
   593 }
   595 function addImage(url, type, alt, elem, isBg)
   596 {
   597   if (!url)
   598     return;
   600   if (!gImageHash.hasOwnProperty(url))
   601     gImageHash[url] = { };
   602   if (!gImageHash[url].hasOwnProperty(type))
   603     gImageHash[url][type] = { };
   604   if (!gImageHash[url][type].hasOwnProperty(alt)) {
   605     gImageHash[url][type][alt] = gImageView.data.length;
   606     var row = [url, type, -1, alt, 1, elem, isBg];
   607     gImageView.addRow(row);
   609     // Fill in cache data asynchronously
   610     openCacheEntry(url, function(cacheEntry) {
   611       // The data at row[2] corresponds to the data size.
   612       if (cacheEntry) {
   613         row[2] = cacheEntry.dataSize;
   614         // Invalidate the row to trigger a repaint.
   615         gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
   616       }
   617     });
   619     // Add the observer, only once.
   620     if (gImageView.data.length == 1) {
   621       document.getElementById("mediaTab").hidden = false;
   622       Components.classes["@mozilla.org/observer-service;1"]
   623                 .getService(Components.interfaces.nsIObserverService)
   624                 .addObserver(imagePermissionObserver, "perm-changed", false);
   625     }
   626   }
   627   else {
   628     var i = gImageHash[url][type][alt];
   629     gImageView.data[i][COL_IMAGE_COUNT]++;
   630     if (elem == gImageElement)
   631       gImageView.data[i][COL_IMAGE_NODE] = elem;
   632   }
   633 }
   635 function grabAll(elem)
   636 {
   637   // check for images defined in CSS (e.g. background, borders), any node may have multiple
   638   var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
   640   if (computedStyle) {
   641     var addImgFunc = function (label, val) {
   642       if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
   643         addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
   644       }
   645       else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
   646         // This is for -moz-image-rect.
   647         // TODO: Reimplement once bug 714757 is fixed
   648         var strVal = val.getStringValue();
   649         if (strVal.search(/^.*url\(\"?/) > -1) {
   650           url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
   651           addImage(url, label, gStrings.notSet, elem, true);
   652         }
   653       }
   654       else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
   655         // recursively resolve multiple nested CSS value lists
   656         for (var i = 0; i < val.length; i++)
   657           addImgFunc(label, val.item(i));
   658       }
   659     };
   661     addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
   662     addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
   663     addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
   664     addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
   665   }
   667   // one swi^H^H^Hif-else to rule them all
   668   if (elem instanceof HTMLImageElement)
   669     addImage(elem.src, gStrings.mediaImg,
   670              (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
   671   else if (elem instanceof SVGImageElement) {
   672     try {
   673       // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
   674       //       or the URI formed from the baseURI and the URL is not a valid URI
   675       var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
   676       addImage(href, gStrings.mediaImg, "", elem, false);
   677     } catch (e) { }
   678   }
   679   else if (elem instanceof HTMLVideoElement) {
   680     addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false);
   681   }
   682   else if (elem instanceof HTMLAudioElement) {
   683     addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false);
   684   }
   685   else if (elem instanceof HTMLLinkElement) {
   686     if (elem.rel && /\bicon\b/i.test(elem.rel))
   687       addImage(elem.href, gStrings.mediaLink, "", elem, false);
   688   }
   689   else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) {
   690     if (elem.type.toLowerCase() == "image")
   691       addImage(elem.src, gStrings.mediaInput,
   692                (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
   693   }
   694   else if (elem instanceof HTMLObjectElement)
   695     addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false);
   696   else if (elem instanceof HTMLEmbedElement)
   697     addImage(elem.src, gStrings.mediaEmbed, "", elem, false);
   699   onProcessElement.forEach(function(func) { func(elem); });
   701   return NodeFilter.FILTER_ACCEPT;
   702 }
   704 //******** Link Stuff
   705 function openURL(target)
   706 {
   707   var url = target.parentNode.childNodes[2].value;
   708   window.open(url, "_blank", "chrome");
   709 }
   711 function onBeginLinkDrag(event,urlField,descField)
   712 {
   713   if (event.originalTarget.localName != "treechildren")
   714     return;
   716   var tree = event.target;
   717   if (!("treeBoxObject" in tree))
   718     tree = tree.parentNode;
   720   var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
   721   if (row == -1)
   722     return;
   724   // Adding URL flavor
   725   var col = tree.columns[urlField];
   726   var url = tree.view.getCellText(row, col);
   727   col = tree.columns[descField];
   728   var desc = tree.view.getCellText(row, col);
   730   var dt = event.dataTransfer;
   731   dt.setData("text/x-moz-url", url + "\n" + desc);
   732   dt.setData("text/url-list", url);
   733   dt.setData("text/plain", url);
   734 }
   736 //******** Image Stuff
   737 function getSelectedRows(tree)
   738 {
   739   var start = { };
   740   var end   = { };
   741   var numRanges = tree.view.selection.getRangeCount();
   743   var rowArray = [ ];
   744   for (var t = 0; t < numRanges; t++) {
   745     tree.view.selection.getRangeAt(t, start, end);
   746     for (var v = start.value; v <= end.value; v++)
   747       rowArray.push(v);
   748   }
   750   return rowArray;
   751 }
   753 function getSelectedRow(tree)
   754 {
   755   var rows = getSelectedRows(tree);
   756   return (rows.length == 1) ? rows[0] : -1;
   757 }
   759 function selectSaveFolder(aCallback)
   760 {
   761   const nsILocalFile = Components.interfaces.nsILocalFile;
   762   const nsIFilePicker = Components.interfaces.nsIFilePicker;
   763   let titleText = gBundle.getString("mediaSelectFolder");
   764   let fp = Components.classes["@mozilla.org/filepicker;1"].
   765            createInstance(nsIFilePicker);
   766   let fpCallback = function fpCallback_done(aResult) {
   767     if (aResult == nsIFilePicker.returnOK) {
   768       aCallback(fp.file.QueryInterface(nsILocalFile));
   769     } else {
   770       aCallback(null);
   771     }
   772   };
   774   fp.init(window, titleText, nsIFilePicker.modeGetFolder);
   775   fp.appendFilters(nsIFilePicker.filterAll);
   776   try {
   777     let prefs = Components.classes[PREFERENCES_CONTRACTID].
   778                 getService(Components.interfaces.nsIPrefBranch);
   779     let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
   780     if (initialDir) {
   781       fp.displayDirectory = initialDir;
   782     }
   783   } catch (ex) {
   784   }
   785   fp.open(fpCallback);
   786 }
   788 function saveMedia()
   789 {
   790   var tree = document.getElementById("imagetree");
   791   var rowArray = getSelectedRows(tree);
   792   if (rowArray.length == 1) {
   793     var row = rowArray[0];
   794     var item = gImageView.data[row][COL_IMAGE_NODE];
   795     var url = gImageView.data[row][COL_IMAGE_ADDRESS];
   797     if (url) {
   798       var titleKey = "SaveImageTitle";
   800       if (item instanceof HTMLVideoElement)
   801         titleKey = "SaveVideoTitle";
   802       else if (item instanceof HTMLAudioElement)
   803         titleKey = "SaveAudioTitle";
   805       saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), gDocument);
   806     }
   807   } else {
   808     selectSaveFolder(function(aDirectory) {
   809       if (aDirectory) {
   810         var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
   811           internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
   812                        aChosenData, aBaseURI, gDocument);
   813         };
   815         for (var i = 0; i < rowArray.length; i++) {
   816           var v = rowArray[i];
   817           var dir = aDirectory.clone();
   818           var item = gImageView.data[v][COL_IMAGE_NODE];
   819           var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
   820           var uri = makeURI(uriString);
   822           try {
   823             uri.QueryInterface(Components.interfaces.nsIURL);
   824             dir.append(decodeURIComponent(uri.fileName));
   825           } catch(ex) {
   826             /* data: uris */
   827           }
   829           if (i == 0) {
   830             saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
   831           } else {
   832             // This delay is a hack which prevents the download manager
   833             // from opening many times. See bug 377339.
   834             setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
   835                        makeURI(item.baseURI));
   836           }
   837         }
   838       }
   839     });
   840   }
   841 }
   843 function onBlockImage()
   844 {
   845   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
   846                                     .getService(nsIPermissionManager);
   848   var checkbox = document.getElementById("blockImage");
   849   var uri = makeURI(document.getElementById("imageurltext").value);
   850   if (checkbox.checked)
   851     permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
   852   else
   853     permissionManager.remove(uri.host, "image");
   854 }
   856 function onImageSelect()
   857 {
   858   var previewBox   = document.getElementById("mediaPreviewBox");
   859   var mediaSaveBox = document.getElementById("mediaSaveBox");
   860   var splitter     = document.getElementById("mediaSplitter");
   861   var tree = document.getElementById("imagetree");
   862   var count = tree.view.selection.count;
   863   if (count == 0) {
   864     previewBox.collapsed   = true;
   865     mediaSaveBox.collapsed = true;
   866     splitter.collapsed     = true;
   867     tree.flex = 1;
   868   }
   869   else if (count > 1) {
   870     splitter.collapsed     = true;
   871     previewBox.collapsed   = true;
   872     mediaSaveBox.collapsed = false;
   873     tree.flex = 1;
   874   }
   875   else {
   876     mediaSaveBox.collapsed = true;
   877     splitter.collapsed     = false;
   878     previewBox.collapsed   = false;
   879     tree.flex = 0;
   880     makePreview(getSelectedRows(tree)[0]);
   881   }
   882 }
   884 function makePreview(row)
   885 {
   886   var imageTree = document.getElementById("imagetree");
   887   var item = gImageView.data[row][COL_IMAGE_NODE];
   888   var url = gImageView.data[row][COL_IMAGE_ADDRESS];
   889   var isBG = gImageView.data[row][COL_IMAGE_BG];
   890   var isAudio = false;
   892   setItemValue("imageurltext", url);
   894   var imageText;
   895   if (!isBG &&
   896       !(item instanceof SVGImageElement) &&
   897       !(gDocument instanceof ImageDocument)) {
   898     imageText = item.title || item.alt;
   900     if (!imageText && !(item instanceof HTMLImageElement))
   901       imageText = getValueText(item);
   902   }
   903   setItemValue("imagetext", imageText);
   905   setItemValue("imagelongdesctext", item.longDesc);
   907   // get cache info
   908   var cacheKey = url.replace(/#.*$/, "");
   909   openCacheEntry(cacheKey, function(cacheEntry) {
   910     // find out the file size
   911     var sizeText;
   912     if (cacheEntry) {
   913       var imageSize = cacheEntry.dataSize;
   914       var kbSize = Math.round(imageSize / 1024 * 100) / 100;
   915       sizeText = gBundle.getFormattedString("generalSize",
   916                                             [formatNumber(kbSize), formatNumber(imageSize)]);
   917     }
   918     else
   919       sizeText = gBundle.getString("mediaUnknownNotCached");
   920     setItemValue("imagesizetext", sizeText);
   922     var mimeType;
   923     var numFrames = 1;
   924     if (item instanceof HTMLObjectElement ||
   925         item instanceof HTMLEmbedElement ||
   926         item instanceof HTMLLinkElement)
   927       mimeType = item.type;
   929     if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) {
   930       var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
   931       if (imageRequest) {
   932         mimeType = imageRequest.mimeType;
   933         var image = imageRequest.image;
   934         if (image)
   935           numFrames = image.numFrames;
   936       }
   937     }
   939     if (!mimeType)
   940       mimeType = getContentTypeFromHeaders(cacheEntry);
   942     // if we have a data url, get the MIME type from the url
   943     if (!mimeType && url.startsWith("data:")) {
   944       let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
   945       if (dataMimeType)
   946         mimeType = dataMimeType[1].toLowerCase();
   947     }
   949     var imageType;
   950     if (mimeType) {
   951       // We found the type, try to display it nicely
   952       let imageMimeType = /^image\/(.*)/i.exec(mimeType);
   953       if (imageMimeType) {
   954         imageType = imageMimeType[1].toUpperCase();
   955         if (numFrames > 1)
   956           imageType = gBundle.getFormattedString("mediaAnimatedImageType",
   957                                                  [imageType, numFrames]);
   958         else
   959           imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
   960       }
   961       else {
   962         // the MIME type doesn't begin with image/, display the raw type
   963         imageType = mimeType;
   964       }
   965     }
   966     else {
   967       // We couldn't find the type, fall back to the value in the treeview
   968       imageType = gImageView.data[row][COL_IMAGE_TYPE];
   969     }
   970     setItemValue("imagetypetext", imageType);
   972     var imageContainer = document.getElementById("theimagecontainer");
   973     var oldImage = document.getElementById("thepreviewimage");
   975     var isProtocolAllowed = checkProtocol(gImageView.data[row]);
   977     var newImage = new Image;
   978     newImage.id = "thepreviewimage";
   979     var physWidth = 0, physHeight = 0;
   980     var width = 0, height = 0;
   982     if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement ||
   983          item instanceof HTMLImageElement ||
   984          item instanceof SVGImageElement ||
   985          (item instanceof HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed) {
   986       newImage.setAttribute("src", url);
   987       physWidth = newImage.width || 0;
   988       physHeight = newImage.height || 0;
   990       // "width" and "height" attributes must be set to newImage,
   991       // even if there is no "width" or "height attribute in item;
   992       // otherwise, the preview image cannot be displayed correctly.
   993       if (!isBG) {
   994         newImage.width = ("width" in item && item.width) || newImage.naturalWidth;
   995         newImage.height = ("height" in item && item.height) || newImage.naturalHeight;
   996       }
   997       else {
   998         // the Width and Height of an HTML tag should not be used for its background image
   999         // (for example, "table" can have "width" or "height" attributes)
  1000         newImage.width = newImage.naturalWidth;
  1001         newImage.height = newImage.naturalHeight;
  1004       if (item instanceof SVGImageElement) {
  1005         newImage.width = item.width.baseVal.value;
  1006         newImage.height = item.height.baseVal.value;
  1009       width = newImage.width;
  1010       height = newImage.height;
  1012       document.getElementById("theimagecontainer").collapsed = false
  1013       document.getElementById("brokenimagecontainer").collapsed = true;
  1015     else if (item instanceof HTMLVideoElement && isProtocolAllowed) {
  1016       newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
  1017       newImage.id = "thepreviewimage";
  1018       newImage.src = url;
  1019       newImage.controls = true;
  1020       width = physWidth = item.videoWidth;
  1021       height = physHeight = item.videoHeight;
  1023       document.getElementById("theimagecontainer").collapsed = false;
  1024       document.getElementById("brokenimagecontainer").collapsed = true;
  1026     else if (item instanceof HTMLAudioElement && isProtocolAllowed) {
  1027       newImage = new Audio;
  1028       newImage.id = "thepreviewimage";
  1029       newImage.src = url;
  1030       newImage.controls = true;
  1031       isAudio = true;
  1033       document.getElementById("theimagecontainer").collapsed = false;
  1034       document.getElementById("brokenimagecontainer").collapsed = true;
  1036     else {
  1037       // fallback image for protocols not allowed (e.g., javascript:)
  1038       // or elements not [yet] handled (e.g., object, embed).
  1039       document.getElementById("brokenimagecontainer").collapsed = false;
  1040       document.getElementById("theimagecontainer").collapsed = true;
  1043     var imageSize = "";
  1044     if (url && !isAudio) {
  1045       if (width != physWidth || height != physHeight) {
  1046         imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
  1047                                                [formatNumber(physWidth),
  1048                                                 formatNumber(physHeight),
  1049                                                 formatNumber(width),
  1050                                                 formatNumber(height)]);
  1052       else {
  1053         imageSize = gBundle.getFormattedString("mediaDimensions",
  1054                                                [formatNumber(width),
  1055                                                 formatNumber(height)]);
  1058     setItemValue("imagedimensiontext", imageSize);
  1060     makeBlockImage(url);
  1062     imageContainer.removeChild(oldImage);
  1063     imageContainer.appendChild(newImage);
  1065     onImagePreviewShown.forEach(function(func) { func(); });
  1066   });
  1069 function makeBlockImage(url)
  1071   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
  1072                                     .getService(nsIPermissionManager);
  1073   var prefs = Components.classes[PREFERENCES_CONTRACTID]
  1074                         .getService(Components.interfaces.nsIPrefBranch);
  1076   var checkbox = document.getElementById("blockImage");
  1077   var imagePref = prefs.getIntPref("permissions.default.image");
  1078   if (!(/^https?:/.test(url)) || imagePref == 2)
  1079     // We can't block the images from this host because either is is not
  1080     // for http(s) or we don't load images at all
  1081     checkbox.hidden = true;
  1082   else {
  1083     var uri = makeURI(url);
  1084     if (uri.host) {
  1085       checkbox.hidden = false;
  1086       checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
  1087       var perm = permissionManager.testPermission(uri, "image");
  1088       checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
  1090     else
  1091       checkbox.hidden = true;
  1095 var imagePermissionObserver = {
  1096   observe: function (aSubject, aTopic, aData)
  1098     if (document.getElementById("mediaPreviewBox").collapsed)
  1099       return;
  1101     if (aTopic == "perm-changed") {
  1102       var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
  1103       if (permission.type == "image") {
  1104         var imageTree = document.getElementById("imagetree");
  1105         var row = getSelectedRow(imageTree);
  1106         var item = gImageView.data[row][COL_IMAGE_NODE];
  1107         var url = gImageView.data[row][COL_IMAGE_ADDRESS];
  1108         if (makeURI(url).host == permission.host)
  1109           makeBlockImage(url);
  1115 function getContentTypeFromHeaders(cacheEntryDescriptor)
  1117   if (!cacheEntryDescriptor)
  1118     return null;
  1120   return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
  1121           .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
  1124 //******** Other Misc Stuff
  1125 // Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
  1126 // parse a node to extract the contents of the node
  1127 function getValueText(node)
  1129   var valueText = "";
  1131   // form input elements don't generally contain information that is useful to our callers, so return nothing
  1132   if (node instanceof HTMLInputElement ||
  1133       node instanceof HTMLSelectElement ||
  1134       node instanceof HTMLTextAreaElement)
  1135     return valueText;
  1137   // otherwise recurse for each child
  1138   var length = node.childNodes.length;
  1139   for (var i = 0; i < length; i++) {
  1140     var childNode = node.childNodes[i];
  1141     var nodeType = childNode.nodeType;
  1143     // text nodes are where the goods are
  1144     if (nodeType == Node.TEXT_NODE)
  1145       valueText += " " + childNode.nodeValue;
  1146     // and elements can have more text inside them
  1147     else if (nodeType == Node.ELEMENT_NODE) {
  1148       // images are special, we want to capture the alt text as if the image weren't there
  1149       if (childNode instanceof HTMLImageElement)
  1150         valueText += " " + getAltText(childNode);
  1151       else
  1152         valueText += " " + getValueText(childNode);
  1156   return stripWS(valueText);
  1159 // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
  1160 // traverse the tree in search of an img or area element and grab its alt tag
  1161 function getAltText(node)
  1163   var altText = "";
  1165   if (node.alt)
  1166     return node.alt;
  1167   var length = node.childNodes.length;
  1168   for (var i = 0; i < length; i++)
  1169     if ((altText = getAltText(node.childNodes[i]) != undefined))  // stupid js warning...
  1170       return altText;
  1171   return "";
  1174 // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
  1175 // strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space
  1176 function stripWS(text)
  1178   var middleRE = /\s+/g;
  1179   var endRE = /(^\s+)|(\s+$)/g;
  1181   text = text.replace(middleRE, " ");
  1182   return text.replace(endRE, "");
  1185 function setItemValue(id, value)
  1187   var item = document.getElementById(id);
  1188   if (value) {
  1189     item.parentNode.collapsed = false;
  1190     item.value = value;
  1192   else
  1193     item.parentNode.collapsed = true;
  1196 function formatNumber(number)
  1198   return (+number).toLocaleString();  // coerce number to a numeric value before calling toLocaleString()
  1201 function formatDate(datestr, unknown)
  1203   // scriptable date formatter, for pretty printing dates
  1204   var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
  1205                               .getService(Components.interfaces.nsIScriptableDateFormat);
  1207   var date = new Date(datestr);
  1208   if (!date.valueOf())
  1209     return unknown;
  1211   return dateService.FormatDateTime("", dateService.dateFormatLong,
  1212                                     dateService.timeFormatSeconds,
  1213                                     date.getFullYear(), date.getMonth()+1, date.getDate(),
  1214                                     date.getHours(), date.getMinutes(), date.getSeconds());
  1217 function doCopy()
  1219   if (!gClipboardHelper)
  1220     return;
  1222   var elem = document.commandDispatcher.focusedElement;
  1224   if (elem && "treeBoxObject" in elem) {
  1225     var view = elem.view;
  1226     var selection = view.selection;
  1227     var text = [], tmp = '';
  1228     var min = {}, max = {};
  1230     var count = selection.getRangeCount();
  1232     for (var i = 0; i < count; i++) {
  1233       selection.getRangeAt(i, min, max);
  1235       for (var row = min.value; row <= max.value; row++) {
  1236         view.performActionOnRow("copy", row);
  1238         tmp = elem.getAttribute("copybuffer");
  1239         if (tmp)
  1240           text.push(tmp);
  1241         elem.removeAttribute("copybuffer");
  1244     gClipboardHelper.copyString(text.join("\n"), document);
  1248 function doSelectAll()
  1250   var elem = document.commandDispatcher.focusedElement;
  1252   if (elem && "treeBoxObject" in elem)
  1253     elem.view.selection.selectAll();
  1256 function selectImage()
  1258   if (!gImageElement)
  1259     return;
  1261   var tree = document.getElementById("imagetree");
  1262   for (var i = 0; i < tree.view.rowCount; i++) {
  1263     if (gImageElement == gImageView.data[i][COL_IMAGE_NODE] &&
  1264         !gImageView.data[i][COL_IMAGE_BG]) {
  1265       tree.view.selection.select(i);
  1266       tree.treeBoxObject.ensureRowIsVisible(i);
  1267       tree.focus();
  1268       return;
  1273 function checkProtocol(img)
  1275   var url = img[COL_IMAGE_ADDRESS];
  1276   return /^data:image\//i.test(url) ||
  1277     /^(https?|ftp|file|about|chrome|resource):/.test(url);

mercurial